diff --git a/.circleci/config.yml b/.circleci/config.yml index 8dfc24a82f7ae..d83373f15b68f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ setup: true parameters: default_docker_image: type: string - default: cimg/base:2024.01 + default: cimg/base:2026.03 base_image: type: string default: default @@ -38,9 +38,6 @@ parameters: sdk_dispatch: type: boolean default: false - docker_publish_dispatch: - type: boolean - default: false publish_contract_artifacts_dispatch: type: boolean default: false @@ -53,9 +50,6 @@ parameters: heavy_fuzz_dispatch: type: boolean default: false - sync_test_op_node_dispatch: - type: boolean - default: false ai_contracts_test_dispatch: type: boolean default: false @@ -111,12 +105,10 @@ workflows: .* c-kontrol_dispatch << pipeline.parameters.kontrol_dispatch >> .circleci/continue/main.yml .* c-cannon_full_test_dispatch << pipeline.parameters.cannon_full_test_dispatch >> .circleci/continue/main.yml .* c-sdk_dispatch << pipeline.parameters.sdk_dispatch >> .circleci/continue/main.yml - .* c-docker_publish_dispatch << pipeline.parameters.docker_publish_dispatch >> .circleci/continue/main.yml .* c-publish_contract_artifacts_dispatch << pipeline.parameters.publish_contract_artifacts_dispatch >> .circleci/continue/main.yml .* c-stale_check_dispatch << pipeline.parameters.stale_check_dispatch >> .circleci/continue/main.yml .* c-contracts_coverage_dispatch << pipeline.parameters.contracts_coverage_dispatch >> .circleci/continue/main.yml .* c-heavy_fuzz_dispatch << pipeline.parameters.heavy_fuzz_dispatch >> .circleci/continue/main.yml - .* c-sync_test_op_node_dispatch << pipeline.parameters.sync_test_op_node_dispatch >> .circleci/continue/main.yml .* c-ai_contracts_test_dispatch << pipeline.parameters.ai_contracts_test_dispatch >> .circleci/continue/main.yml .* c-github-event-type << pipeline.parameters.github-event-type >> .circleci/continue/main.yml .* c-github-event-action << pipeline.parameters.github-event-action >> .circleci/continue/main.yml @@ -126,15 +118,23 @@ workflows: .* c-flake-shake-iterations << pipeline.parameters.flake-shake-iterations >> .circleci/continue/main.yml .* c-flake-shake-workers << pipeline.parameters.flake-shake-workers >> .circleci/continue/main.yml .* c-go-cache-version << pipeline.parameters.go-cache-version >> .circleci/continue/main.yml + rust/.* c-rust_files_changed true .circleci/continue/main.yml + ^(?!docs/public-docs/).+ c-non_docs_changes true .circleci/continue/main.yml - (rust|\.circleci)/.* c-rust_ci_dispatch << pipeline.parameters.rust_ci_dispatch >> .circleci/continue/rust-ci.yml - (rust|\.circleci)/.* c-default_docker_image << pipeline.parameters.default_docker_image >> .circleci/continue/rust-ci.yml - (rust|\.circleci)/.* c-base_image << pipeline.parameters.base_image >> .circleci/continue/rust-ci.yml - (rust|\.circleci)/.* c-go-cache-version << pipeline.parameters.go-cache-version >> .circleci/continue/rust-ci.yml + # Rust CI — always include config, gate jobs via c-rust_changes_detected + .* c-default_docker_image << pipeline.parameters.default_docker_image >> .circleci/continue/rust-ci.yml + .* c-base_image << pipeline.parameters.base_image >> .circleci/continue/rust-ci.yml + .* c-go-cache-version << pipeline.parameters.go-cache-version >> .circleci/continue/rust-ci.yml + .* c-rust_ci_dispatch << pipeline.parameters.rust_ci_dispatch >> .circleci/continue/rust-ci.yml + (rust|\.circleci)/.* c-rust_changes_detected true .circleci/continue/rust-ci.yml + ^(?!docs/public-docs/).+ c-non_docs_changes true .circleci/continue/rust-ci.yml - (rust|\.circleci)/.* c-rust_e2e_dispatch << pipeline.parameters.rust_e2e_dispatch >> .circleci/continue/rust-e2e.yml - (rust|\.circleci)/.* c-default_docker_image << pipeline.parameters.default_docker_image >> .circleci/continue/rust-e2e.yml - (rust|\.circleci)/.* c-go-cache-version << pipeline.parameters.go-cache-version >> .circleci/continue/rust-e2e.yml + # Rust E2E — always include config, gate jobs via c-rust_changes_detected + .* c-default_docker_image << pipeline.parameters.default_docker_image >> .circleci/continue/rust-e2e.yml + .* c-go-cache-version << pipeline.parameters.go-cache-version >> .circleci/continue/rust-e2e.yml + .* c-rust_e2e_dispatch << pipeline.parameters.rust_e2e_dispatch >> .circleci/continue/rust-e2e.yml + (rust|op-e2e|\.circleci)/.* c-rust_changes_detected true .circleci/continue/rust-e2e.yml + ^(?!docs/public-docs/).+ c-non_docs_changes true .circleci/continue/rust-e2e.yml setup-tag: when: diff --git a/.circleci/continue/main.yml b/.circleci/continue/main.yml index 4dee69e8e6aae..f1af39798e652 100644 --- a/.circleci/continue/main.yml +++ b/.circleci/continue/main.yml @@ -3,7 +3,7 @@ version: 2.1 parameters: c-default_docker_image: type: string - default: cimg/base:2024.01 + default: cimg/base:2026.03 c-base_image: type: string default: default @@ -32,9 +32,6 @@ parameters: c-sdk_dispatch: type: boolean default: false - c-docker_publish_dispatch: - type: boolean - default: false c-publish_contract_artifacts_dispatch: type: boolean default: false @@ -47,9 +44,6 @@ parameters: c-heavy_fuzz_dispatch: type: boolean default: false - c-sync_test_op_node_dispatch: - type: boolean - default: false c-ai_contracts_test_dispatch: type: boolean default: false @@ -74,10 +68,97 @@ parameters: c-flake-shake-workers: type: integer default: 50 + # Set to true by path-filtering when files under rust/ change. + # When false on feature branches, kona-build-release skips cargo build and + # reuses cached binaries — saving ~9 minutes on PRs that don't touch Rust. + c-rust_files_changed: + type: boolean + default: false + # Set to true by path-filtering when files outside docs/public-docs/ change. + # When false, the main workflow is skipped entirely (docs-only PR). + c-non_docs_changes: + type: boolean + default: false # go-cache-version can be used as a cache buster when making breaking changes to caching strategy c-go-cache-version: type: string default: "v0.0" + # Passthrough declarations for setup config parameters. + # CircleCI forwards all explicitly-passed pipeline parameters to continuation configs. + # Without these declarations, manually triggered pipelines fail with "Unexpected argument(s)". + # These are not referenced by any job — the c- prefixed versions above are used instead. + default_docker_image: + type: string + default: cimg/base:2026.03 + base_image: + type: string + default: default + main_dispatch: + type: boolean + default: true + fault_proofs_dispatch: + type: boolean + default: false + reproducibility_dispatch: + type: boolean + default: false + kontrol_dispatch: + type: boolean + default: false + cannon_full_test_dispatch: + type: boolean + default: false + sdk_dispatch: + type: boolean + default: false + docker_publish_dispatch: + type: boolean + default: false + stale_check_dispatch: + type: boolean + default: false + contracts_coverage_dispatch: + type: boolean + default: false + heavy_fuzz_dispatch: + type: boolean + default: false + sync_test_op_node_dispatch: + type: boolean + default: false + ai_contracts_test_dispatch: + type: boolean + default: false + rust_ci_dispatch: + type: boolean + default: false + rust_e2e_dispatch: + type: boolean + default: false + github-event-type: + type: string + default: "__not_set__" + github-event-action: + type: string + default: "__not_set__" + github-event-base64: + type: string + default: "__not_set__" + devnet-metrics-collect: + type: boolean + default: false + flake-shake-dispatch: + type: boolean + default: false + flake-shake-iterations: + type: integer + default: 300 + flake-shake-workers: + type: integer + default: 50 + go-cache-version: + type: string + default: "v0.0" orbs: go: circleci/go@1.8.0 @@ -302,24 +383,6 @@ commands: fi fi - run-contracts-check: - parameters: - command: - description: Just command that runs the check - type: string - steps: - - run: - name: <> - command: | - git reset --hard - git clean -df - just <> - git status --porcelain - [ -z "$(git status --porcelain)" ] || exit 1 - working_directory: packages/contracts-bedrock - when: always - environment: - FOUNDRY_PROFILE: ci go-restore-cache: parameters: @@ -364,14 +427,6 @@ commands: - ~/go/pkg/mod key: go-<>-<>-<>-{{ checksum "<>/go.mod" }}-{{ checksum "<>/go.sum" }} - pull-artifacts-conditional: - description: "Pull artifacts with conditional fallback based on branch and PR labels" - steps: - - run: - name: Pull artifacts - command: bash scripts/ops/use-latest-fallback.sh - working_directory: packages/contracts-bedrock - # --- Rust environment setup commands --- rust-install-toolchain: description: "Install Rust toolchain via rustup" @@ -420,7 +475,7 @@ commands: ROOT_DIR="$(pwd)" BIN_DIR="$ROOT_DIR/.circleci-cache/rust-binaries" echo "export RUST_BINARY_PATH_KONA_NODE=$ROOT_DIR/rust/target/release/kona-node" >> "$BASH_ENV" - echo "export RUST_BINARY_PATH_KONA_SUPERVISOR=$ROOT_DIR/rust/target/release/kona-supervisor" >> "$BASH_ENV" + echo "export OP_RETH_EXEC_PATH=$ROOT_DIR/rust/target/release/op-reth" >> "$BASH_ENV" echo "export RUST_BINARY_PATH_OP_RBUILDER=$BIN_DIR/op-rbuilder" >> "$BASH_ENV" echo "export RUST_BINARY_PATH_ROLLUP_BOOST=$BIN_DIR/rollup-boost" >> "$BASH_ENV" @@ -452,7 +507,7 @@ commands: version: description: "Version of the cache" type: string - default: "15" + default: &rust-cache-version "16" profile: description: "Profile to restore the cache for" type: string @@ -486,7 +541,7 @@ commands: version: description: "Version of the cache" type: string - default: "15" + default: *rust-cache-version profile: description: "Profile to save the cache for" type: string @@ -527,7 +582,7 @@ commands: version: description: "Version of the cache" type: string - default: "15" + default: *rust-cache-version profile: description: "Profile to restore the cache for" type: string @@ -563,7 +618,7 @@ commands: version: description: "Version of the cache" type: string - default: "15" + default: *rust-cache-version profile: description: "Profile to build the binary with" type: string @@ -588,6 +643,10 @@ commands: description: "Whether to save the cache at the end of the build" type: boolean default: false + rust_files_changed: + description: "Set to false to skip cargo build and reuse cached binaries (feature branches only)." + type: boolean + default: true steps: - utils/checkout-with-mise: checkout-method: blobless @@ -608,6 +667,22 @@ commands: PWD=$(pwd) export CARGO_TARGET_DIR="$PWD/<< parameters.directory >>/target" echo "CARGO_TARGET_DIR: $CARGO_TARGET_DIR" + + # On feature branches where rust/ hasn't changed, reuse binaries from + # the restored target cache instead of running cargo build (~9 min saving). + # Always build on develop/main as a safety backstop. + if [ "<< parameters.rust_files_changed >>" = "false" ] \ + && [ "$CIRCLE_BRANCH" != "develop" ] \ + && [ "$CIRCLE_BRANCH" != "main" ]; then + binary_dir="$CARGO_TARGET_DIR/<< parameters.profile >>" + if [ -d "$binary_dir" ] && ls "$binary_dir"/* >/dev/null 2>&1; then + echo "No rust/ changes on feature branch — skipping cargo build" + ls -lh "$binary_dir"/ | head -20 || true + exit 0 + fi + echo "WARNING: no cached binaries found, building from source" + fi + export PROFILE="--profile << parameters.profile >>" # Debug profile is specified as "debug" in the config/target, but cargo build expects "dev" @@ -659,7 +734,7 @@ jobs: version: description: "Version of the cache" type: string - default: "15" + default: *rust-cache-version profile: description: "Profile to build the binary with" type: string @@ -684,6 +759,14 @@ jobs: description: "Whether to save the cache at the end of the build" type: boolean default: true + persist_to_workspace: + description: "Whether to persist the built binaries to the CircleCI workspace" + type: boolean + default: false + rust_files_changed: + description: "Set to false to skip cargo build and reuse cached binaries (feature branches only)." + type: boolean + default: true steps: - rust-build: directory: << parameters.directory >> @@ -695,6 +778,16 @@ jobs: binary: << parameters.binary >> toolchain: << parameters.toolchain >> save_cache: << parameters.save_cache >> + rust_files_changed: << parameters.rust_files_changed >> + - when: + condition: << parameters.persist_to_workspace >> + steps: + - persist_to_workspace: + root: "." + paths: + - "<< parameters.directory >>/target/<< parameters.profile >>/kona-*" + - "<< parameters.directory >>/target/<< parameters.profile >>/op-*" + - "<< parameters.directory >>/target/<< parameters.profile >>/rollup-boost" # Build a single Rust binary from a submodule. rust-build-submodule: @@ -791,124 +884,6 @@ jobs: paths: - ".circleci-cache/rust-binaries" - # Kurtosis-based acceptance tests - op-acceptance-tests-kurtosis: - parameters: - devnet: - description: | - The name of the pre-defined Kurtosis devnet to run the acceptance tests against - (e.g. 'simple', 'interop', 'jovian'). Empty string uses in-process testing (sysgo orchestrator). - type: string - default: "interop" - gate: - description: The gate to run the acceptance tests against. Must be defined in op-acceptance-tests/acceptance-tests.yaml. - type: string - default: "interop" - no_output_timeout: - description: Timeout for when CircleCI kills the job if there's no output - type: string - default: 30m - docker: - - image: <> - resource_class: xlarge - steps: - - utils/checkout-with-mise: - checkout-method: blobless - enable-mise-cache: true - - setup_remote_docker: - docker_layer_caching: true - - run: - name: Lint/Vet/Build op-acceptance-tests/cmd - working_directory: op-acceptance-tests - command: | - just cmd-check - - run: - name: Setup Kurtosis - command: | - echo "Setting up Kurtosis for external devnet testing..." - echo "Using Kurtosis from: $(which kurtosis || echo 'not found')" - kurtosis version || true - echo "Starting Kurtosis engine..." - kurtosis engine start || true - echo "Cleaning old instances..." - kurtosis clean -a || true - kurtosis engine status || true - echo "Kurtosis setup complete" - - run: - name: Dump kurtosis logs (pre-run) - command: | - # Best-effort: show engine status and existing enclaves before the test run - kurtosis engine status || true - kurtosis enclave ls || true - - run: - name: Run acceptance tests (devnet=<>, gate=<>) - working_directory: op-acceptance-tests - no_output_timeout: 1h - environment: - GOFLAGS: "-mod=mod" - GO111MODULE: "on" - GOGC: "0" - command: | - LOG_LEVEL=info just acceptance-test "<>" "<>" - - run: - name: Dump kurtosis logs - when: on_fail - command: | - # Dump logs & specs - kurtosis dump ./.kurtosis-dump - - # Remove spec.json files - rm -rf ./.kurtosis-dump/enclaves/**/*.json - - # Remove all unnecessary logs - rm -rf ./.kurtosis-dump/enclaves/*/kurtosis-api--* - rm -rf ./.kurtosis-dump/enclaves/*/kurtosis-logs-collector--* - rm -rf ./.kurtosis-dump/enclaves/*/task-* - - # Print enclaves and try to show service logs for the most recent devnet - kurtosis enclave ls || true - # Dump logs for all enclaves to aid debugging - for e in $(kurtosis enclave ls --output json 2>/dev/null | jq -r '.[].identifier' 2>/dev/null); do - echo "\n==== Kurtosis logs for enclave: $e ====" - kurtosis enclave inspect "$e" || true - kurtosis service logs "$e" --all-services --follow=false || true - done - - run: - name: Print results (summary) - working_directory: op-acceptance-tests - command: | - LOG_DIR=$(ls -td -- logs/* | head -1) - cat "$LOG_DIR/summary.log" || true - - run: - name: Print results (failures) - working_directory: op-acceptance-tests - command: | - LOG_DIR=$(ls -td -- logs/* | head -1) - cat "$LOG_DIR/failed/*.log" || true - when: on_fail - - run: - name: Print results (all) - working_directory: op-acceptance-tests - command: | - LOG_DIR=$(ls -td -- logs/* | head -1) - cat "$LOG_DIR/all.log" || true - - run: - name: Generate JUnit XML test report for CircleCI - working_directory: op-acceptance-tests - when: always - command: | - LOG_DIR=$(ls -td -- logs/* | head -1) - gotestsum --junitfile results/results.xml --raw-command cat $LOG_DIR/raw_go_events.log || true - - when: - condition: always - steps: - - store_test_results: - path: ./op-acceptance-tests/results - - when: - condition: always - steps: - - store_artifacts: - path: ./op-acceptance-tests/logs initialize: docker: - image: <> @@ -948,12 +923,12 @@ jobs: mkdir -p ./tmp/testlogs - run: name: build Cannon example binaries - command: make elf # only compile ELF binaries with Go, we do not have MIPS GCC for creating the debug-dumps. + command: just elf # only compile ELF binaries with Go, we do not have MIPS GCC for creating the debug-dumps. working_directory: cannon/testdata - run: name: Cannon Go lint command: | - make lint + just lint working_directory: cannon - run: name: Cannon Go 64-bit tests @@ -964,7 +939,7 @@ jobs: if [ "$SKIP_SLOW_TESTS" = "false" ]; then TIMEOUT="45m" fi - gotestsum --format=testname --junitfile=../tmp/test-results/cannon-64.xml --jsonfile=../tmp/testlogs/log-64.json \ + ../ops/scripts/gotestsum-split.sh --format=testname --junitfile=../tmp/test-results/cannon-64.xml --jsonfile=../tmp/testlogs/log-64.json \ -- -timeout=$TIMEOUT -parallel=$(nproc) -coverpkg=github.com/ethereum-optimism/optimism/cannon/... -coverprofile=coverage-64.out ./... working_directory: cannon - codecov/upload: @@ -999,15 +974,10 @@ jobs: - utils/checkout-with-mise: checkout-method: blobless enable-mise-cache: true - - install-zstd - install-contracts-dependencies - run: name: Print forge version command: forge --version - - run: - name: Pull artifacts - command: bash scripts/ops/pull-artifacts.sh - working_directory: packages/contracts-bedrock - run: name: Build contracts command: just forge-build <> @@ -1026,7 +996,8 @@ jobs: - "packages/contracts-bedrock/artifacts" - "packages/contracts-bedrock/forge-artifacts" - "op-deployer/pkg/deployer/artifacts/forge-artifacts" - - notify-failures-on-develop + - notify-failures-on-develop: + mentions: "@security-oncall" check-kontrol-build: docker: @@ -1050,246 +1021,8 @@ jobs: name: Build Kontrol summary files command: just forge-build ./test/kontrol/proofs working_directory: packages/contracts-bedrock - - notify-failures-on-develop - - docker-build: - environment: - DOCKER_BUILDKIT: 1 - parameters: - docker_tags: - description: Docker image tags, comma-separated - type: string - docker_name: - description: "Docker buildx bake target" - type: string - default: "" - registry: - description: Docker registry - type: string - default: "us-docker.pkg.dev" - repo: - description: Docker repo - type: string - default: "oplabs-tools-artifacts/images" - save_image_tag: - description: Save docker image with given tag - type: string - default: "" - platforms: - description: Platforms to build for, comma-separated - type: string - default: "linux/amd64" - publish: - description: Publish the docker image (multi-platform, all tags) - type: boolean - default: false - release: - description: Run the release script - type: boolean - default: false - resource_class: - description: Docker resource class - type: string - default: medium - machine: - image: <> - resource_class: "<>" - docker_layer_caching: true # we rely on this for faster builds, and actively warm it up for builds with common stages - steps: - - utils/checkout-with-mise: - checkout-method: blobless - enable-mise-cache: true - - attach_workspace: - at: . - - run: - command: mkdir -p /tmp/docker_images - - when: - condition: - or: - - "<>" - - "<>" - steps: - - gcp-cli/install - - when: - condition: - or: - - "<>" - - "<>" - steps: - - gcp-oidc-authenticate - - run: - name: Build - command: | - # Check to see if DOCKER_HUB_READ_ONLY_TOKEN is set (i.e. we are in repo) before attempting to use secrets. - # Building should work without this read only login, but may get rate limited. - if [[ -v DOCKER_HUB_READ_ONLY_TOKEN ]]; then - echo "$DOCKER_HUB_READ_ONLY_TOKEN" | docker login -u "$DOCKER_HUB_READ_ONLY_USER" --password-stdin - fi - - export REGISTRY="<>" - export REPOSITORY="<>" - export IMAGE_TAGS="$(echo -ne "<>" | sed "s/[^a-zA-Z0-9\n,]/-/g")" - export GIT_COMMIT="$(git rev-parse HEAD)" - export GIT_DATE="$(git show -s --format='%ct')" - export PLATFORMS="<>" - - echo "Checking git tags pointing at $GIT_COMMIT:" - tags_at_commit=$(git tag --points-at $GIT_COMMIT) - echo "Tags at commit:\n$tags_at_commit" - - filtered_tags=$(echo "$tags_at_commit" | grep "^<>/" || true) - echo "Filtered tags: $filtered_tags" - - if [ -z "$filtered_tags" ]; then - export GIT_VERSION="untagged" - else - sorted_tags=$(echo "$filtered_tags" | sed "s/<>\///" | sort -V) - echo "Sorted tags: $sorted_tags" - - # prefer full release tag over "-rc" release candidate tag if both exist - full_release_tag=$(echo "$sorted_tags" | grep -v -- "-rc" || true) - if [ -z "$full_release_tag" ]; then - export GIT_VERSION=$(echo "$sorted_tags" | tail -n 1) - else - export GIT_VERSION=$(echo "$full_release_tag" | tail -n 1) - fi - fi - - echo "Setting GIT_VERSION=$GIT_VERSION" - - # Create, start (bootstrap) and use a *named* docker builder - # This allows us to cross-build multi-platform, - # and naming allows us to use the DLC (docker-layer-cache) - docker buildx create --driver=docker-container --name=buildx-build --bootstrap --use - - DOCKER_OUTPUT_DESTINATION="" - if [ "<>" == "true" ]; then - gcloud auth configure-docker <> - echo "Building for platforms $PLATFORMS and then publishing to registry" - DOCKER_OUTPUT_DESTINATION="--push" - if [ "<>" != "" ]; then - echo "ERROR: cannot save image to docker when publishing to registry" - exit 1 - fi - else - if [ "<>" == "" ]; then - echo "Running $PLATFORMS build without destination (cache warm-up)" - DOCKER_OUTPUT_DESTINATION="" - elif [[ $PLATFORMS == *,* ]]; then - echo "ERROR: cannot perform multi-arch (platforms: $PLATFORMS) build while also loading the result into regular docker" - exit 1 - else - echo "Running single-platform $PLATFORMS build and loading into docker" - DOCKER_OUTPUT_DESTINATION="--load" - fi - fi - - # Let them cook! - docker buildx bake \ - --progress plain \ - --builder=buildx-build \ - -f docker-bake.hcl \ - $DOCKER_OUTPUT_DESTINATION \ - <> - - no_output_timeout: 45m - - when: - condition: "<>" - steps: - - notify-failures-on-develop - - when: - condition: "<>" - steps: - - run: - name: Save - command: | - IMAGE_NAME="<>/<>/<>:<>" - docker save -o /tmp/docker_images/<>.tar $IMAGE_NAME - - persist_to_workspace: - root: /tmp/docker_images - paths: # only write the one file, to avoid concurrent workspace-file additions - - "<>.tar" - - when: - condition: "<>" - steps: - - run: - name: Tag - command: | - ./ops/scripts/ci-docker-tag-op-stack-release.sh <>/<> $CIRCLE_TAG $CIRCLE_SHA1 - - when: - condition: - or: - - and: - - "<>" - - "<>" - - and: - - "<>" - - equal: [develop, << pipeline.git.branch >>] - steps: - - gcp-oidc-authenticate: - service_account_email: GCP_SERVICE_ATTESTOR_ACCOUNT_EMAIL - - run: - name: Sign - command: | - VER=$(yq '.tools.binary_signer' mise.toml) - wget -O - "https://github.com/ethereum-optimism/binary_signer/archive/refs/tags/v${VER}.tar.gz" | tar xz - cd "binary_signer-${VER}/signer" - - IMAGE_PATH="<>/<>/<>:<>" - echo $IMAGE_PATH - pip3 install -r requirements.txt - - python3 ./sign_image.py --command="sign"\ - --attestor-project-name="$ATTESTOR_PROJECT_NAME"\ - --attestor-name="$ATTESTOR_NAME"\ - --image-path="$IMAGE_PATH"\ - --signer-logging-level="INFO"\ - --attestor-key-id="//cloudkms.googleapis.com/v1/projects/$ATTESTOR_PROJECT_NAME/locations/global/keyRings/$ATTESTOR_NAME-key-ring/cryptoKeys/$ATTESTOR_NAME-key/cryptoKeyVersions/1" - - # Verify newly published images (built on AMD machine) will run on ARM - check-cross-platform: - docker: - - image: <> - resource_class: arm.medium - parameters: - registry: - description: Docker registry - type: string - default: "us-docker.pkg.dev" - repo: - description: Docker repo - type: string - default: "oplabs-tools-artifacts/images" - op_component: - description: "Name of op-stack component (e.g. op-node)" - type: string - default: "" - docker_tag: - description: "Tag of docker image" - type: string - default: "<>" - steps: - - setup_remote_docker - - run: - name: "Verify Image Platform" - command: | - image_name="<>/<>/<>:<>" - echo "Retrieving Docker image manifest: $image_name" - MANIFEST=$(docker manifest inspect $image_name) - - echo "Verifying 'linux/arm64' is supported..." - SUPPORTED_PLATFORM=$(echo "$MANIFEST" | jq -r '.manifests[] | select(.platform.architecture == "arm64" and .platform.os == "linux")') - echo $SUPPORT_PLATFORM - if [ -z "$SUPPORTED_PLATFORM" ]; then - echo "Platform 'linux/arm64' not supported by this image" - exit 1 - fi - - run: - name: "Pull and run docker image" - command: | - image_name="<>/<>/<>:<>" - docker pull $image_name || exit 1 - docker run $image_name <> --version || exit 1 + - notify-failures-on-develop: + mentions: "@security-oncall" contracts-bedrock-tests: circleci_ip_ranges: true @@ -1320,7 +1053,6 @@ jobs: - utils/checkout-with-mise: checkout-method: full enable-mise-cache: true - - install-zstd - run: name: Check if test list is empty command: | @@ -1341,7 +1073,6 @@ jobs: name: Print forge version command: forge --version working_directory: packages/contracts-bedrock - - pull-artifacts-conditional - go-restore-cache: namespace: packages/contracts-bedrock/scripts/go-ffi - run: @@ -1369,19 +1100,18 @@ jobs: name: Print failed test traces command: just test-rerun environment: - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: <> working_directory: packages/contracts-bedrock when: on_fail - - when: - condition: always - steps: - - store_test_results: - path: packages/contracts-bedrock/results/results.xml + - store_test_results: + path: packages/contracts-bedrock/results + when: always - run: name: Lint forge test names command: just lint-forge-tests-check-no-build working_directory: packages/contracts-bedrock - - notify-failures-on-develop + - notify-failures-on-develop: + mentions: "@security-oncall" contracts-bedrock-heavy-fuzz-nightly: circleci_ip_ranges: true @@ -1393,7 +1123,6 @@ jobs: checkout-method: full enable-mise-cache: true - install-contracts-dependencies - - install-zstd - run: name: Print dependencies command: just dep-status @@ -1402,10 +1131,6 @@ jobs: name: Print forge version command: forge --version working_directory: packages/contracts-bedrock - - run: - name: Pull artifacts - command: bash scripts/ops/pull-artifacts.sh - working_directory: packages/contracts-bedrock - run: name: Build go-ffi command: just build-go-ffi @@ -1432,12 +1157,17 @@ jobs: key: golang-build-cache-contracts-bedrock-heavy-fuzz-{{ checksum "go.sum" }} paths: - "~/.cache/go-build" - - when: - condition: always - steps: - - store_test_results: - path: packages/contracts-bedrock/results/results.xml - - notify-failures-on-develop + # Store raw JUnit XML as artifact for debugging when store_test_results + # shows 0 results (see ethereum-optimism/optimism#19577) + - store_artifacts: + path: packages/contracts-bedrock/results + destination: junit-results + when: always + - store_test_results: + path: packages/contracts-bedrock/results + when: always + - notify-failures-on-develop: + mentions: "@security-oncall" # AI Contracts Test Maintenance System # Runbook: https://github.com/ethereum-optimism/optimism/blob/develop/ops/ai-eng/contracts-test-maintenance/docs/runbook.md @@ -1470,7 +1200,8 @@ jobs: channel: C050F1GUHDG event: always template: AI_PR_SLACK_TEMPLATE - - notify-failures-on-develop + - notify-failures-on-develop: + mentions: "@security-oncall" contracts-bedrock-coverage: circleci_ip_ranges: true @@ -1495,9 +1226,6 @@ jobs: checkout-method: full enable-mise-cache: true - install-contracts-dependencies - - install-zstd - - attach_workspace: - at: . - check-changed: patterns: contracts-bedrock - install-solc-compilers @@ -1509,7 +1237,6 @@ jobs: name: Print forge version command: forge --version working_directory: packages/contracts-bedrock - - pull-artifacts-conditional - run: name: Install lcov command: | @@ -1565,7 +1292,8 @@ jobs: - store_artifacts: path: packages/contracts-bedrock/failed-test-traces.log when: on_fail - - notify-failures-on-develop + - notify-failures-on-develop: + mentions: "@security-oncall" contracts-bedrock-tests-upgrade: circleci_ip_ranges: true @@ -1581,6 +1309,10 @@ jobs: fork_base_rpc: description: Fork Base RPC type: string + test_profile: + description: Profile to use for testing + type: string + default: ci features: description: Comma-separated list of features to enable (e.g., "OPTIMISM_PORTAL_INTEROP", "CUSTOM_GAS_TOKEN") type: string @@ -1592,9 +1324,6 @@ jobs: - utils/checkout-with-mise: enable-mise-cache: true - install-contracts-dependencies - - install-zstd - - attach_workspace: - at: . - check-changed: patterns: contracts-bedrock - install-solc-compilers @@ -1606,7 +1335,6 @@ jobs: name: Print forge version command: forge --version working_directory: packages/contracts-bedrock - - pull-artifacts-conditional - run: name: Write pinned block number for cache key command: | @@ -1630,7 +1358,7 @@ jobs: JUNIT_TEST_PATH: results/results.xml FOUNDRY_FUZZ_SEED: 42424242 FOUNDRY_FUZZ_RUNS: 1 - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: <> ETH_RPC_URL: <> FORK_OP_CHAIN: <> FORK_BASE_CHAIN: <> @@ -1643,7 +1371,7 @@ jobs: environment: FOUNDRY_FUZZ_SEED: 42424242 FOUNDRY_FUZZ_RUNS: 1 - FOUNDRY_PROFILE: ci + FOUNDRY_PROFILE: <> ETH_RPC_URL: <> FORK_OP_CHAIN: <> FORK_BASE_CHAIN: <> @@ -1663,12 +1391,17 @@ jobs: - store_artifacts: path: packages/contracts-bedrock/failed-test-traces.log when: on_fail - - when: - condition: always - steps: - - store_test_results: - path: packages/contracts-bedrock/results/results.xml - - notify-failures-on-develop + # Store raw JUnit XML as artifact for debugging when store_test_results + # shows 0 results (see ethereum-optimism/optimism#19577) + - store_artifacts: + path: packages/contracts-bedrock/results + destination: junit-results + when: always + - store_test_results: + path: packages/contracts-bedrock/results + when: always + - notify-failures-on-develop: + mentions: "@security-oncall" contracts-bedrock-upload: machine: true @@ -1686,53 +1419,6 @@ jobs: command: just update-selectors working_directory: packages/contracts-bedrock - contracts-bedrock-checks: - docker: - - image: <> - resource_class: xlarge - steps: - - utils/checkout-with-mise: - enable-mise-cache: true - - install-contracts-dependencies - - attach_workspace: - at: . - - check-changed: - patterns: contracts-bedrock - - get-target-branch - - run: - name: print forge version - command: forge --version - - run-contracts-check: - command: check-kontrol-summaries-unchanged - - run-contracts-check: - command: semgrep-test-validity-check - - run-contracts-check: - command: semgrep - - run-contracts-check: - command: semver-lock-no-build - - run-contracts-check: - command: semver-diff-check-no-build - - run-contracts-check: - command: validate-deploy-configs - - run-contracts-check: - command: lint - - run-contracts-check: - command: snapshots-check-no-build - - run-contracts-check: - command: interfaces-check-no-build - - run-contracts-check: - command: reinitializer-check-no-build - - run-contracts-check: - command: size-check - - run-contracts-check: - command: unused-imports-check-no-build - - run-contracts-check: - command: strict-pragma-check-no-build - - run-contracts-check: - command: validate-spacers-no-build - - run-contracts-check: - command: opcm-upgrade-checks-no-build - contracts-bedrock-checks-fast: docker: - image: <> @@ -1740,22 +1426,18 @@ jobs: steps: - utils/checkout-with-mise: enable-mise-cache: true - - install-zstd - install-contracts-dependencies - check-changed: patterns: contracts-bedrock - run: name: Print forge version command: forge --version - - run: - name: Pull cached artifacts - command: bash scripts/ops/pull-artifacts.sh - working_directory: packages/contracts-bedrock - run: name: Run checks command: just check-fast working_directory: packages/contracts-bedrock - - notify-failures-on-develop + - notify-failures-on-develop: + mentions: "@security-oncall" todo-issues: parameters: @@ -1806,7 +1488,7 @@ jobs: name: Fuzz no_output_timeout: 15m command: | - make fuzz + just fuzz working_directory: "<>" - go-save-cache: namespace: fuzz-<> @@ -1833,7 +1515,7 @@ jobs: - run: name: run Go linter command: | - make lint-go + just lint-go - save_cache: key: golangci-v1-{{ checksum ".golangci.yaml" }} paths: @@ -1851,7 +1533,7 @@ jobs: namespace: sysgo-go-binaries - run: name: Build Go binaries for sysgo - command: make cannon op-program + command: just cannon op-program - go-save-cache: namespace: sysgo-go-binaries - persist_to_workspace: @@ -1872,7 +1554,20 @@ jobs: - run: name: check op-geth version command: | - make check-op-geth-version + just check-op-geth-version + + check-nut-locks: + docker: + - image: <> + resource_class: small + steps: + - utils/checkout-with-mise: + checkout-method: blobless + enable-mise-cache: true + - run: + name: check nut locks + command: | + go run ./ops/scripts/check-nut-locks go-tests: parameters: @@ -1918,7 +1613,7 @@ jobs: - restore_cache: key: go-tests-v2-{{ checksum "go.mod" }} - run: - name: Run Go tests via Makefile + name: Run Go tests no_output_timeout: <> command: | <> @@ -1926,7 +1621,7 @@ jobs: # set to less than number CPUs (xlarge Docker is 16 CPU) so there's some buffer for things # like Geth export PARALLEL=12 - make <> + just <> - save_cache: key: go-tests-v2-{{ checksum "go.mod" }} paths: @@ -1936,12 +1631,7 @@ jobs: path: ./tmp/test-results - run: name: Compress test logs - command: | - if [ -n "$CIRCLE_NODE_TOTAL" ] && [ "$CIRCLE_NODE_TOTAL" -gt 1 ]; then - tar -czf testlogs-${CIRCLE_NODE_INDEX}-of-${CIRCLE_NODE_TOTAL}.tar.gz -C ./tmp testlogs - else - tar -czf testlogs.tar.gz -C ./tmp testlogs - fi + command: tar -czf testlogs.tar.gz -C ./tmp testlogs when: always - run: name: Clean up op-deployer artifacts @@ -1949,7 +1639,7 @@ jobs: rm -rf ~/.op-deployer/* when: always - store_artifacts: - path: testlogs*.tar.gz + path: testlogs.tar.gz when: always - when: condition: "<>" @@ -1992,155 +1682,59 @@ jobs: - attach_workspace: at: . - run: - name: build op-program-client - command: make op-program-client - working_directory: op-program - - run: - name: build op-program-host - command: make op-program-host - working_directory: op-program - - run: - name: build cannon - command: make cannon - - run: - name: run tests - no_output_timeout: <> - command: | - <> - export TEST_TIMEOUT=<> - make go-tests-fraud-proofs-ci - - codecov/upload: - disable_search: true - files: ./coverage.out - - store_test_results: - path: ./tmp/test-results - - run: - name: Compress test logs - command: tar -czf testlogs.tar.gz -C ./tmp testlogs - when: always - - store_artifacts: - path: testlogs.tar.gz - when: always - - when: - condition: "<>" - steps: - - notify-failures-on-develop: - mentions: "<>" - - op-acceptance-sync-tests-docker: - parameters: - gate: - description: The gate to run the acceptance tests against. This gate should be defined in op-acceptance-tests/acceptance-tests.yaml. - type: string - default: "" - no_output_timeout: - description: Timeout for when CircleCI kills the job if there's no output - type: string - default: 30m - # Optional sync test configuration parameters - network_preset: - description: Network preset - type: string - default: "" - l2_cl_syncmode: - description: L2 CL Sync mode - can be EL Sync or CL Sync - type: string - default: "" - resource_class: xlarge - docker: - - image: <> - circleci_ip_ranges: true - steps: - - utils/checkout-with-mise: - checkout-method: blobless - enable-mise-cache: true - # Restore cached Go modules - - restore_cache: - keys: - - go-mod-v1-{{ checksum "go.sum" }} - - go-mod-v1- - # Download Go dependencies - - run: - name: Download Go dependencies - working_directory: op-acceptance-tests - command: go mod download - - run: - name: Lint/Vet/Build op-acceptance-tests/cmd - working_directory: op-acceptance-tests - command: | - just cmd-check - # Persist schedule name into env var - - run: - name: Persist schedule name into env var - command: | - echo 'export CIRCLECI_PIPELINE_SCHEDULE_NAME="<< pipeline.schedule.name >>"' >> $BASH_ENV - echo 'export CIRCLECI_PARAMETERS_SYNC_TEST_OP_NODE_DISPATCH="<< pipeline.parameters.c-sync_test_op_node_dispatch >>"' >> $BASH_ENV - # Run the acceptance tests - - run: - name: Run acceptance tests (gate=<>) - working_directory: op-acceptance-tests - no_output_timeout: 1h - environment: - GOFLAGS: "-mod=mod" - GO111MODULE: "on" - GOGC: "0" - # Optional sync test configuration environment variables (only set if parameters are provided) - NETWORK_PRESET: "<>" - L2_CL_SYNCMODE: "<>" - command: | - # Run the tests - LOG_LEVEL=debug just acceptance-test "" "<>" - - run: - name: Print results (summary) - working_directory: op-acceptance-tests - command: | - LOG_DIR=$(ls -td -- logs/* | head -1) - cat "$LOG_DIR/summary.log" || true + name: build op-program-client + command: just op-program-client + working_directory: op-program - run: - name: Print results (failures) - working_directory: op-acceptance-tests - command: | - LOG_DIR=$(ls -td -- logs/* | head -1) - cat "$LOG_DIR/failed/*.log" || true - when: on_fail + name: build op-program-host + command: just op-program-host + working_directory: op-program - run: - name: Print results (all) - working_directory: op-acceptance-tests + name: build cannon + command: just cannon + - run: + name: run tests + no_output_timeout: <> command: | - LOG_DIR=$(ls -td -- logs/* | head -1) - cat "$LOG_DIR/all.log" || true + <> + export TEST_TIMEOUT=<> + just go-tests-fraud-proofs-ci + - codecov/upload: + disable_search: true + files: ./coverage.out + - store_test_results: + path: ./tmp/test-results - run: - name: Generate JUnit XML test report for CircleCI - working_directory: op-acceptance-tests + name: Compress test logs + command: tar -czf testlogs.tar.gz -C ./tmp testlogs + when: always + - store_artifacts: + path: testlogs.tar.gz when: always - command: | - LOG_DIR=$(ls -td -- logs/* | head -1) - gotestsum --junitfile results/results.xml --raw-command cat $LOG_DIR/raw_go_events.log || true - # Save the module cache for future runs - - save_cache: - key: go-mod-v1-{{ checksum "go.sum" }} - paths: - - "/go/pkg/mod" - # Store test results and artifacts - - when: - condition: always - steps: - - store_test_results: - path: ./op-acceptance-tests/results - when: - condition: always + condition: "<>" steps: - - store_artifacts: - path: ./op-acceptance-tests/logs - - notify-failures-on-develop: - mentions: "@changwan <@U08L5U8070U>" # @changwan @Anton Evangelatov + - notify-failures-on-develop: + mentions: "<>" op-acceptance-tests: parameters: gate: description: The gate to run the acceptance tests against. This gate should be defined in op-acceptance-tests/acceptance-tests.yaml. type: string - default: "" + default: "base" + l2_cl_kind: + description: "L2 consensus layer client (op-node or kona)" + type: string + default: "op-node" + l2_el_kind: + description: "L2 execution layer client (op-geth or op-reth)" + type: string + default: "op-geth" + run_all: + description: When true, run all tests in gateless mode. + type: boolean + default: false no_output_timeout: description: Timeout for when CircleCI kills the job if there's no output type: string @@ -2155,19 +1749,21 @@ jobs: enable-mise-cache: true - attach_workspace: at: . - # Build kona-node for the acceptance tests. This automatically gets kona from the cache. - - rust-build: - directory: rust - profile: "release" - run: name: Configure Rust binary paths (sysgo) command: | ROOT_DIR="$(pwd)" BIN_DIR="$ROOT_DIR/.circleci-cache/rust-binaries" + echo "export RUST_BINARY_PATH_KONA_NODE=$ROOT_DIR/rust/target/release/kona-node" >> "$BASH_ENV" - echo "export RUST_BINARY_PATH_KONA_SUPERVISOR=$ROOT_DIR/rust/target/release/kona-supervisor" >> "$BASH_ENV" + echo "export OP_RETH_EXEC_PATH=$ROOT_DIR/rust/target/release/op-reth" >> "$BASH_ENV" echo "export RUST_BINARY_PATH_OP_RBUILDER=$BIN_DIR/op-rbuilder" >> "$BASH_ENV" echo "export RUST_BINARY_PATH_ROLLUP_BOOST=$BIN_DIR/rollup-boost" >> "$BASH_ENV" + - run: + name: Configure L2 stack + command: | + echo "export DEVSTACK_L2CL_KIND=<>" >> "$BASH_ENV" + echo "export DEVSTACK_L2EL_KIND=<>" >> "$BASH_ENV" # Restore cached Go modules - restore_cache: keys: @@ -2190,16 +1786,17 @@ jobs: command: go test -v -c -o /dev/null $(go list -f '{{if .TestGoFiles}}{{.ImportPath}}{{end}}' ./tests/...) # Run the acceptance tests (if the devnet is running) - run: - name: Run acceptance tests (gate=<>) + name: Run acceptance tests working_directory: op-acceptance-tests no_output_timeout: 1h command: | - if [[ "<>" == "" ]]; then + if <>; then echo "Running in gateless mode - auto-discovering all tests in ./op-acceptance-tests/..." + LOG_LEVEL=info just acceptance-test-all else echo "Running in gate mode (gate=<>)" + LOG_LEVEL=info just acceptance-test "<>" fi - LOG_LEVEL=info just acceptance-test "" "<>" - run: name: Print results (summary) working_directory: op-acceptance-tests @@ -2253,6 +1850,14 @@ jobs: gate: type: string default: "flake-shake" + l2_cl_kind: + description: "L2 consensus layer client (op-node or kona)" + type: string + default: "op-node" + l2_el_kind: + description: "L2 execution layer client (op-geth or op-reth)" + type: string + default: "op-geth" machine: image: ubuntu-2404:current resource_class: xlarge @@ -2269,9 +1874,14 @@ jobs: ROOT_DIR="$(pwd)" BIN_DIR="$ROOT_DIR/.circleci-cache/rust-binaries" echo "export RUST_BINARY_PATH_KONA_NODE=$BIN_DIR/kona-node" >> "$BASH_ENV" - echo "export RUST_BINARY_PATH_KONA_SUPERVISOR=$BIN_DIR/kona-supervisor" >> "$BASH_ENV" + echo "export OP_RETH_EXEC_PATH=$ROOT_DIR/rust/target/release/op-reth" >> "$BASH_ENV" echo "export RUST_BINARY_PATH_OP_RBUILDER=$BIN_DIR/op-rbuilder" >> "$BASH_ENV" echo "export RUST_BINARY_PATH_ROLLUP_BOOST=$BIN_DIR/rollup-boost" >> "$BASH_ENV" + - run: + name: Configure L2 stack + command: | + echo "export DEVSTACK_L2CL_KIND=<>" >> "$BASH_ENV" + echo "export DEVSTACK_L2EL_KIND=<>" >> "$BASH_ENV" - restore_cache: keys: - go-mod-v1-{{ checksum "go.sum" }} @@ -2303,7 +1913,6 @@ jobs: --flake-shake \ --flake-shake-iterations "$FLAKE_SHAKE_ITERATIONS" \ --log.level debug \ - --orchestrator sysgo \ --logdir "./$OUTPUT_DIR" - persist_to_workspace: root: op-acceptance-tests @@ -2462,13 +2071,13 @@ jobs: sudo apt-get install -y binutils-mips-linux-gnu - run: name: Build cannon - command: make cannon + command: just cannon - run: name: Build op-program - command: make op-program + command: just op-program - run: name: Sanitize op-program client - command: make sanitize-program GUEST_PROGRAM=../op-program/bin/op-program-client64.elf + command: GUEST_PROGRAM=../op-program/bin/op-program-client64.elf just sanitize-program working_directory: cannon cannon-prestate: @@ -2480,7 +2089,21 @@ jobs: enable-mise-cache: true - run: name: Build prestates - command: make -j reproducible-prestate + command: just reproducible-prestate + - run: + name: Capture kona prestate debug artifacts + command: | + mkdir -p /tmp/prestate-debug + + # chainList.json from the checkout (baked into the binary via include_str!) + cp rust/kona/crates/protocol/registry/etc/chainList.json /tmp/prestate-debug/chainList.json + + # The kona-client ELF binaries (for offline inspection with strings/objdump) + cp rust/kona/prestate-artifacts-cannon/kona-client-elf /tmp/prestate-debug/kona-client-elf-cannon 2>/dev/null || true + cp rust/kona/prestate-artifacts-cannon-interop/kona-client-elf /tmp/prestate-debug/kona-client-elf-cannon-interop 2>/dev/null || true + - store_artifacts: + path: /tmp/prestate-debug + destination: prestate-debug - persist_to_workspace: root: . paths: @@ -2488,6 +2111,15 @@ jobs: - "op-program/bin/meta*" - "rust/kona/prestate-artifacts-*/" + # Aggregator job - allows downstream jobs to depend on a single memory-all job. + memory-all: + docker: + - image: <> + resource_class: small + steps: + - run: + name: All memory-all tests passed + command: echo "All memory-all acceptance test variants passed" # Aggregator job - allows downstream jobs to depend on a single job instead of listing all build jobs. rust-binaries-for-sysgo: @@ -2499,7 +2131,6 @@ jobs: name: All Rust binaries ready command: echo "All Rust binaries built and persisted to workspace" - # ============================================================================ publish-cannon-prestates: @@ -2574,7 +2205,8 @@ jobs: enable-mise-cache: true - run: name: Verify reproducibility - command: make -C op-program verify-reproducibility + command: just verify-reproducibility + working_directory: op-program - store_artifacts: path: ./op-program/temp/logs when: always @@ -2591,10 +2223,11 @@ jobs: - setup_remote_docker - run: name: Build cannon - command: make cannon + command: just cannon - run: name: Verify the Cannon STF - command: make -C ./cannon cannon-stf-verify + command: just cannon-stf-verify + working_directory: cannon - notify-failures-on-develop: mentions: "@proofs-team" @@ -2661,8 +2294,12 @@ jobs: - run: name: Run Analyzer command: | - make run-vm-compat + just run-vm-compat working_directory: op-program + - store_artifacts: + path: op-program/bin/vm-compat-output/vm-compat-findings.json + destination: vm-compat-findings.json + when: always op-program-compat: docker: @@ -2675,7 +2312,7 @@ jobs: - run: name: Verify Compatibility command: | - make verify-compat + just verify-compat working_directory: op-program check-generated-mocks-op-node: @@ -2690,7 +2327,7 @@ jobs: patterns: op-node - run: name: check-generated-mocks - command: make generate-mocks-op-node && git diff --exit-code + command: just generate-mocks-op-node && git diff --exit-code check-generated-mocks-op-service: docker: @@ -2704,7 +2341,7 @@ jobs: patterns: op-service - run: name: check-generated-mocks - command: make generate-mocks-op-service && git diff --exit-code + command: just generate-mocks-op-service && git diff --exit-code op-deployer-forge-version: docker: @@ -2749,7 +2386,8 @@ jobs: } }' working_directory: ./packages/contracts-bedrock - - notify-failures-on-develop + - notify-failures-on-develop: + mentions: "@security-oncall" publish-contract-artifacts: docker: @@ -2767,10 +2405,6 @@ jobs: enable-mise-cache: true - install-contracts-dependencies - install-zstd - - run: - name: Pull artifacts - command: bash scripts/ops/pull-artifacts.sh - working_directory: packages/contracts-bedrock - run: name: Build contracts environment: @@ -2782,7 +2416,6 @@ jobs: command: bash scripts/ops/publish-artifacts.sh working_directory: packages/contracts-bedrock - go-release: parameters: module: @@ -2891,7 +2524,7 @@ jobs: - run: name: Collect devnet metrics for op-acceptance-tests command: | - ./devnet-sdk/scripts/metrics-collect-authorship.sh op-acceptance-tests/tests > .metrics--authorship--op-acceptance-tests + ./op-acceptance-tests/scripts/metrics-collect-authorship.sh op-acceptance-tests/tests > .metrics--authorship--op-acceptance-tests echo "Wrote file .metrics--authorship--op-acceptance-tests" - gcp-cli/install - gcp-oidc-authenticate: @@ -2941,7 +2574,9 @@ workflows: and: - equal: ["", << pipeline.git.tag >>] - or: - - equal: ["webhook", << pipeline.trigger_source >>] + - and: + - equal: ["webhook", << pipeline.trigger_source >>] + - << pipeline.parameters.c-non_docs_changes >> - and: - equal: [true, <>] - equal: ["api", << pipeline.trigger_source >>] @@ -2979,12 +2614,32 @@ workflows: - OPCM_V2 - OPCM_V2,CUSTOM_GAS_TOKEN - OPCM_V2,OPTIMISM_PORTAL_INTEROP + - OPCM_V2,ZK_DISPUTE_GAME + - OPCM_V2,CANNON_KONA context: - circleci-repo-readonly-authenticated-github-token - slack + # On PRs, run tests with lite profile for better build times. - contracts-bedrock-tests: name: contracts-bedrock-tests <> test_list: find test -name "*.t.sol" + test_profile: liteci + features: <> + matrix: + parameters: + features: *features_matrix + context: + - circleci-repo-readonly-authenticated-github-token + - slack + check_changed_patterns: contracts-bedrock,op-node + filters: + branches: + ignore: develop + # On develop, run tests with ci profile to mirror production. + - contracts-bedrock-tests: + name: contracts-bedrock-tests-develop <> + test_list: find test -name "*.t.sol" + test_profile: ci features: <> matrix: parameters: @@ -2993,6 +2648,9 @@ workflows: - circleci-repo-readonly-authenticated-github-token - slack check_changed_patterns: contracts-bedrock,op-node + filters: + branches: + only: develop - contracts-bedrock-coverage: # Generate coverage reports. name: contracts-bedrock-coverage <> @@ -3005,11 +2663,30 @@ workflows: context: - circleci-repo-readonly-authenticated-github-token - slack + # On PRs, run upgrade tests with lite profile for better build times. - contracts-bedrock-tests-upgrade: name: contracts-bedrock-tests-upgrade op-mainnet <> fork_op_chain: op fork_base_chain: mainnet fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io + test_profile: liteci + features: <> + matrix: + parameters: + features: *features_matrix + context: + - circleci-repo-readonly-authenticated-github-token + - slack + filters: + branches: + ignore: develop + # On develop, run upgrade tests with ci profile to mirror production. + - contracts-bedrock-tests-upgrade: + name: contracts-bedrock-tests-upgrade-develop op-mainnet <> + fork_op_chain: op + fork_base_chain: mainnet + fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io + test_profile: ci features: <> matrix: parameters: @@ -3017,23 +2694,41 @@ workflows: context: - circleci-repo-readonly-authenticated-github-token - slack + filters: + branches: + only: develop + # On PRs, run chain-specific upgrade tests with lite profile for better build times. - contracts-bedrock-tests-upgrade: name: contracts-bedrock-tests-upgrade <>-mainnet fork_op_chain: <> fork_base_chain: mainnet fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io + test_profile: liteci matrix: parameters: - fork_op_chain: ["base", "ink", "unichain"] + fork_op_chain: ["op", "ink", "unichain"] context: - circleci-repo-readonly-authenticated-github-token - slack - - contracts-bedrock-checks: - requires: - - contracts-bedrock-build + filters: + branches: + ignore: develop + # On develop, run chain-specific upgrade tests with ci profile to mirror production. + - contracts-bedrock-tests-upgrade: + name: contracts-bedrock-tests-upgrade-develop <>-mainnet + fork_op_chain: <> + fork_base_chain: mainnet + fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io + test_profile: ci + matrix: + parameters: + fork_op_chain: ["op", "ink", "unichain"] context: - circleci-repo-readonly-authenticated-github-token - slack + filters: + branches: + only: develop - contracts-bedrock-checks-fast: context: - circleci-repo-readonly-authenticated-github-token @@ -3068,6 +2763,9 @@ workflows: - check-op-geth-version: context: - circleci-repo-readonly-authenticated-github-token + - check-nut-locks: + context: + - circleci-repo-readonly-authenticated-github-token - fuzz-golang: name: fuzz-golang-<> on_changes: <> @@ -3126,6 +2824,7 @@ workflows: requires: - contracts-bedrock-build - cannon-prestate + - go-binaries-for-sysgo context: - circleci-repo-readonly-authenticated-github-token - slack @@ -3146,18 +2845,6 @@ workflows: - sanitize-op-program context: - circleci-repo-readonly-authenticated-github-tokens - - docker-build: - name: <>-docker-build - docker_tags: <>,<> - save_image_tag: <> - matrix: - parameters: - docker_name: - - op-deployer - context: - - circleci-repo-readonly-authenticated-github-token - requires: - - contracts-bedrock-build - cannon-prestate: context: - circleci-repo-readonly-authenticated-github-token @@ -3190,24 +2877,19 @@ workflows: name: shell-check # We don't need the `exclude` key as the orb detects the `.shellcheckrc` dir: . - ignore-dirs: ./packages/contracts-bedrock/lib + ignore-dirs: | + ./packages/contracts-bedrock/lib + ./docs/public-docs context: - circleci-repo-readonly-authenticated-github-token # Acceptance test jobs (formerly in separate acceptance-tests workflow) - - rust-build-binary: &cannon-kona-host - name: cannon-kona-host - directory: rust - profile: "release" - binary: "kona-host" - save_cache: true - context: - - circleci-repo-readonly-authenticated-github-token - - rust-build-binary: &kona-build-release - name: kona-build-release + - rust-build-binary: + name: rust-workspace-binaries directory: rust profile: "release" - features: "default" save_cache: true + persist_to_workspace: true + rust_files_changed: << pipeline.parameters.c-rust_files_changed >> context: - circleci-repo-readonly-authenticated-github-token - rust-build-submodule: &rust-build-op-rbuilder @@ -3227,14 +2909,47 @@ workflows: - circleci-repo-readonly-authenticated-github-token - rust-binaries-for-sysgo: requires: - - kona-build-release + - rust-workspace-binaries - rust-build-op-rbuilder - rust-build-rollup-boost - - go-binaries-for-sysgo - # IN-MEMORY (all) + - go-binaries-for-sysgo: + context: + - circleci-repo-readonly-authenticated-github-token + # IN-MEMORY (all) - op-node/op-geth + - op-acceptance-tests: + name: memory-all-opn-op-geth + run_all: true + no_output_timeout: 120m # Allow longer runs for memory-all gate + context: + - circleci-repo-readonly-authenticated-github-token + - slack + - discord + requires: + - contracts-bedrock-build + - cannon-prestate + - rust-binaries-for-sysgo + - go-binaries-for-sysgo + # IN-MEMORY (all) - op-node/op-reth + - op-acceptance-tests: + name: memory-all-opn-op-reth + gate: "base" + l2_el_kind: op-reth + no_output_timeout: 120m # Allow longer runs for memory-all gate + context: + - circleci-repo-readonly-authenticated-github-token + - slack + - discord + requires: + - contracts-bedrock-build + - cannon-prestate + - rust-binaries-for-sysgo + - go-binaries-for-sysgo + # IN-MEMORY (all) - kona/op-reth - op-acceptance-tests: - name: memory-all - gate: "" # Empty gate = gateless mode + name: memory-all-kona-op-reth + gate: "base" + l2_cl_kind: kona + l2_el_kind: op-reth no_output_timeout: 120m # Allow longer runs for memory-all gate context: - circleci-repo-readonly-authenticated-github-token @@ -3243,9 +2958,14 @@ workflows: requires: - contracts-bedrock-build - cannon-prestate - - cannon-kona-host - rust-binaries-for-sysgo - go-binaries-for-sysgo + # Aggregator for all memory-all acceptance test variants + - memory-all: + requires: + - memory-all-opn-op-geth + - memory-all-opn-op-reth + - memory-all-kona-op-reth # Generate flaky test report - generate-flaky-report: name: generate-flaky-tests-report @@ -3316,46 +3036,6 @@ workflows: only: /^(da-server|cannon|ufm-[a-z0-9\-]*|op-[a-z0-9\-]*)\/v.*/ branches: ignore: /.*/ - - contracts-bedrock-build: - context: - - circleci-repo-readonly-authenticated-github-token - requires: - - initialize - filters: - tags: - only: /^op-deployer.*/ # ensure contract artifacts are embedded in op-deployer binary - branches: - ignore: /.*/ - - docker-build: - matrix: - parameters: - docker_name: - - op-deployer - name: <>-docker-release - docker_tags: <> - platforms: "linux/amd64,linux/arm64" - publish: true - release: true - filters: - tags: - only: /^<>\/v.*/ - branches: - ignore: /.*/ - context: - - oplabs-gcr-release - requires: - - initialize - - contracts-bedrock-build - - check-cross-platform: - matrix: - parameters: - op_component: - - op-deployer - name: <>-cross-platform - requires: - - op-deployer-docker-release - context: - - circleci-repo-readonly-authenticated-github-token - cannon-prestate: filters: tags: @@ -3389,21 +3069,14 @@ workflows: - slack - circleci-repo-readonly-authenticated-github-token - develop-publish-contract-artifacts: - when: - or: - - and: - - equal: ["develop", <>] - - equal: ["webhook", << pipeline.trigger_source >>] - - and: - - equal: - [ - true, - <>, - ] - - equal: ["api", << pipeline.trigger_source >>] + publish-contract-artifacts-on-tag: jobs: - publish-contract-artifacts: + filters: + tags: + only: /^op-contracts\/v.*/ + branches: + ignore: /.*/ context: - circleci-repo-readonly-authenticated-github-token @@ -3487,42 +3160,6 @@ workflows: - slack - circleci-repo-readonly-authenticated-github-token - scheduled-docker-publish: - when: - or: - - equal: [build_daily, <>] - # Trigger on manual triggers if explicitly requested - - equal: [true, << pipeline.parameters.c-docker_publish_dispatch >>] - jobs: - - contracts-bedrock-build: - context: - - circleci-repo-readonly-authenticated-github-token - - docker-build: - matrix: - parameters: - docker_name: - - op-deployer - name: <>-docker-publish - docker_tags: <>,<> - platforms: "linux/amd64,linux/arm64" - publish: true - context: - - oplabs-gcr - - slack - - circleci-repo-readonly-authenticated-github-token - requires: - - contracts-bedrock-build - - check-cross-platform: - matrix: - parameters: - op_component: - - op-deployer - name: <>-cross-platform - requires: - - <>-docker-publish - context: - - circleci-repo-readonly-authenticated-github-token - scheduled-flake-shake: when: or: @@ -3543,6 +3180,7 @@ workflows: directory: rust needs_clang: true profile: "release" + persist_to_workspace: true context: - circleci-repo-readonly-authenticated-github-token - rust-build-submodule: *rust-build-op-rbuilder @@ -3560,6 +3198,8 @@ workflows: - cannon-prestate - rust-binaries-for-sysgo - op-acceptance-tests-flake-shake-report: + context: + - circleci-repo-readonly-authenticated-github-token requires: - op-acceptance-tests-flake-shake - op-acceptance-tests-flake-shake-promote: @@ -3594,42 +3234,6 @@ workflows: context: - circleci-repo-optimism - scheduled-sync-test-op-node: - when: - or: - - equal: [build_daily, <>] - # Trigger on manual triggers if explicitly requested - - equal: [true, << pipeline.parameters.c-sync_test_op_node_dispatch >>] - jobs: - - contracts-bedrock-build: # needed for sysgo tests - build_args: --skip test - context: - - circleci-repo-readonly-authenticated-github-token - - cannon-prestate: # needed for sysgo tests - context: - - circleci-repo-readonly-authenticated-github-token - - op-acceptance-sync-tests-docker: - name: "sync-test-<>-daily-<>" - gate: sync-test-op-node - no_output_timeout: 30m - context: - - circleci-repo-readonly-authenticated-github-token - - slack - requires: - - contracts-bedrock-build - - cannon-prestate - matrix: - parameters: - network_preset: - [ - "op-sepolia", - "base-sepolia", - "unichain-sepolia", - "op-mainnet", - "base-mainnet", - ] - l2_cl_syncmode: ["consensus-layer", "execution-layer"] - scheduled-heavy-fuzz-tests: when: or: diff --git a/.circleci/continue/rust-ci.yml b/.circleci/continue/rust-ci.yml index 852056d981d62..e244492813dc1 100644 --- a/.circleci/continue/rust-ci.yml +++ b/.circleci/continue/rust-ci.yml @@ -11,7 +11,7 @@ orbs: parameters: c-default_docker_image: type: string - default: cimg/base:2024.01 + default: cimg/base:2026.03 c-base_image: type: string default: default @@ -21,6 +21,88 @@ parameters: c-go-cache-version: type: string default: "v0.0" + c-rust_changes_detected: + type: boolean + default: false + c-non_docs_changes: + type: boolean + default: false + # Passthrough declarations for setup config parameters. + # CircleCI forwards all explicitly-passed pipeline parameters to continuation configs. + # Without these declarations, manually triggered pipelines fail with "Unexpected argument(s)". + # These are not referenced by any job — the c- prefixed versions above are used instead. + default_docker_image: + type: string + default: cimg/base:2026.03 + base_image: + type: string + default: default + main_dispatch: + type: boolean + default: true + fault_proofs_dispatch: + type: boolean + default: false + reproducibility_dispatch: + type: boolean + default: false + kontrol_dispatch: + type: boolean + default: false + cannon_full_test_dispatch: + type: boolean + default: false + sdk_dispatch: + type: boolean + default: false + docker_publish_dispatch: + type: boolean + default: false + stale_check_dispatch: + type: boolean + default: false + contracts_coverage_dispatch: + type: boolean + default: false + heavy_fuzz_dispatch: + type: boolean + default: false + sync_test_op_node_dispatch: + type: boolean + default: false + ai_contracts_test_dispatch: + type: boolean + default: false + rust_ci_dispatch: + type: boolean + default: false + rust_e2e_dispatch: + type: boolean + default: false + github-event-type: + type: string + default: "__not_set__" + github-event-action: + type: string + default: "__not_set__" + github-event-base64: + type: string + default: "__not_set__" + devnet-metrics-collect: + type: boolean + default: false + flake-shake-dispatch: + type: boolean + default: false + flake-shake-iterations: + type: integer + default: 300 + flake-shake-workers: + type: integer + default: 50 + go-cache-version: + type: string + default: "v0.0" # ============================================================================ # COMMANDS @@ -61,7 +143,10 @@ commands: - run: name: Install Rust toolchain (<< parameters.channel >>) command: | - rustup default << parameters.toolchain_version >> + TOOLCHAIN="<< parameters.toolchain_version >>" + + rustup toolchain install "$TOOLCHAIN" + rustup default "$TOOLCHAIN" if [ -n "<< parameters.components >>" ]; then rustup component add << parameters.components >> @@ -79,13 +164,13 @@ commands: echo "export CARGO_HOME=${MISE_CARGO_HOME}" >> "$BASH_ENV" echo "export RUSTUP_HOME=${MISE_RUSTUP_HOME}" >> "$BASH_ENV" echo "source ${MISE_CARGO_HOME}/env" >> "$BASH_ENV" + echo "export CARGO_INCREMENTAL=0" >> "$BASH_ENV" - run: name: Configure Rust binary paths (sysgo) command: | ROOT_DIR="$(pwd)" BIN_DIR="$ROOT_DIR/.circleci-cache/rust-binaries" echo "export RUST_BINARY_PATH_KONA_NODE=$ROOT_DIR/rust/target/release/kona-node" >> "$BASH_ENV" - echo "export RUST_BINARY_PATH_KONA_SUPERVISOR=$ROOT_DIR/rust/target/release/kona-supervisor" >> "$BASH_ENV" echo "export RUST_BINARY_PATH_OP_RBUILDER=$BIN_DIR/op-rbuilder" >> "$BASH_ENV" echo "export RUST_BINARY_PATH_ROLLUP_BOOST=$BIN_DIR/rollup-boost" >> "$BASH_ENV" @@ -117,7 +202,7 @@ commands: version: description: "Version of the cache" type: string - default: "15" + default: "16" profile: description: "Profile to restore the cache for" type: string @@ -324,7 +409,7 @@ jobs: command: description: "Format check command to run" type: string - default: "cargo +nightly fmt --all --check" + default: "just fmt-check" docker: - image: <> resource_class: medium @@ -334,10 +419,10 @@ jobs: - rust-prepare-and-restore-cache: &fmt-cache-args directory: <> prefix: <>-fmt - - rust-install-toolchain: - channel: nightly - toolchain_version: nightly - components: rustfmt + - run: + name: Install nightly toolchain + working_directory: <> + command: just install-nightly - run: name: Check formatting working_directory: <> @@ -514,11 +599,7 @@ jobs: command: description: "Doc build command to run" type: string - default: "cargo +nightly doc --workspace --all-features --no-deps --document-private-items" - rustdocflags: - description: "RUSTDOCFLAGS environment variable" - type: string - default: "--cfg docsrs -D warnings --show-type-layout --generate-link-to-definition -Zunstable-options" + default: "just lint-docs" docker: - image: <> resource_class: xlarge @@ -529,15 +610,14 @@ jobs: directory: <> prefix: <>-docs features: "all" - - rust-install-toolchain: - channel: nightly - toolchain_version: nightly + - run: + name: Install nightly toolchain + working_directory: <> + command: just install-nightly - run: name: Build documentation working_directory: <> no_output_timeout: 30m - environment: - RUSTDOCFLAGS: <> command: <> - rust-save-build-cache: *docs-cache-args @@ -605,7 +685,7 @@ jobs: - run: name: Install nextest command: | - command -v cargo-nextest >/dev/null || cargo binstall --no-confirm cargo-nextest + command -v cargo-nextest >/dev/null || cargo binstall --locked --no-confirm cargo-nextest - run: name: Run cargo tests working_directory: <> @@ -633,9 +713,10 @@ jobs: directory: <> prefix: <>-udeps profile: "release" - - rust-install-toolchain: - channel: nightly - toolchain_version: nightly + - run: + name: Install nightly toolchain + working_directory: <> + command: just install-nightly - install-cargo-binstall - run: name: Install cargo-udeps @@ -756,53 +837,6 @@ jobs: cargo run --bin op-reth --features "dev" --manifest-path rust/op-reth/bin/Cargo.toml -- test-vectors compact --read - rust-save-build-cache: *op-reth-compact-cache - # OP-Reth Windows cross-compile check - op-reth-windows-check: - docker: - - image: <> - resource_class: xlarge - steps: - - utils/checkout-with-mise: - checkout-method: blobless - - rust-prepare-and-restore-cache: &op-reth-windows-cache - directory: rust - prefix: op-reth-windows - profile: debug - - run: - name: Install mingw-w64 - command: sudo apt-get update && sudo apt-get install -y mingw-w64 - - run: - name: Check OP-Reth Windows build - working_directory: rust - no_output_timeout: 40m - command: just --justfile op-reth/justfile check-windows - - rust-save-build-cache: *op-reth-windows-cache - - # -------------------------------------------------------------------------- - # Op-Alloy crate-specific jobs - # -------------------------------------------------------------------------- - # Op-Alloy cfg check - op-alloy-cfg-check: - docker: - - image: <> - resource_class: xlarge - steps: - - utils/checkout-with-mise: - checkout-method: blobless - - rust-prepare-and-restore-cache: &op-alloy-cfg-check-cache - directory: rust - prefix: op-alloy-cfg-check - - rust-install-toolchain: - channel: nightly - toolchain_version: nightly - - run: - name: Run cfg check - working_directory: rust - no_output_timeout: 40m - command: | - just --justfile op-alloy/Justfile check - - rust-save-build-cache: *op-alloy-cfg-check-cache - # -------------------------------------------------------------------------- # Kona crate-specific jobs # -------------------------------------------------------------------------- @@ -827,7 +861,7 @@ jobs: - run: name: Build cannon command: | - cd cannon && make + cd cannon && just cannon sudo mv ./bin/cannon /usr/local/bin/ - run: name: Set run environment @@ -860,11 +894,11 @@ jobs: $L2_CHAIN_ID cargo llvm-cov report --lcov --output-path client_host_cov.lcov - # Kona Rust CI - Lint (cannon/asterisc targets) + # Kona Rust CI - Lint (cannon target) kona-cargo-lint: parameters: target: - description: The lint target (native, cannon, asterisc) + description: The lint target (native, cannon) type: string machine: image: <> @@ -889,7 +923,7 @@ jobs: kona-build-fpvm: parameters: target: - description: The build target (cannon-client, asterisc-client) + description: The build target (cannon-client) type: string machine: image: <> @@ -1054,7 +1088,7 @@ jobs: no_output_timeout: 60m command: | cd docker/fpvm-prestates - just "<>" "<>" "../.." + just "<>" "<>" "../../prestate-artifacts-<>" - run: name: Upload prestates to GCS working_directory: rust/kona @@ -1074,18 +1108,28 @@ jobs: echo "Successfully published prestates artifacts to GCS" - rust-save-build-cache: *kona-publish-prestate-cache + required-rust-ci: + docker: + - image: <> + resource_class: small + steps: + - run: echo "Required Rust CI checks passed" + # ============================================================================ # WORKFLOWS # ============================================================================ workflows: # ========================================================================== # Unified Rust CI workflow - # Runs on any rust/.* change or manual dispatch with rust_ci_dispatch=true + # Runs on rust path changes or manual dispatch with rust_ci_dispatch=true # ========================================================================== rust-ci: when: or: - - equal: ["webhook", << pipeline.trigger_source >>] + - and: + - equal: ["webhook", << pipeline.trigger_source >>] + - << pipeline.parameters.c-rust_changes_detected >> + - << pipeline.parameters.c-non_docs_changes >> - and: - equal: [true, <>] - equal: ["api", << pipeline.trigger_source >>] @@ -1108,7 +1152,7 @@ workflows: - rust-ci-deny: name: rust-deny directory: rust - command: "cargo deny --all-features check all" + command: "just deny" context: *rust-ci-context - rust-ci-typos: @@ -1166,7 +1210,7 @@ workflows: - rust-build-binary: name: rust-msrv directory: rust - toolchain: "1.88.0" + toolchain: "1.92.0" context: *rust-ci-context # ----------------------------------------------------------------------- @@ -1177,7 +1221,7 @@ workflows: directory: rust command: | just check-no-std - toolchain: "1.88.0" + toolchain: "1.92.0" context: *rust-ci-context - rust-ci-cargo-hack-build: @@ -1200,9 +1244,6 @@ workflows: - op-reth-compact-codec: context: *rust-ci-context - - op-reth-windows-check: - context: *rust-ci-context - - rust-ci-cargo-tests: name: op-reth-integration-tests directory: rust @@ -1218,27 +1259,17 @@ workflows: cache_profile: debug context: *rust-ci-context - # ----------------------------------------------------------------------- - # Op-Alloy crate-specific jobs - # ----------------------------------------------------------------------- - - op-alloy-cfg-check: - context: *rust-ci-context - # ----------------------------------------------------------------------- # Kona crate-specific jobs (lint, FPVM builds, benches, coverage) # ----------------------------------------------------------------------- - kona-cargo-lint: - name: kona-lint-<> - matrix: - parameters: - target: ["cannon", "asterisc"] + name: kona-lint-cannon + target: "cannon" context: *rust-ci-context - kona-build-fpvm: - name: kona-build-fpvm-<> - matrix: - parameters: - target: ["cannon-client", "asterisc-client"] + name: kona-build-fpvm-cannon-client + target: "cannon-client" context: *rust-ci-context - kona-coverage: @@ -1250,31 +1281,76 @@ workflows: name: kona-host-client-offline-cannon context: *rust-ci-context + # ----------------------------------------------------------------------- + # Required gate — fans in on required Rust CI jobs + # ----------------------------------------------------------------------- + - required-rust-ci: + requires: + - rust-tests + - rust-clippy + - rust-docs # ========================================================================== - # Kona scheduled workflows + # Required Rust CI gate (skip) — runs when no rust changes and no dispatch + # Just runs the rust cargo tests for the different packages in the repo and try to build the fpvm-prestates # ========================================================================== - scheduled-kona-link-checker: + rust-ci-gate-short: when: - equal: [build_weekly, <>] + and: + - equal: ["webhook", << pipeline.trigger_source >>] + - not: << pipeline.parameters.c-rust_changes_detected >> jobs: - - kona-link-checker: - context: - - circleci-repo-readonly-authenticated-github-token + - rust-ci-cargo-tests: + name: rust-tests + directory: rust + context: *rust-ci-context + + - rust-ci-cargo-tests: + name: op-reth-integration-tests + directory: rust + command: "--justfile op-reth/justfile test-integration" + cache_profile: debug + context: *rust-ci-context + + - rust-ci-cargo-tests: + name: op-reth-tests-edge + directory: rust + command: "--justfile op-reth/justfile test" + flags: "edge" + cache_profile: debug + context: *rust-ci-context + + - kona-build-fpvm: + name: kona-build-fpvm-cannon-client + target: "cannon-client" + context: *rust-ci-context + + - required-rust-ci: + requires: + - rust-tests + - op-reth-integration-tests + - op-reth-tests-edge + - kona-build-fpvm-cannon-client + - scheduled-kona-sync: + # ========================================================================== + # Kona scheduled workflows + # ========================================================================== + scheduled-kona-link-checker: when: equal: [build_weekly, <>] jobs: - - kona-update-monorepo: + - kona-link-checker: context: - circleci-repo-readonly-authenticated-github-token # Kona publish prestate artifacts - on push to develop kona-publish-prestates: when: - or: + and: - equal: ["develop", <>] + - equal: ["webhook", << pipeline.trigger_source >>] # Only trigger on push to develop, not scheduled runs + - << pipeline.parameters.c-rust_changes_detected >> # Only publish when rust paths changed jobs: - kona-publish-prestate-artifacts: name: kona-publish-<> @@ -1283,4 +1359,5 @@ workflows: version: ["kona-client", "kona-client-int"] context: - circleci-repo-readonly-authenticated-github-token - - oplabs-gcr + - oplabs-network-optimism-io-bucket + diff --git a/.circleci/continue/rust-e2e.yml b/.circleci/continue/rust-e2e.yml index 8e6e0e61c4d3d..bf63b4465e3f2 100644 --- a/.circleci/continue/rust-e2e.yml +++ b/.circleci/continue/rust-e2e.yml @@ -9,13 +9,95 @@ parameters: # Required parameters (also in main.yml, merged during continuation) c-default_docker_image: type: string - default: cimg/base:2024.01 + default: cimg/base:2026.03 c-rust_e2e_dispatch: type: boolean default: false c-go-cache-version: type: string default: "v0.0" + c-rust_changes_detected: + type: boolean + default: false + c-non_docs_changes: + type: boolean + default: false + # Passthrough declarations for setup config parameters. + # CircleCI forwards all explicitly-passed pipeline parameters to continuation configs. + # Without these declarations, manually triggered pipelines fail with "Unexpected argument(s)". + # These are not referenced by any job — the c- prefixed versions above are used instead. + default_docker_image: + type: string + default: cimg/base:2026.03 + base_image: + type: string + default: default + main_dispatch: + type: boolean + default: true + fault_proofs_dispatch: + type: boolean + default: false + reproducibility_dispatch: + type: boolean + default: false + kontrol_dispatch: + type: boolean + default: false + cannon_full_test_dispatch: + type: boolean + default: false + sdk_dispatch: + type: boolean + default: false + docker_publish_dispatch: + type: boolean + default: false + stale_check_dispatch: + type: boolean + default: false + contracts_coverage_dispatch: + type: boolean + default: false + heavy_fuzz_dispatch: + type: boolean + default: false + sync_test_op_node_dispatch: + type: boolean + default: false + ai_contracts_test_dispatch: + type: boolean + default: false + rust_ci_dispatch: + type: boolean + default: false + rust_e2e_dispatch: + type: boolean + default: false + github-event-type: + type: string + default: "__not_set__" + github-event-action: + type: string + default: "__not_set__" + github-event-base64: + type: string + default: "__not_set__" + devnet-metrics-collect: + type: boolean + default: false + flake-shake-dispatch: + type: boolean + default: false + flake-shake-iterations: + type: integer + default: 300 + flake-shake-workers: + type: integer + default: 50 + go-cache-version: + type: string + default: "v0.0" # Commands used by rust-e2e jobs commands: @@ -107,6 +189,11 @@ jobs: cd rust/kona && just test-e2e-sysgo-run node node/reorgs "<>" - go-save-cache: namespace: kona-ci + - store_test_results: + path: rust/kona/tests/tmp/test-results + - store_artifacts: + path: rust/kona/tests/tmp/testlogs + when: always # Kona Node Restart Tests (from node_e2e_sysgo_tests.yaml) rust-restart-sysgo-tests: @@ -132,28 +219,42 @@ jobs: cd rust/kona && just test-e2e-sysgo node node/restart - go-save-cache: namespace: kona-ci + - store_test_results: + path: rust/kona/tests/tmp/test-results + - store_artifacts: + path: rust/kona/tests/tmp/testlogs + when: always - # Kona Supervisor E2E Tests - kona-supervisor-e2e-tests: - parameters: - test_pkg: - description: The test package to run - type: string + # op-reth E2E Sysgo Tests + op-reth-e2e-sysgo-tests: docker: - image: <> resource_class: xlarge steps: - utils/checkout-with-mise: checkout-method: blobless + - attach_workspace: + at: . + - go-restore-cache: + namespace: op-reth-e2e - rust-build: - <<: *kona-rust-build-release - binary: "kona-supervisor" + directory: rust + profile: release + binary: "op-reth" - run: - name: Run supervisor tests for <> - working_directory: rust/kona - no_output_timeout: 40m + name: Run op-reth E2E tests with sysgo orchestrator + working_directory: rust/op-reth/tests + no_output_timeout: 60m command: | - just test-e2e-sysgo supervisor "/supervisor/<>" + export OP_RETH_EXEC_PATH="$(pwd)/../../target/release/op-reth" + just test-e2e-sysgo + - go-save-cache: + namespace: op-reth-e2e + - store_test_results: + path: rust/op-reth/tests/tmp/test-results + - store_artifacts: + path: rust/op-reth/tests/tmp/testlogs + when: always # Kona Proof Action Tests (from proof.yaml) kona-proof-action-tests: @@ -185,15 +286,31 @@ jobs: just action-tests-<>-run - go-save-cache: namespace: kona-ci + - store_test_results: + path: op-e2e/actions/proofs/tmp/test-results + - store_artifacts: + path: op-e2e/actions/proofs/tmp/testlogs + when: always + + required-rust-e2e: + docker: + - image: <> + resource_class: small + steps: + - run: echo "Required Rust E2E checks passed" # ============================================================================ # RUST E2E WORKFLOWS # ============================================================================ workflows: + # Rust E2E CI — runs on rust path changes or manual dispatch rust-e2e-ci: when: or: - - equal: ["webhook", << pipeline.trigger_source >>] + - and: + - equal: ["webhook", << pipeline.trigger_source >>] + - << pipeline.parameters.c-rust_changes_detected >> + - << pipeline.parameters.c-non_docs_changes >> - and: - equal: [true, <>] - equal: ["api", << pipeline.trigger_source >>] @@ -229,7 +346,7 @@ workflows: name: rust-e2e-<> matrix: parameters: - devnet_config: ["simple-kona", "simple-kona-geth", "simple-kona-sequencer", "large-kona-sequencer"] + devnet_config: ["simple-kona", "simple-kona-geth", "simple-kona-sequencer"] context: - circleci-repo-readonly-authenticated-github-token requires: @@ -238,6 +355,11 @@ workflows: - cannon-kona-host - kona-build-release - op-reth-build + - op-reth-e2e-sysgo-tests: + <<: *rust-e2e-job-base + requires: + - contracts-bedrock-build + - op-reth-build - rust-restart-sysgo-tests: name: rust-e2e-restart <<: *rust-e2e-job-base @@ -255,18 +377,47 @@ workflows: - contracts-bedrock-build context: - circleci-repo-readonly-authenticated-github-token + # ----------------------------------------------------------------------- + # Required gate — fans in on all E2E test jobs + # ----------------------------------------------------------------------- + - required-rust-e2e: + requires: + - rust-e2e-simple-kona + - rust-e2e-simple-kona-geth + - rust-e2e-simple-kona-sequencer + - rust-e2e-restart + - kona-proof-action-single - # Kona supervisor E2E tests - manual dispatch only - kona-supervisor-e2e: + # Required Rust E2E gate (skip) — runs when no rust changes and no dispatch + # Just runs action tests + rust-e2e-gate-skip: when: and: - - equal: [true, <>] - - equal: ["api", << pipeline.trigger_source >>] + - equal: ["webhook", << pipeline.trigger_source >>] + - not: << pipeline.parameters.c-rust_changes_detected >> jobs: - - kona-supervisor-e2e-tests: - name: kona-supervisor-<> - matrix: - parameters: - test_pkg: ["pre_interop", "l1reorg/sysgo"] + - contracts-bedrock-build: + build_args: --skip test + context: + - circleci-repo-readonly-authenticated-github-token + - rust-build-binary: + name: kona-build-release + directory: rust + profile: release + context: + - circleci-repo-readonly-authenticated-github-token + # Proof tests - single kind only, interop excluded per original config + - kona-proof-action-tests: + name: kona-proof-action-single + kind: single + requires: + - kona-build-release + - contracts-bedrock-build context: - circleci-repo-readonly-authenticated-github-token + + - required-rust-e2e: + requires: + - kona-proof-action-single + + diff --git a/.circleci/rust-nightly-bump.yml b/.circleci/rust-nightly-bump.yml new file mode 100644 index 0000000000000..318f1c2ac1518 --- /dev/null +++ b/.circleci/rust-nightly-bump.yml @@ -0,0 +1,63 @@ +version: 2.1 + +# Scheduled workflow to bump the pinned nightly Rust toolchain version. +# Runs daily and opens a PR if the pin in rust/justfile is out of date. + +jobs: + bump-nightly: + docker: + - image: cimg/base:2026.03 + steps: + - checkout + + - run: + name: Compute nightly versions + command: | + TODAY=$(date -u +%Y-%m-%d) + LATEST="nightly-${TODAY}" + CURRENT=$(grep -oE 'nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}' rust/justfile | head -1) + + echo "Latest nightly: $LATEST" + echo "Current pin: $CURRENT" + + echo "export LATEST=$LATEST" >> "$BASH_ENV" + echo "export CURRENT=$CURRENT" >> "$BASH_ENV" + + - run: + name: Open PR if pin is outdated + command: | + if [ "$LATEST" = "$CURRENT" ]; then + echo "Pin is already up to date ($CURRENT). Nothing to do." + exit 0 + fi + + BRANCH="ci/bump-rust-nightly-${LATEST}" + + # Authenticate git push via GITHUB_TOKEN_GOVERNANCE + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN_GOVERNANCE}@github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}.git" + git checkout -b "$BRANCH" + + sed -i "s/NIGHTLY := \"${CURRENT}\"/NIGHTLY := \"${LATEST}\"/" rust/justfile + git add rust/justfile + git commit -m "ci: bump rust nightly pin to ${LATEST}" + git push origin "$BRANCH" + + GH_TOKEN="$GITHUB_TOKEN_GOVERNANCE" gh pr create \ + --title "ci: bump rust nightly pin to ${LATEST}" \ + --body "Automated daily bump of the pinned nightly Rust toolchain from \`${CURRENT}\` to \`${LATEST}\`. CI on this PR will validate the new toolchain compiles cleanly." \ + --base main \ + --label "ci" || echo "PR may already exist for this branch." + +workflows: + scheduled-rust-nightly-bump: + triggers: + - schedule: + cron: "0 9 * * *" # 09:00 UTC daily + filters: + branches: + only: + - main + jobs: + - bump-nightly: + context: + - circleci-repo-optimism diff --git a/.claude/skills/fix-rust-fmt/SKILL.md b/.claude/skills/fix-rust-fmt/SKILL.md new file mode 100644 index 0000000000000..d87b23bfa9233 --- /dev/null +++ b/.claude/skills/fix-rust-fmt/SKILL.md @@ -0,0 +1,55 @@ +# fix-rust-fmt + +Fix Rust formatting issues in the optimism monorepo which would cause the `rust-fmt` CI job to fail. + +## When to Use + +Use this skill when the `rust-fmt` CI job fails on a PR that touches Rust code. + +### Trigger Phrases + +- "Fix rust formatting" +- "rust-fmt is failing" +- "Fix the rust-fmt CI job" + +## Prerequisites + +- `mise` must be trusted and installed for this repo (`mise trust && mise install`) + +## Workflow + +### Step 1: Ensure mise tools are installed + +From the repo root (or worktree root): + +```bash +cd && mise install +``` + +This installs `rust`, `just`, and all other tools pinned in `mise.toml`. + +### Step 2: Install the nightly toolchain with rustfmt + +The justfile pins a specific nightly (see `NIGHTLY` variable in `rust/justfile`). +Install it: + +```bash +cd /rust && mise exec -- just install-nightly +``` + +### Step 3: Run the formatter + +```bash +cd /rust && mise exec -- just fmt-fix +``` + +Any files that change are the reason the CI job failed. Stage and commit them. + +## Notes + +- `mise exec --` activates the mise environment so `cargo`, `just`, and + `rustup` resolve to the versions pinned in `mise.toml`. +- The nightly toolchain is required because the workspace uses unstable + `rustfmt` options (see `rust/rustfmt.toml`). +- There is no need to inspect `rust-fmt` CI errors — if the job failed, running + `just fmt-fix` and committing the result is the complete fix. diff --git a/.claude/skills/vm-compat-triage/SKILL.md b/.claude/skills/vm-compat-triage/SKILL.md new file mode 100644 index 0000000000000..748f9e50ba963 --- /dev/null +++ b/.claude/skills/vm-compat-triage/SKILL.md @@ -0,0 +1,318 @@ +# vm-compat-triage + +Triage `analyze-op-program-client` CI failures by reviewing new syscall/opcode findings, determining reachability, and updating the baseline. + +## When to Use + +Use when the `analyze-op-program-client` CI job fails on a PR. The job runs `vm-compat` to detect syscalls and opcodes in `op-program` that are not supported by Cannon. New findings need human triage to determine if they are reachable at runtime. + +### Trigger Phrases + +- "Triage the vm-compat failure" +- "analyze-op-program-client failed" +- "vm-compat CI failure" +- "op-program compatibility test failed" + +## Prerequisites + +- `gh` CLI authenticated with GitHub +- `jq` available +- `vm-compat` binary (install: `mise use -g ubi:ChainSafe/vm-compat@1.1.0`, or download from GitHub releases — binary name is `analyzer-linux-arm64` / `analyzer-linux-amd64`) +- `llvm-objdump` — **Linux only** (install: `sudo apt-get install -y llvm`). Not available on macOS. On macOS, use `just run-vm-compat` in the `op-program` directory which runs the analysis inside Docker. +- The PR URL or number (ask the user if not provided) + +## MIPS64 Syscall Reference + +Syscall numbers in findings are Linux MIPS64 ABI (start at 5000). Use this lookup: + +| Number | Name | Number | Name | Number | Name | +|--------|------|--------|------|--------|------| +| 5000 | read | 5001 | write | 5002 | open | +| 5003 | close | 5004 | stat | 5005 | fstat | +| 5006 | lstat | 5007 | poll | 5008 | lseek | +| 5009 | mmap | 5010 | mprotect | 5011 | munmap | +| 5012 | brk | 5013 | ioctl | 5014 | pread64 | +| 5015 | pwrite64 | 5016 | readv | 5017 | writev | +| 5018 | access | 5019 | pipe | 5020 | select | +| 5021 | sched_yield | 5022 | mremap | 5023 | msync | +| 5024 | mincore | 5025 | madvise | 5026 | shmget | +| 5027 | shmat | 5028 | shmctl | 5029 | dup | +| 5030 | dup2 | 5031 | pause | 5032 | nanosleep | +| 5033 | getitimer | 5034 | setitimer | 5035 | alarm | +| 5036 | getpid | 5037 | sendfile | 5038 | socket | +| 5039 | connect | 5040 | accept | 5041 | sendto | +| 5042 | recvfrom | 5043 | sendmsg | 5044 | recvmsg | +| 5045 | shutdown | 5046 | bind | 5047 | listen | +| 5048 | getsockname | 5049 | getpeername | 5050 | socketpair | +| 5051 | setsockopt | 5052 | getsockopt | 5053 | clone | +| 5054 | fork | 5055 | execve | 5056 | exit | +| 5057 | wait4 | 5058 | kill | 5059 | uname | +| 5060 | semget | 5061 | semop | 5062 | semctl | +| 5063 | shmdt | 5064 | msgget | 5065 | msgsnd | +| 5066 | msgrcv | 5067 | msgctl | 5068 | fcntl | +| 5069 | flock | 5070 | fsync | 5071 | fdatasync | +| 5072 | truncate | 5073 | ftruncate | 5074 | getdents | +| 5075 | getcwd | 5076 | chdir | 5077 | fchdir | +| 5078 | rename | 5079 | mkdir | 5080 | rmdir | +| 5081 | creat | 5082 | link | 5083 | unlink | +| 5084 | symlink | 5085 | readlink | 5086 | chmod | +| 5087 | fchmod | 5088 | chown | 5089 | fchown | +| 5090 | lchown | 5091 | umask | 5092 | gettimeofday | +| 5093 | getrlimit | 5094 | getrusage | 5095 | sysinfo | +| 5096 | times | 5097 | ptrace | 5098 | getuid | +| 5099 | syslog | 5100 | getgid | 5101 | setuid | +| 5102 | setgid | 5103 | geteuid | 5104 | getegid | +| 5105 | setpgid | 5106 | getppid | 5107 | getpgrp | +| 5108 | setsid | 5109 | setreuid | 5110 | setregid | +| 5111 | getgroups | 5112 | setgroups | 5113 | setresuid | +| 5114 | getresuid | 5115 | setresgid | 5116 | getresgid | +| 5117 | getpgid | 5118 | setfsuid | 5119 | setfsgid | +| 5120 | getsid | 5121 | capget | 5122 | capset | +| 5129 | rt_sigqueueinfo | 5130 | rt_sigsuspend | 5131 | sigaltstack | +| 5132 | utime | 5133 | mknod | 5134 | personality | +| 5135 | ustat | 5136 | statfs | 5137 | fstatfs | +| 5138 | sysfs | 5139 | getpriority | 5140 | setpriority | +| 5141 | sched_setparam | 5142 | sched_getparam | 5143 | sched_setscheduler | +| 5144 | sched_getscheduler | 5145 | sched_get_priority_max | 5146 | sched_get_priority_min | +| 5147 | sched_rr_get_interval | 5148 | mlock | 5149 | munlock | +| 5150 | mlockall | 5151 | munlockall | 5152 | vhangup | +| 5153 | pivot_root | 5154 | _sysctl | 5155 | prctl | +| 5190 | semtimedop | 5196 | fadvise64 | 5205 | epoll_create | +| 5206 | epoll_ctl | 5207 | epoll_wait | 5208 | remap_file_pages | +| 5209 | rt_sigreturn | 5210 | set_tid_address | 5211 | restart_syscall | +| 5215 | clock_gettime | 5216 | clock_getres | 5217 | clock_nanosleep | +| 5220 | exit_group | 5223 | tgkill | 5225 | openat | +| 5247 | waitid | 5248 | set_robust_list | 5249 | get_robust_list | +| 5253 | unlinkat | 5254 | renameat | 5257 | fchmodat | +| 5261 | futimesat | 5272 | utimensat | 5279 | epoll_create1 | +| 5284 | preadv | 5285 | pwritev | 5288 | prlimit64 | +| 5297 | getrandom | 5308 | mlock2 | 5316 | copy_file_range | +| 5317 | preadv2 | 5318 | pwritev2 | | | + +For syscall numbers not in this table, look up the number at the MIPS64 syscall table in the Linux kernel source (`arch/mips/kernel/syscalls/syscall_n64.tbl`), or search the web for "linux mips64 syscall {number}". + +## Workflow + +### Step 1: Get the PR and failure data + +If the user hasn't provided a PR URL, ask for it. + +Extract the PR number and fetch the failing check run. The `analyze-op-program-client` job runs inside the CircleCI `main` workflow, not as a GitHub check run directly. Look it up via CircleCI API: + +```bash +PR_NUM="" +BRANCH=$(gh pr view "$PR_NUM" --repo ethereum-optimism/optimism --json headRefName -q '.headRefName') + +# Get the latest pipeline for this branch +PIPELINE_ID=$(curl -s "https://circleci.com/api/v2/project/gh/ethereum-optimism/optimism/pipeline?branch=$BRANCH" | \ + python3 -c "import json,sys; print(json.load(sys.stdin)['items'][0]['id'])") + +# Find the main workflow +WORKFLOW_ID=$(curl -s "https://circleci.com/api/v2/pipeline/$PIPELINE_ID/workflow" | \ + python3 -c "import json,sys; [print(w['id']) for w in json.load(sys.stdin)['items'] if w['name']=='main']") + +# Find the failed job +JOB_NUMBER=$(curl -s "https://circleci.com/api/v2/workflow/$WORKFLOW_ID/job" | \ + python3 -c "import json,sys; [print(j['job_number']) for j in json.load(sys.stdin)['items'] if j['name']=='analyze-op-program-client']") +``` + +### Step 2: Get the findings + +Try these methods in order. Use the first one that works. + +**1. CI artifact (preferred when available).** Check if the CI job stored the findings JSON as an artifact. This is the fastest path — no local tooling needed. + +```bash +# Get artifacts for the failed job +ARTIFACTS=$(curl -s "https://circleci.com/api/v2/project/gh/ethereum-optimism/optimism/$JOB_NUMBER/artifacts") + +# Look for the findings JSON artifact +ARTIFACT_URL=$(echo "$ARTIFACTS" | python3 -c " +import json, sys +data = json.load(sys.stdin) +for item in data.get('items', []): + if 'findings' in item.get('path', '') and item['path'].endswith('.json'): + print(item['url']) + break +") + +if [ -n "$ARTIFACT_URL" ]; then + curl -sL "$ARTIFACT_URL" -o /tmp/vm-compat-full-findings.json +fi +``` + +If the artifact exists and contains findings, use it. No auth is needed for public repos. + +**2. Run locally (if no artifact available).** + +**On Linux** (requires `vm-compat` and `llvm-objdump` in PATH): +```bash +# Checkout the PR branch in a worktree, then from the op-program directory: +vm-compat analyze \ + --with-trace=true \ + --skip-warnings=false \ + --format=json \ + --vm-profile-config vm-profiles/cannon-multithreaded-64.yaml \ + --baseline-report compatibility-test/baseline-cannon-multithreaded-64.json \ + --report-output-path /tmp/vm-compat-full-findings.json \ + ./client/cmd/main.go +``` + +**On macOS** (`llvm-objdump` is not available natively — use Docker): +```bash +# From the op-program directory in the PR branch worktree: +just run-vm-compat +``` +This builds and runs the analysis inside Docker. The findings JSON will be in the Docker build output (and as a CI artifact if the artifact capture PR is merged). + +**Do NOT use CI log output.** CircleCI truncates large log output, which silently drops findings from the beginning of the JSON array. Triage based on incomplete data is worse than useless — it gives false confidence. If neither CI artifacts nor local execution are available, stop and tell the user to set up one of those options. + +### Step 3: Load and parse findings + +Parse the JSON findings. Each finding has: +- `callStack`: Nested object with `function` (and optionally `file`, `line`, `absPath`) and `callStack` fields forming the call chain. The outermost level is the leaf (syscall), innermost is `main.main`. +- `message`: e.g. "Potential Incompatible Syscall Detected: 5043" +- `severity`: "CRITICAL" or "WARNING" +- `hash`: Unique identifier + +### Step 4: Load the baseline and compare + +Read the baseline file (`op-program/compatibility-test/baseline-cannon-multithreaded-64.json`). Also check if the failure is for the `-next` variant. + +Flatten each callStack into an ordered list of function names (ignoring `line`, `file`, `absPath`). A finding **matches** a baseline entry if the function name sequence is identical. + +Mark matched findings as **existing/accepted**. + +### Step 5: Present each new finding to the user + +Present findings one at a time. Do NOT group or summarize multiple findings — let the user make individual decisions. When the user marks a function as unreachable, auto-resolve other findings that share that unreachable path. + +#### Display format: + +Always show the **full call stack from main.main to the leaf syscall**, with main.main at the top. This gives the user the execution context they need to judge reachability. + +Include the **source file path** (no line number) for each function in the stack so the user can navigate to the right file. The `line` and `file` fields in the vm-compat JSON are assembly output positions, NOT Go source lines — they are useless. vm-compat does not provide Go source line numbers, and function definition lines would be misleading since we want call sites, not definitions. + +To resolve file paths: + +1. **Use the PR branch worktree for all lookups** — the source must match the code that produced the findings. Never look up locations from develop or another branch. +2. **Preferred: use `go_search` (gopls MCP)** to find function definitions. This resolves symbols accurately including methods on types, handles vendored/replaced modules, and works across the full dependency tree. However, gopls must be running from a Go workspace — if Claude was started from a non-Go directory (e.g., op-claude), gopls won't work and you must fall back to grep. +3. **Fallback: `grep -rn "func "`** in the PR worktree for optimism code, and in `go env GOMODCACHE` for geth/dependency code (find the exact module path from the `replace` directive in go.mod). +4. For stdlib functions (syscall.*, os.*, internal/*): omit the file path. +5. Cache the lookups — many findings share the same functions. + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Finding [N of TOTAL] | SEVERITY +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Type: Incompatible Syscall: () + or: Incompatible Opcode:
+ +Call Stack (main → leaf): + 1. main.main + 2. client.Main op-program/client/program.go + 3. client.RunProgram op-program/client/program.go + ... + 14. pathdb.repairHistory triedb/pathdb/history.go + 15. rawdb.NewStateFreezer core/rawdb/ancient_scheme.go + ... + 21. os.Remove + 22. syscall.unlinkat +``` + +**Display conventions:** +- Show full package paths in the raw data but use shortened names (last path component) in the display for readability +- Number every line so the user can easily reference by number +- main.main is always line 1, leaf syscall is always the last line +- Show source file path (no line number) for optimism and geth functions; omit for stdlib/runtime +- For geth functions, show paths relative to the geth module root (e.g., `core/rawdb/freezer.go`) + +#### User options: + +After showing each finding, ask: + +- **Number (1-N)** — That line is the first unreachable point in the call stack +- **A** — Acceptable (reachable but Cannon handles it correctly) +- **?** — Needs investigation (allow follow-up questions before deciding) + +#### When user provides a line number (unreachable): + +Ask for the reason it's unreachable (or offer a default like "Function is not called in the Cannon execution environment"). + +Then scan ALL remaining unreviewed findings for any that pass through the same function at the same position in their call stack (i.e., the path from main.main to that function is identical). Mark all matches as unreachable with the same reason. Report: + +``` +Marked N additional findings as unreachable (same path through ) +``` + +Proceed to the next unresolved finding. + +#### When user selects "Acceptable": + +Record the finding as acceptable. Proceed to the next finding. + +#### When user asks clarifying questions (?): + +Help the user investigate. Common queries: +- Show the source code of a function in the call stack +- Find what calls a particular function +- Check if a function is used in Cannon's execution path +- Look at the vm-profile YAML to see allowed/noop syscalls + +Return to the options prompt after answering. + +### Step 6: Update the baseline + +Only proceed if ALL findings are marked as either unreachable or acceptable (none remaining as "needs investigation"). + +**IMPORTANT:** Update the baseline in the PR branch worktree — the same code that produced the findings. The baseline must match the code it will be committed with. Never update the baseline on develop or a different branch. + +**Do NOT manually add entries to the existing baseline.** The baseline must be regenerated from scratch so that stale entries (from code paths that no longer exist) are removed. + +To regenerate: + +1. Run vm-compat with **no baseline** to get the complete report for the current code: + +```bash +cd op-program && vm-compat analyze \ + --with-trace=true --skip-warnings=false --format=json \ + --vm-profile-config vm-profiles/cannon-multithreaded-64.yaml \ + --report-output-path /tmp/vm-compat-full-report.json \ + ./client/cmd/main.go +``` + +2. Normalize the output by stripping `line`, `file`, and `absPath` fields (these are assembly positions, not Go source lines, and cause false positives when they change): + +```bash +cat /tmp/vm-compat-full-report.json | jq 'walk( + if type == "object" and has("line") then del(.line) else . end | + if type == "object" and has("absPath") then del(.absPath) else . end | + if type == "object" and has("file") then del(.file) else . end +)' > op-program/compatibility-test/baseline-cannon-multithreaded-64.json +``` + +3. This replaces the entire baseline with the current state. The old baseline is not merged — it is replaced. + +### Step 7: Verify + +After regenerating the baseline, re-run `vm-compat` with the new baseline to confirm zero new findings: + +```bash +cd op-program && vm-compat analyze \ + --with-trace=true --skip-warnings=false --format=json \ + --vm-profile-config vm-profiles/cannon-multithreaded-64.yaml \ + --baseline-report compatibility-test/baseline-cannon-multithreaded-64.json \ + --report-output-path /tmp/verify.json \ + ./client/cmd/main.go +``` + +If the output file contains an empty array `[]`, the baseline is complete. + +## Notes + +- The `baseline-cannon-multithreaded-64-next.json` file is for a future Cannon version. If the failure is from the `-next` variant, use that baseline instead. Ask the user if unclear. +- Findings with severity "WARNING" are typically less critical than "CRITICAL" but still need triage. +- The vm-compat tool performs static analysis — it cannot determine runtime reachability. Many flagged call paths are through library code that op-program never actually invokes. +- Common sources of unreachable code: p2p networking (geth's node package), disk I/O (freezer, database compaction), OS-level features (signals, process management). +- When a dependency upgrade (e.g., geth) changes internal call paths, many findings may have zero baseline matches even though the conceptual paths are the same. The user must still triage each one individually — do not assume they are safe just because similar paths existed before. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 39c66c4853f7a..e6b87161c4995 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,8 +6,8 @@ /op-node/rollup/derive @ethereum-optimism/consensus # exclusive /rust/kona/crates/protocol @ethereum-optimism/consensus # exclusive -/op-deployer @ethereum-optimism/platforms-team @ethereum-optimism/monorepo-reviewers -/op-validator @ethereum-optimism/platforms-team @ethereum-optimism/monorepo-reviewers +/op-deployer @ethereum-optimism/core-team @ethereum-optimism/monorepo-reviewers +/op-validator @ethereum-optimism/core-team @ethereum-optimism/monorepo-reviewers /op-conductor @ethereum-optimism/op-conductor @ethereum-optimism/monorepo-reviewers @@ -21,5 +21,8 @@ # Security docs /docs/security-reviews @ethereum-optimism/evm-safety +# Public developer documentation +/docs/public-docs/ @ethereum-optimism/solutions + # Must come last to avoid being overridden /.github/CODEOWNERS @ethereum-optimism/cloud-security diff --git a/.github/actions/docker-build-prep/action.yml b/.github/actions/docker-build-prep/action.yml index 4cde0a3dd2c4a..3722fb6950ecb 100644 --- a/.github/actions/docker-build-prep/action.yml +++ b/.github/actions/docker-build-prep/action.yml @@ -12,6 +12,11 @@ outputs: runs: using: 'composite' steps: + - name: Install mise + uses: jdx/mise-action@c1ecc8f748cd28cdeabf76dab3cccde4ce692fe4 # v4.0.0 + with: + install_args: just + - name: Get date id: date shell: bash @@ -22,8 +27,10 @@ runs: - name: Compute GIT_VERSION for all images id: compute_versions shell: bash + env: + GIT_COMMIT: ${{ github.sha }} run: | - VERSIONS=$(GIT_COMMIT="${{ github.sha }}" make compute-git-versions) + VERSIONS=$(just compute-git-versions) echo "versions=$VERSIONS" >> $GITHUB_OUTPUT echo "Computed versions: $VERSIONS" diff --git a/.github/docker-images.json b/.github/docker-images.json new file mode 100644 index 0000000000000..4404b17a53fb5 --- /dev/null +++ b/.github/docker-images.json @@ -0,0 +1,134 @@ +{ + "shared_paths": [ + "docker-bake.hcl", + ".github/workflows/branches.yaml", + ".github/docker-images.json", + ".github/actions/docker-build-prep/**", + "ops/scripts/compute-git-versions.sh" + ], + "shared_go_paths": [ + "go.mod", + "go.sum", + "ops/docker/op-stack-go/**", + "op-service/**", + "op-core/**", + "op-chain-ops/**" + ], + "images": { + "op-node": { + "type": "go", + "check": "go", + "paths": ["op-node/**"] + }, + "op-batcher": { + "type": "go", + "check": "go", + "paths": ["op-batcher/**"] + }, + "op-faucet": { + "type": "go", + "check": "go", + "paths": ["op-faucet/**"] + }, + "op-program": { + "type": "go", + "check": "go", + "paths": ["op-program/**", "op-preimage/**"] + }, + "op-proposer": { + "type": "go", + "check": "go", + "paths": ["op-proposer/**"] + }, + "op-challenger": { + "type": "go", + "check": "go", + "paths": ["op-challenger/**", "op-program/**", "cannon/**", "op-preimage/**", "rust/kona/**"] + }, + "op-dispute-mon": { + "type": "go", + "check": "go", + "paths": ["op-dispute-mon/**"] + }, + "op-conductor": { + "type": "go", + "check": "go", + "paths": ["op-conductor/**", "op-node/**"] + }, + "da-server": { + "type": "go", + "check": "go", + "paths": ["op-alt-da/**"] + }, + "op-supervisor": { + "type": "go", + "check": "go", + "paths": ["op-supervisor/**"] + }, + "op-supernode": { + "type": "go", + "check": "go", + "paths": ["op-supernode/**", "op-node/**"] + }, + "op-test-sequencer": { + "type": "go", + "check": "go", + "paths": ["op-test-sequencer/**"] + }, + "cannon": { + "type": "go", + "check": "go", + "paths": ["cannon/**", "op-preimage/**"] + }, + "op-deployer": { + "type": "go", + "check": "go", + "paths": ["op-deployer/**", "packages/contracts-bedrock/**"] + }, + "op-dripper": { + "type": "go", + "check": "go", + "paths": ["op-dripper/**"] + }, + "op-interop-mon": { + "type": "go", + "check": "go", + "paths": ["op-interop-mon/**"] + }, + "op-interop-filter": { + "type": "go", + "check": "go", + "paths": ["op-interop-filter/**"] + }, + "op-rbuilder": { + "type": "rust", + "check": "rust", + "paths": ["op-rbuilder/**"] + }, + "kona-node": { + "type": "rust", + "check": "rust", + "paths": ["rust/**"] + }, + "kona-client": { + "type": "rust", + "check": "none", + "paths": ["rust/**"] + }, + "kona-host": { + "type": "rust", + "check": "rust", + "paths": ["rust/**"] + }, + "op-reth": { + "type": "rust", + "check": "rust", + "paths": ["rust/**"] + }, + "cannon-builder": { + "type": "rust", + "check": "none", + "paths": ["rust/kona/docker/cannon/**"] + } + } +} diff --git a/.github/workflows/branches.yaml b/.github/workflows/branches.yaml index 6492986e63d44..6647edaa9b805 100644 --- a/.github/workflows/branches.yaml +++ b/.github/workflows/branches.yaml @@ -8,18 +8,224 @@ on: branches: - 'develop' paths: - - 'ops/docker/**' + - 'op-*/**' + - 'cannon/**' - 'packages/contracts-bedrock/**' + - 'rust/**' + - 'ops/docker/**' + - 'ops/scripts/compute-git-versions.sh' - 'docker-bake.hcl' + - 'go.mod' + - 'go.sum' - '.github/workflows/branches.yaml' - - 'ops/scripts/compute-git-versions.sh' - - 'op-rbuilder/**' - - 'rust/**' + - '.github/docker-images.json' + - '.github/actions/docker-build-prep/**' schedule: # Daily builds at 2 AM UTC (matches CircleCI schedule) - cron: '0 2 * * *' jobs: + detect-changes: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + images_json: ${{ steps.detect.outputs.images_json }} + go_check_images_json: ${{ steps.detect.outputs.go_check_images_json }} + rust_check_images_json: ${{ steps.detect.outputs.rust_check_images_json }} + has_images: ${{ steps.detect.outputs.has_images }} + has_go_check_images: ${{ steps.detect.outputs.has_go_check_images }} + has_rust_check_images: ${{ steps.detect.outputs.has_rust_check_images }} + is_fork: ${{ steps.detect.outputs.is_fork }} + steps: + - name: Harden the runner + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2 + with: + egress-policy: audit + - name: Checkout + uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6 + with: + fetch-depth: 0 + ref: ${{ github.event_name == 'schedule' && 'develop' || '' }} + - name: Detect affected images + id: detect + env: + EVENT_NAME: ${{ github.event_name }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + THIS_REPO: ${{ github.repository }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + + CONFIG=".github/docker-images.json" + + # Determine if this is a fork PR + IS_FORK="false" + if [[ "$EVENT_NAME" == "pull_request" ]]; then + if [[ "$HEAD_REPO" != "$THIS_REPO" ]]; then + IS_FORK="true" + fi + fi + echo "is_fork=$IS_FORK" >> "$GITHUB_OUTPUT" + + # For push to develop or scheduled builds, build all images + if [[ "$EVENT_NAME" != "pull_request" ]]; then + echo "Non-PR event ($EVENT_NAME): building all images" + ALL_IMAGES=$(jq -c '[.images | keys[]]' "$CONFIG") + GO_CHECK=$(jq -c '[.images | to_entries[] | select(.value.check == "go") | .key]' "$CONFIG") + RUST_CHECK=$(jq -c '[.images | to_entries[] | select(.value.check == "rust") | .key]' "$CONFIG") + + echo "images_json=$ALL_IMAGES" >> "$GITHUB_OUTPUT" + echo "go_check_images_json=$GO_CHECK" >> "$GITHUB_OUTPUT" + echo "rust_check_images_json=$RUST_CHECK" >> "$GITHUB_OUTPUT" + echo "has_images=true" >> "$GITHUB_OUTPUT" + echo "has_go_check_images=true" >> "$GITHUB_OUTPUT" + echo "has_rust_check_images=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # For PRs, detect which files changed (three-dot diff = only the PR's changes) + CHANGED_FILES=$(git diff --name-only "${BASE_SHA}...${HEAD_SHA}" 2>/dev/null || true) + + if [[ -z "$CHANGED_FILES" ]]; then + echo "WARNING: Could not detect changed files — building all images as fallback" + ALL_IMAGES=$(jq -c '[.images | keys[]]' "$CONFIG") + GO_CHECK=$(jq -c '[.images | to_entries[] | select(.value.check == "go") | .key]' "$CONFIG") + RUST_CHECK=$(jq -c '[.images | to_entries[] | select(.value.check == "rust") | .key]' "$CONFIG") + + echo "images_json=$ALL_IMAGES" >> "$GITHUB_OUTPUT" + echo "go_check_images_json=$GO_CHECK" >> "$GITHUB_OUTPUT" + echo "rust_check_images_json=$RUST_CHECK" >> "$GITHUB_OUTPUT" + echo "has_images=true" >> "$GITHUB_OUTPUT" + echo "has_go_check_images=true" >> "$GITHUB_OUTPUT" + echo "has_rust_check_images=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "Changed files:" + echo "$CHANGED_FILES" | head -50 + TOTAL=$(echo "$CHANGED_FILES" | wc -l) + echo "($TOTAL files total)" + + # matches_pattern checks if a file matches a path pattern + # "foo/**" matches any file under foo/ + # "foo" matches the exact file foo + matches_pattern() { + local file="$1" + local pattern="$2" + if [[ "$pattern" == *"/**" ]]; then + local prefix="${pattern%/**}" + [[ "$file" == "$prefix/"* ]] && return 0 + else + [[ "$file" == "$pattern" ]] && return 0 + fi + return 1 + } + + BUILD_ALL=false + BUILD_ALL_GO=false + + # Check shared paths (trigger all images) + SHARED_PATHS=$(jq -r '.shared_paths[]' "$CONFIG") + while IFS= read -r pattern; do + while IFS= read -r file; do + if matches_pattern "$file" "$pattern"; then + echo "Shared path match: $file matches $pattern → building all images" + BUILD_ALL=true + break 2 + fi + done <<< "$CHANGED_FILES" + done <<< "$SHARED_PATHS" + + # Check shared Go paths (trigger all Go images) + if [[ "$BUILD_ALL" != "true" ]]; then + SHARED_GO_PATHS=$(jq -r '.shared_go_paths[]' "$CONFIG") + while IFS= read -r pattern; do + while IFS= read -r file; do + if matches_pattern "$file" "$pattern"; then + echo "Shared Go path match: $file matches $pattern → building all Go images" + BUILD_ALL_GO=true + break 2 + fi + done <<< "$CHANGED_FILES" + done <<< "$SHARED_GO_PATHS" + fi + + # Collect affected images + declare -A AFFECTED + + if [[ "$BUILD_ALL" == "true" ]]; then + # All images affected + while IFS= read -r name; do + AFFECTED["$name"]=1 + done < <(jq -r '.images | keys[]' "$CONFIG") + else + if [[ "$BUILD_ALL_GO" == "true" ]]; then + # All Go images affected + while IFS= read -r name; do + AFFECTED["$name"]=1 + done < <(jq -r '.images | to_entries[] | select(.value.type == "go") | .key' "$CONFIG") + fi + + # Check per-image paths for remaining images + while IFS= read -r entry; do + name=$(echo "$entry" | jq -r '.key') + # Skip if already affected + [[ -n "${AFFECTED[$name]+x}" ]] && continue + + paths=$(echo "$entry" | jq -r '.value.paths[]') + while IFS= read -r pattern; do + while IFS= read -r file; do + if matches_pattern "$file" "$pattern"; then + echo "Image path match: $file matches $pattern → building $name" + AFFECTED["$name"]=1 + break 2 + fi + done <<< "$CHANGED_FILES" + done <<< "$paths" + done < <(jq -c '.images | to_entries[]' "$CONFIG") + fi + + # Build output arrays + AFFECTED_LIST=$(printf '%s\n' "${!AFFECTED[@]}" | sort) + + IMAGES_JSON="[]" + GO_CHECK_JSON="[]" + RUST_CHECK_JSON="[]" + + if [[ -n "$AFFECTED_LIST" ]]; then + IMAGES_JSON=$(echo "$AFFECTED_LIST" | jq -R -s -c 'split("\n") | map(select(length > 0))') + + GO_CHECK_JSON=$(echo "$AFFECTED_LIST" | while IFS= read -r name; do + [[ -z "$name" ]] && continue + check=$(jq -r --arg n "$name" '.images[$n].check' "$CONFIG") + if [[ "$check" == "go" ]]; then echo "$name"; fi + done | jq -R -s -c 'split("\n") | map(select(length > 0))') + + RUST_CHECK_JSON=$(echo "$AFFECTED_LIST" | while IFS= read -r name; do + [[ -z "$name" ]] && continue + check=$(jq -r --arg n "$name" '.images[$n].check' "$CONFIG") + if [[ "$check" == "rust" ]]; then echo "$name"; fi + done | jq -R -s -c 'split("\n") | map(select(length > 0))') + fi + + echo "Affected images: $IMAGES_JSON" + echo "Go check images: $GO_CHECK_JSON" + echo "Rust check images: $RUST_CHECK_JSON" + + echo "images_json=$IMAGES_JSON" >> "$GITHUB_OUTPUT" + echo "go_check_images_json=$GO_CHECK_JSON" >> "$GITHUB_OUTPUT" + echo "rust_check_images_json=$RUST_CHECK_JSON" >> "$GITHUB_OUTPUT" + + HAS_IMAGES=$( [[ $(echo "$IMAGES_JSON" | jq 'length') -gt 0 ]] && echo "true" || echo "false" ) + HAS_GO=$( [[ $(echo "$GO_CHECK_JSON" | jq 'length') -gt 0 ]] && echo "true" || echo "false" ) + HAS_RUST=$( [[ $(echo "$RUST_CHECK_JSON" | jq 'length') -gt 0 ]] && echo "true" || echo "false" ) + + echo "has_images=$HAS_IMAGES" >> "$GITHUB_OUTPUT" + echo "has_go_check_images=$HAS_GO" >> "$GITHUB_OUTPUT" + echo "has_rust_check_images=$HAS_RUST" >> "$GITHUB_OUTPUT" + prep: runs-on: ubuntu-latest permissions: @@ -41,34 +247,14 @@ jobs: id: prep build: - needs: prep - # only build if push to develop, scheduled run, or PR from a local branch (not a fork) - if: github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) + needs: [prep, detect-changes] + if: | + needs.detect-changes.outputs.is_fork == 'false' + && needs.detect-changes.outputs.has_images == 'true' strategy: fail-fast: false matrix: - image_name: - - op-node - - op-batcher - - op-faucet - - op-program - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - da-server - - op-supervisor - - op-supernode - - op-test-sequencer - - cannon - - op-dripper - - op-interop-mon - - op-interop-filter - - op-rbuilder - - kona-node - - kona-client - - kona-host - - op-reth + image_name: ${{ fromJson(needs.detect-changes.outputs.images_json) }} uses: ethereum-optimism/factory/.github/workflows/docker.yaml@f8f3cb4800e538003134fb5f50cc734c2c98d762 with: mode: bake @@ -88,34 +274,14 @@ jobs: attestations: write build-fork: - needs: prep - # only build if PR from a fork - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + needs: [prep, detect-changes] + if: | + needs.detect-changes.outputs.is_fork == 'true' + && needs.detect-changes.outputs.has_images == 'true' strategy: fail-fast: false matrix: - image_name: - - op-node - - op-batcher - - op-faucet - - op-program - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - da-server - - op-supervisor - - op-supernode - - op-test-sequencer - - cannon - - op-dripper - - op-interop-mon - - op-interop-filter - - op-rbuilder - - kona-node - - kona-client - - kona-host - - op-reth + image_name: ${{ fromJson(needs.detect-changes.outputs.images_json) }} uses: ethereum-optimism/factory/.github/workflows/docker.yaml@f8f3cb4800e538003134fb5f50cc734c2c98d762 with: mode: bake @@ -133,63 +299,38 @@ jobs: contents: read check-cross-platform: - needs: [build, build-fork] - if: always() && (needs.build.result == 'success' || needs.build-fork.result == 'success') + needs: [build, build-fork, detect-changes] + if: | + always() + && needs.detect-changes.outputs.has_go_check_images == 'true' + && (needs.build.result == 'success' || needs.build-fork.result == 'success') strategy: fail-fast: false matrix: - image_name: - - op-node - - op-batcher - - op-faucet - - op-program - - op-proposer - - op-challenger - - op-dispute-mon - - op-conductor - - da-server - - op-supervisor - - op-supernode - - op-test-sequencer - - cannon - - op-dripper - - op-interop-mon - - op-interop-filter - - op-rbuilder - - kona-node - - kona-host - - kona-client - - op-reth + image_name: ${{ fromJson(needs.detect-changes.outputs.go_check_images_json) }} runner: - ubuntu-24.04 - ubuntu-24.04-arm - exclude: - # Rust images use ENTRYPOINT, so the version check command differs - - image_name: op-rbuilder - - image_name: kona-node - - image_name: kona-host - - image_name: kona-client - - image_name: op-reth runs-on: ${{ matrix.runner }} env: IMAGE: ${{ needs.build-fork.result == 'success' && format('ttl.sh/{0}/{1}:24h', github.sha, matrix.image_name) || format('us-docker.pkg.dev/oplabs-tools-artifacts/images/{0}:{1}', matrix.image_name, github.sha) }} steps: - name: Run image - run: docker run $IMAGE ${{ matrix.image_name }} --version + env: + IMAGE_NAME: ${{ matrix.image_name }} + run: docker run "$IMAGE" "$IMAGE_NAME" --version # Separate cross-platform check for Rust images (they use ENTRYPOINT instead of CMD) check-cross-platform-rust: - needs: [build, build-fork] - if: always() && (needs.build.result == 'success' || needs.build-fork.result == 'success') + needs: [build, build-fork, detect-changes] + if: | + always() + && needs.detect-changes.outputs.has_rust_check_images == 'true' + && (needs.build.result == 'success' || needs.build-fork.result == 'success') strategy: fail-fast: false matrix: - image_name: - - op-rbuilder - - kona-node - - kona-host - - kona-client - - op-reth + image_name: ${{ fromJson(needs.detect-changes.outputs.rust_check_images_json) }} runner: - ubuntu-24.04 - ubuntu-24.04-arm @@ -198,4 +339,63 @@ jobs: IMAGE: ${{ needs.build-fork.result == 'success' && format('ttl.sh/{0}/{1}:24h', github.sha, matrix.image_name) || format('us-docker.pkg.dev/oplabs-tools-artifacts/images/{0}:{1}', matrix.image_name, github.sha) }} steps: - name: Run image - run: docker run $IMAGE --version + run: docker run "$IMAGE" --version + + # Gate job for branch protection — provides a stable check name regardless of dynamic matrix contents + docker-build-result: + needs: [detect-changes, build, build-fork, check-cross-platform, check-cross-platform-rust] + if: always() + runs-on: ubuntu-latest + steps: + - name: Check results + env: + DETECT_RESULT: ${{ needs.detect-changes.result }} + HAS_IMAGES: ${{ needs.detect-changes.outputs.has_images }} + IS_FORK: ${{ needs.detect-changes.outputs.is_fork }} + BUILD_RESULT: ${{ needs.build.result }} + BUILD_FORK_RESULT: ${{ needs.build-fork.result }} + HAS_GO: ${{ needs.detect-changes.outputs.has_go_check_images }} + HAS_RUST: ${{ needs.detect-changes.outputs.has_rust_check_images }} + GO_CHECK_RESULT: ${{ needs.check-cross-platform.result }} + RUST_CHECK_RESULT: ${{ needs.check-cross-platform-rust.result }} + run: | + # Fail if detect-changes itself failed (otherwise empty outputs silently pass) + if [[ "$DETECT_RESULT" != "success" ]]; then + echo "detect-changes failed: $DETECT_RESULT" + exit 1 + fi + + # If no images needed building, that's a success + if [[ "$HAS_IMAGES" != "true" ]]; then + echo "No images needed building — success" + exit 0 + fi + + # Check that the applicable build job succeeded + if [[ "$IS_FORK" == "true" ]]; then + RESULT="$BUILD_FORK_RESULT" + else + RESULT="$BUILD_RESULT" + fi + + if [[ "$RESULT" != "success" ]]; then + echo "Build failed with result: $RESULT" + exit 1 + fi + + # Check cross-platform results (they use always() so check explicitly) + if [[ "$HAS_GO" == "true" ]]; then + if [[ "$GO_CHECK_RESULT" != "success" ]]; then + echo "Go cross-platform check failed: $GO_CHECK_RESULT" + exit 1 + fi + fi + + if [[ "$HAS_RUST" == "true" ]]; then + if [[ "$RUST_CHECK_RESULT" != "success" ]]; then + echo "Rust cross-platform check failed: $RUST_CHECK_RESULT" + exit 1 + fi + fi + + echo "All builds and checks passed" diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index 3fe1754098d3e..da3cabc55b944 100644 --- a/.semgrep/rules/sol-rules.yaml +++ b/.semgrep/rules/sol-rules.yaml @@ -46,6 +46,7 @@ rules: paths: exclude: - packages/contracts-bedrock/test/universal/WETH98.t.sol + - packages/contracts-bedrock/test/legacy/L1ChugSplashProxy.t.sol - id: sol-safety-natspec-semver-match languages: [generic] @@ -330,6 +331,7 @@ rules: - packages/contracts-bedrock/src/L2/FeeVault.sol - packages/contracts-bedrock/src/L2/OptimismMintableERC721.sol - packages/contracts-bedrock/src/L2/OptimismMintableERC721Factory.sol + - packages/contracts-bedrock/src/L2/L2ContractsManager.sol - packages/contracts-bedrock/src/cannon/MIPS64.sol - packages/contracts-bedrock/src/cannon/PreimageOracle.sol - packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol @@ -455,3 +457,13 @@ rules: - packages/contracts-bedrock/src/L1/ProtocolVersions.sol # DataAvailabilityChallenge is a beta/non-standard contract. - packages/contracts-bedrock/src/L1/DataAvailabilityChallenge.sol + + - id: sol-safety-use-deployutils-getcode + languages: [solidity] + severity: ERROR + message: Use DeployUtils.getCode() instead of vm.getCode(). When additional_compiler_profiles is configured in foundry.toml, vm.getCode() can non-deterministically resolve to the wrong compiler profile's artifact, causing bytecode mismatches across platforms. + pattern-either: + - pattern: vm.getCode(...) + paths: + exclude: + - packages/contracts-bedrock/scripts/libraries/DeployUtils.sol diff --git a/.semgrep/tests/sol-rules.t.sol b/.semgrep/tests/sol-rules.t.sol index 9d0179318c978..cb1dde5a1f0e5 100644 --- a/.semgrep/tests/sol-rules.t.sol +++ b/.semgrep/tests/sol-rules.t.sol @@ -732,3 +732,25 @@ contract SemgrepTest__sol_style_event_param_fmt { // ruleid: sol-style-event-param-fmt event SomethingWithMint(uint256 _mint); } + +contract SemgrepTest__sol_safety_use_deployutils_getcode { + function test() { + // ok: sol-safety-use-deployutils-getcode + DeployUtils.getCode("ProxyAdmin"); + + // ok: sol-safety-use-deployutils-getcode + DeployUtils.getCode("AddressManager"); + + // ok: sol-safety-use-deployutils-getcode + DeployUtils.getCode("FeeSplitter.sol:FeeSplitter"); + + // ruleid: sol-safety-use-deployutils-getcode + vm.getCode("ProxyAdmin"); + + // ruleid: sol-safety-use-deployutils-getcode + vm.getCode("FeeSplitter.sol:FeeSplitter"); + + // ruleid: sol-safety-use-deployutils-getcode + vm.getCode(string.concat(cname, ".sol:", cname)); + } +} diff --git a/.semgrepignore b/.semgrepignore index 5a5c4eea4ea16..59b50d67b33f1 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -22,3 +22,7 @@ rust/op-alloy/book/ # Op-reth test contracts (not production Solidity code) rust/op-reth/crates/tests/ +rust/op-reth/tests/ + +# Public docs (example code, not production) +docs/public-docs/ diff --git a/AGENTS.md b/AGENTS.md index 867e8ea1a7030..467ca88ed809d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,9 @@ When this happens, offer to submit the improvement to the relevant file in `docs ## Repository Overview +- **Default branch**: `develop` (not `main`) +- **Build system**: migrating from Make to [Just](https://github.com/casey/just) — shared justfile infra lives in `justfiles/` + This repository contains multiple components spanning different technologies: ### Go Services @@ -36,7 +39,7 @@ Solidity smart contracts for the OP Stack, including the core protocol contracts The OP Stack includes significant Rust implementations: -- **kona**: Rust implementation of the OP Stack rollup state transition, including fault proof program, rollup node, and supervisor +- **kona**: Rust implementation of the OP Stack rollup state transition, including fault proof program and rollup node - **op-reth**: OP Stack execution client built on reth - **op-alloy**: Rust crates providing OP Stack types and providers for the alloy ecosystem - **alloy-op-hardforks** / **alloy-op-evm**: OP Stack hardfork and EVM support for alloy @@ -48,8 +51,6 @@ The OP Stack includes significant Rust implementations: ### Development and Testing Infrastructure -- **devnet-sdk**: Toolkit for devnet interactions -- **kurtosis-devnet**: Kurtosis-based devnet environment (DEPRECATED) - **op-e2e**: End-to-end testing framework - **op-acceptance-tests**: Acceptance test suite diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e851085a56e44..b4bd03412d27d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,11 @@ # Optimism Monorepo Contributing Guide +## Documentation + +Public developer documentation for `docs.optimism.io` lives in [`docs/public-docs/`](./docs/public-docs/). +To update the docs site, edit files in `docs/public-docs/` and open a PR against `develop`. +See [`docs/public-docs/DOCS_CONTRIBUTING.md`](./docs/public-docs/DOCS_CONTRIBUTING.md) for style guide and contribution standards. + ## What to Contribute Welcome to the Optimism Monorepo Contributing Guide! @@ -14,7 +20,7 @@ You can: - Help improve the [Optimism Docs] by reporting issues or adding missing sections. - Get involved in the protocol design process by joining discussions within the [OP Stack Specs](https://github.com/ethereum-optimism/specs/discussions) repository. -[Optimism Docs]: https://github.com/ethereum-optimism/docs +[Optimism Docs]: https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs ### Contributions Related to Spelling and Grammar @@ -108,7 +114,7 @@ You must install all of the required [Software Dependencies](#software-dependenc Optimism Monorepo. Once you've done so, run the following command to build: ```bash -make build +just build ``` Packages built on one branch may not be compatible with packages on a different branch. diff --git a/Makefile b/Makefile index bfe1871860484..9222bce992f98 100644 --- a/Makefile +++ b/Makefile @@ -1,379 +1,3 @@ -# provide JUSTFLAGS for just-backed targets -include ./justfiles/flags.mk +DEPRECATED_TARGETS := help build build-go build-contracts build-customlint lint-go lint-go-fix check-op-geth-version golang-docker docker-builder-clean docker-builder cross-op-node contracts-bedrock-docker submodules op-node generate-mocks-op-node generate-mocks-op-service op-batcher op-proposer op-challenger op-dispute-mon op-supernode op-interop-filter op-program cannon reproducible-prestate-op-program reproducible-prestate-kona reproducible-prestate cannon-prestates mod-tidy clean nuke test-unit semgrep-ci op-program-client op-program-host make-pre-test go-tests go-tests-short go-tests-short-ci go-tests-ci go-tests-ci-kona-action go-tests-fraud-proofs-ci test update-op-geth compute-git-versions -BEDROCK_TAGS_REMOTE?=origin -OP_STACK_GO_BUILDER?=us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:latest - -# Requires at least Python v3.9; specify a minor version below if needed -PYTHON?=python3 - -help: ## Prints this help message - @grep -h -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -build: build-go build-contracts ## Builds Go components and contracts-bedrock -.PHONY: build - -build-go: submodules op-node op-proposer op-batcher op-challenger op-dispute-mon op-program cannon ## Builds main Go components -.PHONY: build-go - -build-contracts: - (cd packages/contracts-bedrock && just build) -.PHONY: build-contracts - -build-customlint: - make -C linter build -.PHONY: build-customlint - -lint-go: build-customlint ## Lints Go code with specific linters - ./linter/bin/op-golangci-lint run ./... - go mod tidy -diff -.PHONY: lint-go - -lint-go-fix: build-customlint ## Lints Go code with specific linters and fixes reported issues - ./linter/bin/op-golangci-lint run ./... --fix -.PHONY: lint-go-fix - -check-op-geth-version: ## Checks that op-geth version in go.mod is valid - go run ./ops/scripts/check-op-geth-version -.PHONY: check-op-geth-version - -golang-docker: ## Builds Docker images for Go components using buildx - # We don't use a buildx builder here, and just load directly into regular docker, for convenience. - GIT_COMMIT=$$(git rev-parse HEAD) \ - GIT_DATE=$$(git show -s --format='%ct') \ - IMAGE_TAGS=$$(git rev-parse HEAD),latest \ - docker buildx bake \ - --progress plain \ - --load \ - -f docker-bake.hcl \ - op-node op-batcher op-proposer op-challenger op-dispute-mon op-supervisor -.PHONY: golang-docker - -docker-builder-clean: ## Removes the Docker buildx builder - docker buildx rm buildx-build -.PHONY: docker-builder-clean - -docker-builder: ## Creates a Docker buildx builder - docker buildx create \ - --driver=docker-container --name=buildx-build --bootstrap --use -.PHONY: docker-builder - -compute-git-versions: ## Computes GIT_VERSION for all images and outputs JSON - @GIT_COMMIT=$$(git rev-parse HEAD) ./ops/scripts/compute-git-versions.sh -.PHONY: compute-git-versions - -# add --print to dry-run -cross-op-node: ## Builds cross-platform Docker image for op-node - # We don't use a buildx builder here, and just load directly into regular docker, for convenience. - GIT_COMMIT=$$(git rev-parse HEAD) \ - GIT_DATE=$$(git show -s --format='%ct') \ - IMAGE_TAGS=$$(git rev-parse HEAD),latest \ - PLATFORMS="linux/arm64" \ - GIT_VERSION=$(shell tags=$$(git tag --points-at $(GITCOMMIT) | grep '^op-node/' | sed 's/op-node\///' | sort -V); \ - preferred_tag=$$(echo "$$tags" | grep -v -- '-rc' | tail -n 1); \ - if [ -z "$$preferred_tag" ]; then \ - if [ -z "$$tags" ]; then \ - echo "untagged"; \ - else \ - echo "$$tags" | tail -n 1; \ - fi \ - else \ - echo $$preferred_tag; \ - fi) \ - docker buildx bake \ - --progress plain \ - --builder=buildx-build \ - --load \ - --no-cache \ - -f docker-bake.hcl \ - op-node -.PHONY: cross-op-node - -contracts-bedrock-docker: ## Builds Docker image for Bedrock contracts - IMAGE_TAGS=$$(git rev-parse HEAD),latest \ - docker buildx bake \ - --progress plain \ - --load \ - -f docker-bake.hcl \ - contracts-bedrock -.PHONY: contracts-bedrock-docker - -submodules: ## Updates git submodules - git submodule update --init --recursive -.PHONY: submodules - - -op-node: ## Builds op-node binary - just $(JUSTFLAGS) ./op-node/op-node -.PHONY: op-node - -generate-mocks-op-node: ## Generates mocks for op-node - make -C ./op-node generate-mocks -.PHONY: generate-mocks-op-node - -generate-mocks-op-service: ## Generates mocks for op-service - make -C ./op-service generate-mocks -.PHONY: generate-mocks-op-service - -op-batcher: ## Builds op-batcher binary - just $(JUSTFLAGS) ./op-batcher/op-batcher -.PHONY: op-batcher - -op-proposer: ## Builds op-proposer binary - just $(JUSTFLAGS) ./op-proposer/op-proposer -.PHONY: op-proposer - -op-challenger: ## Builds op-challenger binary - make -C ./op-challenger op-challenger -.PHONY: op-challenger - -op-dispute-mon: ## Builds op-dispute-mon binary - make -C ./op-dispute-mon op-dispute-mon -.PHONY: op-dispute-mon - -op-supernode: ## Builds op-supernode binary - just $(JUSTFLAGS) ./op-supernode/op-supernode -.PHONY: op-supernode - -op-interop-filter: ## Builds op-interop-filter binary - just $(JUSTFLAGS) ./op-interop-filter/op-interop-filter -.PHONY: op-interop-filter - -op-program: ## Builds op-program binary - make -C ./op-program op-program -.PHONY: op-program - -cannon: ## Builds cannon binary - make -C ./cannon cannon -.PHONY: cannon - -reproducible-prestate-op-program: - make -C ./op-program build-reproducible-prestate -.PHONY: reproducible-prestate-op-program - -reproducible-prestate-kona: - cd rust && just build-kona-reproducible-prestate -.PHONY: reproducible-prestate-kona - -reproducible-prestate: reproducible-prestate-op-program reproducible-prestate-kona ## Builds reproducible prestates for op-program and kona - # Output the prestate hashes after all the builds complete so they are easy to find at the end of the build logs. - make -C ./op-program output-prestate-hash - cd rust && just output-kona-prestate-hash -.PHONY: reproducible-prestate - -cannon-prestates: cannon op-program - go run ./op-program/builder/main.go build-all-prestates -.PHONY: cannon-prestates - -mod-tidy: ## Cleans up unused dependencies in Go modules - # Below GOPRIVATE line allows mod-tidy to be run immediately after - # releasing new versions. This bypasses the Go modules proxy, which - # can take a while to index new versions. - # - # See https://proxy.golang.org/ for more info. - export GOPRIVATE="github.com/ethereum-optimism" && go mod tidy -.PHONY: mod-tidy - -clean: ## Removes all generated files under bin/ - rm -rf ./bin - cd packages/contracts-bedrock/ && forge clean -.PHONY: clean - -nuke: clean ## Completely clean the project directory - git clean -Xdf -.PHONY: nuke - -test-unit: ## Runs unit tests for individual components - make -C ./op-node test - make -C ./op-proposer test - make -C ./op-batcher test - make -C ./op-e2e test - (cd packages/contracts-bedrock && just test) -.PHONY: test-unit - -# Remove the baseline-commit to generate a base reading & show all issues -semgrep: ## Runs Semgrep checks - $(eval DEV_REF := $(shell git rev-parse develop)) - SEMGREP_REPO_NAME=ethereum-optimism/optimism semgrep ci --baseline-commit=$(DEV_REF) -.PHONY: semgrep - -op-program-client: ## Builds op-program-client binary - make -C ./op-program op-program-client -.PHONY: op-program-client - -op-program-host: ## Builds op-program-host binary - make -C ./op-program op-program-host -.PHONY: op-program-host - -make-pre-test: ## Makes pre-test setup - make -C ./op-e2e pre-test -.PHONY: make-pre-test - -# Common prerequisites and package list for Go tests -TEST_DEPS := op-program-client op-program-host cannon build-contracts cannon-prestates make-pre-test - -# Excludes: op-validator, op-deployer/pkg/{validation,deployer/{bootstrap,manage,opcm,pipeline,upgrade}} (need RPC) -TEST_PKGS := \ - ./op-alt-da/... \ - ./op-batcher/... \ - ./op-chain-ops/... \ - ./op-node/... \ - ./op-proposer/... \ - ./op-challenger/... \ - ./op-faucet/... \ - ./op-dispute-mon/... \ - ./op-conductor/... \ - ./op-program/... \ - ./op-service/... \ - ./op-supervisor/... \ - ./op-test-sequencer/... \ - ./op-fetcher/... \ - ./op-e2e/system/... \ - ./op-e2e/e2eutils/... \ - ./op-e2e/opgeth/... \ - ./op-e2e/interop/... \ - ./op-e2e/actions/altda \ - ./op-e2e/actions/batcher \ - ./op-e2e/actions/derivation \ - ./op-e2e/actions/helpers \ - ./op-e2e/actions/interop \ - ./op-e2e/actions/proofs \ - ./op-e2e/actions/proposer \ - ./op-e2e/actions/safedb \ - ./op-e2e/actions/sequencer \ - ./op-e2e/actions/sync \ - ./op-e2e/actions/upgrades \ - ./packages/contracts-bedrock/scripts/checks/... \ - ./op-dripper/... \ - ./devnet-sdk/... \ - ./kurtosis-devnet/... \ - ./op-devstack/... \ - ./op-deployer/pkg/deployer/artifacts/... \ - ./op-deployer/pkg/deployer/broadcaster/... \ - ./op-deployer/pkg/deployer/clean/... \ - ./op-deployer/pkg/deployer/integration_test/ \ - ./op-deployer/pkg/deployer/integration_test/cli/... \ - ./op-deployer/pkg/deployer/standard/... \ - ./op-deployer/pkg/deployer/state/... \ - ./op-deployer/pkg/deployer/verify/... \ - ./op-sync-tester/... \ - ./op-supernode/... - -FRAUD_PROOF_TEST_PKGS := \ - ./op-e2e/faultproofs/... - -# Includes: op-validator, op-deployer/pkg/{bootstrap,manage,opcm,pipeline,upgrade} (need RPC) -RPC_TEST_PKGS := \ - ./op-validator/pkg/validations/... \ - ./op-deployer/pkg/deployer/bootstrap/... \ - ./op-deployer/pkg/deployer/manage/... \ - ./op-deployer/pkg/deployer/opcm/... \ - ./op-deployer/pkg/deployer/pipeline/... \ - ./op-deployer/pkg/deployer/upgrade/... - -# All test packages used by CI (combination of all package groups) -ALL_TEST_PACKAGES := $(TEST_PKGS) $(RPC_TEST_PKGS) $(FRAUD_PROOF_TEST_PKGS) - -# Common test environment variables -# For setting PARALLEL, nproc is for linux, sysctl for Mac and then fallback to 4 if neither is available -define DEFAULT_TEST_ENV_VARS -export ENABLE_KURTOSIS=true && \ -export OP_E2E_CANNON_ENABLED="false" && \ -export OP_E2E_USE_HTTP=true && \ -export ENABLE_ANVIL=true && \ -export PARALLEL=$$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) -endef - -# Additional CI-specific environment variables -define CI_ENV_VARS -export OP_TESTLOG_FILE_LOGGER_OUTDIR=$$(realpath ./tmp/testlogs) && \ -export SEPOLIA_RPC_URL="https://ci-sepolia-l1-archive.optimism.io" && \ -export MAINNET_RPC_URL="https://ci-mainnet-l1-archive.optimism.io" && \ -export NAT_INTEROP_LOADTEST_TARGET=10 && \ -export NAT_INTEROP_LOADTEST_TIMEOUT=30s -endef - -# Test timeout (can be overridden via environment) -TEST_TIMEOUT ?= 10m - -go-tests: $(TEST_DEPS) ## Runs comprehensive Go tests across all packages (cached for fast repeated runs) - $(DEFAULT_TEST_ENV_VARS) && \ - go test -parallel=$$PARALLEL -timeout=$(TEST_TIMEOUT) $(TEST_PKGS) -.PHONY: go-tests - -go-tests-short: $(TEST_DEPS) ## Runs comprehensive Go tests with -short flag - $(DEFAULT_TEST_ENV_VARS) && \ - go test -short -parallel=$$PARALLEL -timeout=$(TEST_TIMEOUT) $(TEST_PKGS) -.PHONY: go-tests-short - -# Internal target for running Go tests with gotestsum for CI -# Usage: make _go-tests-ci-internal GO_TEST_FLAGS="-short" -_go-tests-ci-internal: - $(MAKE) -C cannon cannon elf # Required for cannon/provider_test TestLastStepCacheAccuracy - @echo "Setting up test directories..." - mkdir -p ./tmp/test-results ./tmp/testlogs - @echo "Running Go tests with gotestsum..." - $(DEFAULT_TEST_ENV_VARS) && \ - $(CI_ENV_VARS) && \ - if [ -n "$$CIRCLE_NODE_TOTAL" ] && [ "$$CIRCLE_NODE_TOTAL" -gt 1 ]; then \ - export NODE_INDEX=$${CIRCLE_NODE_INDEX:-0} && \ - export NODE_TOTAL=$${CIRCLE_NODE_TOTAL:-1} && \ - export PARALLEL_PACKAGES=$$(echo "$(ALL_TEST_PACKAGES)" | tr ' ' '\n' | awk -v idx=$$NODE_INDEX -v total=$$NODE_TOTAL 'NR % total == idx' | tr '\n' ' ') && \ - if [ -n "$$PARALLEL_PACKAGES" ]; then \ - echo "Node $$NODE_INDEX/$$NODE_TOTAL running packages: $$PARALLEL_PACKAGES"; \ - gotestsum --format=testname \ - --junitfile=./tmp/test-results/results-$$NODE_INDEX.xml \ - --jsonfile=./tmp/testlogs/log-$$NODE_INDEX.json \ - --rerun-fails=3 \ - --rerun-fails-max-failures=50 \ - --packages="$$PARALLEL_PACKAGES" \ - -- -parallel=$$PARALLEL -coverprofile=coverage-$$NODE_INDEX.out $(GO_TEST_FLAGS) -timeout=$(TEST_TIMEOUT) -tags="ci"; \ - else \ - echo "ERROR: Node $$NODE_INDEX/$$NODE_TOTAL has no packages to run! Perhaps parallelism is set too high? (ALL_TEST_PACKAGES has $$(echo '$(ALL_TEST_PACKAGES)' | wc -w) packages)"; \ - exit 1; \ - fi; \ - else \ - gotestsum --format=testname \ - --junitfile=./tmp/test-results/results.xml \ - --jsonfile=./tmp/testlogs/log.json \ - --rerun-fails=3 \ - --rerun-fails-max-failures=50 \ - --packages="$(ALL_TEST_PACKAGES)" \ - -- -parallel=$$PARALLEL -coverprofile=coverage.out $(GO_TEST_FLAGS) -timeout=$(TEST_TIMEOUT) -tags="ci"; \ - fi -.PHONY: _go-tests-ci-internal - -go-tests-short-ci: ## Runs short Go tests with gotestsum for CI (assumes deps built by CI) - $(MAKE) _go-tests-ci-internal GO_TEST_FLAGS="-short" -.PHONY: go-tests-short-ci - -go-tests-ci: ## Runs comprehensive Go tests with gotestsum for CI (assumes deps built by CI) - $(MAKE) _go-tests-ci-internal GO_TEST_FLAGS="" -.PHONY: go-tests-ci - -go-tests-ci-kona-action: ## Runs action tests for kona with gotestsum for CI (assumes deps built by CI) - $(MAKE) _go-tests-ci-internal GO_TEST_FLAGS="-count=1 -timeout 60m -run Test_ProgramAction" -.PHONY: go-tests-ci-kona-action - -go-tests-fraud-proofs-ci: ## Runs fraud proofs Go tests with gotestsum for CI (assumes deps built by CI) - @echo "Setting up test directories..." - mkdir -p ./tmp/test-results ./tmp/testlogs - @echo "Running Go tests with gotestsum..." - $(DEFAULT_TEST_ENV_VARS) && \ - $(CI_ENV_VARS) && \ - export OP_E2E_CANNON_ENABLED="true" && \ - gotestsum --format=testname \ - --junitfile=./tmp/test-results/results.xml \ - --jsonfile=./tmp/testlogs/log.json \ - --rerun-fails=3 \ - --rerun-fails-max-failures=50 \ - --packages="$(FRAUD_PROOF_TEST_PKGS)" \ - -- -parallel=$$PARALLEL -coverprofile=coverage.out -timeout=$(TEST_TIMEOUT) -.PHONY: go-tests-fraud-proofs-ci - -test: go-tests ## Runs comprehensive Go tests (alias for go-tests) -.PHONY: test - -update-op-geth: ## Updates the Geth version used in the project - ./ops/scripts/update-op-geth.py -.PHONY: update-op-geth +include ./justfiles/deprecated.mk diff --git a/README.md b/README.md index a7693b417e731..c11217bdf99b6 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,8 @@ The Optimism Immunefi program offers up to $2,000,042 for in-scope critical vuln
 ├── cannon: Onchain MIPS instruction emulator for fault proofs
-├── devnet-sdk: Comprehensive toolkit for standardized devnet interactions
 ├── docs: A collection of documents including audits and post-mortems
-├── kurtosis-devnet: OP-Stack Kurtosis devnet
+│   └── public-docs: Public developer documentation for docs.optimism.io
 ├── op-acceptance-tests: Acceptance tests and configuration for OP Stack
 ├── op-alt-da: Alternative Data Availability mode (beta)
 ├── op-batcher: L2-Batch Submitter, submits bundles of batches to L1
@@ -145,6 +144,33 @@ Some exceptions to this rule exist for cases in which we absolutely must deploy
 If you're changing or adding a contract and you're unsure about which branch to make a PR into, default to using a feature branch.
 Feature branches are typically used when there are conflicts between 2 projects touching the same code, to avoid conflicts from merging both into `develop`.
 
+## Downloading & Shallow-Cloning the Monorepo
+
+If you want to use the monorepo as a dependency, e.g. in CI, you can greatly speed up the fetching process by either downloading it directly as an archive from Github instead of cloning as a git repository or shallow-cloning it.
+This avoids downloading the full monorepo git history, which is unfortunately a few GBs in size, but which also isn't needed for many use cases, like CI.
+
+To fetch the monorepo at a specific commit/branch/tag `$REF`, download and unpack with
+```
+curl -L https://github.com/ethereum-optimism/optimism/archive/$REF.tar.gz | tar xz
+```
+Note that if you need any of its submodules, you'd need to manually download those too.
+
+If you want a shallow git clone of latest `develop`, you can just do
+```
+git clone --depth 1 --shallow-submodules https://github.com/ethereum-optimism/optimism.git
+```
+which takes only a few seconds on a good internet connection.
+
+If you want to shallow-checkout a specific branch or tag `$REF`, do
+
+```
+git clone --no-checkout --depth 1 --shallow-submodules https://github.com/ethereum-optimism/optimism.git
+cd optimism
+git fetch --depth 1 origin "$REF"
+git checkout "$REF"
+```
+which should also only take a few seconds.
+
 ## License
 
 All other files within this repository are licensed under the [MIT License](https://github.com/ethereum-optimism/optimism/blob/master/LICENSE) unless stated otherwise.
diff --git a/cannon/Dockerfile.diff b/cannon/Dockerfile.diff
index 3cb33a6f5e96f..50d1eb9577cd4 100644
--- a/cannon/Dockerfile.diff
+++ b/cannon/Dockerfile.diff
@@ -1,6 +1,10 @@
-FROM golang:1.24.10-alpine3.21 AS builder
+FROM golang:1.24.13-alpine3.22 AS builder
 
-RUN apk add --no-cache make bash
+# Install just from GitHub releases to match the version pinned in mise.toml.
+# The Alpine package is too old and doesn't support the [script] attribute
+# used in testdata justfiles.
+RUN apk add --no-cache bash curl && \
+    curl -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin --tag 1.46.0
 
 COPY ./go.mod /app/go.mod
 COPY ./go.sum /app/go.sum
@@ -30,7 +34,8 @@ COPY --from=cannon-multithreaded64-5 /usr/local/bin/cannon /usr/local/bin/cannon
 
 # Check cannon-multithreaded64-5
 # verify the latest multithreaded VM behavior against multithreaded64-5
-RUN cd cannon && make diff-multithreaded64-5-cannon -e OTHER_CANNON=/usr/local/bin/cannon-multithreaded64-5
+RUN cd cannon && OTHER_CANNON=/usr/local/bin/cannon-multithreaded64-5 just diff-cannon multithreaded64-5
 RUN --mount=type=cache,target=/root/.cache/go-build cd cannon && \
- make diff-multithreaded64-5-cannon -e OTHER_CANNON=/usr/local/bin/cannon-multithreaded64-5 \
- GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE
+ OTHER_CANNON=/usr/local/bin/cannon-multithreaded64-5 \
+ GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE \
+ just diff-cannon multithreaded64-5
diff --git a/cannon/Makefile b/cannon/Makefile
index 54a72ff54783a..b944ef3a69e0d 100644
--- a/cannon/Makefile
+++ b/cannon/Makefile
@@ -1,118 +1,27 @@
-GITCOMMIT ?= $(shell git rev-parse HEAD)
-GITDATE ?= $(shell git show -s --format='%ct')
-VERSION ?= v0.0.0
+DEPRECATED_TARGETS := cannon cannon64-impl cannon-embeds clean elf elf-go-current sanitize-program contract test cannon-stf-verify fuzz lint
 
-LDFLAGSSTRING +=-X main.GitCommit=$(GITCOMMIT)
-LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
-LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/cannon/multicannon/version.Version=$(VERSION)
-LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/cannon/multicannon/version.Meta=$(VERSION_META)
-LDFLAGS := -ldflags "$(LDFLAGSSTRING)"
+include ../justfiles/deprecated.mk
 
-# Use the old Apple linker to workaround broken xcode - https://github.com/golang/go/issues/65169
-ifeq ($(shell uname),Darwin)
-	FUZZLDFLAGS := -ldflags=-extldflags=-Wl,-ld_classic
+# diff-cannon needs a manual shim because VM is a positional arg in just,
+# not an env var. The generic shim would produce `just VM=X diff-cannon`
+# but just needs `just diff-cannon X`.
+.PHONY: diff-cannon
+diff-cannon:
+ifndef VM
+	$(error VM is required: make diff-cannon VM=multithreaded64-5)
 endif
-
-.DEFAULT_GOAL := cannon
-
-# The MIPS64 r2 opcodes not supported by cannon. This list does not include coprocess-specific and trap opcodes.
-UNSUPPORTED_OPCODES := (dclo|dclz|madd|maddu|seb|seh|wsbh|dsbh|dshd|ins|dins|dinsm|dinsu|ext|dext|dextu|dextm|rotr|drotr|drotr32|rotrv|drotrv|break|sdbbp|pref)
-
-CANNON64_FUZZTIME := 20s
-
-cannon64-impl:
-	env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon64-impl .
-
-# Note: This target is used by ./scripts/build-legacy-cannons.sh
-# It should build the individual versions of cannons and copy them into place in the multicannon/embeds directory
-# Ideally, preserve backwards compatibility with this behaviour but if it needs to change, build-legacy-cannons.sh will
-# need to be updated to account for different behaviours in different versions.
-# Each embed is suffixed with the latest `StateVersion` number corresponding to the target VM and architecture.
-cannon-embeds: cannon64-impl
-	# 64-bit multithreaded vm
-	@cp bin/cannon64-impl ./multicannon/embeds/cannon-8
-
-cannon: cannon-embeds
-	env GO111MODULE=on GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) go build -v $(LDFLAGS) -o ./bin/cannon ./multicannon/
-
-clean:
-	rm -rf bin multicannon/embeds/cannon*
-
-elf:
-	make -C ./testdata elf
-
-elf-go-current:
-	make -C ./testdata/go-1-24 elf
-
-sanitize-program:
-	mips-linux-gnu-objdump -d -j .text $$GUEST_PROGRAM > ./bin/dump.txt
-	@if ! { cat ./bin/dump.txt | awk '{print $$3}' | grep -Ew -m1 "$(UNSUPPORTED_OPCODES)"; }; then \
-		echo "guest program is sanitized for unsupported instructions"; \
-	else \
-		echo "found unsupported instructions in the guest program"; \
-		exit 1; \
-	fi
-
-contract:
-	cd ../packages/contracts-bedrock && forge build
-
-test: elf contract
-	go test -v ./...
-
-
-diff-%-cannon: cannon elf-go-current
-	# Load an elf file to create a prestate, and check that both cannon versions generate the same prestate
-	@VM=$*; \
-	echo "Running diff for VM type $${VM}"; \
-	$$OTHER_CANNON load-elf --type $$VM --path ./testdata/go-1-24/bin/hello.64.elf --out ./bin/prestate-other.bin.gz --meta ""; \
-	./bin/cannon   load-elf --type $$VM --path ./testdata/go-1-24/bin/hello.64.elf --out ./bin/prestate.bin.gz --meta "";
-	@cmp ./bin/prestate-other.bin.gz ./bin/prestate.bin.gz;
-	@if [ $$? -eq 0 ]; then \
-		echo "Generated identical prestates"; \
-	else \
-		echo "Generated different prestates"; \
-		exit 1; \
-	fi
-
-  # Run cannon and check that both cannon versions produce identical states
-	$$OTHER_CANNON run --proof-at '=0' --stop-at '=100000000' --input=./bin/prestate.bin.gz  --output ./bin/out-other.bin.gz --meta ""
-	./bin/cannon   run --proof-at '=0' --stop-at '=100000000' --input=./bin/prestate.bin.gz  --output ./bin/out.bin.gz --meta ""
-	@cmp ./bin/out-other.bin.gz ./bin/out.bin.gz
-	@if [ $$? -eq 0 ]; then \
-		echo "Generated identical post-states"; \
-	else \
-		echo "Generated different post-states"; \
-		exit 1; \
-	fi
-
-cannon-stf-verify:
-	@docker build --progress plain -f Dockerfile.diff ../
-
-fuzz:
-	printf "%s\n" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzMulOp ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzMultOp ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzMultuOp ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallBrk ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallMmap ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallExitGroup ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallFcntl ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateHintRead ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStatePreimageRead ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateHintWrite ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStatePreimageWrite ./mipsevm/tests" \
-		"go test $(FUZZLDFLAGS) -run NOTAREALTEST -v -fuzztime $(CANNON64_FUZZTIME) -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests" \
-	| parallel -j 8 {}
-
-.PHONY: \
-	cannon64-impl \
-	cannon-embeds \
-	cannon \
-	clean \
-	elf \
-	elf-go-current \
-	test \
-	lint \
-	fuzz \
-	diff-%-cannon \
-	cannon-stf-verify
+	@echo
+	@printf '%s\n' 'Deprecated make call: make diff-cannon VM=$(VM)'
+	@printf '%s\n' 'Consider using just instead: just diff-cannon $(VM)'
+	@echo
+	just diff-cannon $(VM)
+
+# Pattern target for backwards compatibility with make diff--cannon invocations.
+# Translates make diff--cannon to just diff-cannon .
+.PHONY: diff-%-cannon
+diff-%-cannon:
+	@echo
+	@printf '%s\n' 'Deprecated make call: make diff-$*-cannon'
+	@printf '%s\n' 'Consider using just instead: just diff-cannon $*'
+	@echo
+	just diff-cannon $*
diff --git a/cannon/README.md b/cannon/README.md
index 6294b38c02f5f..6a7874a575caf 100644
--- a/cannon/README.md
+++ b/cannon/README.md
@@ -22,11 +22,11 @@ For more information, see [Docs](./docs/README.md).
 ```shell
 # Build op-program server-mode and MIPS-client binaries.
 cd ../op-program
-make op-program # build
+just op-program # build
 
 # Switch back to cannon, and build the CLI
 cd ../cannon
-make cannon
+just cannon
 
 # Transform MIPS op-program client binary into first VM state.
 # This outputs state.bin.gz (VM state) and meta.json (for debug symbols).
diff --git a/cannon/justfile b/cannon/justfile
new file mode 100644
index 0000000000000..bf3345601b789
--- /dev/null
+++ b/cannon/justfile
@@ -0,0 +1,118 @@
+import '../justfiles/go.just'
+
+# Build ldflags string
+_VERSION_META_STR := if VERSION_META != "" { "+" + VERSION_META } else { "" }
+_LDFLAGSSTRING := "'" + trim(
+    "-X main.GitCommit=" + GITCOMMIT + " " + \
+    "-X main.GitDate=" + GITDATE + " " + \
+    "-X github.com/ethereum-optimism/optimism/cannon/multicannon/version.Version=" + VERSION + " " + \
+    "-X github.com/ethereum-optimism/optimism/cannon/multicannon/version.Meta=" + _VERSION_META_STR + " " + \
+    "") + "'"
+
+# The MIPS64 r2 opcodes not supported by cannon (excludes coprocessor-specific and trap opcodes)
+UNSUPPORTED_OPCODES := "(dclo|dclz|madd|maddu|seb|seh|wsbh|dsbh|dshd|ins|dins|dinsm|dinsu|ext|dext|dextu|dextm|rotr|drotr|drotr32|rotrv|drotrv|break|sdbbp|pref)"
+
+CANNON64_FUZZTIME := "20s"
+
+# Build cannon binary (default target — must remain first recipe)
+cannon: cannon-embeds
+    env GO111MODULE=on GOOS={{TARGETOS}} GOARCH={{TARGETARCH}} go build -v -ldflags {{_LDFLAGSSTRING}} -o ./bin/cannon ./multicannon/
+
+# Build cannon64-impl binary (note: does NOT use CGO_ENABLED=0)
+cannon64-impl:
+    env GO111MODULE=on GOOS={{TARGETOS}} GOARCH={{TARGETARCH}} go build -v -ldflags {{_LDFLAGSSTRING}} -o ./bin/cannon64-impl .
+
+# Build cannon embeds (used by scripts/build-legacy-cannons.sh)
+# Each embed is suffixed with the latest StateVersion number for the target VM and architecture
+cannon-embeds: cannon64-impl
+    @cp bin/cannon64-impl ./multicannon/embeds/cannon-8
+
+# Clean build artifacts
+clean:
+    rm -rf bin multicannon/embeds/cannon*
+
+# Build ELF test binaries
+elf:
+    just ./testdata/elf
+
+# Build ELF test binaries for current Go version
+elf-go-current:
+    just ./testdata/go-1-24/elf
+
+# Check guest program for unsupported MIPS instructions
+sanitize-program:
+    #!/usr/bin/env bash
+    set -euo pipefail
+    mips-linux-gnu-objdump -d -j .text "$GUEST_PROGRAM" > ./bin/dump.txt
+    if ! { cat ./bin/dump.txt | awk '{print $3}' | grep -Ew -m1 "{{UNSUPPORTED_OPCODES}}"; }; then
+        echo "guest program is sanitized for unsupported instructions"
+    else
+        echo "found unsupported instructions in the guest program"
+        exit 1
+    fi
+
+# Build contracts
+contract:
+    cd ../packages/contracts-bedrock && forge build
+
+# Run tests
+test: elf contract
+    @just go_test "./..."
+
+# Compare cannon output against OTHER_CANNON for a given VM type
+diff-cannon VM: cannon elf-go-current
+    #!/usr/bin/env bash
+    set -euo pipefail
+    VM="{{VM}}"
+    echo "Running diff for VM type ${VM}"
+    # Load an elf file to create a prestate, and check that both cannon versions generate the same prestate
+    $OTHER_CANNON load-elf --type "$VM" --path ./testdata/go-1-24/bin/hello.64.elf --out ./bin/prestate-other.bin.gz --meta ""
+    ./bin/cannon   load-elf --type "$VM" --path ./testdata/go-1-24/bin/hello.64.elf --out ./bin/prestate.bin.gz --meta ""
+    cmp ./bin/prestate-other.bin.gz ./bin/prestate.bin.gz
+    if [ $? -eq 0 ]; then
+        echo "Generated identical prestates"
+    else
+        echo "Generated different prestates"
+        exit 1
+    fi
+    # Run cannon and check that both cannon versions produce identical states
+    $OTHER_CANNON run --proof-at '=0' --stop-at '=100000000' --input=./bin/prestate.bin.gz --output ./bin/out-other.bin.gz --meta ""
+    ./bin/cannon   run --proof-at '=0' --stop-at '=100000000' --input=./bin/prestate.bin.gz --output ./bin/out.bin.gz --meta ""
+    cmp ./bin/out-other.bin.gz ./bin/out.bin.gz
+    if [ $? -eq 0 ]; then
+        echo "Generated identical post-states"
+    else
+        echo "Generated different post-states"
+        exit 1
+    fi
+
+# Verify cannon STF via Docker
+cannon-stf-verify:
+    @docker build --progress plain -f Dockerfile.diff ../
+
+# Lint (cannon is linted via the root lint-go target; this is a CI compatibility stub)
+lint:
+    @echo "cannon lint is handled by the root lint-go target"
+
+# Run fuzz tests in parallel
+fuzz:
+    #!/usr/bin/env bash
+    set -euo pipefail
+    FUZZLDFLAGS=""
+    if [ "$(uname)" = "Darwin" ]; then
+        FUZZLDFLAGS="-ldflags=-extldflags=-Wl,-ld_classic"
+    fi
+    printf "%s\n" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzMulOp ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzMultOp ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzMultuOp ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStateSyscallBrk ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStateSyscallMmap ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStateSyscallExitGroup ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStateSyscallFcntl ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStateHintRead ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStatePreimageRead ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStateHintWrite ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStatePreimageWrite ./mipsevm/tests" \
+        "go test $FUZZLDFLAGS -run NOTAREALTEST -v -fuzztime {{CANNON64_FUZZTIME}} -fuzz=FuzzStateSyscallCloneMT ./mipsevm/tests" \
+    | parallel -j 8 {}
diff --git a/cannon/scripts/build-legacy-cannons.sh b/cannon/scripts/build-legacy-cannons.sh
index e6e3be05557a9..2ed0fd48ba855 100755
--- a/cannon/scripts/build-legacy-cannons.sh
+++ b/cannon/scripts/build-legacy-cannons.sh
@@ -34,7 +34,11 @@ function buildVersion() {
   git checkout "${TAG}" > "${LOG_FILE}" 2>&1
   git submodule update --init --recursive >> "${LOG_FILE}" 2>&1
   rm -rf "${BIN_DIR}/cannon-"*
-  make -C "${REPO_DIR}/cannon" cannon-embeds >> "${LOG_FILE}" 2>&1
+  if [ -f "${REPO_DIR}/cannon/justfile" ] && (cd "${REPO_DIR}/cannon" && just --show cannon-embeds &>/dev/null); then
+    (cd "${REPO_DIR}/cannon" && just cannon-embeds >> "${LOG_FILE}" 2>&1)
+  else
+    make -C "${REPO_DIR}/cannon" cannon-embeds >> "${LOG_FILE}" 2>&1
+  fi
   cp "${BIN_DIR}/cannon-"* "${EMBEDS_DIR}/"
   echo "Built ${TAG} with versions:"
   (cd "${BIN_DIR}" && ls cannon-*)
@@ -52,7 +56,11 @@ done
 cd "${CANNON_DIR}"
 LOG_FILE="${LOGS_DIR}/build-current.txt"
 echo "Building current version of cannon Logs: ${LOG_FILE}"
-make cannon > "${LOG_FILE}" 2>&1
+if [ -f justfile ] && just --show cannon &>/dev/null; then
+  just cannon > "${LOG_FILE}" 2>&1
+else
+  make cannon > "${LOG_FILE}" 2>&1
+fi
 
 echo "All cannon versions successfully built and available in ${EMBEDS_DIR}"
 "${CANNON_DIR}/bin/cannon" list
diff --git a/cannon/testdata/Makefile b/cannon/testdata/Makefile
index a74e1aeae46e6..6b18e6cc787c1 100644
--- a/cannon/testdata/Makefile
+++ b/cannon/testdata/Makefile
@@ -1,17 +1,3 @@
-all: elf
+DEPRECATED_TARGETS := elf go1-24 go1-25 clean
 
-go1-24:
-	make -C ./go-1-24 elf
-.PHONY: go1-24
-
-go1-25:
-	make -C ./go-1-25 elf
-.PHONY: go1-25
-
-.PHONY: elf
-elf: go1-24 go1-25
-
-.PHONY: clean
-clean:
-	make -C ./go-1-24 clean
-	make -C ./go-1-25 clean
+include ../../justfiles/deprecated.mk
diff --git a/cannon/testdata/go-1-24/Makefile b/cannon/testdata/go-1-24/Makefile
index f4a7a51bf81bd..cd4c47589bd43 100644
--- a/cannon/testdata/go-1-24/Makefile
+++ b/cannon/testdata/go-1-24/Makefile
@@ -1,28 +1,3 @@
-all: elf
+DEPRECATED_TARGETS := elf elf64 dump clean
 
-.PHONY: elf64
-elf64: $(patsubst %/go.mod,bin/%.64.elf,$(wildcard */go.mod))
-
-.PHONY: elf
-elf: elf64
-
-.PHONY: dump
-dump: $(patsubst %/go.mod,bin/%.dump,$(wildcard */go.mod))
-
-.PHONY: clean
-clean:
-	@[ -d bin ] && find bin -maxdepth 1 -type f -delete
-
-bin:
-	mkdir bin
-
-# take any directory with a go mod, and build an ELF
-# verify output with: readelf -h bin/.elf
-# result is mips64, big endian, R3000
-bin/%.64.elf: bin
-	cd $(@:bin/%.64.elf=%) && GOOS=linux GOARCH=mips64 GOMIPS64=softfloat go build -o ../$@ .
-
-# take any ELF and dump it
-# TODO: currently have the little-endian toolchain, but should use the big-endian one. The -EB compat flag works though.
-bin/%.dump: bin
-	mipsel-linux-gnu-objdump -D --disassembler-options=no-aliases --wide --source -m mips:3000 -EB $(@:%.dump=%.elf) > $@
+include ../../../justfiles/deprecated.mk
diff --git a/cannon/testdata/go-1-24/alloc/go.mod b/cannon/testdata/go-1-24/alloc/go.mod
index 2af5de6e647a9..86a30c32b4547 100644
--- a/cannon/testdata/go-1-24/alloc/go.mod
+++ b/cannon/testdata/go-1-24/alloc/go.mod
@@ -7,8 +7,8 @@ toolchain go1.24.10
 require github.com/ethereum-optimism/optimism v0.0.0
 
 require (
-	golang.org/x/crypto v0.37.0 // indirect
-	golang.org/x/sys v0.36.0 // indirect
+	golang.org/x/crypto v0.44.0 // indirect
+	golang.org/x/sys v0.40.0 // indirect
 )
 
 replace github.com/ethereum-optimism/optimism v0.0.0 => ./../../../..
diff --git a/cannon/testdata/go-1-24/alloc/go.sum b/cannon/testdata/go-1-24/alloc/go.sum
index 9eec44def3364..4e4b5cd8d6023 100644
--- a/cannon/testdata/go-1-24/alloc/go.sum
+++ b/cannon/testdata/go-1-24/alloc/go.sum
@@ -2,11 +2,11 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
+golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/cannon/testdata/go-1-24/claim/go.mod b/cannon/testdata/go-1-24/claim/go.mod
index b243bcb24f4b6..97750c66ef6b1 100644
--- a/cannon/testdata/go-1-24/claim/go.mod
+++ b/cannon/testdata/go-1-24/claim/go.mod
@@ -7,8 +7,8 @@ toolchain go1.24.10
 require github.com/ethereum-optimism/optimism v0.0.0
 
 require (
-	golang.org/x/crypto v0.37.0 // indirect
-	golang.org/x/sys v0.36.0 // indirect
+	golang.org/x/crypto v0.44.0 // indirect
+	golang.org/x/sys v0.40.0 // indirect
 )
 
 replace github.com/ethereum-optimism/optimism v0.0.0 => ./../../../..
diff --git a/cannon/testdata/go-1-24/claim/go.sum b/cannon/testdata/go-1-24/claim/go.sum
index 9eec44def3364..4e4b5cd8d6023 100644
--- a/cannon/testdata/go-1-24/claim/go.sum
+++ b/cannon/testdata/go-1-24/claim/go.sum
@@ -2,11 +2,11 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
+golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/cannon/testdata/go-1-24/justfile b/cannon/testdata/go-1-24/justfile
new file mode 100644
index 0000000000000..d606bdc635364
--- /dev/null
+++ b/cannon/testdata/go-1-24/justfile
@@ -0,0 +1,31 @@
+# Build all 64-bit ELF test binaries
+elf64: elf
+
+# Build all 64-bit ELF test binaries
+[script('bash')]
+elf:
+    set -euo pipefail
+    mkdir -p bin
+    for mod in */go.mod; do
+        name=$(dirname "$mod")
+        echo "Building bin/${name}.64.elf"
+        (cd "$name" && GOOS=linux GOARCH=mips64 GOMIPS64=softfloat go build -o "../bin/${name}.64.elf" .)
+    done
+
+# Dump all ELF binaries
+# TODO: currently have the little-endian toolchain, but should use the big-endian one. The -EB compat flag works though.
+[script('bash')]
+dump:
+    set -euo pipefail
+    mkdir -p bin
+    for mod in */go.mod; do
+        name=$(dirname "$mod")
+        elf="bin/${name}.64.elf"
+        dump="bin/${name}.64.dump"
+        echo "Dumping $elf -> $dump"
+        mipsel-linux-gnu-objdump -D --disassembler-options=no-aliases --wide --source -m mips:3000 -EB "$elf" > "$dump"
+    done
+
+# Clean build artifacts
+clean:
+    @[ -d bin ] && find bin -maxdepth 1 -type f -delete || true
diff --git a/cannon/testdata/go-1-25/Makefile b/cannon/testdata/go-1-25/Makefile
index f4a7a51bf81bd..cd4c47589bd43 100644
--- a/cannon/testdata/go-1-25/Makefile
+++ b/cannon/testdata/go-1-25/Makefile
@@ -1,28 +1,3 @@
-all: elf
+DEPRECATED_TARGETS := elf elf64 dump clean
 
-.PHONY: elf64
-elf64: $(patsubst %/go.mod,bin/%.64.elf,$(wildcard */go.mod))
-
-.PHONY: elf
-elf: elf64
-
-.PHONY: dump
-dump: $(patsubst %/go.mod,bin/%.dump,$(wildcard */go.mod))
-
-.PHONY: clean
-clean:
-	@[ -d bin ] && find bin -maxdepth 1 -type f -delete
-
-bin:
-	mkdir bin
-
-# take any directory with a go mod, and build an ELF
-# verify output with: readelf -h bin/.elf
-# result is mips64, big endian, R3000
-bin/%.64.elf: bin
-	cd $(@:bin/%.64.elf=%) && GOOS=linux GOARCH=mips64 GOMIPS64=softfloat go build -o ../$@ .
-
-# take any ELF and dump it
-# TODO: currently have the little-endian toolchain, but should use the big-endian one. The -EB compat flag works though.
-bin/%.dump: bin
-	mipsel-linux-gnu-objdump -D --disassembler-options=no-aliases --wide --source -m mips:3000 -EB $(@:%.dump=%.elf) > $@
+include ../../../justfiles/deprecated.mk
diff --git a/cannon/testdata/go-1-25/alloc/go.mod b/cannon/testdata/go-1-25/alloc/go.mod
index 910654cbc7a44..ac600785d6265 100644
--- a/cannon/testdata/go-1-25/alloc/go.mod
+++ b/cannon/testdata/go-1-25/alloc/go.mod
@@ -7,8 +7,8 @@ toolchain go1.25.4
 require github.com/ethereum-optimism/optimism v0.0.0
 
 require (
-	golang.org/x/crypto v0.37.0 // indirect
-	golang.org/x/sys v0.36.0 // indirect
+	golang.org/x/crypto v0.44.0 // indirect
+	golang.org/x/sys v0.40.0 // indirect
 )
 
 replace github.com/ethereum-optimism/optimism v0.0.0 => ./../../../..
diff --git a/cannon/testdata/go-1-25/alloc/go.sum b/cannon/testdata/go-1-25/alloc/go.sum
index 9eec44def3364..4e4b5cd8d6023 100644
--- a/cannon/testdata/go-1-25/alloc/go.sum
+++ b/cannon/testdata/go-1-25/alloc/go.sum
@@ -2,11 +2,11 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
+golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/cannon/testdata/go-1-25/claim/go.mod b/cannon/testdata/go-1-25/claim/go.mod
index 4e186c41cbb90..4f1fd00112183 100644
--- a/cannon/testdata/go-1-25/claim/go.mod
+++ b/cannon/testdata/go-1-25/claim/go.mod
@@ -7,8 +7,8 @@ toolchain go1.25.4
 require github.com/ethereum-optimism/optimism v0.0.0
 
 require (
-	golang.org/x/crypto v0.37.0 // indirect
-	golang.org/x/sys v0.36.0 // indirect
+	golang.org/x/crypto v0.44.0 // indirect
+	golang.org/x/sys v0.40.0 // indirect
 )
 
 replace github.com/ethereum-optimism/optimism v0.0.0 => ./../../../..
diff --git a/cannon/testdata/go-1-25/claim/go.sum b/cannon/testdata/go-1-25/claim/go.sum
index 9eec44def3364..4e4b5cd8d6023 100644
--- a/cannon/testdata/go-1-25/claim/go.sum
+++ b/cannon/testdata/go-1-25/claim/go.sum
@@ -2,11 +2,11 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
+golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/cannon/testdata/go-1-25/justfile b/cannon/testdata/go-1-25/justfile
new file mode 100644
index 0000000000000..d606bdc635364
--- /dev/null
+++ b/cannon/testdata/go-1-25/justfile
@@ -0,0 +1,31 @@
+# Build all 64-bit ELF test binaries
+elf64: elf
+
+# Build all 64-bit ELF test binaries
+[script('bash')]
+elf:
+    set -euo pipefail
+    mkdir -p bin
+    for mod in */go.mod; do
+        name=$(dirname "$mod")
+        echo "Building bin/${name}.64.elf"
+        (cd "$name" && GOOS=linux GOARCH=mips64 GOMIPS64=softfloat go build -o "../bin/${name}.64.elf" .)
+    done
+
+# Dump all ELF binaries
+# TODO: currently have the little-endian toolchain, but should use the big-endian one. The -EB compat flag works though.
+[script('bash')]
+dump:
+    set -euo pipefail
+    mkdir -p bin
+    for mod in */go.mod; do
+        name=$(dirname "$mod")
+        elf="bin/${name}.64.elf"
+        dump="bin/${name}.64.dump"
+        echo "Dumping $elf -> $dump"
+        mipsel-linux-gnu-objdump -D --disassembler-options=no-aliases --wide --source -m mips:3000 -EB "$elf" > "$dump"
+    done
+
+# Clean build artifacts
+clean:
+    @[ -d bin ] && find bin -maxdepth 1 -type f -delete || true
diff --git a/cannon/testdata/justfile b/cannon/testdata/justfile
new file mode 100644
index 0000000000000..8eda529505286
--- /dev/null
+++ b/cannon/testdata/justfile
@@ -0,0 +1,15 @@
+# Build all ELF test binaries
+elf: go1-24 go1-25
+
+# Build go-1-24 ELF binaries
+go1-24:
+    just ./go-1-24/elf
+
+# Build go-1-25 ELF binaries
+go1-25:
+    just ./go-1-25/elf
+
+# Clean all build artifacts
+clean:
+    just ./go-1-24/clean
+    just ./go-1-25/clean
diff --git a/devnet-sdk/book/.gitignore b/devnet-sdk/book/.gitignore
deleted file mode 100644
index 7585238efedfc..0000000000000
--- a/devnet-sdk/book/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-book
diff --git a/devnet-sdk/book/book.toml b/devnet-sdk/book/book.toml
deleted file mode 100644
index bde35e66b7da0..0000000000000
--- a/devnet-sdk/book/book.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[book]
-authors = ["Optimism Contributors"]
-language = "en"
-multilingual = false
-src = "src"
-title = "Devnet SDK Book"
-
-[output.html]
-site-url = "/devnet-sdk/"
-git-repository-url = "https://github.com/ethereum-optimism/optimism/tree/develop/devnet-sdk/book"
-edit-url-template = "https://github.com/ethereum-optimism/optimism/tree/develop/devnet-sdk/book/{path}"
-additional-css = ["custom.css", "theme/css/footer.css"]
-additional-js = ["theme/js/footer.js"]
diff --git a/devnet-sdk/book/custom.css b/devnet-sdk/book/custom.css
deleted file mode 100644
index 7c94143752af4..0000000000000
--- a/devnet-sdk/book/custom.css
+++ /dev/null
@@ -1,5 +0,0 @@
-.content main {
-  max-width: 85%;
-  margin-left: auto;
-  margin-right: auto;
-}
diff --git a/devnet-sdk/book/src/README.md b/devnet-sdk/book/src/README.md
deleted file mode 100644
index 9947175682465..0000000000000
--- a/devnet-sdk/book/src/README.md
+++ /dev/null
@@ -1,54 +0,0 @@
-> ⚠️ **UNDER HEAVY DEVELOPMENT** ⚠️
->
-> This documentation is actively being developed and may change frequently.
-
-# Introduction
-
-# Devnet SDK
-
-The Devnet SDK is a comprehensive toolkit designed to standardize and simplify interactions with Optimism devnets. It provides a robust set of tools and interfaces for deploying, managing, and testing Optimism networks in development environments.
-
-## Core Components
-
-### 1. Devnet Descriptors
-
-The descriptors package defines a standard interface for describing and interacting with devnets. It provides:
-
-- Structured representation of devnet environments including L1 and L2 chains
-- Service discovery and endpoint management
-- Wallet and address management
-- Standardized configuration for chain components (nodes, services, endpoints)
-
-### 2. Shell Integration
-
-The shell package provides a preconfigured shell environment for interacting with devnets. For example, you can quickly:
-
-- Launch a shell with all environment variables set and run commands like `cast balance 
` that automatically use the correct RPC endpoints -- Access chain-specific configuration like JWT secrets and contract addresses - -This makes it easy to interact with your devnet without manually configuring tools or managing connection details. - -### 3. System Interface - -The system package provides a devnet-agnostic programmatic interface, constructed for example from the descriptors above, for interacting with Optimism networks. Key features include: - -- Unified interface for L1 and L2 chain interactions -- Transaction management and processing -- Wallet management and operations -- Contract interaction capabilities -- Interoperability features between different L2 chains - -Core interfaces include: -- `System`: Represents a complete Optimism system with L1 and L2 chains -- `Chain`: Provides access to chain-specific operations -- `Wallet`: Manages accounts, transactions, and signing operations -- `Transaction`: Handles transaction creation and processing - -### 4. Testing Framework - -The testing package provides a comprehensive framework for testing devnet deployments: - -- Standardized testing utilities -- System test capabilities -- Integration test helpers -- Test fixtures and utilities diff --git a/devnet-sdk/book/src/SUMMARY.md b/devnet-sdk/book/src/SUMMARY.md deleted file mode 100644 index fb46d91bb1094..0000000000000 --- a/devnet-sdk/book/src/SUMMARY.md +++ /dev/null @@ -1,15 +0,0 @@ -# Summary - -[Introduction](README.md) - -# Usage - -- [Descriptors Documentation](./descriptors.md) -- [Shell Integration Guide](./shell.md) -- [System Interface Reference](./system.md) -- [Testing Framework Guide](./testing.md) - -# DSL - -- [Introduction](dsl/intro.md) -- [Style Guide](dsl/style_guide.md) diff --git a/devnet-sdk/book/src/descriptors.md b/devnet-sdk/book/src/descriptors.md deleted file mode 100644 index 20dc1e21a460c..0000000000000 --- a/devnet-sdk/book/src/descriptors.md +++ /dev/null @@ -1,142 +0,0 @@ -# Devnet Descriptors - -The devnet descriptor is a standardized format that describes the complete topology and configuration of an Optimism devnet deployment. This standard serves as a bridge between different devnet implementations and the higher-level tooling provided by the devnet-sdk. - -## Universal Descriptor Format - -Both `kurtosis-devnet` and `netchef` emit the same descriptor format, despite being completely different devnet implementations: - -- **kurtosis-devnet**: Uses Kurtosis to orchestrate containerized devnet deployments -- **netchef**: Provides a lightweight, local devnet deployment - -This standardization enables a powerful ecosystem where tools can be built independently of the underlying devnet implementation. - -## Descriptor Structure - -A devnet descriptor provides a complete view of a running devnet: - -```json -{ - "l1": { - "name": "l1", - "id": "900", - "services": { - "geth": { - "name": "geth", - "endpoints": { - "rpc": { - "host": "localhost", - "port": 8545 - } - } - } - }, - "nodes": [...], - "addresses": { - "deployer": "0x...", - "admin": "0x..." - }, - "wallets": { - "admin": { - "address": "0x...", - "private_key": "0x..." - } - } - }, - "l2": [ - { - "name": "op-sepolia", - "id": "11155420", - "services": {...}, - "nodes": [...], - "addresses": {...}, - "wallets": {...} - } - ], - "features": ["eip1559", "shanghai"] -} -``` - -## Enabling Devnet-Agnostic Tooling - -The power of the descriptor format lies in its ability to make any compliant devnet implementation immediately accessible to the entire devnet-sdk toolset: - -1. **Universal Interface**: Any devnet that can emit this descriptor format can be managed through devnet-sdk's tools -2. **Automatic Integration**: New devnet implementations only need to implement the descriptor format to gain access to: - - System interface for chain interaction - - Testing framework - - Shell integration tools - - Wallet management - - Transaction processing - -## Benefits - -This standardization provides several key advantages: - -- **Portability**: Tools built against the devnet-sdk work with any compliant devnet implementation -- **Consistency**: Developers get the same experience regardless of the underlying devnet -- **Extensibility**: New devnet implementations can focus on deployment mechanics while leveraging existing tooling -- **Interoperability**: Tools can be built that work across different devnet implementations - -## Implementation Requirements - -To make a devnet implementation compatible with devnet-sdk, it needs to: - -1. Provide a mechanism to output the descriptor (typically as JSON) -2. Ensure all required services and endpoints are properly described - -Once these requirements are met, the devnet automatically gains access to the full suite of devnet-sdk capabilities. - -## Status - -The descriptor format is currently in active development, particularly regarding endpoint specifications: - -### Endpoint Requirements - -- **Current State**: The format does not strictly specify which endpoints must be included in a compliant descriptor -- **Minimum Known Requirements**: - - RPC endpoints are essential for basic chain interaction - - Other endpoints may be optional depending on use case - -### Implementation Notes - -- `kurtosis-devnet` currently outputs all service endpoints by default, including many that may not be necessary for testing -- Other devnet implementations can be more selective about which endpoints they expose -- Different testing scenarios may require different sets of endpoints - -### Future Development - -We plan to develop more specific recommendations for: -- Required vs optional endpoints -- Standard endpoint naming conventions -- Service-specific endpoint requirements -- Best practices for endpoint exposure - -Until these specifications are finalized, devnet implementations should: -1. Always include RPC endpoints -2. Document which additional endpoints they expose -3. Consider their specific use cases when deciding which endpoints to include - -## Example Usage - -Here's how a tool might use the descriptor to interact with any compliant devnet: - -```go -// Load descriptor from any compliant devnet -descriptor, err := descriptors.Load("devnet.json") -if err != nil { - log.Fatal(err) -} - -// Use the descriptor with devnet-sdk tools -system, err := system.FromDescriptor(descriptor) -if err != nil { - log.Fatal(err) -} - -// Now you can use all devnet-sdk features -l1 := system.L1() -l2 := system.L2(descriptor.L2[0].ID) -``` - -This standardization enables a rich ecosystem of tools that work consistently across different devnet implementations, making development and testing more efficient and reliable. diff --git a/devnet-sdk/book/src/dsl/intro.md b/devnet-sdk/book/src/dsl/intro.md deleted file mode 100644 index bc6b3b4757f74..0000000000000 --- a/devnet-sdk/book/src/dsl/intro.md +++ /dev/null @@ -1,41 +0,0 @@ -# Introduction - -The devnet-sdk DSL is a high level test library, specifically designed for end to end / acceptance testing of the -OP Stack. It aims to make the development and maintenance of whole system tests faster and easier. - -The high level API helps make the actual test read in a more declarative style and separate the technical details of how -an action is actually performed. The intended result is that tests express the requirements, while the DSL provides the -technical details of how those requirements are met. This ensures that as the technical details change, the DSL can -be updated rather than requiring that each test be updated individual - significantly reducing the maintenance cost for -a large test suite. Similarly, if there is flakiness in tests, it can often be solved by improving the DSL to -properly wait for pre or post conditions or automatically perform required setup steps and that fix is automatically -applied everywhere, including tests added in the future. - -## Guiding Principles - -These guiding principles allow the test suite to evolve and grow over time in a way that ensures the tests are -maintainable and continue to be easy to write. With multiple different teams contributing to tests, over a long time -period, shared principles are required to avoid many divergent approaches and frameworks emerging which increase the -cognitive load for developers writing tests and increase the maintenance costs for existing tests. - -### Keep It Simple - -Avoid attempting to make the DSL read like plain English. This is a domain-specific language and the domain experts are -actually the test developers, not non-technical users. Each statement should clearly describe what it is trying to do, -but does not need to read like an English sentence. - -Bias very strongly towards making the tests simpler, even if the DSL implementation then needs to be more complex. -Complexity in tests will be duplicated for each test case whereas complexity in the DSL is more centralised and is -encapsulated so it is much less likely to be a distraction. - -### Consistency - -The "language" of the DSL emerges by being consistent in the structures and naming used for things. Take the time to -refactor things to ensure that the same name is used consistently for a concept right across the DSL. - -Bias towards following established patterns rather than doing something new. While introducing a new pattern might make -things cleaner in a particular test, it introduces additional cognitive load for people to understand when working with -the tests. It is usually (but not always) better to preserve consistency than to have a marginally nicer solution for -one specific scenario. - -The [style guide](./style_guide.md) defines a set of common patterns and guidelines that should be followed. diff --git a/devnet-sdk/book/src/dsl/style_guide.md b/devnet-sdk/book/src/dsl/style_guide.md deleted file mode 100644 index 3095892ddb9fa..0000000000000 --- a/devnet-sdk/book/src/dsl/style_guide.md +++ /dev/null @@ -1,133 +0,0 @@ -# DSL Style Guide - -This style guide outlines common patterns and anti-patterns used by the testing DSL. Following this guide not only -improves consistency, it helps keep the separation of requirements (in test files) from implementation details (in DSL -implementation), which in turn ensures tests are maintainable even as the number of tests keeps increasing over time. - -## Entry Points - -What are the key entry points for the system? Nodes/services, users, contracts?? - -## Action Methods - -Methods that perform actions will typically have three steps: - -1. Check (and if needed, wait) for any required preconditions -2. Perform the action, allowing components to fully process the effects of it -3. Assert that the action completed. These are intended to be a sanity check to ensure tests fail fast if something - doesn't work as expected. Options may be provided to perform more detailed or specific assertions - -## Verification Methods - -Verification methods in the DSL provide additional assertions about the state of the system, beyond the minimal -assertions performed by action methods. - -Verification methods should include any required waiting or retrying. - -Verification methods should generally only be used in tests to assert the specific behaviours the test is covering. -Avoid adding additional verification steps in a test to assert that setup actions were performed correctly - such -assertions should be built into the action methods. While sanity checking setup can be useful, adding additional -verification method calls into tests makes it harder to see what the test is actually intending to cover and increases -the number of places that need to be updated if the behaviour being verified changes in the future. - -### Avoid Getter Methods - -The DSL generally avoids exposing methods that return data from the system state. Instead verification methods are -exposed which combine the fetching and assertion of the data. This allows the DSL to handle any waiting or retrying -that may be necessary (or become necessary). This avoids a common source of flakiness where tests assume an asynchronous -operation will have completed instead of explicitly waiting for the expected final state. - - -```go -// Avoid: the balance of an account is data from the system which changes over time -block := node.GetBalance(user) - -// Good: use a verification method -node.VerifyBalance(user, 10 * constants.Eth) - -// Better? Select the entry point to be as declarative as possible -user.VerifyBalacne(10 * constants.Eth) // implementation could verify balance on all nodes automatically -``` - - -Note however that this doesn't mean that DSL methods never return anything. While returning raw data is avoided, -returning objects that represent something in the system is ok. e.g. - -```go -claim := game.RootClaim() - -// Waits for op-challenger to counter the root claim and returns a value representing that counter claim -// which can expose further verification or action methods. -counter := claim.VerifyCountered() -counter.VerifyClaimant(honestChallenger) -counter.Attack() -``` - -## Method Arguments - -Required inputs to methods are specified as normal parameters, so type checking enforces their presence. - -Optional inputs to methods are specified by a config struct and accept a vararg of functions that can update that struct. -This is roughly inline with the typical opts pattern in Golang but with significantly reduced boilerplate code since -so many methods will define their own config. With* methods are only provided for the most common optional args and -tests will normally supply a custom function that sets all the optional values they need at once. - -## Logging - -Include logging to indicate what the test is doing within the DSL methods. - -Methods that wait should log what they are waiting for and the current state of the system on each poll cycle. - -## No Sleeps - -Neither tests nor DSL code should use hard coded sleeps. CI systems tend to be under heavy and unpredictable load so -short sleep times lead to flaky tests when the system is slower than expected. Long sleeps waste time, causing test runs -to be too slow. By using a waiter pattern, a long timeout can be applied to avoid flakiness, while allowing the test to -progress quickly once the condition is met. - -```go -// Avoid: arbitrary delays -node.DoSomething() -time.Sleep(2 * time.Minute) -node.VerifyResult() - -// Good: build wait/retry loops into the testlib method -node.DoSomething() -node.VerifyResult() // Automatically waits -``` - -## Test Smells - -"Smells" are patterns that indicate there may be a problem. They aren't hard rules, but indicate that something may not -be right and the developer should take a little time to consider if there are better alternatives. - -### Comment and Code Block - -Where possible, test code should be self-explanatory with testlib method calls that are high level enough to not need -comments explaining what they do in the test. When comments are required to explain simple setup, it's an indication -that the testlib method is either poorly named or that a higher level method should be introduced. - -```go -// Smelly: Test code is far too low level and needs to be explained with a comment -// Deploy test contract -storeProgram := program.New().Sstore(0, 0xbeef).Bytes() -walletv2, err := system.NewWalletV2FromWalletAndChain(ctx, wallet, l2Chain) -require.NoError(t, err) -storeAddr, err := DeployProgram(ctx, walletv2, storeProgram) -require.NoError(t, err) -code, err := l2Client.CodeAt(ctx, storeAddr, nil) -require.NoError(t, err) -require.NotEmpty(t, code, "Store contract not deployed") -require.Equal(t, code, storeProgram, "Store contract code incorrect") - -// Good: Introduce a testlib method to encapsulate the detail and keep the test high level -contract := contracts.SStoreContract.Deploy(l2Node, 0xbeef) -``` - -However, not all comments are bad: - -```go -// Good: Explain the calculations behind specific numbers -// operatorFeeCharged = gasUsed * operatorFeeScalar == 1000 * 5 == 5000 -tx.VerifyOperatorFeeCharged(5000) -``` diff --git a/devnet-sdk/book/src/shell.md b/devnet-sdk/book/src/shell.md deleted file mode 100644 index f0ad9cad1dc88..0000000000000 --- a/devnet-sdk/book/src/shell.md +++ /dev/null @@ -1,81 +0,0 @@ -# Shell Integration - -The devnet-sdk provides powerful shell integration capabilities that allow developers to "enter" a devnet environment, making interactions with the network more intuitive and streamlined. - -## Devnet Shell Environment - -Using a devnet's descriptor, we can create a shell environment that is automatically configured with all the necessary context to interact with the devnet: - -```bash -# Enter a shell configured for your devnet -devnet-sdk shell --descriptor path/to/devnet.json -``` - -### Automatic Configuration - -When you enter a devnet shell, the environment is automatically configured with: - -- Environment variables for RPC endpoints -- JWT authentication tokens where required -- Named wallet addresses -- Chain IDs -- Other devnet-specific configuration - -### Simplified Tool Usage - -This automatic configuration enables seamless use of Ethereum development tools without explicit endpoint configuration: - -```bash -# Without devnet shell -cast balance 0x123... --rpc-url http://localhost:8545 --jwt-secret /path/to/jwt - -# With devnet shell -cast balance 0x123... # RPC and JWT automatically configured -``` - -## Supported Tools - -The shell environment enhances the experience with various Ethereum development tools: - -- `cast`: For sending transactions and querying state - -## Environment Variables - -The shell automatically sets up standard Ethereum environment variables based on the descriptor: - -```bash -# Chain enpointpoit -export ETH_RPC_URL=... -export ETH_JWT_SECRET=... -``` - -## Usage Examples - -```bash -# Enter devnet shell -go run devnet-sdk/shell/cmd/enter/main.go --descriptor devnet.json --chain ... - -# Now you can use tools directly -cast block latest - -# Exit the shell -exit -``` - -## Benefits - -- **Simplified Workflow**: No need to manually configure RPC endpoints or authentication -- **Consistent Environment**: Same configuration across all tools and commands -- **Reduced Error Risk**: Eliminates misconfigurations and copy-paste errors -- **Context Awareness**: Shell knows about all chains and services in your devnet - -## Implementation Details - -The shell integration: - -1. Reads the descriptor file -2. Sets up environment variables based on the descriptor content -3. Creates a new shell session with the configured environment -4. Maintains the environment until you exit the shell - -This feature makes it significantly easier to work with devnets by removing the need to manually manage connection details and authentication tokens. diff --git a/devnet-sdk/book/src/system.md b/devnet-sdk/book/src/system.md deleted file mode 100644 index 7ef3bd10352c5..0000000000000 --- a/devnet-sdk/book/src/system.md +++ /dev/null @@ -1,199 +0,0 @@ -# System Interfaces - -The devnet-sdk provides a set of Go interfaces that abstract away the specifics of devnet deployments, enabling automation solutions to work consistently across different deployment types and implementations. - -## Core Philosophy - -While the Descriptor interfaces provide a common way to describe actual devnet deployments (like production-like or Kurtosis-based deployments), the System interfaces operate at a higher level of abstraction. They are designed to support both real deployments and lightweight testing environments. - -The key principles are: - -- **Deployment-Agnostic Automation**: Code written against these interfaces works with any implementation - from full deployments described by Descriptors to in-memory stacks or completely fake environments -- **Flexible Testing Options**: Enables testing against: - - Complete devnet deployments - - Partial mock implementations - - Fully simulated environments -- **One-Way Abstraction**: While Descriptors can be converted into System interfaces, System interfaces can represent additional constructs beyond what Descriptors describe -- **Implementation Freedom**: New deployment types or testing environments can be added without modifying existing automation code - -## Interface Purity - -A critical design principle of these interfaces is their **purity**. This means that interfaces: - -1. **Only Reference Other Pure Interfaces**: Each interface method can only return or accept: - - Other pure interfaces from this package - - Simple data objects that can be fully instantiated - - Standard Go types and primitives - -2. **Avoid Backend-Specific Types**: The interfaces never expose types that would create dependencies on specific implementations: - ```go - // BAD: Creates dependency on specific client implementation - func (c Chain) GetNodeClient() *specific.NodeClient - - // GOOD: Returns pure interface that can be implemented by any backend - func (c Chain) Client() (ChainClient, error) - ``` - -3. **Use Generic Data Types**: When complex data structures are needed, they are defined as pure data objects: - ```go - // Pure data type that any implementation can create - type TransactionData interface { - From() common.Address - To() *common.Address - Value() *big.Int - Data() []byte - } - ``` - -### Why Purity Matters - -Interface purity is crucial because it: -- Preserves implementation freedom -- Prevents accidental coupling to specific backends -- Enables creation of new implementations without constraints -- Allows mixing different implementation types (e.g., partial fakes) - -### Example: Maintaining Purity - -```go -// IMPURE: Forces dependency on eth client -type Chain interface { - GetEthClient() *ethclient.Client // 👎 Locks us to specific client -} - -// PURE: Allows any implementation -type Chain interface { - Client() (ChainClient, error) // 👍 Implementation-agnostic -} - -type ChainClient interface { - BlockNumber(ctx context.Context) (uint64, error) - // ... other methods -} -``` - -## Interface Hierarchy - -### System - -The top-level interface representing a complete Optimism deployment: - -```go -type System interface { - // Unique identifier for this system - Identifier() string - - // Access to L1 chain - L1() Chain - - // Access to L2 chain(s) - L2(chainID uint64) Chain -} -``` - -### Chain - -Represents an individual chain (L1 or L2) within the system: - -```go -type Chain interface { - // Chain identification - RPCURL() string - ID() types.ChainID - - // Core functionality - Client() (*ethclient.Client, error) - Wallets(ctx context.Context) ([]Wallet, error) - ContractsRegistry() interfaces.ContractsRegistry - - // Chain capabilities - SupportsEIP(ctx context.Context, eip uint64) bool - - // Transaction management - GasPrice(ctx context.Context) (*big.Int, error) - GasLimit(ctx context.Context, tx TransactionData) (uint64, error) - PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) -} -``` - -### Wallet - -Manages accounts and transaction signing: - -```go -type Wallet interface { - // Account management - PrivateKey() types.Key - Address() types.Address - Balance() types.Balance - Nonce() uint64 - - // Transaction operations - Sign(tx Transaction) (Transaction, error) - Send(ctx context.Context, tx Transaction) error - - // Convenience methods - SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] - Transactor() *bind.TransactOpts -} -``` - -## Implementation Types - -The interfaces can be implemented in various ways to suit different needs: - -### 1. Real Deployments -- **Kurtosis-based**: Full containerized deployment -- **Netchef**: Remote devnet deployment -- -### 2. Testing Implementations -- **In-memory**: Fast, lightweight implementation for unit tests -- **Mocks**: Controlled behavior for specific test scenarios -- **Recording**: Record and replay real interactions - -### 3. Specialized Implementations -- **Partial**: Combining pieces from fake and real deployments -- **Filtered**: Limited functionality for specific use cases -- **Instrumented**: Added logging/metrics for debugging - -## Usage Examples - -### Writing Tests - -The System interfaces are primarily used through our testing framework. See the [Testing Framework](./testing.md) documentation for detailed examples and best practices. - -### Creating a Mock Implementation - -```go -type MockSystem struct { - l1 *MockChain - l2Map map[uint64]*MockChain -} - -func NewMockSystem() *MockSystem { - return &MockSystem{ - l1: NewMockChain(), - l2Map: make(map[uint64]*MockChain), - } -} - -// Implement System interface... -``` - -## Benefits - -- **Abstraction**: Automation code is isolated from deployment details -- **Flexibility**: Easy to add new deployment types -- **Testability**: Support for various testing approaches -- **Consistency**: Same interface across all implementations -- **Extensibility**: Can add specialized implementations for specific needs - -## Best Practices - -1. **Write Against Interfaces**: Never depend on specific implementations -2. **Use Context**: For proper cancellation and timeouts -3. **Handle Errors**: All operations can fail -4. **Test Multiple Implementations**: Ensure code works across different types -5. **Consider Performance**: Choose appropriate implementation for use case - -The System interfaces provide a powerful abstraction layer that enables writing robust, deployment-agnostic automation code while supporting a wide range of implementation types for different use cases. diff --git a/devnet-sdk/book/src/testing.md b/devnet-sdk/book/src/testing.md deleted file mode 100644 index c88a3cb28b4e6..0000000000000 --- a/devnet-sdk/book/src/testing.md +++ /dev/null @@ -1,165 +0,0 @@ -# Testing Framework - -The devnet-sdk provides a comprehensive testing framework designed to make testing against Optimism devnets both powerful and developer-friendly. - -## Testing Philosophy - -Our testing approach is built on several key principles: - -### 1. Native Go Tests - -Tests are written as standard Go tests, providing: -- Full IDE integration -- Native debugging capabilities -- Familiar testing patterns -- Integration with standard Go tooling - -```go -func TestSystemWrapETH(t *testing.T) { - // Standard Go test function - systest.SystemTest(t, wrapETHScenario(...)) -} -``` - -### 2. Safe Test Execution - -Tests are designed to be safe and self-aware: -- Tests verify their prerequisites before execution -- Tests skip gracefully when prerequisites aren't met -- Clear distinction between precondition failures and test failures - -```go -// Test will skip if the system doesn't support required features -walletGetter, fundsValidator := validators.AcquireL2WalletWithFunds( - chainIdx, - types.NewBalance(big.NewInt(1.0 * constants.ETH)), -) -``` - -### 3. Testable Scenarios - -Test scenarios themselves are designed to be testable: -- Scenarios work against any compliant System implementation -- Mocks and fakes can be used for scenario validation -- Clear separation between test logic and system interaction - -### 4. Framework Integration - -The `systest` package provides integration helpers that: -- Handle system acquisition and setup -- Manage test context and cleanup -- Provide precondition validation -- Enable consistent test patterns - -## Example Test - -Here's a complete example showing these principles in action: - -```go -import ( - "math/big" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" - "github.com/ethereum-optimism/optimism/devnet-sdk/system" - "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" - "github.com/ethereum-optimism/optimism/devnet-sdk/testing/testlib/validators" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum/log" - "github.com/stretchr/testify/require" -) - -// Define test scenario as a function that works with any System implementation -func wrapETHScenario(chainIdx uint64, walletGetter validators.WalletGetter) systest.SystemTestFunc { - return func(t systest.T, sys system.System) { - ctx := t.Context() - - logger := testlog.Logger(t, log.LevelInfo) - logger := logger.With("test", "WrapETH", "devnet", sys.Identifier()) - - // Get the L2 chain we want to test with - chain := sys.L2(chainIdx) - logger = logger.With("chain", chain.ID()) - - // Get a funded wallet for testing - user := walletGetter(ctx) - - // Access contract registry - wethAddr := constants.WETH - weth, err := chain.ContractsRegistry().WETH(wethAddr) - require.NoError(t, err) - - // Test logic using pure interfaces - funds := types.NewBalance(big.NewInt(0.5 * constants.ETH)) - initialBalance, err := weth.BalanceOf(user.Address()).Call(ctx) - require.NoError(t, err) - - require.NoError(t, user.SendETH(wethAddr, funds).Send(ctx).Wait()) - - finalBalance, err := weth.BalanceOf(user.Address()).Call(ctx) - require.NoError(t, err) - - require.Equal(t, initialBalance.Add(funds), finalBalance) - } -} - -func TestSystemWrapETH(t *testing.T) { - chainIdx := uint64(0) // First L2 chain - - // Setup wallet with required funds - this acts as a precondition - walletGetter, fundsValidator := validators.AcquireL2WalletWithFunds( - chainIdx, - types.NewBalance(big.NewInt(1.0 * constants.ETH)), - ) - - // Run the test with system management handled by framework - systest.SystemTest(t, - wrapETHScenario(chainIdx, walletGetter), - fundsValidator, - ) -} -``` - -## Framework Components - -### 1. Test Context Management - -The framework provides context management through `systest.T`: -- Proper test timeouts -- Cleanup handling -- Resource management -- Logging context - -### 2. Precondition Validators - -Validators ensure test prerequisites are met: -```go -// Validator ensures required funds are available -fundsValidator := validators.AcquireL2WalletWithFunds(...) -``` - -### 3. System Acquisition - -The framework handles system creation and setup: -```go -systest.SystemTest(t, func(t systest.T, sys system.System) { - // System is ready to use -}) -``` - -### 4. Resource Management - -Resources are properly managed: -- Automatic cleanup -- Proper error handling -- Context cancellation - -## Best Practices - -1. **Use Scenarios**: Write reusable test scenarios that work with any System implementation -2. **Validate Prerequisites**: Always check test prerequisites using validators -3. **Handle Resources**: Use the framework's resource management -4. **Use Pure Interfaces**: Write tests against the interfaces, not specific implementations -5. **Proper Logging**: Use structured logging with test context -6. **Clear Setup**: Keep test setup clear and explicit -7. **Error Handling**: Always handle errors and provide clear failure messages diff --git a/devnet-sdk/book/theme/css/footer.css b/devnet-sdk/book/theme/css/footer.css deleted file mode 100644 index cb7be80ab2145..0000000000000 --- a/devnet-sdk/book/theme/css/footer.css +++ /dev/null @@ -1,71 +0,0 @@ -.mdbook-footer { - width: 100%; - padding: 4rem 2.5rem; /* Increased padding */ - background-color: var(--bg); - border-top: 1px solid var(--sidebar-bg); - margin-top: 5rem; /* Increased margin */ -} - -.mdbook-footer .footer-container { - max-width: 1200px; - margin: 0 auto; - display: flex; - flex-direction: column; - gap: 2.5rem; /* Increased gap */ - align-items: center; -} - -.mdbook-footer .policy-links { - display: flex; - gap: 4rem; /* Increased gap between links */ - flex-wrap: wrap; - justify-content: center; -} - -.mdbook-footer .policy-links a { - color: var(--fg); - text-decoration: none; - transition: opacity 0.2s; - font-size: 1.35rem; /* Increased font size */ - opacity: 0.85; - font-weight: 400; - line-height: 1.6; /* Increased line height */ -} - -.mdbook-footer .policy-links a:hover { - opacity: 1; - text-decoration: underline; -} - -.mdbook-footer .copyright { - color: var(--fg); - font-size: 1.35rem; /* Increased font size */ - opacity: 0.85; - text-align: center; - font-weight: 400; - line-height: 1.6; /* Increased line height */ -} - -.mdbook-footer .copyright a { - color: var(--fg); - text-decoration: none; -} - -.mdbook-footer .copyright a:hover { - text-decoration: underline; -} - -@media (max-width: 640px) { - .mdbook-footer .policy-links { - gap: 2.5rem; /* Increased gap for mobile */ - } - - .mdbook-footer { - padding: 3rem 2rem; /* Increased padding for mobile */ - } - - .mdbook-footer .policy-links a, - .mdbook-footer .copyright { - font-size: 1.25rem; /* Increased font size for mobile */ - } -} \ No newline at end of file diff --git a/devnet-sdk/book/theme/js/footer.js b/devnet-sdk/book/theme/js/footer.js deleted file mode 100644 index 014f44f2d6c54..0000000000000 --- a/devnet-sdk/book/theme/js/footer.js +++ /dev/null @@ -1,41 +0,0 @@ -// Create footer element -function createFooter() { - const footer = document.createElement('footer'); - footer.className = 'mdbook-footer'; - - const container = document.createElement('div'); - container.className = 'footer-container'; - - // Add legal links - const policyLinks = document.createElement('div'); - policyLinks.className = 'policy-links'; - - const links = [ - { href: 'https://optimism.io/community-agreement', text: 'Community Agreement' }, - { href: 'https://optimism.io/terms', text: 'Terms of Service' }, - { href: 'https://optimism.io/data-privacy-policy', text: 'Privacy Policy' } - ]; - - links.forEach(link => { - const a = document.createElement('a'); - a.href = link.href; - a.textContent = link.text; - policyLinks.appendChild(a); - }); - - // Add copyright notice - const copyright = document.createElement('div'); - copyright.className = 'copyright'; - copyright.innerHTML = `© ${new Date().getFullYear()} Optimism Foundation. All rights reserved.`; - - // Assemble footer - container.appendChild(policyLinks); - container.appendChild(copyright); - footer.appendChild(container); - - // Add footer to page - document.body.appendChild(footer); -} - -// Run after DOM is loaded -document.addEventListener('DOMContentLoaded', createFooter); \ No newline at end of file diff --git a/devnet-sdk/cmd/mf2kt/main.go b/devnet-sdk/cmd/mf2kt/main.go deleted file mode 100644 index de1ad36b4ec79..0000000000000 --- a/devnet-sdk/cmd/mf2kt/main.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/devnet-sdk/kt" - "github.com/ethereum-optimism/optimism/devnet-sdk/manifest" - "github.com/urfave/cli/v2" - "gopkg.in/yaml.v3" -) - -func main() { - app := &cli.App{ - Name: "devnet", - Usage: "Generate Kurtosis parameters from a devnet manifest", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "manifest", - Aliases: []string{"m"}, - Usage: "Path to the manifest YAML file", - Required: true, - }, - &cli.StringFlag{ - Name: "output", - Aliases: []string{"o"}, - Usage: "Path to write the Kurtosis parameters file (default: stdout)", - }, - }, - Action: func(c *cli.Context) error { - // Read manifest file - manifestPath := c.String("manifest") - manifestBytes, err := os.ReadFile(manifestPath) - if err != nil { - return fmt.Errorf("failed to read manifest file: %w", err) - } - - // Parse manifest YAML - var m manifest.Manifest - if err := yaml.Unmarshal(manifestBytes, &m); err != nil { - return fmt.Errorf("failed to parse manifest YAML: %w", err) - } - - // Create visitor and process manifest - visitor := kt.NewKurtosisVisitor() - m.Accept(visitor) - - // Get params and write to file or stdout - params := visitor.GetParams() - paramsBytes, err := yaml.Marshal(params) - if err != nil { - return fmt.Errorf("failed to marshal params: %w", err) - } - - outputPath := c.String("output") - if outputPath != "" { - if err := os.WriteFile(outputPath, paramsBytes, 0644); err != nil { - return fmt.Errorf("failed to write params file: %w", err) - } - } else { - fmt.Print(string(paramsBytes)) - } - - return nil - }, - } - - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) - os.Exit(1) - } -} diff --git a/devnet-sdk/constraints/constraints.go b/devnet-sdk/constraints/constraints.go deleted file mode 100644 index c4566bb350408..0000000000000 --- a/devnet-sdk/constraints/constraints.go +++ /dev/null @@ -1,26 +0,0 @@ -package constraints - -import ( - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/devnet-sdk/system" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" -) - -type WalletConstraint interface { - CheckWallet(wallet system.Wallet) bool -} - -type WalletConstraintFunc func(wallet system.Wallet) bool - -func (f WalletConstraintFunc) CheckWallet(wallet system.Wallet) bool { - return f(wallet) -} - -func WithBalance(amount types.Balance) WalletConstraint { - return WalletConstraintFunc(func(wallet system.Wallet) bool { - balance := wallet.Balance() - log.Debug("checking balance", "wallet", wallet.Address(), "balance", balance, "needed", amount) - return balance.GreaterThan(amount) - }) -} diff --git a/devnet-sdk/constraints/constraints_test.go b/devnet-sdk/constraints/constraints_test.go deleted file mode 100644 index b55bd2892d261..0000000000000 --- a/devnet-sdk/constraints/constraints_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package constraints - -import ( - "context" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/system" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/assert" -) - -type mockWallet struct { - balance types.Balance - address types.Address -} - -func (m mockWallet) Balance() types.Balance { - return m.balance -} - -func (m mockWallet) Address() types.Address { - return m.address -} - -func (m mockWallet) PrivateKey() types.Key { - key, _ := crypto.HexToECDSA("123") - return types.Key(key) -} - -func (m mockWallet) SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] { - panic("not implemented") -} - -func (m mockWallet) InitiateMessage(chainID types.ChainID, target common.Address, message []byte) types.WriteInvocation[any] { - panic("not implemented") -} - -func (m mockWallet) ExecuteMessage(identifier bindings.Identifier, sentMessage []byte) types.WriteInvocation[any] { - panic("not implemented") -} - -func (m mockWallet) Nonce() uint64 { - return 0 -} - -func (m mockWallet) Sign(tx system.Transaction) (system.Transaction, error) { - return tx, nil -} - -func (m mockWallet) Send(ctx context.Context, tx system.Transaction) error { - return nil -} - -func (m mockWallet) Transactor() *bind.TransactOpts { - return nil -} - -var _ system.Wallet = (*mockWallet)(nil) - -func newBigInt(x int64) *big.Int { - return big.NewInt(x) -} - -func TestWithBalance(t *testing.T) { - tests := []struct { - name string - walletBalance types.Balance - requiredAmount types.Balance - expectPass bool - }{ - { - name: "balance greater than required", - walletBalance: types.NewBalance(newBigInt(200)), - requiredAmount: types.NewBalance(newBigInt(100)), - expectPass: true, - }, - { - name: "balance equal to required", - walletBalance: types.NewBalance(newBigInt(100)), - requiredAmount: types.NewBalance(newBigInt(100)), - expectPass: false, - }, - { - name: "balance less than required", - walletBalance: types.NewBalance(newBigInt(50)), - requiredAmount: types.NewBalance(newBigInt(100)), - expectPass: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - wallet := mockWallet{ - balance: tt.walletBalance, - address: common.HexToAddress("0x123"), - } - constraint := WithBalance(tt.requiredAmount) - result := constraint.CheckWallet(wallet) - assert.Equal(t, tt.expectPass, result, "balance check should match expected result") - }) - } -} - -func TestWalletConstraintFunc(t *testing.T) { - called := false - testFunc := WalletConstraintFunc(func(wallet system.Wallet) bool { - called = true - return true - }) - - wallet := mockWallet{ - balance: types.NewBalance(newBigInt(100)), - address: common.HexToAddress("0x123"), - } - - result := testFunc.CheckWallet(wallet) - assert.True(t, called, "constraint function should have been called") - assert.True(t, result, "constraint function should return true") -} diff --git a/devnet-sdk/contracts/bindings/eventlogger.go b/devnet-sdk/contracts/bindings/eventlogger.go deleted file mode 100644 index c92749341e9cb..0000000000000 --- a/devnet-sdk/contracts/bindings/eventlogger.go +++ /dev/null @@ -1,68 +0,0 @@ -package bindings - -// This file was generated and edited by below sequences: -// cd packages/contracts-bedrock -// solc --optimize --bin --abi -o out src/integration/EventLogger.sol -// abigen --abi out/EventLogger.abi --bin out/EventLogger.bin --pkg bindings --out eventlogger.go -// Resulting eventlogger.go was moved to this file, and only the needed implementation was left here. - -import ( - "errors" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -// EventloggerMetaData contains all meta data concerning the Eventlogger contract. -var EventloggerMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"_topics\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"emitLog\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"origin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"logIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"structIdentifier\",\"name\":\"_id\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_msgHash\",\"type\":\"bytes32\"}],\"name\":\"validateMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x6080604052348015600e575f80fd5b506102ac8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063ab4d6f7514610038578063edebc13b1461004d575b5f80fd5b61004b61004636600461013e565b610060565b005b61004b61005b36600461016c565b6100bd565b60405163ab4d6f7560e01b81526022602160991b019063ab4d6f759061008c9085908590600401610226565b5f604051808303815f87803b1580156100a3575f80fd5b505af11580156100b5573d5f803e3d5ffd5b505050505050565b80604051818482378486356020880135604089013560608a0135848015610102576001811461010a5760028114610113576003811461011d5760048114610128575f80fd5b8787a0610130565b848888a1610130565b83858989a2610130565b8284868a8aa3610130565b818385878b8ba45b505050505050505050505050565b5f8082840360c0811215610150575f80fd5b60a081121561015d575f80fd5b50919360a08501359350915050565b5f805f806040858703121561017f575f80fd5b843567ffffffffffffffff80821115610196575f80fd5b818701915087601f8301126101a9575f80fd5b8135818111156101b7575f80fd5b8860208260051b85010111156101cb575f80fd5b6020928301965094509086013590808211156101e5575f80fd5b818701915087601f8301126101f8575f80fd5b813581811115610206575f80fd5b886020828501011115610217575f80fd5b95989497505060200194505050565b60c0810183356001600160a01b038116808214610241575f80fd5b8352506020848101359083015260408085013590830152606080850135908301526080938401359382019390935260a001529056fea26469706673582212206da9bc84d514e1a78e2b4160f99f93aa58672040ece82f45ac2a878aeefdfbe164736f6c63430008190033", -} - -// EventloggerABI is the input ABI used to generate the binding from. -// Deprecated: Use EventloggerMetaData.ABI instead. -var EventloggerABI = EventloggerMetaData.ABI - -// EventloggerBin is the compiled bytecode used for deploying new contracts. -// Deprecated: Use EventloggerMetaData.Bin instead. -var EventloggerBin = EventloggerMetaData.Bin - -// DeployEventlogger deploys a new Ethereum contract, binding an instance of Eventlogger to it. -func DeployEventlogger(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Eventlogger, error) { - parsed, err := EventloggerMetaData.GetAbi() - if err != nil { - return common.Address{}, nil, nil, err - } - if parsed == nil { - return common.Address{}, nil, nil, errors.New("GetABI returned nil") - } - - address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(EventloggerBin), backend) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &Eventlogger{EventloggerCaller: EventloggerCaller{contract: contract}, EventloggerTransactor: EventloggerTransactor{contract: contract}, EventloggerFilterer: EventloggerFilterer{contract: contract}}, nil -} - -// Eventlogger is an auto generated Go binding around an Ethereum contract. -type Eventlogger struct { - EventloggerCaller // Read-only binding to the contract - EventloggerTransactor // Write-only binding to the contract - EventloggerFilterer // Log filterer for contract events -} - -// EventloggerCaller is an auto generated read-only Go binding around an Ethereum contract. -type EventloggerCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// EventloggerTransactor is an auto generated write-only Go binding around an Ethereum contract. -type EventloggerTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// EventloggerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type EventloggerFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} diff --git a/devnet-sdk/contracts/bindings/l2tol2crossdomainmessenger.go b/devnet-sdk/contracts/bindings/l2tol2crossdomainmessenger.go deleted file mode 100644 index 01d733398b636..0000000000000 --- a/devnet-sdk/contracts/bindings/l2tol2crossdomainmessenger.go +++ /dev/null @@ -1,788 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package bindings - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription -) - -// Identifier is an auto generated low-level Go binding around an user-defined struct. -type Identifier struct { - Origin common.Address - BlockNumber *big.Int - LogIndex *big.Int - Timestamp *big.Int - ChainId *big.Int -} - -// L2ToL2CrossDomainMessengerMetaData contains all meta data concerning the L2ToL2CrossDomainMessenger contract. -var L2ToL2CrossDomainMessengerMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"name\":\"crossDomainMessageContext\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"sender_\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"source_\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"crossDomainMessageSender\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"sender_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"crossDomainMessageSource\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"source_\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"messageNonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"messageVersion\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"origin\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"logIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"structIdentifier\",\"name\":\"_id\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"_sentMessage\",\"type\":\"bytes\"}],\"name\":\"relayMessage\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"returnData_\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_destination\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"}],\"name\":\"sendMessage\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"successfulMessages\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"source\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"messageNonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"}],\"name\":\"RelayedMessage\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"destination\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"messageNonce\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"}],\"name\":\"SentMessage\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"EventPayloadNotSentMessage\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IdOriginNotL2ToL2CrossDomainMessenger\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidChainId\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageAlreadyRelayed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageDestinationNotRelayChain\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageDestinationSameChain\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageTargetCrossL2Inbox\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MessageTargetL2ToL2CrossDomainMessenger\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotEntered\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReentrantCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TargetCallFailed\",\"type\":\"error\"}]", -} - -// L2ToL2CrossDomainMessengerABI is the input ABI used to generate the binding from. -// Deprecated: Use L2ToL2CrossDomainMessengerMetaData.ABI instead. -var L2ToL2CrossDomainMessengerABI = L2ToL2CrossDomainMessengerMetaData.ABI - -// L2ToL2CrossDomainMessenger is an auto generated Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessenger struct { - L2ToL2CrossDomainMessengerCaller // Read-only binding to the contract - L2ToL2CrossDomainMessengerTransactor // Write-only binding to the contract - L2ToL2CrossDomainMessengerFilterer // Log filterer for contract events -} - -// L2ToL2CrossDomainMessengerCaller is an auto generated read-only Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// L2ToL2CrossDomainMessengerTransactor is an auto generated write-only Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// L2ToL2CrossDomainMessengerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type L2ToL2CrossDomainMessengerFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// L2ToL2CrossDomainMessengerSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type L2ToL2CrossDomainMessengerSession struct { - Contract *L2ToL2CrossDomainMessenger // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// L2ToL2CrossDomainMessengerCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type L2ToL2CrossDomainMessengerCallerSession struct { - Contract *L2ToL2CrossDomainMessengerCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// L2ToL2CrossDomainMessengerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type L2ToL2CrossDomainMessengerTransactorSession struct { - Contract *L2ToL2CrossDomainMessengerTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// L2ToL2CrossDomainMessengerRaw is an auto generated low-level Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerRaw struct { - Contract *L2ToL2CrossDomainMessenger // Generic contract binding to access the raw methods on -} - -// L2ToL2CrossDomainMessengerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerCallerRaw struct { - Contract *L2ToL2CrossDomainMessengerCaller // Generic read-only contract binding to access the raw methods on -} - -// L2ToL2CrossDomainMessengerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type L2ToL2CrossDomainMessengerTransactorRaw struct { - Contract *L2ToL2CrossDomainMessengerTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewL2ToL2CrossDomainMessenger creates a new instance of L2ToL2CrossDomainMessenger, bound to a specific deployed contract. -func NewL2ToL2CrossDomainMessenger(address common.Address, backend bind.ContractBackend) (*L2ToL2CrossDomainMessenger, error) { - contract, err := bindL2ToL2CrossDomainMessenger(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessenger{L2ToL2CrossDomainMessengerCaller: L2ToL2CrossDomainMessengerCaller{contract: contract}, L2ToL2CrossDomainMessengerTransactor: L2ToL2CrossDomainMessengerTransactor{contract: contract}, L2ToL2CrossDomainMessengerFilterer: L2ToL2CrossDomainMessengerFilterer{contract: contract}}, nil -} - -// NewL2ToL2CrossDomainMessengerCaller creates a new read-only instance of L2ToL2CrossDomainMessenger, bound to a specific deployed contract. -func NewL2ToL2CrossDomainMessengerCaller(address common.Address, caller bind.ContractCaller) (*L2ToL2CrossDomainMessengerCaller, error) { - contract, err := bindL2ToL2CrossDomainMessenger(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerCaller{contract: contract}, nil -} - -// NewL2ToL2CrossDomainMessengerTransactor creates a new write-only instance of L2ToL2CrossDomainMessenger, bound to a specific deployed contract. -func NewL2ToL2CrossDomainMessengerTransactor(address common.Address, transactor bind.ContractTransactor) (*L2ToL2CrossDomainMessengerTransactor, error) { - contract, err := bindL2ToL2CrossDomainMessenger(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerTransactor{contract: contract}, nil -} - -// NewL2ToL2CrossDomainMessengerFilterer creates a new log filterer instance of L2ToL2CrossDomainMessenger, bound to a specific deployed contract. -func NewL2ToL2CrossDomainMessengerFilterer(address common.Address, filterer bind.ContractFilterer) (*L2ToL2CrossDomainMessengerFilterer, error) { - contract, err := bindL2ToL2CrossDomainMessenger(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerFilterer{contract: contract}, nil -} - -// bindL2ToL2CrossDomainMessenger binds a generic wrapper to an already deployed contract. -func bindL2ToL2CrossDomainMessenger(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(L2ToL2CrossDomainMessengerABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _L2ToL2CrossDomainMessenger.Contract.L2ToL2CrossDomainMessengerCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.L2ToL2CrossDomainMessengerTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.L2ToL2CrossDomainMessengerTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _L2ToL2CrossDomainMessenger.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.contract.Transact(opts, method, params...) -} - -// CrossDomainMessageContext is a free data retrieval call binding the contract method 0x7936cbee. -// -// Solidity: function crossDomainMessageContext() view returns(address sender_, uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) CrossDomainMessageContext(opts *bind.CallOpts) (struct { - Sender common.Address - Source *big.Int -}, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "crossDomainMessageContext") - - outstruct := new(struct { - Sender common.Address - Source *big.Int - }) - if err != nil { - return *outstruct, err - } - - outstruct.Sender = *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - outstruct.Source = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) - - return *outstruct, err - -} - -// CrossDomainMessageContext is a free data retrieval call binding the contract method 0x7936cbee. -// -// Solidity: function crossDomainMessageContext() view returns(address sender_, uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) CrossDomainMessageContext() (struct { - Sender common.Address - Source *big.Int -}, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageContext(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageContext is a free data retrieval call binding the contract method 0x7936cbee. -// -// Solidity: function crossDomainMessageContext() view returns(address sender_, uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) CrossDomainMessageContext() (struct { - Sender common.Address - Source *big.Int -}, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageContext(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageSender is a free data retrieval call binding the contract method 0x38ffde18. -// -// Solidity: function crossDomainMessageSender() view returns(address sender_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) CrossDomainMessageSender(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "crossDomainMessageSender") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// CrossDomainMessageSender is a free data retrieval call binding the contract method 0x38ffde18. -// -// Solidity: function crossDomainMessageSender() view returns(address sender_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) CrossDomainMessageSender() (common.Address, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageSender(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageSender is a free data retrieval call binding the contract method 0x38ffde18. -// -// Solidity: function crossDomainMessageSender() view returns(address sender_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) CrossDomainMessageSender() (common.Address, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageSender(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageSource is a free data retrieval call binding the contract method 0x24794462. -// -// Solidity: function crossDomainMessageSource() view returns(uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) CrossDomainMessageSource(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "crossDomainMessageSource") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// CrossDomainMessageSource is a free data retrieval call binding the contract method 0x24794462. -// -// Solidity: function crossDomainMessageSource() view returns(uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) CrossDomainMessageSource() (*big.Int, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageSource(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// CrossDomainMessageSource is a free data retrieval call binding the contract method 0x24794462. -// -// Solidity: function crossDomainMessageSource() view returns(uint256 source_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) CrossDomainMessageSource() (*big.Int, error) { - return _L2ToL2CrossDomainMessenger.Contract.CrossDomainMessageSource(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// MessageNonce is a free data retrieval call binding the contract method 0xecc70428. -// -// Solidity: function messageNonce() view returns(uint256) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) MessageNonce(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "messageNonce") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// MessageNonce is a free data retrieval call binding the contract method 0xecc70428. -// -// Solidity: function messageNonce() view returns(uint256) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) MessageNonce() (*big.Int, error) { - return _L2ToL2CrossDomainMessenger.Contract.MessageNonce(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// MessageNonce is a free data retrieval call binding the contract method 0xecc70428. -// -// Solidity: function messageNonce() view returns(uint256) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) MessageNonce() (*big.Int, error) { - return _L2ToL2CrossDomainMessenger.Contract.MessageNonce(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// MessageVersion is a free data retrieval call binding the contract method 0x52617f3c. -// -// Solidity: function messageVersion() view returns(uint16) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) MessageVersion(opts *bind.CallOpts) (uint16, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "messageVersion") - - if err != nil { - return *new(uint16), err - } - - out0 := *abi.ConvertType(out[0], new(uint16)).(*uint16) - - return out0, err - -} - -// MessageVersion is a free data retrieval call binding the contract method 0x52617f3c. -// -// Solidity: function messageVersion() view returns(uint16) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) MessageVersion() (uint16, error) { - return _L2ToL2CrossDomainMessenger.Contract.MessageVersion(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// MessageVersion is a free data retrieval call binding the contract method 0x52617f3c. -// -// Solidity: function messageVersion() view returns(uint16) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) MessageVersion() (uint16, error) { - return _L2ToL2CrossDomainMessenger.Contract.MessageVersion(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// SuccessfulMessages is a free data retrieval call binding the contract method 0xb1b1b209. -// -// Solidity: function successfulMessages(bytes32 ) view returns(bool) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) SuccessfulMessages(opts *bind.CallOpts, arg0 [32]byte) (bool, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "successfulMessages", arg0) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// SuccessfulMessages is a free data retrieval call binding the contract method 0xb1b1b209. -// -// Solidity: function successfulMessages(bytes32 ) view returns(bool) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) SuccessfulMessages(arg0 [32]byte) (bool, error) { - return _L2ToL2CrossDomainMessenger.Contract.SuccessfulMessages(&_L2ToL2CrossDomainMessenger.CallOpts, arg0) -} - -// SuccessfulMessages is a free data retrieval call binding the contract method 0xb1b1b209. -// -// Solidity: function successfulMessages(bytes32 ) view returns(bool) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) SuccessfulMessages(arg0 [32]byte) (bool, error) { - return _L2ToL2CrossDomainMessenger.Contract.SuccessfulMessages(&_L2ToL2CrossDomainMessenger.CallOpts, arg0) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCaller) Version(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _L2ToL2CrossDomainMessenger.contract.Call(opts, &out, "version") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) Version() (string, error) { - return _L2ToL2CrossDomainMessenger.Contract.Version(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerCallerSession) Version() (string, error) { - return _L2ToL2CrossDomainMessenger.Contract.Version(&_L2ToL2CrossDomainMessenger.CallOpts) -} - -// RelayMessage is a paid mutator transaction binding the contract method 0x8d1d298f. -// -// Solidity: function relayMessage((address,uint256,uint256,uint256,uint256) _id, bytes _sentMessage) payable returns(bytes returnData_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactor) RelayMessage(opts *bind.TransactOpts, _id Identifier, _sentMessage []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.contract.Transact(opts, "relayMessage", _id, _sentMessage) -} - -// RelayMessage is a paid mutator transaction binding the contract method 0x8d1d298f. -// -// Solidity: function relayMessage((address,uint256,uint256,uint256,uint256) _id, bytes _sentMessage) payable returns(bytes returnData_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) RelayMessage(_id Identifier, _sentMessage []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.RelayMessage(&_L2ToL2CrossDomainMessenger.TransactOpts, _id, _sentMessage) -} - -// RelayMessage is a paid mutator transaction binding the contract method 0x8d1d298f. -// -// Solidity: function relayMessage((address,uint256,uint256,uint256,uint256) _id, bytes _sentMessage) payable returns(bytes returnData_) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactorSession) RelayMessage(_id Identifier, _sentMessage []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.RelayMessage(&_L2ToL2CrossDomainMessenger.TransactOpts, _id, _sentMessage) -} - -// SendMessage is a paid mutator transaction binding the contract method 0x7056f41f. -// -// Solidity: function sendMessage(uint256 _destination, address _target, bytes _message) returns(bytes32) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactor) SendMessage(opts *bind.TransactOpts, _destination *big.Int, _target common.Address, _message []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.contract.Transact(opts, "sendMessage", _destination, _target, _message) -} - -// SendMessage is a paid mutator transaction binding the contract method 0x7056f41f. -// -// Solidity: function sendMessage(uint256 _destination, address _target, bytes _message) returns(bytes32) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerSession) SendMessage(_destination *big.Int, _target common.Address, _message []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.SendMessage(&_L2ToL2CrossDomainMessenger.TransactOpts, _destination, _target, _message) -} - -// SendMessage is a paid mutator transaction binding the contract method 0x7056f41f. -// -// Solidity: function sendMessage(uint256 _destination, address _target, bytes _message) returns(bytes32) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerTransactorSession) SendMessage(_destination *big.Int, _target common.Address, _message []byte) (*types.Transaction, error) { - return _L2ToL2CrossDomainMessenger.Contract.SendMessage(&_L2ToL2CrossDomainMessenger.TransactOpts, _destination, _target, _message) -} - -// L2ToL2CrossDomainMessengerRelayedMessageIterator is returned from FilterRelayedMessage and is used to iterate over the raw logs and unpacked data for RelayedMessage events raised by the L2ToL2CrossDomainMessenger contract. -type L2ToL2CrossDomainMessengerRelayedMessageIterator struct { - Event *L2ToL2CrossDomainMessengerRelayedMessage // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *L2ToL2CrossDomainMessengerRelayedMessageIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(L2ToL2CrossDomainMessengerRelayedMessage) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(L2ToL2CrossDomainMessengerRelayedMessage) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *L2ToL2CrossDomainMessengerRelayedMessageIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *L2ToL2CrossDomainMessengerRelayedMessageIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// L2ToL2CrossDomainMessengerRelayedMessage represents a RelayedMessage event raised by the L2ToL2CrossDomainMessenger contract. -type L2ToL2CrossDomainMessengerRelayedMessage struct { - Source *big.Int - MessageNonce *big.Int - MessageHash [32]byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterRelayedMessage is a free log retrieval operation binding the contract event 0x5948076590932b9d173029c7df03fe386e755a61c86c7fe2671011a2faa2a379. -// -// Solidity: event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) FilterRelayedMessage(opts *bind.FilterOpts, source []*big.Int, messageNonce []*big.Int, messageHash [][32]byte) (*L2ToL2CrossDomainMessengerRelayedMessageIterator, error) { - - var sourceRule []interface{} - for _, sourceItem := range source { - sourceRule = append(sourceRule, sourceItem) - } - var messageNonceRule []interface{} - for _, messageNonceItem := range messageNonce { - messageNonceRule = append(messageNonceRule, messageNonceItem) - } - var messageHashRule []interface{} - for _, messageHashItem := range messageHash { - messageHashRule = append(messageHashRule, messageHashItem) - } - - logs, sub, err := _L2ToL2CrossDomainMessenger.contract.FilterLogs(opts, "RelayedMessage", sourceRule, messageNonceRule, messageHashRule) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerRelayedMessageIterator{contract: _L2ToL2CrossDomainMessenger.contract, event: "RelayedMessage", logs: logs, sub: sub}, nil -} - -// WatchRelayedMessage is a free log subscription operation binding the contract event 0x5948076590932b9d173029c7df03fe386e755a61c86c7fe2671011a2faa2a379. -// -// Solidity: event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) WatchRelayedMessage(opts *bind.WatchOpts, sink chan<- *L2ToL2CrossDomainMessengerRelayedMessage, source []*big.Int, messageNonce []*big.Int, messageHash [][32]byte) (event.Subscription, error) { - - var sourceRule []interface{} - for _, sourceItem := range source { - sourceRule = append(sourceRule, sourceItem) - } - var messageNonceRule []interface{} - for _, messageNonceItem := range messageNonce { - messageNonceRule = append(messageNonceRule, messageNonceItem) - } - var messageHashRule []interface{} - for _, messageHashItem := range messageHash { - messageHashRule = append(messageHashRule, messageHashItem) - } - - logs, sub, err := _L2ToL2CrossDomainMessenger.contract.WatchLogs(opts, "RelayedMessage", sourceRule, messageNonceRule, messageHashRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(L2ToL2CrossDomainMessengerRelayedMessage) - if err := _L2ToL2CrossDomainMessenger.contract.UnpackLog(event, "RelayedMessage", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseRelayedMessage is a log parse operation binding the contract event 0x5948076590932b9d173029c7df03fe386e755a61c86c7fe2671011a2faa2a379. -// -// Solidity: event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) ParseRelayedMessage(log types.Log) (*L2ToL2CrossDomainMessengerRelayedMessage, error) { - event := new(L2ToL2CrossDomainMessengerRelayedMessage) - if err := _L2ToL2CrossDomainMessenger.contract.UnpackLog(event, "RelayedMessage", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// L2ToL2CrossDomainMessengerSentMessageIterator is returned from FilterSentMessage and is used to iterate over the raw logs and unpacked data for SentMessage events raised by the L2ToL2CrossDomainMessenger contract. -type L2ToL2CrossDomainMessengerSentMessageIterator struct { - Event *L2ToL2CrossDomainMessengerSentMessage // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *L2ToL2CrossDomainMessengerSentMessageIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(L2ToL2CrossDomainMessengerSentMessage) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(L2ToL2CrossDomainMessengerSentMessage) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *L2ToL2CrossDomainMessengerSentMessageIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *L2ToL2CrossDomainMessengerSentMessageIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// L2ToL2CrossDomainMessengerSentMessage represents a SentMessage event raised by the L2ToL2CrossDomainMessenger contract. -type L2ToL2CrossDomainMessengerSentMessage struct { - Destination *big.Int - Target common.Address - MessageNonce *big.Int - Sender common.Address - Message []byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterSentMessage is a free log retrieval operation binding the contract event 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320. -// -// Solidity: event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) FilterSentMessage(opts *bind.FilterOpts, destination []*big.Int, target []common.Address, messageNonce []*big.Int) (*L2ToL2CrossDomainMessengerSentMessageIterator, error) { - - var destinationRule []interface{} - for _, destinationItem := range destination { - destinationRule = append(destinationRule, destinationItem) - } - var targetRule []interface{} - for _, targetItem := range target { - targetRule = append(targetRule, targetItem) - } - var messageNonceRule []interface{} - for _, messageNonceItem := range messageNonce { - messageNonceRule = append(messageNonceRule, messageNonceItem) - } - - logs, sub, err := _L2ToL2CrossDomainMessenger.contract.FilterLogs(opts, "SentMessage", destinationRule, targetRule, messageNonceRule) - if err != nil { - return nil, err - } - return &L2ToL2CrossDomainMessengerSentMessageIterator{contract: _L2ToL2CrossDomainMessenger.contract, event: "SentMessage", logs: logs, sub: sub}, nil -} - -// WatchSentMessage is a free log subscription operation binding the contract event 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320. -// -// Solidity: event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) WatchSentMessage(opts *bind.WatchOpts, sink chan<- *L2ToL2CrossDomainMessengerSentMessage, destination []*big.Int, target []common.Address, messageNonce []*big.Int) (event.Subscription, error) { - - var destinationRule []interface{} - for _, destinationItem := range destination { - destinationRule = append(destinationRule, destinationItem) - } - var targetRule []interface{} - for _, targetItem := range target { - targetRule = append(targetRule, targetItem) - } - var messageNonceRule []interface{} - for _, messageNonceItem := range messageNonce { - messageNonceRule = append(messageNonceRule, messageNonceItem) - } - - logs, sub, err := _L2ToL2CrossDomainMessenger.contract.WatchLogs(opts, "SentMessage", destinationRule, targetRule, messageNonceRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(L2ToL2CrossDomainMessengerSentMessage) - if err := _L2ToL2CrossDomainMessenger.contract.UnpackLog(event, "SentMessage", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseSentMessage is a log parse operation binding the contract event 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320. -// -// Solidity: event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message) -func (_L2ToL2CrossDomainMessenger *L2ToL2CrossDomainMessengerFilterer) ParseSentMessage(log types.Log) (*L2ToL2CrossDomainMessengerSentMessage, error) { - event := new(L2ToL2CrossDomainMessengerSentMessage) - if err := _L2ToL2CrossDomainMessenger.contract.UnpackLog(event, "SentMessage", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/devnet-sdk/contracts/bindings/superchainweth.go b/devnet-sdk/contracts/bindings/superchainweth.go deleted file mode 100644 index e0049ff999669..0000000000000 --- a/devnet-sdk/contracts/bindings/superchainweth.go +++ /dev/null @@ -1,1879 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package bindings - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription -) - -// SuperchainWETHMetaData contains all meta data concerning the SuperchainWETH contract. -var SuperchainWETHMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"fallback\",\"stateMutability\":\"payable\"},{\"type\":\"receive\",\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"allowance\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"approve\",\"inputs\":[{\"name\":\"guy\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"balanceOf\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"crosschainBurn\",\"inputs\":[{\"name\":\"_from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"crosschainMint\",\"inputs\":[{\"name\":\"_to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"decimals\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"deposit\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"name\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"relayETH\",\"inputs\":[{\"name\":\"_from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"sendETH\",\"inputs\":[{\"name\":\"_to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"msgHash_\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"_interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"symbol\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"totalSupply\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transfer\",\"inputs\":[{\"name\":\"dst\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferFrom\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"dst\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"withdraw\",\"inputs\":[{\"name\":\"_amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Approval\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"guy\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"CrosschainBurn\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"CrosschainMint\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Deposit\",\"inputs\":[{\"name\":\"dst\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RelayETH\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"source\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SendETH\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"destination\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"dst\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Withdrawal\",\"inputs\":[{\"name\":\"src\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"wad\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"InvalidCrossDomainSender\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotCustomGasToken\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"Unauthorized\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ZeroAddress\",\"inputs\":[]}]", -} - -// SuperchainWETHABI is the input ABI used to generate the binding from. -// Deprecated: Use SuperchainWETHMetaData.ABI instead. -var SuperchainWETHABI = SuperchainWETHMetaData.ABI - -// SuperchainWETH is an auto generated Go binding around an Ethereum contract. -type SuperchainWETH struct { - SuperchainWETHCaller // Read-only binding to the contract - SuperchainWETHTransactor // Write-only binding to the contract - SuperchainWETHFilterer // Log filterer for contract events -} - -// SuperchainWETHCaller is an auto generated read-only Go binding around an Ethereum contract. -type SuperchainWETHCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// SuperchainWETHTransactor is an auto generated write-only Go binding around an Ethereum contract. -type SuperchainWETHTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// SuperchainWETHFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type SuperchainWETHFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// SuperchainWETHSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type SuperchainWETHSession struct { - Contract *SuperchainWETH // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// SuperchainWETHCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type SuperchainWETHCallerSession struct { - Contract *SuperchainWETHCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// SuperchainWETHTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type SuperchainWETHTransactorSession struct { - Contract *SuperchainWETHTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// SuperchainWETHRaw is an auto generated low-level Go binding around an Ethereum contract. -type SuperchainWETHRaw struct { - Contract *SuperchainWETH // Generic contract binding to access the raw methods on -} - -// SuperchainWETHCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type SuperchainWETHCallerRaw struct { - Contract *SuperchainWETHCaller // Generic read-only contract binding to access the raw methods on -} - -// SuperchainWETHTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type SuperchainWETHTransactorRaw struct { - Contract *SuperchainWETHTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewSuperchainWETH creates a new instance of SuperchainWETH, bound to a specific deployed contract. -func NewSuperchainWETH(address common.Address, backend bind.ContractBackend) (*SuperchainWETH, error) { - contract, err := bindSuperchainWETH(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &SuperchainWETH{SuperchainWETHCaller: SuperchainWETHCaller{contract: contract}, SuperchainWETHTransactor: SuperchainWETHTransactor{contract: contract}, SuperchainWETHFilterer: SuperchainWETHFilterer{contract: contract}}, nil -} - -// NewSuperchainWETHCaller creates a new read-only instance of SuperchainWETH, bound to a specific deployed contract. -func NewSuperchainWETHCaller(address common.Address, caller bind.ContractCaller) (*SuperchainWETHCaller, error) { - contract, err := bindSuperchainWETH(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &SuperchainWETHCaller{contract: contract}, nil -} - -// NewSuperchainWETHTransactor creates a new write-only instance of SuperchainWETH, bound to a specific deployed contract. -func NewSuperchainWETHTransactor(address common.Address, transactor bind.ContractTransactor) (*SuperchainWETHTransactor, error) { - contract, err := bindSuperchainWETH(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &SuperchainWETHTransactor{contract: contract}, nil -} - -// NewSuperchainWETHFilterer creates a new log filterer instance of SuperchainWETH, bound to a specific deployed contract. -func NewSuperchainWETHFilterer(address common.Address, filterer bind.ContractFilterer) (*SuperchainWETHFilterer, error) { - contract, err := bindSuperchainWETH(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &SuperchainWETHFilterer{contract: contract}, nil -} - -// bindSuperchainWETH binds a generic wrapper to an already deployed contract. -func bindSuperchainWETH(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(SuperchainWETHABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_SuperchainWETH *SuperchainWETHRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _SuperchainWETH.Contract.SuperchainWETHCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_SuperchainWETH *SuperchainWETHRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _SuperchainWETH.Contract.SuperchainWETHTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_SuperchainWETH *SuperchainWETHRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _SuperchainWETH.Contract.SuperchainWETHTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_SuperchainWETH *SuperchainWETHCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _SuperchainWETH.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_SuperchainWETH *SuperchainWETHTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _SuperchainWETH.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_SuperchainWETH *SuperchainWETHTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _SuperchainWETH.Contract.contract.Transact(opts, method, params...) -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCaller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "allowance", owner, spender) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { - return _SuperchainWETH.Contract.Allowance(&_SuperchainWETH.CallOpts, owner, spender) -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { - return _SuperchainWETH.Contract.Allowance(&_SuperchainWETH.CallOpts, owner, spender) -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address src) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCaller) BalanceOf(opts *bind.CallOpts, src common.Address) (*big.Int, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "balanceOf", src) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address src) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHSession) BalanceOf(src common.Address) (*big.Int, error) { - return _SuperchainWETH.Contract.BalanceOf(&_SuperchainWETH.CallOpts, src) -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address src) view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCallerSession) BalanceOf(src common.Address) (*big.Int, error) { - return _SuperchainWETH.Contract.BalanceOf(&_SuperchainWETH.CallOpts, src) -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_SuperchainWETH *SuperchainWETHCaller) Decimals(opts *bind.CallOpts) (uint8, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "decimals") - - if err != nil { - return *new(uint8), err - } - - out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) - - return out0, err - -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_SuperchainWETH *SuperchainWETHSession) Decimals() (uint8, error) { - return _SuperchainWETH.Contract.Decimals(&_SuperchainWETH.CallOpts) -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_SuperchainWETH *SuperchainWETHCallerSession) Decimals() (uint8, error) { - return _SuperchainWETH.Contract.Decimals(&_SuperchainWETH.CallOpts) -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_SuperchainWETH *SuperchainWETHCaller) Name(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "name") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_SuperchainWETH *SuperchainWETHSession) Name() (string, error) { - return _SuperchainWETH.Contract.Name(&_SuperchainWETH.CallOpts) -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_SuperchainWETH *SuperchainWETHCallerSession) Name() (string, error) { - return _SuperchainWETH.Contract.Name(&_SuperchainWETH.CallOpts) -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 _interfaceId) view returns(bool) -func (_SuperchainWETH *SuperchainWETHCaller) SupportsInterface(opts *bind.CallOpts, _interfaceId [4]byte) (bool, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "supportsInterface", _interfaceId) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 _interfaceId) view returns(bool) -func (_SuperchainWETH *SuperchainWETHSession) SupportsInterface(_interfaceId [4]byte) (bool, error) { - return _SuperchainWETH.Contract.SupportsInterface(&_SuperchainWETH.CallOpts, _interfaceId) -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 _interfaceId) view returns(bool) -func (_SuperchainWETH *SuperchainWETHCallerSession) SupportsInterface(_interfaceId [4]byte) (bool, error) { - return _SuperchainWETH.Contract.SupportsInterface(&_SuperchainWETH.CallOpts, _interfaceId) -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_SuperchainWETH *SuperchainWETHCaller) Symbol(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "symbol") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_SuperchainWETH *SuperchainWETHSession) Symbol() (string, error) { - return _SuperchainWETH.Contract.Symbol(&_SuperchainWETH.CallOpts) -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_SuperchainWETH *SuperchainWETHCallerSession) Symbol() (string, error) { - return _SuperchainWETH.Contract.Symbol(&_SuperchainWETH.CallOpts) -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCaller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "totalSupply") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_SuperchainWETH *SuperchainWETHSession) TotalSupply() (*big.Int, error) { - return _SuperchainWETH.Contract.TotalSupply(&_SuperchainWETH.CallOpts) -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_SuperchainWETH *SuperchainWETHCallerSession) TotalSupply() (*big.Int, error) { - return _SuperchainWETH.Contract.TotalSupply(&_SuperchainWETH.CallOpts) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_SuperchainWETH *SuperchainWETHCaller) Version(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _SuperchainWETH.contract.Call(opts, &out, "version") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_SuperchainWETH *SuperchainWETHSession) Version() (string, error) { - return _SuperchainWETH.Contract.Version(&_SuperchainWETH.CallOpts) -} - -// Version is a free data retrieval call binding the contract method 0x54fd4d50. -// -// Solidity: function version() view returns(string) -func (_SuperchainWETH *SuperchainWETHCallerSession) Version() (string, error) { - return _SuperchainWETH.Contract.Version(&_SuperchainWETH.CallOpts) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address guy, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactor) Approve(opts *bind.TransactOpts, guy common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "approve", guy, wad) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address guy, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHSession) Approve(guy common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Approve(&_SuperchainWETH.TransactOpts, guy, wad) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address guy, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactorSession) Approve(guy common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Approve(&_SuperchainWETH.TransactOpts, guy, wad) -} - -// CrosschainBurn is a paid mutator transaction binding the contract method 0x2b8c49e3. -// -// Solidity: function crosschainBurn(address _from, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactor) CrosschainBurn(opts *bind.TransactOpts, _from common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "crosschainBurn", _from, _amount) -} - -// CrosschainBurn is a paid mutator transaction binding the contract method 0x2b8c49e3. -// -// Solidity: function crosschainBurn(address _from, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHSession) CrosschainBurn(_from common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.CrosschainBurn(&_SuperchainWETH.TransactOpts, _from, _amount) -} - -// CrosschainBurn is a paid mutator transaction binding the contract method 0x2b8c49e3. -// -// Solidity: function crosschainBurn(address _from, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) CrosschainBurn(_from common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.CrosschainBurn(&_SuperchainWETH.TransactOpts, _from, _amount) -} - -// CrosschainMint is a paid mutator transaction binding the contract method 0x18bf5077. -// -// Solidity: function crosschainMint(address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactor) CrosschainMint(opts *bind.TransactOpts, _to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "crosschainMint", _to, _amount) -} - -// CrosschainMint is a paid mutator transaction binding the contract method 0x18bf5077. -// -// Solidity: function crosschainMint(address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHSession) CrosschainMint(_to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.CrosschainMint(&_SuperchainWETH.TransactOpts, _to, _amount) -} - -// CrosschainMint is a paid mutator transaction binding the contract method 0x18bf5077. -// -// Solidity: function crosschainMint(address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) CrosschainMint(_to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.CrosschainMint(&_SuperchainWETH.TransactOpts, _to, _amount) -} - -// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. -// -// Solidity: function deposit() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactor) Deposit(opts *bind.TransactOpts) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "deposit") -} - -// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. -// -// Solidity: function deposit() payable returns() -func (_SuperchainWETH *SuperchainWETHSession) Deposit() (*types.Transaction, error) { - return _SuperchainWETH.Contract.Deposit(&_SuperchainWETH.TransactOpts) -} - -// Deposit is a paid mutator transaction binding the contract method 0xd0e30db0. -// -// Solidity: function deposit() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) Deposit() (*types.Transaction, error) { - return _SuperchainWETH.Contract.Deposit(&_SuperchainWETH.TransactOpts) -} - -// RelayETH is a paid mutator transaction binding the contract method 0x4f0edcc9. -// -// Solidity: function relayETH(address _from, address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactor) RelayETH(opts *bind.TransactOpts, _from common.Address, _to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "relayETH", _from, _to, _amount) -} - -// RelayETH is a paid mutator transaction binding the contract method 0x4f0edcc9. -// -// Solidity: function relayETH(address _from, address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHSession) RelayETH(_from common.Address, _to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.RelayETH(&_SuperchainWETH.TransactOpts, _from, _to, _amount) -} - -// RelayETH is a paid mutator transaction binding the contract method 0x4f0edcc9. -// -// Solidity: function relayETH(address _from, address _to, uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) RelayETH(_from common.Address, _to common.Address, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.RelayETH(&_SuperchainWETH.TransactOpts, _from, _to, _amount) -} - -// SendETH is a paid mutator transaction binding the contract method 0x64a197f3. -// -// Solidity: function sendETH(address _to, uint256 _chainId) payable returns(bytes32 msgHash_) -func (_SuperchainWETH *SuperchainWETHTransactor) SendETH(opts *bind.TransactOpts, _to common.Address, _chainId *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "sendETH", _to, _chainId) -} - -// SendETH is a paid mutator transaction binding the contract method 0x64a197f3. -// -// Solidity: function sendETH(address _to, uint256 _chainId) payable returns(bytes32 msgHash_) -func (_SuperchainWETH *SuperchainWETHSession) SendETH(_to common.Address, _chainId *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.SendETH(&_SuperchainWETH.TransactOpts, _to, _chainId) -} - -// SendETH is a paid mutator transaction binding the contract method 0x64a197f3. -// -// Solidity: function sendETH(address _to, uint256 _chainId) payable returns(bytes32 msgHash_) -func (_SuperchainWETH *SuperchainWETHTransactorSession) SendETH(_to common.Address, _chainId *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.SendETH(&_SuperchainWETH.TransactOpts, _to, _chainId) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactor) Transfer(opts *bind.TransactOpts, dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "transfer", dst, wad) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHSession) Transfer(dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Transfer(&_SuperchainWETH.TransactOpts, dst, wad) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactorSession) Transfer(dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Transfer(&_SuperchainWETH.TransactOpts, dst, wad) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address src, address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactor) TransferFrom(opts *bind.TransactOpts, src common.Address, dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "transferFrom", src, dst, wad) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address src, address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHSession) TransferFrom(src common.Address, dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.TransferFrom(&_SuperchainWETH.TransactOpts, src, dst, wad) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address src, address dst, uint256 wad) returns(bool) -func (_SuperchainWETH *SuperchainWETHTransactorSession) TransferFrom(src common.Address, dst common.Address, wad *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.TransferFrom(&_SuperchainWETH.TransactOpts, src, dst, wad) -} - -// Withdraw is a paid mutator transaction binding the contract method 0x2e1a7d4d. -// -// Solidity: function withdraw(uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactor) Withdraw(opts *bind.TransactOpts, _amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.contract.Transact(opts, "withdraw", _amount) -} - -// Withdraw is a paid mutator transaction binding the contract method 0x2e1a7d4d. -// -// Solidity: function withdraw(uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHSession) Withdraw(_amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Withdraw(&_SuperchainWETH.TransactOpts, _amount) -} - -// Withdraw is a paid mutator transaction binding the contract method 0x2e1a7d4d. -// -// Solidity: function withdraw(uint256 _amount) returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) Withdraw(_amount *big.Int) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Withdraw(&_SuperchainWETH.TransactOpts, _amount) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { - return _SuperchainWETH.contract.RawTransact(opts, calldata) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() payable returns() -func (_SuperchainWETH *SuperchainWETHSession) Fallback(calldata []byte) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Fallback(&_SuperchainWETH.TransactOpts, calldata) -} - -// Fallback is a paid mutator transaction binding the contract fallback function. -// -// Solidity: fallback() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { - return _SuperchainWETH.Contract.Fallback(&_SuperchainWETH.TransactOpts, calldata) -} - -// Receive is a paid mutator transaction binding the contract receive function. -// -// Solidity: receive() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { - return _SuperchainWETH.contract.RawTransact(opts, nil) // calldata is disallowed for receive function -} - -// Receive is a paid mutator transaction binding the contract receive function. -// -// Solidity: receive() payable returns() -func (_SuperchainWETH *SuperchainWETHSession) Receive() (*types.Transaction, error) { - return _SuperchainWETH.Contract.Receive(&_SuperchainWETH.TransactOpts) -} - -// Receive is a paid mutator transaction binding the contract receive function. -// -// Solidity: receive() payable returns() -func (_SuperchainWETH *SuperchainWETHTransactorSession) Receive() (*types.Transaction, error) { - return _SuperchainWETH.Contract.Receive(&_SuperchainWETH.TransactOpts) -} - -// SuperchainWETHApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the SuperchainWETH contract. -type SuperchainWETHApprovalIterator struct { - Event *SuperchainWETHApproval // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHApprovalIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHApproval) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHApproval) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHApprovalIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHApprovalIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHApproval represents a Approval event raised by the SuperchainWETH contract. -type SuperchainWETHApproval struct { - Src common.Address - Guy common.Address - Wad *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed src, address indexed guy, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterApproval(opts *bind.FilterOpts, src []common.Address, guy []common.Address) (*SuperchainWETHApprovalIterator, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - var guyRule []interface{} - for _, guyItem := range guy { - guyRule = append(guyRule, guyItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "Approval", srcRule, guyRule) - if err != nil { - return nil, err - } - return &SuperchainWETHApprovalIterator{contract: _SuperchainWETH.contract, event: "Approval", logs: logs, sub: sub}, nil -} - -// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed src, address indexed guy, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *SuperchainWETHApproval, src []common.Address, guy []common.Address) (event.Subscription, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - var guyRule []interface{} - for _, guyItem := range guy { - guyRule = append(guyRule, guyItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "Approval", srcRule, guyRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHApproval) - if err := _SuperchainWETH.contract.UnpackLog(event, "Approval", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed src, address indexed guy, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseApproval(log types.Log) (*SuperchainWETHApproval, error) { - event := new(SuperchainWETHApproval) - if err := _SuperchainWETH.contract.UnpackLog(event, "Approval", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHCrosschainBurnIterator is returned from FilterCrosschainBurn and is used to iterate over the raw logs and unpacked data for CrosschainBurn events raised by the SuperchainWETH contract. -type SuperchainWETHCrosschainBurnIterator struct { - Event *SuperchainWETHCrosschainBurn // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHCrosschainBurnIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHCrosschainBurn) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHCrosschainBurn) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHCrosschainBurnIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHCrosschainBurnIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHCrosschainBurn represents a CrosschainBurn event raised by the SuperchainWETH contract. -type SuperchainWETHCrosschainBurn struct { - From common.Address - Amount *big.Int - Sender common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterCrosschainBurn is a free log retrieval operation binding the contract event 0xb90795a66650155983e242cac3e1ac1a4dc26f8ed2987f3ce416a34e00111fd4. -// -// Solidity: event CrosschainBurn(address indexed from, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterCrosschainBurn(opts *bind.FilterOpts, from []common.Address, sender []common.Address) (*SuperchainWETHCrosschainBurnIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "CrosschainBurn", fromRule, senderRule) - if err != nil { - return nil, err - } - return &SuperchainWETHCrosschainBurnIterator{contract: _SuperchainWETH.contract, event: "CrosschainBurn", logs: logs, sub: sub}, nil -} - -// WatchCrosschainBurn is a free log subscription operation binding the contract event 0xb90795a66650155983e242cac3e1ac1a4dc26f8ed2987f3ce416a34e00111fd4. -// -// Solidity: event CrosschainBurn(address indexed from, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchCrosschainBurn(opts *bind.WatchOpts, sink chan<- *SuperchainWETHCrosschainBurn, from []common.Address, sender []common.Address) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "CrosschainBurn", fromRule, senderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHCrosschainBurn) - if err := _SuperchainWETH.contract.UnpackLog(event, "CrosschainBurn", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseCrosschainBurn is a log parse operation binding the contract event 0xb90795a66650155983e242cac3e1ac1a4dc26f8ed2987f3ce416a34e00111fd4. -// -// Solidity: event CrosschainBurn(address indexed from, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseCrosschainBurn(log types.Log) (*SuperchainWETHCrosschainBurn, error) { - event := new(SuperchainWETHCrosschainBurn) - if err := _SuperchainWETH.contract.UnpackLog(event, "CrosschainBurn", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHCrosschainMintIterator is returned from FilterCrosschainMint and is used to iterate over the raw logs and unpacked data for CrosschainMint events raised by the SuperchainWETH contract. -type SuperchainWETHCrosschainMintIterator struct { - Event *SuperchainWETHCrosschainMint // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHCrosschainMintIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHCrosschainMint) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHCrosschainMint) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHCrosschainMintIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHCrosschainMintIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHCrosschainMint represents a CrosschainMint event raised by the SuperchainWETH contract. -type SuperchainWETHCrosschainMint struct { - To common.Address - Amount *big.Int - Sender common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterCrosschainMint is a free log retrieval operation binding the contract event 0xde22baff038e3a3e08407cbdf617deed74e869a7ba517df611e33131c6e6ea04. -// -// Solidity: event CrosschainMint(address indexed to, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterCrosschainMint(opts *bind.FilterOpts, to []common.Address, sender []common.Address) (*SuperchainWETHCrosschainMintIterator, error) { - - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "CrosschainMint", toRule, senderRule) - if err != nil { - return nil, err - } - return &SuperchainWETHCrosschainMintIterator{contract: _SuperchainWETH.contract, event: "CrosschainMint", logs: logs, sub: sub}, nil -} - -// WatchCrosschainMint is a free log subscription operation binding the contract event 0xde22baff038e3a3e08407cbdf617deed74e869a7ba517df611e33131c6e6ea04. -// -// Solidity: event CrosschainMint(address indexed to, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchCrosschainMint(opts *bind.WatchOpts, sink chan<- *SuperchainWETHCrosschainMint, to []common.Address, sender []common.Address) (event.Subscription, error) { - - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "CrosschainMint", toRule, senderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHCrosschainMint) - if err := _SuperchainWETH.contract.UnpackLog(event, "CrosschainMint", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseCrosschainMint is a log parse operation binding the contract event 0xde22baff038e3a3e08407cbdf617deed74e869a7ba517df611e33131c6e6ea04. -// -// Solidity: event CrosschainMint(address indexed to, uint256 amount, address indexed sender) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseCrosschainMint(log types.Log) (*SuperchainWETHCrosschainMint, error) { - event := new(SuperchainWETHCrosschainMint) - if err := _SuperchainWETH.contract.UnpackLog(event, "CrosschainMint", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHDepositIterator is returned from FilterDeposit and is used to iterate over the raw logs and unpacked data for Deposit events raised by the SuperchainWETH contract. -type SuperchainWETHDepositIterator struct { - Event *SuperchainWETHDeposit // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHDepositIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHDeposit) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHDeposit) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHDepositIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHDepositIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHDeposit represents a Deposit event raised by the SuperchainWETH contract. -type SuperchainWETHDeposit struct { - Dst common.Address - Wad *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterDeposit is a free log retrieval operation binding the contract event 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c. -// -// Solidity: event Deposit(address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterDeposit(opts *bind.FilterOpts, dst []common.Address) (*SuperchainWETHDepositIterator, error) { - - var dstRule []interface{} - for _, dstItem := range dst { - dstRule = append(dstRule, dstItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "Deposit", dstRule) - if err != nil { - return nil, err - } - return &SuperchainWETHDepositIterator{contract: _SuperchainWETH.contract, event: "Deposit", logs: logs, sub: sub}, nil -} - -// WatchDeposit is a free log subscription operation binding the contract event 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c. -// -// Solidity: event Deposit(address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchDeposit(opts *bind.WatchOpts, sink chan<- *SuperchainWETHDeposit, dst []common.Address) (event.Subscription, error) { - - var dstRule []interface{} - for _, dstItem := range dst { - dstRule = append(dstRule, dstItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "Deposit", dstRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHDeposit) - if err := _SuperchainWETH.contract.UnpackLog(event, "Deposit", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseDeposit is a log parse operation binding the contract event 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c. -// -// Solidity: event Deposit(address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseDeposit(log types.Log) (*SuperchainWETHDeposit, error) { - event := new(SuperchainWETHDeposit) - if err := _SuperchainWETH.contract.UnpackLog(event, "Deposit", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHRelayETHIterator is returned from FilterRelayETH and is used to iterate over the raw logs and unpacked data for RelayETH events raised by the SuperchainWETH contract. -type SuperchainWETHRelayETHIterator struct { - Event *SuperchainWETHRelayETH // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHRelayETHIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHRelayETH) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHRelayETH) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHRelayETHIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHRelayETHIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHRelayETH represents a RelayETH event raised by the SuperchainWETH contract. -type SuperchainWETHRelayETH struct { - From common.Address - To common.Address - Amount *big.Int - Source *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterRelayETH is a free log retrieval operation binding the contract event 0xe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d359. -// -// Solidity: event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterRelayETH(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*SuperchainWETHRelayETHIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "RelayETH", fromRule, toRule) - if err != nil { - return nil, err - } - return &SuperchainWETHRelayETHIterator{contract: _SuperchainWETH.contract, event: "RelayETH", logs: logs, sub: sub}, nil -} - -// WatchRelayETH is a free log subscription operation binding the contract event 0xe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d359. -// -// Solidity: event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchRelayETH(opts *bind.WatchOpts, sink chan<- *SuperchainWETHRelayETH, from []common.Address, to []common.Address) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "RelayETH", fromRule, toRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHRelayETH) - if err := _SuperchainWETH.contract.UnpackLog(event, "RelayETH", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseRelayETH is a log parse operation binding the contract event 0xe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d359. -// -// Solidity: event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseRelayETH(log types.Log) (*SuperchainWETHRelayETH, error) { - event := new(SuperchainWETHRelayETH) - if err := _SuperchainWETH.contract.UnpackLog(event, "RelayETH", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHSendETHIterator is returned from FilterSendETH and is used to iterate over the raw logs and unpacked data for SendETH events raised by the SuperchainWETH contract. -type SuperchainWETHSendETHIterator struct { - Event *SuperchainWETHSendETH // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHSendETHIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHSendETH) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHSendETH) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHSendETHIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHSendETHIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHSendETH represents a SendETH event raised by the SuperchainWETH contract. -type SuperchainWETHSendETH struct { - From common.Address - To common.Address - Amount *big.Int - Destination *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterSendETH is a free log retrieval operation binding the contract event 0xed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402. -// -// Solidity: event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterSendETH(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*SuperchainWETHSendETHIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "SendETH", fromRule, toRule) - if err != nil { - return nil, err - } - return &SuperchainWETHSendETHIterator{contract: _SuperchainWETH.contract, event: "SendETH", logs: logs, sub: sub}, nil -} - -// WatchSendETH is a free log subscription operation binding the contract event 0xed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402. -// -// Solidity: event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchSendETH(opts *bind.WatchOpts, sink chan<- *SuperchainWETHSendETH, from []common.Address, to []common.Address) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "SendETH", fromRule, toRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHSendETH) - if err := _SuperchainWETH.contract.UnpackLog(event, "SendETH", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseSendETH is a log parse operation binding the contract event 0xed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402. -// -// Solidity: event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseSendETH(log types.Log) (*SuperchainWETHSendETH, error) { - event := new(SuperchainWETHSendETH) - if err := _SuperchainWETH.contract.UnpackLog(event, "SendETH", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHTransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the SuperchainWETH contract. -type SuperchainWETHTransferIterator struct { - Event *SuperchainWETHTransfer // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHTransferIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHTransferIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHTransferIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHTransfer represents a Transfer event raised by the SuperchainWETH contract. -type SuperchainWETHTransfer struct { - Src common.Address - Dst common.Address - Wad *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed src, address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterTransfer(opts *bind.FilterOpts, src []common.Address, dst []common.Address) (*SuperchainWETHTransferIterator, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - var dstRule []interface{} - for _, dstItem := range dst { - dstRule = append(dstRule, dstItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "Transfer", srcRule, dstRule) - if err != nil { - return nil, err - } - return &SuperchainWETHTransferIterator{contract: _SuperchainWETH.contract, event: "Transfer", logs: logs, sub: sub}, nil -} - -// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed src, address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *SuperchainWETHTransfer, src []common.Address, dst []common.Address) (event.Subscription, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - var dstRule []interface{} - for _, dstItem := range dst { - dstRule = append(dstRule, dstItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "Transfer", srcRule, dstRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHTransfer) - if err := _SuperchainWETH.contract.UnpackLog(event, "Transfer", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed src, address indexed dst, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseTransfer(log types.Log) (*SuperchainWETHTransfer, error) { - event := new(SuperchainWETHTransfer) - if err := _SuperchainWETH.contract.UnpackLog(event, "Transfer", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// SuperchainWETHWithdrawalIterator is returned from FilterWithdrawal and is used to iterate over the raw logs and unpacked data for Withdrawal events raised by the SuperchainWETH contract. -type SuperchainWETHWithdrawalIterator struct { - Event *SuperchainWETHWithdrawal // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *SuperchainWETHWithdrawalIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHWithdrawal) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(SuperchainWETHWithdrawal) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *SuperchainWETHWithdrawalIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *SuperchainWETHWithdrawalIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// SuperchainWETHWithdrawal represents a Withdrawal event raised by the SuperchainWETH contract. -type SuperchainWETHWithdrawal struct { - Src common.Address - Wad *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterWithdrawal is a free log retrieval operation binding the contract event 0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65. -// -// Solidity: event Withdrawal(address indexed src, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) FilterWithdrawal(opts *bind.FilterOpts, src []common.Address) (*SuperchainWETHWithdrawalIterator, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - - logs, sub, err := _SuperchainWETH.contract.FilterLogs(opts, "Withdrawal", srcRule) - if err != nil { - return nil, err - } - return &SuperchainWETHWithdrawalIterator{contract: _SuperchainWETH.contract, event: "Withdrawal", logs: logs, sub: sub}, nil -} - -// WatchWithdrawal is a free log subscription operation binding the contract event 0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65. -// -// Solidity: event Withdrawal(address indexed src, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) WatchWithdrawal(opts *bind.WatchOpts, sink chan<- *SuperchainWETHWithdrawal, src []common.Address) (event.Subscription, error) { - - var srcRule []interface{} - for _, srcItem := range src { - srcRule = append(srcRule, srcItem) - } - - logs, sub, err := _SuperchainWETH.contract.WatchLogs(opts, "Withdrawal", srcRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(SuperchainWETHWithdrawal) - if err := _SuperchainWETH.contract.UnpackLog(event, "Withdrawal", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseWithdrawal is a log parse operation binding the contract event 0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65. -// -// Solidity: event Withdrawal(address indexed src, uint256 wad) -func (_SuperchainWETH *SuperchainWETHFilterer) ParseWithdrawal(log types.Log) (*SuperchainWETHWithdrawal, error) { - event := new(SuperchainWETHWithdrawal) - if err := _SuperchainWETH.contract.UnpackLog(event, "Withdrawal", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/devnet-sdk/contracts/constants/constants.go b/devnet-sdk/contracts/constants/constants.go deleted file mode 100644 index 67801db723dde..0000000000000 --- a/devnet-sdk/contracts/constants/constants.go +++ /dev/null @@ -1,55 +0,0 @@ -package constants - -import ( - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/common" -) - -var ( - LegacyMessagePasser types.Address = common.HexToAddress("0x4200000000000000000000000000000000000000") - DeployerWhitelist types.Address = common.HexToAddress("0x4200000000000000000000000000000000000002") - WETH types.Address = common.HexToAddress("0x4200000000000000000000000000000000000006") - L2CrossDomainMessenger types.Address = common.HexToAddress("0x4200000000000000000000000000000000000007") - GasPriceOracle types.Address = common.HexToAddress("0x420000000000000000000000000000000000000F") - L2StandardBridge types.Address = common.HexToAddress("0x4200000000000000000000000000000000000010") - SequencerFeeVault types.Address = common.HexToAddress("0x4200000000000000000000000000000000000011") - OptimismMintableERC20Factory types.Address = common.HexToAddress("0x4200000000000000000000000000000000000012") - L1BlockNumber types.Address = common.HexToAddress("0x4200000000000000000000000000000000000013") - L1Block types.Address = common.HexToAddress("0x4200000000000000000000000000000000000015") - L2ToL1MessagePasser types.Address = common.HexToAddress("0x4200000000000000000000000000000000000016") - L2ERC721Bridge types.Address = common.HexToAddress("0x4200000000000000000000000000000000000014") - OptimismMintableERC721Factory types.Address = common.HexToAddress("0x4200000000000000000000000000000000000017") - ProxyAdmin types.Address = common.HexToAddress("0x4200000000000000000000000000000000000018") - BaseFeeVault types.Address = common.HexToAddress("0x4200000000000000000000000000000000000019") - L1FeeVault types.Address = common.HexToAddress("0x420000000000000000000000000000000000001a") - OperatorFeeVault types.Address = common.HexToAddress("0x420000000000000000000000000000000000001B") - SchemaRegistry types.Address = common.HexToAddress("0x4200000000000000000000000000000000000020") - EAS types.Address = common.HexToAddress("0x4200000000000000000000000000000000000021") - CrossL2Inbox types.Address = common.HexToAddress("0x4200000000000000000000000000000000000022") - L2ToL2CrossDomainMessenger types.Address = common.HexToAddress("0x4200000000000000000000000000000000000023") - SuperchainETHBridge types.Address = common.HexToAddress("0x4200000000000000000000000000000000000024") - ETHLiquidity types.Address = common.HexToAddress("0x4200000000000000000000000000000000000025") - SuperchainTokenBridge types.Address = common.HexToAddress("0x4200000000000000000000000000000000000028") - NativeAssetLiquidity types.Address = common.HexToAddress("0x4200000000000000000000000000000000000029") - LiquidityController types.Address = common.HexToAddress("0x420000000000000000000000000000000000002a") - FeeSplitter types.Address = common.HexToAddress("0x420000000000000000000000000000000000002B") - GovernanceToken types.Address = common.HexToAddress("0x4200000000000000000000000000000000000042") - Create2Deployer types.Address = common.HexToAddress("0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2") - MultiCall3 types.Address = common.HexToAddress("0xcA11bde05977b3631167028862bE2a173976CA11") - Safe_v130 types.Address = common.HexToAddress("0x69f4D1788e39c87893C980c06EdF4b7f686e2938") - SafeL2_v130 types.Address = common.HexToAddress("0xfb1bffC9d739B8D520DaF37dF666da4C687191EA") - MultiSendCallOnly_v130 types.Address = common.HexToAddress("0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B") - SafeSingletonFactory types.Address = common.HexToAddress("0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7") - DeterministicDeploymentProxy types.Address = common.HexToAddress("0x4e59b44847b379578588920cA78FbF26c0B4956C") - MultiSend_v130 types.Address = common.HexToAddress("0x998739BFdAAdde7C933B942a68053933098f9EDa") - Permit2 types.Address = common.HexToAddress("0x000000000022D473030F116dDEE9F6B43aC78BA3") - SenderCreator_v060 types.Address = common.HexToAddress("0x7fc98430eaedbb6070b35b39d798725049088348") - EntryPoint_v060 types.Address = common.HexToAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789") - SenderCreator_v070 types.Address = common.HexToAddress("0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C") - EntryPoint_v070 types.Address = common.HexToAddress("0x0000000071727De22E5E9d8BAf0edAc6f37da032") -) - -const ( - ETH = 1e18 - Gwei = 1e9 -) diff --git a/devnet-sdk/contracts/contracts.go b/devnet-sdk/contracts/contracts.go deleted file mode 100644 index ad2e55dc2d471..0000000000000 --- a/devnet-sdk/contracts/contracts.go +++ /dev/null @@ -1,17 +0,0 @@ -package contracts - -import ( - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/registry/client" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/registry/empty" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum/go-ethereum/ethclient" -) - -// NewClientRegistry creates a new Registry that uses the provided client -func NewClientRegistry(c *ethclient.Client) interfaces.ContractsRegistry { - return &client.ClientRegistry{Client: c} -} - -func NewEmptyRegistry() interfaces.ContractsRegistry { - return &empty.EmptyRegistry{} -} diff --git a/devnet-sdk/contracts/registry/client/client.go b/devnet-sdk/contracts/registry/client/client.go deleted file mode 100644 index d60f35639e6b0..0000000000000 --- a/devnet-sdk/contracts/registry/client/client.go +++ /dev/null @@ -1,50 +0,0 @@ -package client - -import ( - "fmt" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/ethclient" -) - -// ClientRegistry is a Registry implementation that uses an ethclient.Client -type ClientRegistry struct { - Client *ethclient.Client -} - -var _ interfaces.ContractsRegistry = (*ClientRegistry)(nil) - -func (r *ClientRegistry) WETH(address types.Address) (interfaces.WETH, error) { - // SuperchainWETH was removed and replaced with SuperchainETHBridge - // NewSuperchainWETH can be still used for fetching WETH balance - binding, err := bindings.NewSuperchainWETH(address, r.Client) - if err != nil { - return nil, fmt.Errorf("failed to create WETH binding: %w", err) - } - return &WETHBinding{ - contractAddress: address, - client: r.Client, - binding: binding, - }, nil -} - -func (r *ClientRegistry) L2ToL2CrossDomainMessenger(address types.Address) (interfaces.L2ToL2CrossDomainMessenger, error) { - binding, err := bindings.NewL2ToL2CrossDomainMessenger(address, r.Client) - if err != nil { - return nil, fmt.Errorf("failed to create L2ToL2CrossDomainMessenger binding: %w", err) - } - abi, err := abi.JSON(strings.NewReader(bindings.L2ToL2CrossDomainMessengerMetaData.ABI)) - if err != nil { - return nil, fmt.Errorf("failed to create L2ToL2CrossDomainMessenger binding ABI: %w", err) - } - return &L2ToL2CrossDomainMessengerBinding{ - contractAddress: address, - client: r.Client, - binding: binding, - abi: &abi, - }, nil -} diff --git a/devnet-sdk/contracts/registry/client/l2tol2crossdomainmessenger.go b/devnet-sdk/contracts/registry/client/l2tol2crossdomainmessenger.go deleted file mode 100644 index f2ff3da7ab7e8..0000000000000 --- a/devnet-sdk/contracts/registry/client/l2tol2crossdomainmessenger.go +++ /dev/null @@ -1,22 +0,0 @@ -package client - -import ( - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/ethclient" -) - -type L2ToL2CrossDomainMessengerBinding struct { - contractAddress types.Address - client *ethclient.Client - binding *bindings.L2ToL2CrossDomainMessenger - abi *abi.ABI -} - -var _ interfaces.L2ToL2CrossDomainMessenger = (*L2ToL2CrossDomainMessengerBinding)(nil) - -func (b *L2ToL2CrossDomainMessengerBinding) ABI() *abi.ABI { - return b.abi -} diff --git a/devnet-sdk/contracts/registry/client/weth.go b/devnet-sdk/contracts/registry/client/weth.go deleted file mode 100644 index 68d30af479ce5..0000000000000 --- a/devnet-sdk/contracts/registry/client/weth.go +++ /dev/null @@ -1,38 +0,0 @@ -package client - -import ( - "context" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/ethclient" -) - -type WETHBinding struct { - contractAddress types.Address - client *ethclient.Client - binding *bindings.SuperchainWETH -} - -var _ interfaces.WETH = (*WETHBinding)(nil) - -func (b *WETHBinding) BalanceOf(addr types.Address) types.ReadInvocation[types.Balance] { - return &WETHBalanceOfImpl{ - contract: b, - addr: addr, - } -} - -type WETHBalanceOfImpl struct { - contract *WETHBinding - addr types.Address -} - -func (i *WETHBalanceOfImpl) Call(ctx context.Context) (types.Balance, error) { - balance, err := i.contract.binding.BalanceOf(nil, i.addr) - if err != nil { - return types.Balance{}, err - } - return types.NewBalance(balance), nil -} diff --git a/devnet-sdk/contracts/registry/empty/empty.go b/devnet-sdk/contracts/registry/empty/empty.go deleted file mode 100644 index e5534908fd76c..0000000000000 --- a/devnet-sdk/contracts/registry/empty/empty.go +++ /dev/null @@ -1,25 +0,0 @@ -package empty - -import ( - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" -) - -// EmptyRegistry represents a registry that returns not found errors for all contract accesses -type EmptyRegistry struct{} - -var _ interfaces.ContractsRegistry = (*EmptyRegistry)(nil) - -func (r *EmptyRegistry) WETH(address types.Address) (interfaces.WETH, error) { - return nil, &interfaces.ErrContractNotFound{ - ContractType: "WETH", - Address: address, - } -} - -func (r *EmptyRegistry) L2ToL2CrossDomainMessenger(address types.Address) (interfaces.L2ToL2CrossDomainMessenger, error) { - return nil, &interfaces.ErrContractNotFound{ - ContractType: "L2ToL2CrossDomainMessenger", - Address: address, - } -} diff --git a/devnet-sdk/controller/kt/kt.go b/devnet-sdk/controller/kt/kt.go deleted file mode 100644 index 7e2d338b9bf80..0000000000000 --- a/devnet-sdk/controller/kt/kt.go +++ /dev/null @@ -1,109 +0,0 @@ -package kt - -import ( - "context" - "fmt" - "strings" - "sync" - - "github.com/ethereum-optimism/optimism/devnet-sdk/controller/surface" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/run" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/wrappers" -) - -type KurtosisControllerSurface struct { - env *descriptors.DevnetEnvironment - kurtosisCtx interfaces.KurtosisContextInterface - runner *run.KurtosisRunner - devnetfs *fs.DevnetFS - - // control operations are disruptive, let's make sure we don't run them - // concurrently so that test logic has a fighting chance of being correct. - mtx sync.Mutex -} - -func NewKurtosisControllerSurface(env *descriptors.DevnetEnvironment) (*KurtosisControllerSurface, error) { - enclave := env.Name - - kurtosisCtx, err := wrappers.GetDefaultKurtosisContext() - if err != nil { - return nil, err - } - - runner, err := run.NewKurtosisRunner( - run.WithKurtosisRunnerEnclave(enclave), - run.WithKurtosisRunnerKurtosisContext(kurtosisCtx), - ) - if err != nil { - return nil, err - } - - enclaveFS, err := fs.NewEnclaveFS(context.TODO(), enclave) - if err != nil { - return nil, err - } - // Create a new DevnetFS instance using the enclaveFS - devnetfs := fs.NewDevnetFS(enclaveFS) - - return &KurtosisControllerSurface{ - env: env, - kurtosisCtx: kurtosisCtx, - runner: runner, - devnetfs: devnetfs, - }, nil -} - -func (s *KurtosisControllerSurface) StartService(ctx context.Context, serviceName string) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - script := fmt.Sprintf(` -def run(plan): - plan.start_service(name="%s") -`, serviceName) - // start_service is not idempotent, and doesn't return a typed error, - // so we need to check the error message - if err := s.runner.RunScript(ctx, script); err != nil { - msg := err.Error() - if strings.Contains(strings.ToLower(msg), "is already in use by container") { - // we know we don't need to update the env, as the service was already running - return nil - } - return err - } - return s.updateDevnetEnvironmentForService(ctx, serviceName, true) -} - -func (s *KurtosisControllerSurface) StopService(ctx context.Context, serviceName string) error { - s.mtx.Lock() - defer s.mtx.Unlock() - - script := fmt.Sprintf(` -def run(plan): - plan.stop_service(name="%s") -`, serviceName) - // stop_service is idempotent, so errors here are real - if err := s.runner.RunScript(ctx, script); err != nil { - return err - } - // conversely, we don't know if the service was running or not, so we need to update the env - return s.updateDevnetEnvironmentForService(ctx, serviceName, false) -} - -func (s *KurtosisControllerSurface) updateDevnetEnvironmentForService(ctx context.Context, serviceName string, on bool) error { - // - refreshed, err := s.updateDevnetEnvironmentService(ctx, serviceName, on) - if err != nil { - return err - } - if !refreshed { - return nil - } - - return s.devnetfs.UploadDevnetDescriptor(ctx, s.env) -} - -var _ surface.ServiceLifecycleSurface = (*KurtosisControllerSurface)(nil) diff --git a/devnet-sdk/controller/kt/kt_test.go b/devnet-sdk/controller/kt/kt_test.go deleted file mode 100644 index c84452102882c..0000000000000 --- a/devnet-sdk/controller/kt/kt_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package kt - -import ( - "context" - "errors" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/fake" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/run" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestKurtosisControllerSurface(t *testing.T) { - ctx := context.Background() - testErr := errors.New("test error") - - // Create a test environment - env := &descriptors.DevnetEnvironment{ - Name: "test-env", - L1: &descriptors.Chain{ - Services: descriptors.RedundantServiceMap{ - "test-service": []*descriptors.Service{ - &descriptors.Service{ - Name: "test-service", - Endpoints: descriptors.EndpointMap{ - "http": { - Port: 0, - PrivatePort: 0, - }, - }, - }, - }, - }, - }, - } - - // Create a test service context with port data - testSvcCtx := &testServiceContext{ - publicPorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8080}, - }, - privatePorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8082}, - }, - } - - tests := []struct { - name string - serviceName string - operation string // "start" or "stop" - runErr error - wantErr bool - }{ - { - name: "successful service start", - serviceName: "test-service", - operation: "start", - runErr: nil, - wantErr: false, - }, - { - name: "service already running", - serviceName: "test-service", - operation: "start", - runErr: errors.New("is already in use by container"), - wantErr: false, - }, - { - name: "error starting service", - serviceName: "test-service", - operation: "start", - runErr: testErr, - wantErr: true, - }, - { - name: "successful service stop", - serviceName: "test-service", - operation: "stop", - runErr: nil, - wantErr: false, - }, - { - name: "error stopping service", - serviceName: "test-service", - operation: "stop", - runErr: testErr, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a fake enclave context that will return our test service context - fakeEnclaveCtx := &fake.EnclaveContext{ - RunErr: tt.runErr, - Services: map[services.ServiceName]interfaces.ServiceContext{ - "test-service": testSvcCtx, - }, - } - - // Create a fake Kurtosis context that will return our fake enclave context - fakeCtx := &fake.KurtosisContext{ - EnclaveCtx: fakeEnclaveCtx, - } - - // Create a KurtosisRunner with our fake context - runner, err := run.NewKurtosisRunner( - run.WithKurtosisRunnerEnclave("test-enclave"), - run.WithKurtosisRunnerKurtosisContext(fakeCtx), - ) - require.NoError(t, err) - - // Create the controller surface with all required fields - surface := &KurtosisControllerSurface{ - env: env, - kurtosisCtx: fakeCtx, - runner: runner, - } - - // Create the mock DevnetFS - mockDevnetFS, err := newMockDevnetFS(env) - require.NoError(t, err) - surface.devnetfs = mockDevnetFS - - switch tt.operation { - case "start": - err = surface.StartService(ctx, tt.serviceName) - case "stop": - err = surface.StopService(ctx, tt.serviceName) - default: - t.Fatalf("unknown operation: %s", tt.operation) - } - - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - // For successful start operations, verify that the service endpoints were updated - if tt.operation == "start" && !tt.wantErr { - svc := findSvcInEnv(env, tt.serviceName) - require.NotNil(t, svc) - require.Equal(t, 8080, svc[0].Endpoints["http"].Port) - require.Equal(t, 8082, svc[0].Endpoints["http"].PrivatePort) - } - }) - } -} diff --git a/devnet-sdk/controller/kt/mutate_env.go b/devnet-sdk/controller/kt/mutate_env.go deleted file mode 100644 index 66c5c8cde27cf..0000000000000 --- a/devnet-sdk/controller/kt/mutate_env.go +++ /dev/null @@ -1,100 +0,0 @@ -package kt - -import ( - "context" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" -) - -// a hack because some L2 services are duplicated across chains -type redundantService []*descriptors.Service - -func (s redundantService) getEndpoints() descriptors.EndpointMap { - if len(s) == 0 { - return nil - } - - return s[0].Endpoints -} - -func (s redundantService) setEndpoints(endpoints descriptors.EndpointMap) { - for _, svc := range s { - svc.Endpoints = endpoints - } -} - -func (s redundantService) refreshEndpoints(serviceCtx interfaces.ServiceContext) { - endpoints := s.getEndpoints() - - publicPorts := serviceCtx.GetPublicPorts() - privatePorts := serviceCtx.GetPrivatePorts() - - for name, info := range publicPorts { - endpoints[name].Port = int(info.GetNumber()) - } - for name, info := range privatePorts { - endpoints[name].PrivatePort = int(info.GetNumber()) - } - - s.setEndpoints(endpoints) -} - -func findSvcInEnv(env *descriptors.DevnetEnvironment, serviceName string) redundantService { - if svc := findSvcInChain(env.L1, serviceName); svc != nil { - return redundantService{svc} - } - - var services redundantService = nil - for _, l2 := range env.L2 { - if svc := findSvcInChain(l2.Chain, serviceName); svc != nil { - services = append(services, svc) - } - } - return services -} - -func findSvcInChain(chain *descriptors.Chain, serviceName string) *descriptors.Service { - for _, instances := range chain.Services { - for _, svc := range instances { - if svc.Name == serviceName { - return svc - } - } - } - - for _, node := range chain.Nodes { - for _, svc := range node.Services { - if svc.Name == serviceName { - return svc - } - } - } - - return nil -} - -func (s *KurtosisControllerSurface) updateDevnetEnvironmentService(ctx context.Context, serviceName string, on bool) (bool, error) { - svc := findSvcInEnv(s.env, serviceName) - if svc == nil { - // service is not part of the env, so we don't need to do anything - return false, nil - } - - // get the enclave - enclaveCtx, err := s.kurtosisCtx.GetEnclave(ctx, s.env.Name) - if err != nil { - return false, err - } - - serviceCtx, err := enclaveCtx.GetService(serviceName) - if err != nil { - return false, err - } - - if on { - svc.refreshEndpoints(serviceCtx) - } - // otherwise the service is down anyway, it doesn't matter if it has outdated endpoints - return on, nil -} diff --git a/devnet-sdk/controller/kt/mutate_env_test.go b/devnet-sdk/controller/kt/mutate_env_test.go deleted file mode 100644 index 3a83dc585a948..0000000000000 --- a/devnet-sdk/controller/kt/mutate_env_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package kt - -import ( - "context" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/stretchr/testify/require" -) - -func TestRedundantServiceRefreshEndpoints(t *testing.T) { - // Create a test service with some initial endpoints - svc1 := &descriptors.Service{ - Name: "test-service", - Endpoints: descriptors.EndpointMap{ - "http": { - Port: 0, - PrivatePort: 0, - }, - "ws": { - Port: 0, - PrivatePort: 0, - }, - }, - } - svc2 := &descriptors.Service{ - Name: "test-service", - Endpoints: descriptors.EndpointMap{ - "http": { - Port: 0, - PrivatePort: 0, - }, - "ws": { - Port: 0, - PrivatePort: 0, - }, - }, - } - - // Create a redundant service with both services - redundant := redundantService{svc1, svc2} - - // Create a test service context with new port numbers - testCtx := &testServiceContext{ - publicPorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8080}, - "ws": &testPortSpec{number: 8081}, - }, - privatePorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8082}, - "ws": &testPortSpec{number: 8083}, - }, - } - - // Call refreshEndpoints - redundant.refreshEndpoints(testCtx) - - // Verify that both services have been updated with the new port numbers - for _, svc := range redundant { - require.Equal(t, 8080, svc.Endpoints["http"].Port) - require.Equal(t, 8081, svc.Endpoints["ws"].Port) - require.Equal(t, 8082, svc.Endpoints["http"].PrivatePort) - require.Equal(t, 8083, svc.Endpoints["ws"].PrivatePort) - } -} - -func TestRedundantServiceEmpty(t *testing.T) { - // Test behavior with empty redundant service - redundant := redundantService{} - testCtx := &testServiceContext{ - publicPorts: map[string]interfaces.PortSpec{}, - privatePorts: map[string]interfaces.PortSpec{}, - } - - // This should not panic - redundant.refreshEndpoints(testCtx) -} - -func TestUpdateDevnetEnvironmentService(t *testing.T) { - // Create a test environment with a service - env := &descriptors.DevnetEnvironment{ - Name: "test-env", - L1: &descriptors.Chain{ - Services: descriptors.RedundantServiceMap{ - "test-service": []*descriptors.Service{ - &descriptors.Service{ - Name: "test-service", - Endpoints: descriptors.EndpointMap{ - "http": { - Port: 0, - PrivatePort: 0, - }, - }, - }, - }, - }, - }, - } - - // Create a test service context with new port numbers - testSvcCtx := &testServiceContext{ - publicPorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8080}, - }, - privatePorts: map[string]interfaces.PortSpec{ - "http": &testPortSpec{number: 8082}, - }, - } - - // Create a mock enclave context with our service - mockEnclave := &mockEnclaveContext{ - services: map[services.ServiceName]interfaces.ServiceContext{ - "test-service": testSvcCtx, - }, - } - - // Create a mock kurtosis context with our enclave - mockKurtosisCtx := &mockKurtosisContext{ - enclaves: map[string]interfaces.EnclaveContext{ - "test-env": mockEnclave, - }, - } - - // Create the controller surface - controller := &KurtosisControllerSurface{ - kurtosisCtx: mockKurtosisCtx, - env: env, - } - - // Create the mock DevnetFS - mockDevnetFS, err := newMockDevnetFS(env) - require.NoError(t, err) - controller.devnetfs = mockDevnetFS - - // Test updating the service (turning it on) - updated, err := controller.updateDevnetEnvironmentService(context.Background(), "test-service", true) - require.NoError(t, err) - require.True(t, updated) - - // Verify that the service's endpoints were updated - svc := findSvcInEnv(env, "test-service") - require.NotNil(t, svc) - require.Equal(t, 8080, svc[0].Endpoints["http"].Port) - require.Equal(t, 8082, svc[0].Endpoints["http"].PrivatePort) - - // Test updating a non-existent service - updated, err = controller.updateDevnetEnvironmentService(context.Background(), "non-existent-service", true) - require.NoError(t, err) - require.False(t, updated) -} diff --git a/devnet-sdk/controller/kt/test_helpers.go b/devnet-sdk/controller/kt/test_helpers.go deleted file mode 100644 index f61df2f1fcff4..0000000000000 --- a/devnet-sdk/controller/kt/test_helpers.go +++ /dev/null @@ -1,145 +0,0 @@ -package kt - -import ( - "context" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/interfaces" - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" - "github.com/spf13/afero" -) - -// testPortSpec implements interfaces.PortSpec -type testPortSpec struct { - number uint16 -} - -func (m *testPortSpec) GetNumber() uint16 { - return m.number -} - -// testServiceContext implements interfaces.ServiceContext -type testServiceContext struct { - publicPorts map[string]interfaces.PortSpec - privatePorts map[string]interfaces.PortSpec -} - -func (m *testServiceContext) GetServiceUUID() services.ServiceUUID { - return "mock-service-uuid" -} - -func (m *testServiceContext) GetMaybePublicIPAddress() string { - return "127.0.0.1" -} - -func (m *testServiceContext) GetPublicPorts() map[string]interfaces.PortSpec { - return m.publicPorts -} - -func (m *testServiceContext) GetPrivatePorts() map[string]interfaces.PortSpec { - return m.privatePorts -} - -func (m *testServiceContext) GetLabels() map[string]string { - return make(map[string]string) -} - -// mockEnclaveFS implements fs.EnclaveContextIface for testing -type mockEnclaveFS struct { - env *descriptors.DevnetEnvironment -} - -func (m *mockEnclaveFS) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - return nil, nil -} - -func (m *mockEnclaveFS) DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) { - return nil, nil -} - -func (m *mockEnclaveFS) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) { - return "", "", nil -} - -// newMockDevnetFS creates a new mock DevnetFS for testing -func newMockDevnetFS(env *descriptors.DevnetEnvironment) (*fs.DevnetFS, error) { - mockEnclaveFS := &mockEnclaveFS{env: env} - enclaveFS, err := fs.NewEnclaveFS(context.Background(), "test-enclave", - fs.WithEnclaveCtx(mockEnclaveFS), - fs.WithFs(afero.NewMemMapFs()), - ) - if err != nil { - return nil, err - } - return fs.NewDevnetFS(enclaveFS), nil -} - -type mockEnclaveContext struct { - services map[services.ServiceName]interfaces.ServiceContext -} - -func (m *mockEnclaveContext) GetEnclaveUuid() enclaves.EnclaveUUID { - return "mock-enclave-uuid" -} - -func (m *mockEnclaveContext) GetService(serviceIdentifier string) (interfaces.ServiceContext, error) { - if svc, ok := m.services[services.ServiceName(serviceIdentifier)]; ok { - return svc, nil - } - return nil, nil -} - -func (m *mockEnclaveContext) GetServices() (map[services.ServiceName]services.ServiceUUID, error) { - result := make(map[services.ServiceName]services.ServiceUUID) - for name, svc := range m.services { - result[name] = svc.GetServiceUUID() - } - return result, nil -} - -func (m *mockEnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - return nil, nil -} - -func (m *mockEnclaveContext) RunStarlarkPackage(ctx context.Context, pkg string, serializedParams *starlark_run_config.StarlarkRunConfig) (<-chan interfaces.StarlarkResponse, string, error) { - return nil, "", nil -} - -func (m *mockEnclaveContext) RunStarlarkScript(ctx context.Context, script string, serializedParams *starlark_run_config.StarlarkRunConfig) error { - return nil -} - -// mockKurtosisContext implements interfaces.KurtosisContextInterface -type mockKurtosisContext struct { - enclaves map[string]interfaces.EnclaveContext -} - -func (m *mockKurtosisContext) CreateEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - if enclave, ok := m.enclaves[name]; ok { - return enclave, nil - } - return nil, nil -} - -func (m *mockKurtosisContext) GetEnclave(ctx context.Context, name string) (interfaces.EnclaveContext, error) { - if enclave, ok := m.enclaves[name]; ok { - return enclave, nil - } - return nil, nil -} - -func (m *mockKurtosisContext) Clean(ctx context.Context, destroyAll bool) ([]interfaces.EnclaveNameAndUuid, error) { - return []interfaces.EnclaveNameAndUuid{}, nil -} - -func (m *mockKurtosisContext) GetEnclaveStatus(ctx context.Context, name string) (interfaces.EnclaveStatus, error) { - return interfaces.EnclaveStatusRunning, nil -} - -func (m *mockKurtosisContext) DestroyEnclave(ctx context.Context, name string) error { - return nil -} diff --git a/devnet-sdk/controller/surface/surface.go b/devnet-sdk/controller/surface/surface.go deleted file mode 100644 index 519d9d1c302b1..0000000000000 --- a/devnet-sdk/controller/surface/surface.go +++ /dev/null @@ -1,11 +0,0 @@ -package surface - -import "context" - -type ControlSurface interface { -} - -type ServiceLifecycleSurface interface { - StartService(context.Context, string) error - StopService(context.Context, string) error -} diff --git a/devnet-sdk/descriptors/deployment.go b/devnet-sdk/descriptors/deployment.go deleted file mode 100644 index 60e67ea2ced6a..0000000000000 --- a/devnet-sdk/descriptors/deployment.go +++ /dev/null @@ -1,87 +0,0 @@ -package descriptors - -import ( - "encoding/json" - "net/http" - - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum/go-ethereum/params" -) - -type PortInfo struct { - Host string `json:"host"` - Scheme string `json:"scheme,omitempty"` - Port int `json:"port,omitempty"` - PrivatePort int `json:"private_port,omitempty"` - - ReverseProxyHeader http.Header `json:"reverse_proxy_header,omitempty"` -} - -// EndpointMap is a map of service names to their endpoints. -type EndpointMap map[string]*PortInfo - -// Service represents a chain service (e.g. batcher, proposer, challenger) -type Service struct { - Name string `json:"name"` - Endpoints EndpointMap `json:"endpoints"` - Labels map[string]string `json:"labels,omitempty"` -} - -// ServiceMap is a map of service names to services. -type ServiceMap map[string]*Service - -// RedundantServiceMap is a map of service names to services. -// It is used to represent services that are redundant, i.e. they can have multiple instances. -type RedundantServiceMap map[string][]*Service - -// Node represents a node for a chain -type Node struct { - Name string `json:"name"` - Services ServiceMap `json:"services"` - Labels map[string]string `json:"labels,omitempty"` -} - -// AddressMap is a map of address names to their corresponding addresses -type AddressMap map[string]types.Address - -type Chain struct { - Name string `json:"name"` - ID string `json:"id,omitempty"` - Services RedundantServiceMap `json:"services,omitempty"` - Nodes []Node `json:"nodes"` - Wallets WalletMap `json:"wallets,omitempty"` - JWT string `json:"jwt,omitempty"` - Config *params.ChainConfig `json:"config,omitempty"` - Addresses AddressMap `json:"addresses,omitempty"` -} - -type L2Chain struct { - *Chain - L1Wallets WalletMap `json:"l1_wallets,omitempty"` - RollupConfig *rollup.Config `json:"rollup_config"` -} - -// Wallet represents a wallet with an address and optional private key. -type Wallet struct { - Address types.Address `json:"address"` - PrivateKey string `json:"private_key,omitempty"` -} - -// WalletMap is a map of wallet names to wallets. -type WalletMap map[string]*Wallet - -type DepSet = json.RawMessage - -// DevnetEnvironment exposes the relevant information to interact with a devnet. -type DevnetEnvironment struct { - Name string `json:"name"` - - ReverseProxyURL string `json:"reverse_proxy_url,omitempty"` - - L1 *Chain `json:"l1"` - L2 []*L2Chain `json:"l2"` - - Features []string `json:"features,omitempty"` - DepSets map[string]DepSet `json:"dep_sets,omitempty"` -} diff --git a/devnet-sdk/images/repository.go b/devnet-sdk/images/repository.go deleted file mode 100644 index 2732d35649fef..0000000000000 --- a/devnet-sdk/images/repository.go +++ /dev/null @@ -1,45 +0,0 @@ -package images - -import "fmt" - -// Repository maps component versions to their corresponding Docker image URLs -type Repository struct { - mapping map[string]string -} - -const ( - opLabsToolsRegistry = "us-docker.pkg.dev/oplabs-tools-artifacts/images" - paradigmRegistry = "ghcr.io/paradigmxyz" -) - -// NewRepository creates a new Repository instance with predefined mappings -func NewRepository() *Repository { - return &Repository{ - mapping: map[string]string{ - // OP Labs images - "op-deployer": opLabsToolsRegistry, - "op-geth": opLabsToolsRegistry, - "op-node": opLabsToolsRegistry, - "op-batcher": opLabsToolsRegistry, - "op-proposer": opLabsToolsRegistry, - "op-challenger": opLabsToolsRegistry, - // Paradigm images - "op-reth": paradigmRegistry, - }, - } -} - -// GetImage returns the full Docker image URL for a given component and version -func (r *Repository) GetImage(component string, version string) string { - if imageTemplate, ok := r.mapping[component]; ok { - - if version == "" { - version = "latest" - } - return fmt.Sprintf("%s/%s:%s", imageTemplate, component, version) - } - - // TODO: that's our way to convey that the "default" image should be used. - // We should probably have a more explicit way to do this. - return "" -} diff --git a/devnet-sdk/interfaces/registry.go b/devnet-sdk/interfaces/registry.go deleted file mode 100644 index 9ab21b04f1991..0000000000000 --- a/devnet-sdk/interfaces/registry.go +++ /dev/null @@ -1,33 +0,0 @@ -package interfaces - -import ( - "fmt" - - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" -) - -// ErrContractNotFound indicates that a contract is not available at the requested address -type ErrContractNotFound struct { - ContractType string - Address types.Address -} - -func (e *ErrContractNotFound) Error() string { - return fmt.Sprintf("%s contract not found at %s", e.ContractType, e.Address) -} - -// ContractsRegistry provides access to all supported contract instances -type ContractsRegistry interface { - WETH(address types.Address) (WETH, error) - L2ToL2CrossDomainMessenger(address types.Address) (L2ToL2CrossDomainMessenger, error) -} - -// WETH represents the interface for interacting with the WETH contract -type WETH interface { - BalanceOf(user types.Address) types.ReadInvocation[types.Balance] -} - -type L2ToL2CrossDomainMessenger interface { - ABI() *abi.ABI -} diff --git a/devnet-sdk/kt/fs/devnet_fs.go b/devnet-sdk/kt/fs/devnet_fs.go deleted file mode 100644 index 9062728e8b452..0000000000000 --- a/devnet-sdk/kt/fs/devnet_fs.go +++ /dev/null @@ -1,171 +0,0 @@ -package fs - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "log" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" -) - -const ( - DevnetEnvArtifactNamePrefix = "devnet-descriptor-" - DevnetEnvArtifactPath = "env.json" -) - -type DevnetFS struct { - *EnclaveFS -} - -type DevnetFSDescriptorOption func(*options) - -type options struct { - artifactName string - artifactPath string -} - -func newOptions() *options { - return &options{ - artifactPath: DevnetEnvArtifactPath, - } -} - -func WithArtifactName(name string) DevnetFSDescriptorOption { - return func(o *options) { - o.artifactName = name - } -} - -func WithArtifactPath(path string) DevnetFSDescriptorOption { - return func(o *options) { - o.artifactPath = path - } -} - -func NewDevnetFS(fs *EnclaveFS) *DevnetFS { - return &DevnetFS{EnclaveFS: fs} -} - -func (fs *DevnetFS) GetDevnetDescriptor(ctx context.Context, opts ...DevnetFSDescriptorOption) (*descriptors.DevnetEnvironment, error) { - options := newOptions() - for _, opt := range opts { - opt(options) - } - - if options.artifactName == "" { - if err := fs.loadLatestDevnetDescriptorName(ctx, options); err != nil { - return nil, err - } - } - - artifact, err := fs.GetArtifact(ctx, options.artifactName) - if err != nil { - return nil, fmt.Errorf("error getting artifact: %w", err) - } - - var buf bytes.Buffer - writer := NewArtifactFileWriter(options.artifactPath, &buf) - - if err := artifact.ExtractFiles(writer); err != nil { - return nil, fmt.Errorf("error extracting file from artifact: %w", err) - } - - var env descriptors.DevnetEnvironment - if err := json.Unmarshal(buf.Bytes(), &env); err != nil { - return nil, fmt.Errorf("error unmarshalling environment: %w", err) - } - - return &env, nil -} - -func (fs *DevnetFS) UploadDevnetDescriptor(ctx context.Context, env *descriptors.DevnetEnvironment, opts ...DevnetFSDescriptorOption) error { - envBuf := bytes.NewBuffer(nil) - enc := json.NewEncoder(envBuf) - enc.SetIndent("", " ") - if err := enc.Encode(env); err != nil { - return fmt.Errorf("error encoding environment: %w", err) - } - - options := newOptions() - for _, opt := range opts { - opt(options) - } - - if options.artifactName == "" { - if err := fs.loadNextDevnetDescriptorName(ctx, options); err != nil { - return fmt.Errorf("error getting next devnet descriptor: %w", err) - } - } - - if err := fs.PutArtifact(ctx, options.artifactName, NewArtifactFileReader(options.artifactPath, envBuf)); err != nil { - return fmt.Errorf("error putting environment artifact: %w", err) - } - - return nil -} - -func (fs *DevnetFS) loadLatestDevnetDescriptorName(ctx context.Context, options *options) error { - names, err := fs.GetAllArtifactNames(ctx) - if err != nil { - return fmt.Errorf("error getting artifact names: %w", err) - } - - var maxSuffix int = -1 - var maxName string - for _, name := range names { - _, suffix, found := strings.Cut(name, DevnetEnvArtifactNamePrefix) - if !found { - continue - } - - // Parse the suffix as a number - var num int - if _, err := fmt.Sscanf(suffix, "%d", &num); err != nil { - continue // Skip if suffix is not a valid number - } - - // Update maxName if this number is larger - if num > maxSuffix { - maxSuffix = num - maxName = name - } - } - - if maxName == "" { - return fmt.Errorf("no descriptor found with valid numerical suffix") - } - - options.artifactName = maxName - return nil -} - -func (fs *DevnetFS) loadNextDevnetDescriptorName(ctx context.Context, options *options) error { - artifactNames, err := fs.GetAllArtifactNames(ctx) - if err != nil { - return fmt.Errorf("error getting artifact names: %w", err) - } - - maxNum := -1 - for _, artifactName := range artifactNames { - if !strings.HasPrefix(artifactName, DevnetEnvArtifactNamePrefix) { - continue - } - - numStr := strings.TrimPrefix(artifactName, DevnetEnvArtifactNamePrefix) - num := 0 - if _, err := fmt.Sscanf(numStr, "%d", &num); err != nil { - log.Printf("Warning: invalid devnet descriptor format: %s", artifactName) - continue - } - - if num > maxNum { - maxNum = num - } - } - - options.artifactName = fmt.Sprintf("%s%d", DevnetEnvArtifactNamePrefix, maxNum+1) - return nil -} diff --git a/devnet-sdk/kt/fs/devnet_fs_test.go b/devnet-sdk/kt/fs/devnet_fs_test.go deleted file mode 100644 index d8bb6824001f4..0000000000000 --- a/devnet-sdk/kt/fs/devnet_fs_test.go +++ /dev/null @@ -1,293 +0,0 @@ -package fs - -import ( - "context" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestGetDevnetDescriptor(t *testing.T) { - envContent := `{"name": "test-env", "l1": {"name": "l1", "id": "1", "nodes": []}, "l2": []}` - expectedEnv := &descriptors.DevnetEnvironment{ - Name: "test-env", - L1: &descriptors.Chain{ - Name: "l1", - ID: "1", - Nodes: []descriptors.Node{}, - }, - L2: []*descriptors.L2Chain{}, - } - - tests := []struct { - name string - artifactName string - artifactPath string - envContent string - wantErr bool - expectedEnv *descriptors.DevnetEnvironment - }{ - { - name: "successful retrieval with default path", - artifactName: "devnet-descriptor-1", - artifactPath: DevnetEnvArtifactPath, - envContent: envContent, - expectedEnv: expectedEnv, - }, - { - name: "successful retrieval with custom path", - artifactName: "devnet-descriptor-1", - artifactPath: "custom/path/env.json", - envContent: envContent, - expectedEnv: expectedEnv, - }, - { - name: "invalid json content", - artifactName: "devnet-descriptor-1", - artifactPath: DevnetEnvArtifactPath, - envContent: `invalid json`, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context with artifact - mockCtx := &mockEnclaveContext{ - artifacts: map[string][]byte{ - tt.artifactName: createTarGzArtifact(t, map[string]string{ - tt.artifactPath: tt.envContent, - }), - }, - fs: afero.NewMemMapFs(), - } - - enclaveFS, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - devnetFS := NewDevnetFS(enclaveFS) - - // Get descriptor with options - opts := []DevnetFSDescriptorOption{} - if tt.artifactName != "" { - opts = append(opts, WithArtifactName(tt.artifactName)) - } - if tt.artifactPath != DevnetEnvArtifactPath { - opts = append(opts, WithArtifactPath(tt.artifactPath)) - } - - env, err := devnetFS.GetDevnetDescriptor(context.Background(), opts...) - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.expectedEnv, env) - }) - } -} - -func TestUploadDevnetDescriptor(t *testing.T) { - env := &descriptors.DevnetEnvironment{ - Name: "test-env", - L1: &descriptors.Chain{ - Name: "l1", - ID: "1", - Nodes: []descriptors.Node{}, - }, - L2: []*descriptors.L2Chain{}, - } - - tests := []struct { - name string - artifactName string - artifactPath string - env *descriptors.DevnetEnvironment - wantErr bool - }{ - { - name: "successful upload with default path", - artifactName: "devnet-descriptor-1", - artifactPath: DevnetEnvArtifactPath, - env: env, - }, - { - name: "successful upload with custom path", - artifactName: "devnet-descriptor-1", - artifactPath: "custom/path/env.json", - env: env, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context - mockCtx := &mockEnclaveContext{ - artifacts: make(map[string][]byte), - fs: afero.NewMemMapFs(), - } - - enclaveFS, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - devnetFS := NewDevnetFS(enclaveFS) - - // Upload descriptor with options - opts := []DevnetFSDescriptorOption{} - if tt.artifactName != "" { - opts = append(opts, WithArtifactName(tt.artifactName)) - } - if tt.artifactPath != DevnetEnvArtifactPath { - opts = append(opts, WithArtifactPath(tt.artifactPath)) - } - - err = devnetFS.UploadDevnetDescriptor(context.Background(), tt.env, opts...) - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - - // Verify the artifact was uploaded - require.NotNil(t, mockCtx.uploaded) - uploaded := mockCtx.uploaded[tt.artifactName] - require.NotNil(t, uploaded) - require.Contains(t, uploaded, tt.artifactPath) - }) - } -} - -func TestLoadLatestDevnetDescriptorName(t *testing.T) { - tests := []struct { - name string - existingNames []string - expectedName string - wantErr bool - }{ - { - name: "single descriptor", - existingNames: []string{ - "devnet-descriptor-1", - }, - expectedName: "devnet-descriptor-1", - }, - { - name: "multiple descriptors", - existingNames: []string{ - "devnet-descriptor-1", - "devnet-descriptor-3", - "devnet-descriptor-2", - }, - expectedName: "devnet-descriptor-3", - }, - { - name: "no descriptors", - existingNames: []string{}, - wantErr: true, - }, - { - name: "invalid descriptor names", - existingNames: []string{ - "invalid-name", - "devnet-descriptor-invalid", - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context with artifacts - mockCtx := &mockEnclaveContext{ - artifacts: make(map[string][]byte), - fs: afero.NewMemMapFs(), - } - - // Add artifacts to the mock context - for _, name := range tt.existingNames { - mockCtx.artifacts[name] = []byte{} - } - - enclaveFS, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - devnetFS := NewDevnetFS(enclaveFS) - - options := newOptions() - err = devnetFS.loadLatestDevnetDescriptorName(context.Background(), options) - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.expectedName, options.artifactName) - }) - } -} - -func TestLoadNextDevnetDescriptorName(t *testing.T) { - tests := []struct { - name string - existingNames []string - expectedName string - }{ - { - name: "no existing descriptors", - existingNames: []string{}, - expectedName: "devnet-descriptor-0", - }, - { - name: "single descriptor", - existingNames: []string{ - "devnet-descriptor-1", - }, - expectedName: "devnet-descriptor-2", - }, - { - name: "multiple descriptors", - existingNames: []string{ - "devnet-descriptor-1", - "devnet-descriptor-3", - "devnet-descriptor-2", - }, - expectedName: "devnet-descriptor-4", - }, - { - name: "with invalid descriptor names", - existingNames: []string{ - "invalid-name", - "devnet-descriptor-1", - "devnet-descriptor-invalid", - }, - expectedName: "devnet-descriptor-2", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context with artifacts - mockCtx := &mockEnclaveContext{ - artifacts: make(map[string][]byte), - fs: afero.NewMemMapFs(), - } - - // Add artifacts to the mock context - for _, name := range tt.existingNames { - mockCtx.artifacts[name] = []byte{} - } - - enclaveFS, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - devnetFS := NewDevnetFS(enclaveFS) - - options := newOptions() - err = devnetFS.loadNextDevnetDescriptorName(context.Background(), options) - require.NoError(t, err) - assert.Equal(t, tt.expectedName, options.artifactName) - }) - } -} diff --git a/devnet-sdk/kt/fs/fs.go b/devnet-sdk/kt/fs/fs.go deleted file mode 100644 index f1acb7e12f851..0000000000000 --- a/devnet-sdk/kt/fs/fs.go +++ /dev/null @@ -1,272 +0,0 @@ -package fs - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" - "github.com/spf13/afero" -) - -// EnclaveContextIface abstracts the EnclaveContext for testing -type EnclaveContextIface interface { - GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) - DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) - UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) -} - -type EnclaveFS struct { - enclaveCtx EnclaveContextIface - fs afero.Fs -} - -type EnclaveFSOption func(*EnclaveFS) - -func WithFs(fs afero.Fs) EnclaveFSOption { - return func(e *EnclaveFS) { - e.fs = fs - } -} - -func WithEnclaveCtx(enclaveCtx EnclaveContextIface) EnclaveFSOption { - return func(e *EnclaveFS) { - e.enclaveCtx = enclaveCtx - } -} - -func NewEnclaveFS(ctx context.Context, enclave string, opts ...EnclaveFSOption) (*EnclaveFS, error) { - enclaveFS := &EnclaveFS{} - - for _, opt := range opts { - opt(enclaveFS) - } - - if enclaveFS.fs == nil { - enclaveFS.fs = afero.NewOsFs() - } - - if enclaveFS.enclaveCtx == nil { - kurtosisCtx, err := kurtosis_context.NewKurtosisContextFromLocalEngine() - if err != nil { - return nil, err - } - - enclaveCtx, err := kurtosisCtx.GetEnclaveContext(ctx, enclave) - if err != nil { - return nil, err - } - - enclaveFS.enclaveCtx = enclaveCtx - } - - return enclaveFS, nil -} - -type Artifact struct { - rawData []byte - reader *tar.Reader - fs afero.Fs -} - -func (fs *EnclaveFS) GetAllArtifactNames(ctx context.Context) ([]string, error) { - artifacts, err := fs.enclaveCtx.GetAllFilesArtifactNamesAndUuids(ctx) - if err != nil { - return nil, err - } - - names := make([]string, len(artifacts)) - for i, artifact := range artifacts { - names[i] = artifact.GetFileName() - } - - return names, nil -} - -func (fs *EnclaveFS) GetArtifact(ctx context.Context, name string) (*Artifact, error) { - artifact, err := fs.enclaveCtx.DownloadFilesArtifact(ctx, name) - if err != nil { - return nil, err - } - - // Store the raw data - buffer := bytes.NewBuffer(artifact) - zipReader, err := gzip.NewReader(buffer) - if err != nil { - return nil, err - } - tarReader := tar.NewReader(zipReader) - return &Artifact{ - rawData: artifact, - reader: tarReader, - fs: fs.fs, - }, nil -} - -func (a *Artifact) newReader() (*tar.Reader, error) { - buffer := bytes.NewBuffer(a.rawData) - zipReader, err := gzip.NewReader(buffer) - if err != nil { - return nil, err - } - return tar.NewReader(zipReader), nil -} - -func (a *Artifact) Download(path string) error { - // Create a new reader for this operation - reader, err := a.newReader() - if err != nil { - return fmt.Errorf("failed to create reader: %w", err) - } - - for { - header, err := reader.Next() - if err == io.EOF { - return nil - } - if err != nil { - return fmt.Errorf("failed to read tar header: %w", err) - } - - fpath := filepath.Join(path, filepath.Clean(header.Name)) - - switch header.Typeflag { - case tar.TypeDir: - if err := a.fs.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil { - return fmt.Errorf("failed to create directory %s: %w", fpath, err) - } - case tar.TypeReg: - // Create parent directories if they don't exist - if err := a.fs.MkdirAll(filepath.Dir(fpath), 0755); err != nil { - return fmt.Errorf("failed to create directory for %s: %w", fpath, err) - } - - // Create the file - f, err := a.fs.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, os.FileMode(header.Mode)) - if err != nil { - return fmt.Errorf("failed to create file %s: %w", fpath, err) - } - - // Copy contents from tar reader to file - if _, err := io.Copy(f, reader); err != nil { - f.Close() - return fmt.Errorf("failed to write contents to %s: %w", fpath, err) - } - f.Close() - default: - return fmt.Errorf("unsupported file type %d for %s", header.Typeflag, header.Name) - } - } -} - -func (a *Artifact) ExtractFiles(writers ...*ArtifactFileWriter) error { - // Create a new reader for this operation - reader, err := a.newReader() - if err != nil { - return fmt.Errorf("failed to create reader: %w", err) - } - - paths := make(map[string]io.Writer) - for _, writer := range writers { - canonicalPath := filepath.Clean(writer.path) - paths[canonicalPath] = writer.writer - } - - for { - header, err := reader.Next() - if err == io.EOF { - break - } - if err != nil { - return fmt.Errorf("failed to read tar header: %w", err) - } - - headerPath := filepath.Clean(header.Name) - if _, ok := paths[headerPath]; !ok { - continue - } - - writer := paths[headerPath] - _, err = io.Copy(writer, reader) - if err != nil { - return fmt.Errorf("failed to copy content: %w", err) - } - } - - return nil -} - -func (fs *EnclaveFS) PutArtifact(ctx context.Context, name string, readers ...*ArtifactFileReader) (retErr error) { - // Create a temporary directory using afero - tempDir, err := afero.TempDir(fs.fs, "", "artifact-*") - if err != nil { - return err - } - defer func() { - if err := fs.fs.RemoveAll(tempDir); err != nil && retErr == nil { - retErr = fmt.Errorf("failed to cleanup temporary directory: %w", err) - } - }() - - // Process each reader - for _, reader := range readers { - // Create the full path in the temp directory - fullPath := filepath.Join(tempDir, reader.path) - - // Ensure the parent directory exists - if err := fs.fs.MkdirAll(filepath.Dir(fullPath), 0755); err != nil { - return err - } - - // Create the file - file, err := fs.fs.Create(fullPath) - if err != nil { - return err - } - - // Copy the content - _, err = io.Copy(file, reader.reader) - file.Close() // Close file after writing - if err != nil { - return err - } - } - - // Upload the directory to Kurtosis - if _, _, err := fs.enclaveCtx.UploadFiles(tempDir, name); err != nil { - return err - } - - return -} - -type ArtifactFileReader struct { - path string - reader io.Reader -} - -func NewArtifactFileReader(path string, reader io.Reader) *ArtifactFileReader { - return &ArtifactFileReader{ - path: path, - reader: reader, - } -} - -type ArtifactFileWriter struct { - path string - writer io.Writer -} - -func NewArtifactFileWriter(path string, writer io.Writer) *ArtifactFileWriter { - return &ArtifactFileWriter{ - path: path, - writer: writer, - } -} diff --git a/devnet-sdk/kt/fs/fs_test.go b/devnet-sdk/kt/fs/fs_test.go deleted file mode 100644 index c347a75c4aa16..0000000000000 --- a/devnet-sdk/kt/fs/fs_test.go +++ /dev/null @@ -1,450 +0,0 @@ -package fs - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "os" - "path/filepath" - "testing" - - "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" - "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type mockEnclaveContext struct { - artifacts map[string][]byte - uploaded map[string]map[string][]byte // artifactName -> path -> content - fs afero.Fs // filesystem to use for operations -} - -func (m *mockEnclaveContext) DownloadFilesArtifact(_ context.Context, name string) ([]byte, error) { - return m.artifacts[name], nil -} - -func (m *mockEnclaveContext) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) { - if m.uploaded == nil { - m.uploaded = make(map[string]map[string][]byte) - } - m.uploaded[artifactName] = make(map[string][]byte) - - err := afero.Walk(m.fs, pathToUpload, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - - relPath, err := filepath.Rel(pathToUpload, path) - if err != nil { - return err - } - - content, err := afero.ReadFile(m.fs, path) - if err != nil { - return err - } - - m.uploaded[artifactName][relPath] = content - return nil - }) - - return "test-uuid", services.FileArtifactName(artifactName), err -} - -func (m *mockEnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { - var result []*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid - for name := range m.artifacts { - result = append(result, &kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid{ - FileName: name, - FileUuid: "test-uuid", - }) - } - return result, nil -} - -var _ EnclaveContextIface = (*mockEnclaveContext)(nil) - -func createTarGzArtifact(t *testing.T, files map[string]string) []byte { - var buf bytes.Buffer - gzWriter := gzip.NewWriter(&buf) - tarWriter := tar.NewWriter(gzWriter) - - for name, content := range files { - err := tarWriter.WriteHeader(&tar.Header{ - Name: name, - Mode: 0600, - Size: int64(len(content)), - }) - require.NoError(t, err) - - _, err = tarWriter.Write([]byte(content)) - require.NoError(t, err) - } - - require.NoError(t, tarWriter.Close()) - require.NoError(t, gzWriter.Close()) - return buf.Bytes() -} - -func TestArtifactExtraction(t *testing.T) { - tests := []struct { - name string - files map[string]string - requests map[string]string - wantErr bool - }{ - { - name: "simple path", - files: map[string]string{ - "file1.txt": "content1", - }, - requests: map[string]string{ - "file1.txt": "content1", - }, - }, - { - name: "path with dot prefix", - files: map[string]string{ - "./file1.txt": "content1", - }, - requests: map[string]string{ - "file1.txt": "content1", - }, - }, - { - name: "mixed paths", - files: map[string]string{ - "./file1.txt": "content1", - "file2.txt": "content2", - "./dir/f3.txt": "content3", - }, - requests: map[string]string{ - "file1.txt": "content1", - "file2.txt": "content2", - "dir/f3.txt": "content3", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create mock context with artifact - mockCtx := &mockEnclaveContext{ - artifacts: map[string][]byte{ - "test-artifact": createTarGzArtifact(t, tt.files), - }, - fs: afero.NewMemMapFs(), - } - - fs, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - artifact, err := fs.GetArtifact(context.Background(), "test-artifact") - require.NoError(t, err) - - // Create writers for all requested files - writers := make([]*ArtifactFileWriter, 0, len(tt.requests)) - buffers := make(map[string]*bytes.Buffer, len(tt.requests)) - for reqPath := range tt.requests { - buf := &bytes.Buffer{} - buffers[reqPath] = buf - writers = append(writers, NewArtifactFileWriter(reqPath, buf)) - } - - // Extract all files at once - err = artifact.ExtractFiles(writers...) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - // Verify contents - for reqPath, wantContent := range tt.requests { - require.Equal(t, wantContent, buffers[reqPath].String(), "content mismatch for %s", reqPath) - } - }) - } -} - -func TestPutArtifact(t *testing.T) { - tests := []struct { - name string - files map[string]string - wantErr bool - }{ - { - name: "single file", - files: map[string]string{ - "file1.txt": "content1", - }, - }, - { - name: "multiple files", - files: map[string]string{ - "file1.txt": "content1", - "dir/file2.txt": "content2", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fs := afero.NewMemMapFs() - mockCtx := &mockEnclaveContext{ - artifacts: make(map[string][]byte), - fs: fs, - } - - enclaveFs := &EnclaveFS{ - enclaveCtx: mockCtx, - fs: fs, - } - - // Create readers for all files - var readers []*ArtifactFileReader - for path, content := range tt.files { - readers = append(readers, NewArtifactFileReader( - path, - bytes.NewReader([]byte(content)), - )) - } - - // Put the artifact - err := enclaveFs.PutArtifact(context.Background(), "test-artifact", readers...) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - // Verify uploaded contents - require.NotNil(t, mockCtx.uploaded) - uploaded := mockCtx.uploaded["test-artifact"] - require.NotNil(t, uploaded) - require.Equal(t, len(tt.files), len(uploaded)) - - for path, wantContent := range tt.files { - content, exists := uploaded[path] - require.True(t, exists, "missing file: %s", path) - require.Equal(t, wantContent, string(content), "content mismatch for %s", path) - } - }) - } -} - -func TestMultipleExtractCalls(t *testing.T) { - // Create a test artifact with multiple files - files := map[string]string{ - "file1.txt": "content1", - "file2.txt": "content2", - "dir/file3.txt": "content3", - "dir/file4.txt": "content4", - } - - // Create mock context with artifact - mockCtx := &mockEnclaveContext{ - artifacts: map[string][]byte{ - "test-artifact": createTarGzArtifact(t, files), - }, - fs: afero.NewMemMapFs(), - } - - fs, err := NewEnclaveFS(context.Background(), "test-enclave", WithEnclaveCtx(mockCtx), WithFs(mockCtx.fs)) - require.NoError(t, err) - - artifact, err := fs.GetArtifact(context.Background(), "test-artifact") - require.NoError(t, err) - - // First extraction - get file1.txt and file3.txt - firstExtractFiles := map[string]string{ - "file1.txt": "content1", - "dir/file3.txt": "content3", - } - - firstWriters := make([]*ArtifactFileWriter, 0, len(firstExtractFiles)) - firstBuffers := make(map[string]*bytes.Buffer, len(firstExtractFiles)) - - for reqPath := range firstExtractFiles { - buf := &bytes.Buffer{} - firstBuffers[reqPath] = buf - firstWriters = append(firstWriters, NewArtifactFileWriter(reqPath, buf)) - } - - // First extraction - err = artifact.ExtractFiles(firstWriters...) - require.NoError(t, err) - - // Verify first extraction - for reqPath, wantContent := range firstExtractFiles { - require.Equal(t, wantContent, firstBuffers[reqPath].String(), - "first extraction: content mismatch for %s", reqPath) - } - - // Second extraction - get file2.txt and file4.txt - secondExtractFiles := map[string]string{ - "file2.txt": "content2", - "dir/file4.txt": "content4", - } - - secondWriters := make([]*ArtifactFileWriter, 0, len(secondExtractFiles)) - secondBuffers := make(map[string]*bytes.Buffer, len(secondExtractFiles)) - - for reqPath := range secondExtractFiles { - buf := &bytes.Buffer{} - secondBuffers[reqPath] = buf - secondWriters = append(secondWriters, NewArtifactFileWriter(reqPath, buf)) - } - - // Second extraction using the same artifact - err = artifact.ExtractFiles(secondWriters...) - require.NoError(t, err) - - // Verify second extraction - for reqPath, wantContent := range secondExtractFiles { - require.Equal(t, wantContent, secondBuffers[reqPath].String(), - "second extraction: content mismatch for %s", reqPath) - } - - // Third extraction - extract all files again to prove we can keep extracting - allFiles := map[string]string{ - "file1.txt": "content1", - "file2.txt": "content2", - "dir/file3.txt": "content3", - "dir/file4.txt": "content4", - } - - allWriters := make([]*ArtifactFileWriter, 0, len(allFiles)) - allBuffers := make(map[string]*bytes.Buffer, len(allFiles)) - - for reqPath := range allFiles { - buf := &bytes.Buffer{} - allBuffers[reqPath] = buf - allWriters = append(allWriters, NewArtifactFileWriter(reqPath, buf)) - } - - // Third extraction - err = artifact.ExtractFiles(allWriters...) - require.NoError(t, err) - - // Verify third extraction - for reqPath, wantContent := range allFiles { - require.Equal(t, wantContent, allBuffers[reqPath].String(), - "third extraction: content mismatch for %s", reqPath) - } -} - -func TestArtifact_Download(t *testing.T) { - tests := []struct { - name string - files map[string][]byte // map of filepath to content - wantErr bool - validate func(t *testing.T, fs afero.Fs) - }{ - { - name: "single file download", - files: map[string][]byte{ - "test.txt": []byte("hello world"), - }, - validate: func(t *testing.T, fs afero.Fs) { - content, err := afero.ReadFile(fs, "test.txt") - require.NoError(t, err) - assert.Equal(t, []byte("hello world"), content) - }, - }, - { - name: "nested directory structure", - files: map[string][]byte{ - "dir/test.txt": []byte("hello"), - "dir/subdir/test.txt": []byte("world"), - }, - validate: func(t *testing.T, fs afero.Fs) { - content1, err := afero.ReadFile(fs, "dir/test.txt") - require.NoError(t, err) - assert.Equal(t, []byte("hello"), content1) - - content2, err := afero.ReadFile(fs, "dir/subdir/test.txt") - require.NoError(t, err) - assert.Equal(t, []byte("world"), content2) - }, - }, - { - name: "empty directory", - files: map[string][]byte{ - "dir/": nil, - }, - validate: func(t *testing.T, fs afero.Fs) { - exists, err := afero.DirExists(fs, "dir") - require.NoError(t, err) - assert.True(t, exists) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a tar.gz archive in memory - var buf bytes.Buffer - gw := gzip.NewWriter(&buf) - tw := tar.NewWriter(gw) - - // Add files to the archive - for path, content := range tt.files { - header := &tar.Header{ - Name: path, - } - if content == nil { - header.Typeflag = tar.TypeDir - header.Mode = 0755 - } else { - header.Typeflag = tar.TypeReg - header.Size = int64(len(content)) - header.Mode = 0644 - } - - err := tw.WriteHeader(header) - require.NoError(t, err) - - if content != nil { - _, err = tw.Write(content) - require.NoError(t, err) - } - } - - err := tw.Close() - require.NoError(t, err) - err = gw.Close() - require.NoError(t, err) - - // Create in-memory filesystem - memFs := afero.NewMemMapFs() - - // Create an Artifact from the archive - rawData := buf.Bytes() - zipReader, err := gzip.NewReader(bytes.NewReader(rawData)) - require.NoError(t, err) - artifact := &Artifact{ - rawData: rawData, - reader: tar.NewReader(zipReader), - fs: memFs, - } - - // Test Download function - err = artifact.Download("") - if tt.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - - // Run validation - tt.validate(t, memFs) - }) - } -} diff --git a/devnet-sdk/kt/params.go b/devnet-sdk/kt/params.go deleted file mode 100644 index 2125e08e56cfd..0000000000000 --- a/devnet-sdk/kt/params.go +++ /dev/null @@ -1,82 +0,0 @@ -package kt - -// KurtosisParams represents the top-level Kurtosis configuration -type KurtosisParams struct { - OptimismPackage OptimismPackage `yaml:"optimism_package"` - EthereumPackage EthereumPackage `yaml:"ethereum_package"` -} - -// OptimismPackage represents the Optimism-specific configuration -type OptimismPackage struct { - Chains []ChainConfig `yaml:"chains"` - OpContractDeployerParams OpContractDeployerParams `yaml:"op_contract_deployer_params"` - Persistent bool `yaml:"persistent"` -} - -// ChainConfig represents a single chain configuration -type ChainConfig struct { - Participants []ParticipantConfig `yaml:"participants"` - NetworkParams NetworkParams `yaml:"network_params"` - BatcherParams BatcherParams `yaml:"batcher_params"` - ChallengerParams ChallengerParams `yaml:"challenger_params"` - ProposerParams ProposerParams `yaml:"proposer_params"` -} - -// ParticipantConfig represents a participant in the network -type ParticipantConfig struct { - ElType string `yaml:"el_type"` - ElImage string `yaml:"el_image"` - ClType string `yaml:"cl_type"` - ClImage string `yaml:"cl_image"` - Count int `yaml:"count"` -} - -// TimeOffsets represents a map of time offset values -type TimeOffsets map[string]int - -// NetworkParams represents network-specific parameters -type NetworkParams struct { - Network string `yaml:"network"` - NetworkID string `yaml:"network_id"` - SecondsPerSlot int `yaml:"seconds_per_slot"` - Name string `yaml:"name"` - FundDevAccounts bool `yaml:"fund_dev_accounts"` - TimeOffsets `yaml:",inline"` -} - -// BatcherParams represents batcher-specific parameters -type BatcherParams struct { - Image string `yaml:"image"` -} - -// ChallengerParams represents challenger-specific parameters -type ChallengerParams struct { - Image string `yaml:"image"` - CannonPrestatesURL string `yaml:"cannon_prestates_url,omitempty"` -} - -// ProposerParams represents proposer-specific parameters -type ProposerParams struct { - Image string `yaml:"image"` - GameType int `yaml:"game_type"` - ProposalInterval string `yaml:"proposal_interval"` -} - -// OpContractDeployerParams represents contract deployer parameters -type OpContractDeployerParams struct { - Image string `yaml:"image"` - L1ArtifactsLocator string `yaml:"l1_artifacts_locator"` - L2ArtifactsLocator string `yaml:"l2_artifacts_locator"` -} - -// EthereumPackage represents Ethereum-specific configuration -type EthereumPackage struct { - NetworkParams EthereumNetworkParams `yaml:"network_params"` -} - -// EthereumNetworkParams represents Ethereum network parameters -type EthereumNetworkParams struct { - Preset string `yaml:"preset"` - GenesisDelay int `yaml:"genesis_delay"` - AdditionalPreloadedContracts string `yaml:"additional_preloaded_contracts"` -} diff --git a/devnet-sdk/kt/visitor.go b/devnet-sdk/kt/visitor.go deleted file mode 100644 index e237a6724f7cf..0000000000000 --- a/devnet-sdk/kt/visitor.go +++ /dev/null @@ -1,265 +0,0 @@ -package kt - -import ( - "strconv" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/images" - "github.com/ethereum-optimism/optimism/devnet-sdk/manifest" -) - -const ( - defaultProposalInterval = "10m" - defaultGameType = 1 - defaultPreset = "minimal" - defaultGenesisDelay = 5 - defaultPreloadedContracts = `{ - "0x4e59b44847b379578588920cA78FbF26c0B4956C": { - "balance": "0ETH", - "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - "storage": {}, - "nonce": "1" - } - }` -) - -// KurtosisVisitor implements the manifest.ManifestVisitor interface -type KurtosisVisitor struct { - params *KurtosisParams - repository *images.Repository - l2Visitor *l2Visitor -} - -// Component visitor for handling component versions -type componentVisitor struct { - name string - version string -} - -// Chain visitor for handling chain configuration -type chainVisitor struct { - name string - id uint64 -} - -// Contracts visitor for handling contract configuration -type contractsVisitor struct { - locator string -} - -// Overrides represents deployment overrides -type Overrides struct { - SecondsPerSlot int `yaml:"seconds_per_slot"` - TimeOffsets `yaml:",inline"` -} - -// Deployment visitor for handling deployment configuration -type deploymentVisitor struct { - deployer *componentVisitor - l1Contracts *contractsVisitor - l2Contracts *contractsVisitor - overrides *Overrides -} - -// L2 visitor for handling L2 configuration -type l2Visitor struct { - components map[string]*componentVisitor - deployment *deploymentVisitor - chains []*chainVisitor -} - -// NewKurtosisVisitor creates a new KurtosisVisitor -func NewKurtosisVisitor() *KurtosisVisitor { - return &KurtosisVisitor{ - params: &KurtosisParams{ - OptimismPackage: OptimismPackage{ - Chains: make([]ChainConfig, 0), - Persistent: false, - }, - EthereumPackage: EthereumPackage{ - NetworkParams: EthereumNetworkParams{ - Preset: defaultPreset, - GenesisDelay: defaultGenesisDelay, - AdditionalPreloadedContracts: defaultPreloadedContracts, - }, - }, - }, - repository: images.NewRepository(), - } -} - -func (v *KurtosisVisitor) VisitName(name string) {} - -func (v *KurtosisVisitor) VisitType(manifestType string) {} - -func (v *KurtosisVisitor) VisitL1() manifest.ChainVisitor { - return &chainVisitor{} -} - -func (v *KurtosisVisitor) VisitL2() manifest.L2Visitor { - v.l2Visitor = &l2Visitor{ - components: make(map[string]*componentVisitor), - deployment: &deploymentVisitor{ - deployer: &componentVisitor{}, - l1Contracts: &contractsVisitor{}, - l2Contracts: &contractsVisitor{}, - overrides: &Overrides{ - TimeOffsets: make(TimeOffsets), - }, - }, - chains: make([]*chainVisitor, 0), - } - return v.l2Visitor -} - -// Component visitor implementation -func (v *componentVisitor) VisitVersion(version string) { - // Strip the component name from the version string - parts := strings.SplitN(version, "/", 2) - if len(parts) == 2 { - v.version = parts[1] - } else { - v.version = version - } -} - -// Chain visitor implementation -func (v *chainVisitor) VisitName(name string) { - v.name = name -} - -func (v *chainVisitor) VisitID(id uint64) { - // TODO: this is horrible but unfortunately the funding script breaks for - // chain IDs larger than 32 bits. - v.id = id & 0xFFFFFFFF -} - -// Contracts visitor implementation -func (v *contractsVisitor) VisitVersion(version string) { - if v.locator == "" { - v.locator = "tag://" + version - } -} - -func (v *contractsVisitor) VisitLocator(locator string) { - v.locator = locator -} - -// Deployment visitor implementation -func (v *deploymentVisitor) VisitDeployer() manifest.ComponentVisitor { - return v.deployer -} - -func (v *deploymentVisitor) VisitL1Contracts() manifest.ContractsVisitor { - return v.l1Contracts -} - -func (v *deploymentVisitor) VisitL2Contracts() manifest.ContractsVisitor { - return v.l2Contracts -} - -func (v *deploymentVisitor) VisitOverride(key string, value interface{}) { - if key == "seconds_per_slot" { - if intValue, ok := value.(int); ok { - v.overrides.SecondsPerSlot = intValue - } - } else if strings.HasSuffix(key, "_time_offset") { - if intValue, ok := value.(int); ok { - v.overrides.TimeOffsets[key] = intValue - } - } -} - -// L2 visitor implementation -func (v *l2Visitor) VisitL2Component(name string) manifest.ComponentVisitor { - comp := &componentVisitor{name: name} - v.components[name] = comp - return comp -} - -func (v *l2Visitor) VisitL2Deployment() manifest.DeploymentVisitor { - return v.deployment -} - -func (v *l2Visitor) VisitL2Chain(idx int) manifest.ChainVisitor { - chain := &chainVisitor{} - if idx >= len(v.chains) { - v.chains = append(v.chains, chain) - } else { - v.chains[idx] = chain - } - return chain -} - -// GetParams returns the generated Kurtosis parameters -func (v *KurtosisVisitor) GetParams() *KurtosisParams { - if v.l2Visitor != nil { - v.BuildKurtosisParams(v.l2Visitor) - } - return v.params -} - -// getComponentVersion returns the version for a component, or empty string if not found -func (l2 *l2Visitor) getComponentVersion(name string) string { - if comp, ok := l2.components[name]; ok { - return comp.version - } - return "" -} - -// getComponentImage returns the image for a component, or empty string if component doesn't exist -func (v *KurtosisVisitor) getComponentImage(l2 *l2Visitor, name string) string { - if _, ok := l2.components[name]; ok { - return v.repository.GetImage(name, l2.getComponentVersion(name)) - } - return "" -} - -// BuildKurtosisParams builds the final Kurtosis parameters from the collected visitor data -func (v *KurtosisVisitor) BuildKurtosisParams(l2 *l2Visitor) { - // Set deployer params - v.params.OptimismPackage.OpContractDeployerParams = OpContractDeployerParams{ - Image: v.repository.GetImage("op-deployer", l2.deployment.deployer.version), - L1ArtifactsLocator: l2.deployment.l1Contracts.locator, - L2ArtifactsLocator: l2.deployment.l2Contracts.locator, - } - - // Build chain configs - for _, chain := range l2.chains { - // Create network params with embedded map - networkParams := NetworkParams{ - Network: "kurtosis", - NetworkID: strconv.FormatUint(chain.id, 10), - SecondsPerSlot: l2.deployment.overrides.SecondsPerSlot, - Name: chain.name, - FundDevAccounts: true, - TimeOffsets: l2.deployment.overrides.TimeOffsets, - } - - chainConfig := ChainConfig{ - Participants: []ParticipantConfig{ - { - ElType: "op-geth", - ElImage: v.getComponentImage(l2, "op-geth"), - ClType: "op-node", - ClImage: v.getComponentImage(l2, "op-node"), - Count: 1, - }, - }, - NetworkParams: networkParams, - BatcherParams: BatcherParams{ - Image: v.getComponentImage(l2, "op-batcher"), - }, - ChallengerParams: ChallengerParams{ - Image: v.getComponentImage(l2, "op-challenger"), - }, - ProposerParams: ProposerParams{ - Image: v.getComponentImage(l2, "op-proposer"), - GameType: defaultGameType, - ProposalInterval: defaultProposalInterval, - }, - } - - v.params.OptimismPackage.Chains = append(v.params.OptimismPackage.Chains, chainConfig) - } -} diff --git a/devnet-sdk/kt/visitor_test.go b/devnet-sdk/kt/visitor_test.go deleted file mode 100644 index 17ce7bdfb0202..0000000000000 --- a/devnet-sdk/kt/visitor_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package kt - -import ( - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/manifest" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" -) - -func TestKurtosisVisitor_TransformsManifest(t *testing.T) { - input := ` -name: alpaca -type: alphanet -l1: - name: sepolia - chain_id: 11155111 -l2: - deployment: - op-deployer: - version: op-deployer/v0.0.11 - l1-contracts: - locator: https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-c3f2e2adbd52a93c2c08cab018cd637a4e203db53034e59c6c139c76b4297953.tar.gz - version: 984bae9146398a2997ec13757bfe2438ca8f92eb - l2-contracts: - version: op-contracts/v1.7.0-beta.1+l2-contracts - overrides: - seconds_per_slot: 2 - fjord_time_offset: 0 - granite_time_offset: 0 - holocene_time_offset: 0 - components: - op-node: - version: op-node/v1.10.2 - op-geth: - version: op-geth/v1.101411.4-rc.4 - op-reth: - version: op-reth/v1.1.5 - op-proposer: - version: op-proposer/v1.10.0-rc.2 - op-batcher: - version: op-batcher/v1.10.0 - op-challenger: - version: op-challenger/v1.3.1-rc.4 - chains: - - name: alpaca-0 - chain_id: 11155111100000 -` - - // Then the output should match the expected YAML structure - expected := KurtosisParams{ - OptimismPackage: OptimismPackage{ - Chains: []ChainConfig{ - { - Participants: []ParticipantConfig{ - { - ElType: "op-geth", - ElImage: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101411.4-rc.4", - ClType: "op-node", - ClImage: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.10.2", - Count: 1, - }, - }, - NetworkParams: NetworkParams{ - Network: "kurtosis", - NetworkID: "1081032288", - SecondsPerSlot: 2, - Name: "alpaca-0", - FundDevAccounts: true, - TimeOffsets: TimeOffsets{ - "fjord_time_offset": 0, - "granite_time_offset": 0, - "holocene_time_offset": 0, - }, - }, - BatcherParams: BatcherParams{ - Image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:v1.10.0", - }, - ChallengerParams: ChallengerParams{ - Image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.3.1-rc.4", - CannonPrestatesURL: "", - }, - ProposerParams: ProposerParams{ - Image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:v1.10.0-rc.2", - GameType: 1, - ProposalInterval: "10m", - }, - }, - }, - OpContractDeployerParams: OpContractDeployerParams{ - Image: "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-deployer:v0.0.11", - L1ArtifactsLocator: "https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-c3f2e2adbd52a93c2c08cab018cd637a4e203db53034e59c6c139c76b4297953.tar.gz", - L2ArtifactsLocator: "tag://op-contracts/v1.7.0-beta.1+l2-contracts", - }, - Persistent: false, - }, - EthereumPackage: EthereumPackage{ - NetworkParams: EthereumNetworkParams{ - Preset: "minimal", - GenesisDelay: 5, - AdditionalPreloadedContracts: defaultPreloadedContracts, - }, - }, - } - - // Convert the input to a manifest - var manifest manifest.Manifest - err := yaml.Unmarshal([]byte(input), &manifest) - require.NoError(t, err) - - // Create visitor and have manifest accept it - visitor := NewKurtosisVisitor() - manifest.Accept(visitor) - - // Get the generated params - actual := *visitor.GetParams() - - // Compare the actual and expected params - require.Equal(t, expected, actual, "Generated params should match expected params") - -} diff --git a/devnet-sdk/manifest/acceptor.go b/devnet-sdk/manifest/acceptor.go deleted file mode 100644 index b7873c6f60714..0000000000000 --- a/devnet-sdk/manifest/acceptor.go +++ /dev/null @@ -1,25 +0,0 @@ -package manifest - -type ManifestAcceptor interface { - Accept(visitor ManifestVisitor) -} - -type ChainAcceptor interface { - Accept(visitor ChainVisitor) -} - -type L2Acceptor interface { - Accept(visitor L2Visitor) -} - -type DeploymentAcceptor interface { - Accept(visitor DeploymentVisitor) -} - -type ContractsAcceptor interface { - Accept(visitor ContractsVisitor) -} - -type ComponentAcceptor interface { - Accept(visitor ComponentVisitor) -} diff --git a/devnet-sdk/manifest/manifest.go b/devnet-sdk/manifest/manifest.go deleted file mode 100644 index 65d9e5f7da623..0000000000000 --- a/devnet-sdk/manifest/manifest.go +++ /dev/null @@ -1,104 +0,0 @@ -package manifest - -// L1Config represents L1 configuration -type L1Config struct { - Name string `yaml:"name"` - ChainID uint64 `yaml:"chain_id"` -} - -func (c *L1Config) Accept(visitor ChainVisitor) { - visitor.VisitName(c.Name) - visitor.VisitID(c.ChainID) -} - -var _ ChainAcceptor = (*L1Config)(nil) - -type Component struct { - Version string `yaml:"version"` -} - -func (c *Component) Accept(visitor ComponentVisitor) { - visitor.VisitVersion(c.Version) -} - -var _ ComponentAcceptor = (*Component)(nil) - -type Contracts struct { - Version string `yaml:"version"` - Locator string `yaml:"locator"` -} - -func (c *Contracts) Accept(visitor ContractsVisitor) { - visitor.VisitLocator(c.Locator) - visitor.VisitVersion(c.Version) -} - -var _ ContractsAcceptor = (*Contracts)(nil) - -// L2Deployment represents deployment configuration -type L2Deployment struct { - OpDeployer *Component `yaml:"op-deployer"` - L1Contracts *Contracts `yaml:"l1-contracts"` - L2Contracts *Contracts `yaml:"l2-contracts"` - Overrides map[string]interface{} `yaml:"overrides"` -} - -func (d *L2Deployment) Accept(visitor DeploymentVisitor) { - d.OpDeployer.Accept(visitor.VisitDeployer()) - d.L1Contracts.Accept(visitor.VisitL1Contracts()) - d.L2Contracts.Accept(visitor.VisitL2Contracts()) - for key, value := range d.Overrides { - visitor.VisitOverride(key, value) - } -} - -var _ DeploymentAcceptor = (*L2Deployment)(nil) - -// L2Chain represents an L2 chain configuration -type L2Chain struct { - Name string `yaml:"name"` - ChainID uint64 `yaml:"chain_id"` -} - -func (c *L2Chain) Accept(visitor ChainVisitor) { - visitor.VisitName(c.Name) - visitor.VisitID(c.ChainID) -} - -var _ ChainAcceptor = (*L2Chain)(nil) - -// L2Config represents L2 configuration -type L2Config struct { - Deployment *L2Deployment `yaml:"deployment"` - Components map[string]*Component `yaml:"components"` - Chains []*L2Chain `yaml:"chains"` -} - -func (c *L2Config) Accept(visitor L2Visitor) { - for name, component := range c.Components { - component.Accept(visitor.VisitL2Component(name)) - } - for i, chain := range c.Chains { - chain.Accept(visitor.VisitL2Chain(i)) - } - c.Deployment.Accept(visitor.VisitL2Deployment()) -} - -var _ L2Acceptor = (*L2Config)(nil) - -// Manifest represents the top-level manifest configuration -type Manifest struct { - Name string `yaml:"name"` - Type string `yaml:"type"` - L1 *L1Config `yaml:"l1"` - L2 *L2Config `yaml:"l2"` -} - -func (m *Manifest) Accept(visitor ManifestVisitor) { - visitor.VisitName(m.Name) - visitor.VisitType(m.Type) - m.L1.Accept(visitor.VisitL1()) - m.L2.Accept(visitor.VisitL2()) -} - -var _ ManifestAcceptor = (*Manifest)(nil) diff --git a/devnet-sdk/manifest/visitor.go b/devnet-sdk/manifest/visitor.go deleted file mode 100644 index c6a3861e33dbc..0000000000000 --- a/devnet-sdk/manifest/visitor.go +++ /dev/null @@ -1,35 +0,0 @@ -package manifest - -type ManifestVisitor interface { - VisitName(name string) - VisitType(manifestType string) - VisitL1() ChainVisitor - VisitL2() L2Visitor -} - -type L2Visitor interface { - VisitL2Component(name string) ComponentVisitor - VisitL2Deployment() DeploymentVisitor - VisitL2Chain(int) ChainVisitor -} - -type ComponentVisitor interface { - VisitVersion(version string) -} - -type DeploymentVisitor interface { - VisitDeployer() ComponentVisitor - VisitL1Contracts() ContractsVisitor - VisitL2Contracts() ContractsVisitor - VisitOverride(string, interface{}) -} - -type ContractsVisitor interface { - VisitVersion(version string) - VisitLocator(locator string) -} - -type ChainVisitor interface { - VisitName(name string) - VisitID(id uint64) -} diff --git a/devnet-sdk/proofs/prestate/client.go b/devnet-sdk/proofs/prestate/client.go deleted file mode 100644 index a5a6e7b4c6aba..0000000000000 --- a/devnet-sdk/proofs/prestate/client.go +++ /dev/null @@ -1,228 +0,0 @@ -package prestate - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "mime/multipart" - "net/http" - "path/filepath" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" -) - -// These constants should be in sync with op-program/chainconfig/chaincfg.go -const ( - InteropDepSetName = "depsets.json" - rollupConfigSuffix = "-rollup.json" - genensisConfigSuffix = "-genesis-l2.json" -) - -// PrestateManifest maps prestate identifiers to their hashes -type PrestateManifest map[string]string - -// PrestateBuilderClient is a client for the prestate builder service -type PrestateBuilderClient struct { - url string - client *http.Client -} - -// NewPrestateBuilderClient creates a new client for the prestate builder service -func NewPrestateBuilderClient(url string) *PrestateBuilderClient { - return &PrestateBuilderClient{ - url: url, - client: &http.Client{}, - } -} - -// FileInput represents a file to be used in the build process -type FileInput struct { - Name string // Name of the file (used for identification) - Content io.Reader // Content of the file - Type string // Type information (e.g., "rollup-config", "genesis-config", "interop") -} - -// buildContext holds all the inputs for a build operation -type buildContext struct { - chains []string - files []FileInput - generatedInteropDepSet bool -} - -// PrestateBuilderOption is a functional option for configuring a build -type PrestateBuilderOption func(*buildContext) - -// WithInteropDepSet adds an interop dependency set file to the build -func WithInteropDepSet(content io.Reader) PrestateBuilderOption { - return func(c *buildContext) { - c.files = append(c.files, FileInput{ - Name: InteropDepSetName, - Content: content, - Type: "interop", - }) - } -} - -func generateInteropDepSet(chains []string) ([]byte, error) { - deps := make(map[eth.ChainID]*depset.StaticConfigDependency) - for _, chain := range chains { - id, err := eth.ParseDecimalChainID(chain) - if err != nil { - return nil, fmt.Errorf("failed to parse chain ID: %w", err) - } - deps[id] = &depset.StaticConfigDependency{} - } - - interopDepSet, err := depset.NewStaticConfigDependencySet(deps) - if err != nil { - return nil, fmt.Errorf("failed to create interop dependency set: %w", err) - } - - json, err := json.Marshal(interopDepSet) - if err != nil { - return nil, err - } - return json, nil -} - -func WithGeneratedInteropDepSet() PrestateBuilderOption { - return func(c *buildContext) { - c.generatedInteropDepSet = true - } -} - -// WithChainConfig adds a pair of rollup and genesis config files to the build -func WithChainConfig(chainId string, rollupContent io.Reader, genesisContent io.Reader) PrestateBuilderOption { - return func(c *buildContext) { - c.chains = append(c.chains, chainId) - c.files = append(c.files, - FileInput{ - Name: chainId + rollupConfigSuffix, - Content: rollupContent, - Type: "rollup-config", - }, - FileInput{ - Name: chainId + genensisConfigSuffix, - Content: genesisContent, - Type: "genesis-config", - }, - ) - } -} - -// BuildPrestate sends the files to the prestate builder service and returns a manifest of the built prestates -func (c *PrestateBuilderClient) BuildPrestate(ctx context.Context, opts ...PrestateBuilderOption) (PrestateManifest, error) { - fmt.Println("Starting prestate build...") - - // Apply options to build context - bc := &buildContext{ - files: []FileInput{}, - } - - for _, opt := range opts { - opt(bc) - } - - if bc.generatedInteropDepSet { - depSet, err := generateInteropDepSet(bc.chains) - if err != nil { - return nil, fmt.Errorf("failed to generate interop dependency set: %w", err) - } - bc.files = append(bc.files, FileInput{ - Name: InteropDepSetName, - Content: bytes.NewReader(depSet), - Type: "interop", - }) - } - - fmt.Printf("Preparing to upload %d files\n", len(bc.files)) - - // Create a multipart form - var b bytes.Buffer - w := multipart.NewWriter(&b) - - // Add each file to the form - for _, file := range bc.files { - fmt.Printf("Adding file to form: %s (type: %s)\n", file.Name, file.Type) - // Create a form file with the file's name - fw, err := w.CreateFormFile("files[]", filepath.Base(file.Name)) - if err != nil { - return nil, fmt.Errorf("failed to create form file: %w", err) - } - - // Copy the file content to the form - if _, err := io.Copy(fw, file.Content); err != nil { - return nil, fmt.Errorf("failed to copy file content: %w", err) - } - } - - // Close the multipart writer - if err := w.Close(); err != nil { - return nil, fmt.Errorf("failed to close multipart writer: %w", err) - } - - fmt.Printf("Sending build request to %s\n", c.url) - - // Create the HTTP request - req, err := http.NewRequestWithContext(ctx, "POST", c.url, &b) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - - // Set the content type - req.Header.Set("Content-Type", w.FormDataContentType()) - - // Send the request - resp, err := c.client.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to send request: %w", err) - } - defer resp.Body.Close() - - // Check the response status - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotModified { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) - } - - fmt.Println("Build request successful, fetching build manifest...") - - // If the build was successful, get the info.json file - infoURL := c.url - if infoURL[len(infoURL)-1] != '/' { - infoURL += "/" - } - infoURL += "info.json" - - fmt.Printf("Requesting manifest from %s\n", infoURL) - - infoReq, err := http.NewRequestWithContext(ctx, "GET", infoURL, nil) - if err != nil { - return nil, fmt.Errorf("failed to create info request: %w", err) - } - - infoResp, err := c.client.Do(infoReq) - if err != nil { - return nil, fmt.Errorf("failed to get info.json: %w", err) - } - defer infoResp.Body.Close() - - if infoResp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(infoResp.Body) - return nil, fmt.Errorf("unexpected info.json status code: %d, body: %s", infoResp.StatusCode, string(body)) - } - - // Parse the JSON response - var manifest PrestateManifest - if err := json.NewDecoder(infoResp.Body).Decode(&manifest); err != nil { - return nil, fmt.Errorf("failed to decode info.json response: %w", err) - } - - fmt.Printf("Build complete. Generated %d prestate entries\n", len(manifest)) - - return manifest, nil - -} diff --git a/devnet-sdk/proofs/prestate/cmd/main.go b/devnet-sdk/proofs/prestate/cmd/main.go deleted file mode 100644 index 782ac7a642e83..0000000000000 --- a/devnet-sdk/proofs/prestate/cmd/main.go +++ /dev/null @@ -1,99 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "log" - "os" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/proofs/prestate" -) - -type chainConfig struct { - id string - rollupConfig string - genesisConfig string -} - -func parseChainFlag(s string) (*chainConfig, error) { - parts := strings.Split(s, ",") - if len(parts) != 3 { - return nil, fmt.Errorf("chain flag must contain exactly 1 id and 2 files separated by comma") - } - return &chainConfig{ - id: strings.TrimSpace(parts[0]), - rollupConfig: strings.TrimSpace(parts[1]), - genesisConfig: strings.TrimSpace(parts[2]), - }, nil -} - -func main() { - var ( - clientURL = flag.String("url", "http://localhost:8080", "URL of the prestate builder service") - interop = flag.Bool("interop", false, "Generate interop dependency set") - chains = make(chainConfigList, 0) - ) - - flag.Var(&chains, "chain", "Chain configuration files in format: rollup-config.json,genesis-config.json (can be specified multiple times)") - flag.Parse() - - client := prestate.NewPrestateBuilderClient(*clientURL) - ctx := context.Background() - - // Build options list - opts := make([]prestate.PrestateBuilderOption, 0) - - if *interop { - opts = append(opts, prestate.WithGeneratedInteropDepSet()) - } - - // Add chain configs - for i, chain := range chains { - rollupFile, err := os.Open(chain.rollupConfig) - if err != nil { - log.Fatalf("Failed to open rollup config file for chain %d: %v", i, err) - } - defer rollupFile.Close() - - genesisFile, err := os.Open(chain.genesisConfig) - if err != nil { - log.Fatalf("Failed to open genesis config file for chain %d: %v", i, err) - } - defer genesisFile.Close() - - opts = append(opts, prestate.WithChainConfig( - chain.id, - rollupFile, - genesisFile, - )) - } - - // Build prestate - manifest, err := client.BuildPrestate(ctx, opts...) - if err != nil { - log.Fatalf("Failed to build prestate: %v", err) - } - - // Print manifest - for id, hash := range manifest { - fmt.Printf("%s: %s\n", id, hash) - } -} - -// chainConfigList implements flag.Value interface for repeated chain flags -type chainConfigList []*chainConfig - -func (c *chainConfigList) String() string { - return fmt.Sprintf("%v", *c) -} - -func (c *chainConfigList) Set(value string) error { - config, err := parseChainFlag(value) - if err != nil { - return err - } - *c = append(*c, config) - return nil -} diff --git a/devnet-sdk/shell/cmd/ctrl/main.go b/devnet-sdk/shell/cmd/ctrl/main.go deleted file mode 100644 index ed4cdbe30f11c..0000000000000 --- a/devnet-sdk/shell/cmd/ctrl/main.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/devnet-sdk/controller/surface" - "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" - "github.com/urfave/cli/v2" -) - -func run(ctx *cli.Context) error { - devnetURL := ctx.String("devnet") - action := ctx.String("action") - service := ctx.String("service") - - devnetEnv, err := env.LoadDevnetFromURL(devnetURL) - if err != nil { - return err - } - - ctrl, err := devnetEnv.Control() - if err != nil { - return err - } - - lc, ok := ctrl.(surface.ServiceLifecycleSurface) - if !ok { - return fmt.Errorf("control surface does not support lifecycle management") - } - - switch action { - case "start": - return lc.StartService(ctx.Context, service) - case "stop": - return lc.StopService(ctx.Context, service) - default: - return fmt.Errorf("invalid action: %s", action) - } -} - -func main() { - app := &cli.App{ - Name: "ctrl", - Usage: "Control devnet services", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "devnet", - Usage: "URL to devnet JSON file", - EnvVars: []string{env.EnvURLVar}, - Required: true, - }, - &cli.StringFlag{ - Name: "action", - Usage: "Action to perform (start or stop)", - Required: true, - Value: "", - Action: func(ctx *cli.Context, v string) error { - if v != "start" && v != "stop" { - return fmt.Errorf("action must be either 'start' or 'stop'") - } - return nil - }, - }, - &cli.StringFlag{ - Name: "service", - Usage: "Service to perform action on", - Required: true, - }, - }, - Action: run, - } - - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/devnet-sdk/shell/cmd/enter/main.go b/devnet-sdk/shell/cmd/enter/main.go deleted file mode 100644 index 5fb98280c4016..0000000000000 --- a/devnet-sdk/shell/cmd/enter/main.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/exec" - - "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" - "github.com/urfave/cli/v2" -) - -func run(ctx *cli.Context) error { - devnetURL := ctx.String("devnet") - chainName := ctx.String("chain") - nodeIndex := ctx.Int("node-index") - - devnetEnv, err := env.LoadDevnetFromURL(devnetURL) - if err != nil { - return err - } - - chain, err := devnetEnv.GetChain(chainName) - if err != nil { - return err - } - - chainEnv, err := chain.GetEnv( - env.WithCastIntegration(true, nodeIndex), - ) - if err != nil { - return err - } - - if motd := chainEnv.GetMotd(); motd != "" { - fmt.Println(motd) - } - - // Get current environment and append chain-specific vars - env := chainEnv.ApplyToEnv(os.Environ()) - - // Get current shell - shell := os.Getenv("SHELL") - if shell == "" { - shell = "/bin/sh" - } - - // Execute new shell - cmd := exec.Command(shell) - cmd.Env = env - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return fmt.Errorf("error executing shell: %w", err) - } - - return nil -} - -func main() { - app := &cli.App{ - Name: "enter", - Usage: "Enter a shell with devnet environment variables set", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "devnet", - Usage: "URL to devnet JSON file", - EnvVars: []string{env.EnvURLVar}, - Required: true, - }, - &cli.StringFlag{ - Name: "chain", - Usage: "Name of the chain to connect to", - EnvVars: []string{env.ChainNameVar}, - Required: true, - }, - &cli.IntFlag{ - Name: "node-index", - Usage: "Index of the node to connect to (default: 0)", - EnvVars: []string{env.NodeIndexVar}, - Required: false, - Value: 0, - }, - }, - Action: run, - } - - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/devnet-sdk/shell/cmd/motd/main.go b/devnet-sdk/shell/cmd/motd/main.go deleted file mode 100644 index c2436ddd6eaa6..0000000000000 --- a/devnet-sdk/shell/cmd/motd/main.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" - "github.com/urfave/cli/v2" -) - -func run(ctx *cli.Context) error { - devnetURL := ctx.String("devnet") - chainName := ctx.String("chain") - - devnetEnv, err := env.LoadDevnetFromURL(devnetURL) - if err != nil { - return err - } - - chain, err := devnetEnv.GetChain(chainName) - if err != nil { - return err - } - - chainEnv, err := chain.GetEnv() - if err != nil { - return err - } - - fmt.Println(chainEnv.GetMotd()) - return nil -} - -func main() { - app := &cli.App{ - Name: "motd", - Usage: "Display the Message of the Day for a chain environment", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "devnet", - Usage: "URL to devnet JSON file", - EnvVars: []string{env.EnvURLVar}, - Required: true, - }, - &cli.StringFlag{ - Name: "chain", - Usage: "Name of the chain to get MOTD for", - EnvVars: []string{env.ChainNameVar}, - Required: true, - }, - }, - Action: run, - } - - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/devnet-sdk/shell/env/chain.go b/devnet-sdk/shell/env/chain.go deleted file mode 100644 index 446b66fb6581a..0000000000000 --- a/devnet-sdk/shell/env/chain.go +++ /dev/null @@ -1,187 +0,0 @@ -package env - -import ( - "bytes" - "fmt" - "html/template" - "net/url" - "path/filepath" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" -) - -const ( - EnvURLVar = "DEVNET_ENV_URL" - ChainNameVar = "DEVNET_CHAIN_NAME" - NodeIndexVar = "DEVNET_NODE_INDEX" - ExpectPreconditionsMet = "DEVNET_EXPECT_PRECONDITIONS_MET" - EnvCtrlVar = "DEVNET_ENV_CTRL" -) - -type ChainConfig struct { - chain *descriptors.Chain - devnetURL string - name string -} - -type ChainEnv struct { - motd string - envVars map[string]string -} - -func (c *ChainConfig) getRpcUrl(nodeIndex int) func() (string, error) { - return func() (string, error) { - if len(c.chain.Nodes) == 0 { - return "", fmt.Errorf("chain '%s' has no nodes", c.chain.Name) - } - - if nodeIndex >= len(c.chain.Nodes) { - return "", fmt.Errorf("node index %d is out of bounds for chain '%s'", nodeIndex, c.chain.Name) - } - - // Get RPC endpoint from the first node's execution layer service - elService, ok := c.chain.Nodes[nodeIndex].Services["el"] - if !ok { - return "", fmt.Errorf("no execution layer service found for chain '%s'", c.chain.Name) - } - - rpcEndpoint, ok := elService.Endpoints["rpc"] - if !ok { - return "", fmt.Errorf("no RPC endpoint found for chain '%s'", c.chain.Name) - } - - scheme := rpcEndpoint.Scheme - if scheme == "" { - scheme = "http" - } - return fmt.Sprintf("%s://%s:%d", scheme, rpcEndpoint.Host, rpcEndpoint.Port), nil - } -} - -func (c *ChainConfig) getJwtSecret() (string, error) { - jwt := c.chain.JWT - if len(jwt) >= 2 && jwt[:2] == "0x" { - jwt = jwt[2:] - } - - return jwt, nil -} - -func (c *ChainConfig) motd() string { - tmpl := `You're in a {{.Name}} chain subshell. - - Some addresses of interest: - {{ range $key, $value := .Addresses -}} - {{ printf "%-35s" $key }} = {{ $value }} - {{ end -}} - ` - - t := template.Must(template.New("motd").Parse(tmpl)) - - var buf bytes.Buffer - if err := t.Execute(&buf, c.chain); err != nil { - panic(err) - } - - return buf.String() -} - -type ChainConfigOption func(*ChainConfig, *chainConfigOpts) error - -type chainConfigOpts struct { - extraEnvVars map[string]string -} - -func WithCastIntegration(cast bool, nodeIndex int) ChainConfigOption { - return func(c *ChainConfig, o *chainConfigOpts) error { - mapping := map[string]func() (string, error){ - "ETH_RPC_URL": c.getRpcUrl(nodeIndex), - "ETH_RPC_JWT_SECRET": c.getJwtSecret, - } - - for key, fn := range mapping { - value := "" - var err error - if cast { - if value, err = fn(); err != nil { - return err - } - } - o.extraEnvVars[key] = value - } - return nil - } -} - -func WithExpectedPreconditions(pre bool) ChainConfigOption { - return func(c *ChainConfig, o *chainConfigOpts) error { - if pre { - o.extraEnvVars[ExpectPreconditionsMet] = "true" - } else { - o.extraEnvVars[ExpectPreconditionsMet] = "" - } - return nil - } -} - -func (c *ChainConfig) GetEnv(opts ...ChainConfigOption) (*ChainEnv, error) { - motd := c.motd() - o := &chainConfigOpts{ - extraEnvVars: make(map[string]string), - } - - for _, opt := range opts { - if err := opt(c, o); err != nil { - return nil, err - } - } - - // To allow commands within the shell to know which devnet and chain they are in - absPath := c.devnetURL - if u, err := url.Parse(c.devnetURL); err == nil { - if u.Scheme == "" || u.Scheme == "file" { - // make sure the path is absolute - if abs, err := filepath.Abs(u.Path); err == nil { - absPath = abs - } - } - } - o.extraEnvVars[EnvURLVar] = absPath - o.extraEnvVars[ChainNameVar] = c.name - - return &ChainEnv{ - motd: motd, - envVars: o.extraEnvVars, - }, nil -} - -func (e *ChainEnv) ApplyToEnv(env []string) []string { - // first identify which env vars to clear - clearEnv := make(map[string]interface{}) - for key := range e.envVars { - clearEnv[key] = nil - } - - // then actually remove these from the env - cleanEnv := make([]string, 0) - for _, s := range env { - key := strings.SplitN(s, "=", 2)[0] - if _, ok := clearEnv[key]; !ok { - cleanEnv = append(cleanEnv, s) - } - } - - // then add the remaining env vars - for key, value := range e.envVars { - if value == "" { - continue - } - cleanEnv = append(cleanEnv, fmt.Sprintf("%s=%s", key, value)) - } - return cleanEnv -} - -func (e *ChainEnv) GetMotd() string { - return e.motd -} diff --git a/devnet-sdk/shell/env/devnet.go b/devnet-sdk/shell/env/devnet.go deleted file mode 100644 index 2f1d3baf6eb88..0000000000000 --- a/devnet-sdk/shell/env/devnet.go +++ /dev/null @@ -1,182 +0,0 @@ -package env - -import ( - "fmt" - "math/big" - "net/url" - "os" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/controller/kt" - "github.com/ethereum-optimism/optimism/devnet-sdk/controller/surface" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum/go-ethereum/params" -) - -type surfaceGetter func() (surface.ControlSurface, error) -type controllerFactory func(*descriptors.DevnetEnvironment) surfaceGetter - -type DevnetEnv struct { - Env *descriptors.DevnetEnvironment - URL string - - ctrl surfaceGetter -} - -// DataFetcher is a function type for fetching data from a URL -type DataFetcher func(*url.URL) (*descriptors.DevnetEnvironment, error) - -type schemeBackend struct { - fetcher DataFetcher - ctrlFactory controllerFactory -} - -func getKurtosisController(env *descriptors.DevnetEnvironment) surfaceGetter { - return func() (surface.ControlSurface, error) { - return kt.NewKurtosisControllerSurface(env) - } -} - -var ( - ktFetcher = &kurtosisFetcher{ - devnetFSFactory: newDevnetFS, - } - - // schemeToBackend maps URL schemes to their respective data fetcher functions - schemeToBackend = map[string]schemeBackend{ - "": {fetchFileData, nil}, - "file": {fetchFileData, nil}, - "kt": {ktFetcher.fetchKurtosisData, getKurtosisController}, - "ktnative": {fetchKurtosisNativeData, getKurtosisController}, - } -) - -// fetchDevnetData retrieves data from a URL based on its scheme -func fetchDevnetData(parsedURL *url.URL) (*descriptors.DevnetEnvironment, error) { - scheme := strings.ToLower(parsedURL.Scheme) - backend, ok := schemeToBackend[scheme] - if !ok { - return nil, fmt.Errorf("unsupported URL scheme: %s", scheme) - } - - return backend.fetcher(parsedURL) -} - -func LoadDevnetFromURL(devnetURL string) (*DevnetEnv, error) { - parsedURL, err := url.Parse(devnetURL) - if err != nil { - return nil, fmt.Errorf("error parsing URL: %w", err) - } - - env, err := fetchDevnetData(parsedURL) - if err != nil { - return nil, fmt.Errorf("error fetching devnet data: %w", err) - } - - if err := fixupDevnetConfig(env); err != nil { - return nil, fmt.Errorf("error fixing up devnet config: %w", err) - } - - var ctrl surfaceGetter - scheme := parsedURL.Scheme - if val, ok := os.LookupEnv(EnvCtrlVar); ok { - scheme = val - } - backend, ok := schemeToBackend[scheme] - if !ok { - return nil, fmt.Errorf("invalid scheme to lookup control interface: %s", scheme) - } - - if backend.ctrlFactory != nil { - ctrl = backend.ctrlFactory(env) - } - - return &DevnetEnv{ - Env: env, - URL: devnetURL, - ctrl: ctrl, - }, nil -} - -func (d *DevnetEnv) GetChain(chainName string) (*ChainConfig, error) { - var chain *descriptors.Chain - if d.Env.L1.Name == chainName { - chain = d.Env.L1 - } else { - for _, l2Chain := range d.Env.L2 { - if l2Chain.Name == chainName { - chain = l2Chain.Chain - break - } - } - } - - if chain == nil { - return nil, fmt.Errorf("chain '%s' not found in devnet config", chainName) - } - - return &ChainConfig{ - chain: chain, - devnetURL: d.URL, - name: chainName, - }, nil -} - -func (d *DevnetEnv) Control() (surface.ControlSurface, error) { - if d.ctrl == nil { - return nil, fmt.Errorf("devnet is not controllable") - } - return d.ctrl() -} - -func fixupDevnetConfig(config *descriptors.DevnetEnvironment) error { - // we should really get this from the kurtosis output, but the data doesn't exist yet, so craft a minimal one. - l1ID := new(big.Int) - l1ID, ok := l1ID.SetString(config.L1.ID, 10) - if !ok { - return fmt.Errorf("invalid L1 ID: %s", config.L1.ID) - } - if config.L1.Config == nil { - if l1Config := eth.L1ChainConfigByChainID(eth.ChainIDFromBig(l1ID)); l1Config != nil { - config.L1.Config = l1Config - } else { - config.L1.Config = ¶ms.ChainConfig{ - ChainID: l1ID, - } - } - } - for _, l2Chain := range config.L2 { - l2ChainId := l2Chain.Chain.ID - - var l2ID *big.Int - base := 10 - if len(l2ChainId) >= 2 && l2ChainId[:2] == "0x" { - base = 16 - l2ChainId = l2ChainId[2:] - } - - l2ID, ok := new(big.Int).SetString(l2ChainId, base) - if !ok { - return fmt.Errorf("invalid L2 ID: %s", l2ChainId) - } - // Convert the L2 chain ID to decimal string format - decimalId := l2ID.String() - l2Chain.Chain.ID = decimalId - - if l2Chain.Config == nil { - l2Chain.Config = ¶ms.ChainConfig{ - ChainID: l2ID, - } - } - - if l2Chain.RollupConfig == nil { - l2Chain.RollupConfig = &rollup.Config{ - L1ChainID: l1ID, - L2ChainID: l2ID, - } - } - } - return nil -} diff --git a/devnet-sdk/shell/env/env_test.go b/devnet-sdk/shell/env/env_test.go deleted file mode 100644 index d2b7aaf44a766..0000000000000 --- a/devnet-sdk/shell/env/env_test.go +++ /dev/null @@ -1,309 +0,0 @@ -package env - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestLoadDevnetEnv(t *testing.T) { - // Create a temporary test file - content := `{ - "l1": { - "name": "l1", - "id": "1", - "nodes": [{ - "services": { - "el": { - "endpoints": { - "rpc": { - "host": "localhost", - "port": 8545 - } - } - } - } - }], - "jwt": "0x1234567890abcdef", - "addresses": { - "deployer": "0x1234567890123456789012345678901234567890" - } - }, - "l2": [{ - "name": "op", - "id": "2", - "nodes": [{ - "services": { - "el": { - "endpoints": { - "rpc": { - "host": "localhost", - "port": 9545 - } - } - } - } - }], - "jwt": "0xdeadbeef", - "addresses": { - "deployer": "0x2345678901234567890123456789012345678901" - } - }] - }` - - tmpfile, err := os.CreateTemp("", "devnet-*.json") - require.NoError(t, err) - defer os.Remove(tmpfile.Name()) - - _, err = tmpfile.Write([]byte(content)) - require.NoError(t, err) - err = tmpfile.Close() - require.NoError(t, err) - - // Test successful load - t.Run("successful load", func(t *testing.T) { - env, err := LoadDevnetFromURL(tmpfile.Name()) - require.NoError(t, err) - assert.Equal(t, "l1", env.Env.L1.Name) - assert.Equal(t, "op", env.Env.L2[0].Name) - }) - - // Test loading non-existent file - t.Run("non-existent file", func(t *testing.T) { - _, err := LoadDevnetFromURL("non-existent.json") - assert.Error(t, err) - }) - - // Test loading invalid JSON - t.Run("invalid JSON", func(t *testing.T) { - invalidFile := filepath.Join(t.TempDir(), "invalid.json") - err := os.WriteFile(invalidFile, []byte("{invalid json}"), 0644) - require.NoError(t, err) - - _, err = LoadDevnetFromURL(invalidFile) - assert.Error(t, err) - }) -} - -func TestGetChain(t *testing.T) { - devnet := &DevnetEnv{ - Env: &descriptors.DevnetEnvironment{ - L1: &descriptors.Chain{ - Name: "l1", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": { - Endpoints: descriptors.EndpointMap{ - "rpc": { - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }, - }, - JWT: "0x1234", - Addresses: descriptors.AddressMap{ - "deployer": common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - Name: "op", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": { - Endpoints: descriptors.EndpointMap{ - "rpc": { - Host: "localhost", - Port: 9545, - }, - }, - }, - }, - }, - }, - JWT: "0x5678", - Addresses: descriptors.AddressMap{ - "deployer": common.HexToAddress("0x2345678901234567890123456789012345678901"), - }, - }, - L1Wallets: descriptors.WalletMap{ - "deployer": &descriptors.Wallet{ - Address: common.HexToAddress("0x2345678901234567890123456789012345678901"), - PrivateKey: "0x2345678901234567890123456789012345678901", - }, - }, - }, - }, - }, - URL: "test.json", - } - - // Test getting L1 chain - t.Run("get L1 chain", func(t *testing.T) { - chain, err := devnet.GetChain("l1") - require.NoError(t, err) - assert.Equal(t, "l1", chain.name) - assert.Equal(t, "0x1234", chain.chain.JWT) - }) - - // Test getting L2 chain - t.Run("get L2 chain", func(t *testing.T) { - chain, err := devnet.GetChain("op") - require.NoError(t, err) - assert.Equal(t, "op", chain.name) - assert.Equal(t, "0x5678", chain.chain.JWT) - }) - - // Test getting non-existent chain - t.Run("get non-existent chain", func(t *testing.T) { - _, err := devnet.GetChain("invalid") - assert.Error(t, err) - }) -} - -func TestChainConfig(t *testing.T) { - chain := &ChainConfig{ - chain: &descriptors.Chain{ - Name: "test", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": { - Endpoints: descriptors.EndpointMap{ - "rpc": { - Host: "localhost", - Port: 8545, - Scheme: "https", - }, - }, - }, - }, - }, - }, - JWT: "0x1234", - Addresses: descriptors.AddressMap{ - "deployer": common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - devnetURL: "test.json", - name: "test", - } - - // Test getting environment variables - t.Run("get environment variables", func(t *testing.T) { - env, err := chain.GetEnv( - WithCastIntegration(true, 0), - ) - require.NoError(t, err) - - assert.Equal(t, "https://localhost:8545", env.envVars["ETH_RPC_URL"]) - assert.Equal(t, "1234", env.envVars["ETH_RPC_JWT_SECRET"]) - assert.Equal(t, "test.json", filepath.Base(env.envVars[EnvURLVar])) - assert.Equal(t, "test", env.envVars[ChainNameVar]) - assert.Contains(t, env.motd, "deployer") - assert.Contains(t, env.motd, "0x1234567890123456789012345678901234567890") - }) - - // Test chain with no nodes - t.Run("chain with no nodes", func(t *testing.T) { - noNodesChain := &ChainConfig{ - chain: &descriptors.Chain{ - Name: "test", - Nodes: []descriptors.Node{}, - }, - } - _, err := noNodesChain.GetEnv( - WithCastIntegration(true, 0), - ) - assert.Error(t, err) - }) - - // Test chain with missing service - t.Run("chain with missing service", func(t *testing.T) { - missingServiceChain := &ChainConfig{ - chain: &descriptors.Chain{ - Name: "test", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{}, - }, - }, - }, - } - _, err := missingServiceChain.GetEnv( - WithCastIntegration(true, 0), - ) - assert.Error(t, err) - }) - - // Test chain with missing endpoint - t.Run("chain with missing endpoint", func(t *testing.T) { - missingEndpointChain := &ChainConfig{ - chain: &descriptors.Chain{ - Name: "test", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": { - Endpoints: descriptors.EndpointMap{}, - }, - }, - }, - }, - }, - } - _, err := missingEndpointChain.GetEnv( - WithCastIntegration(true, 0), - ) - assert.Error(t, err) - }) -} - -func TestChainEnv_ApplyToEnv(t *testing.T) { - originalEnv := []string{ - "KEEP_ME=old_value", - "OVERRIDE_ME=old_value", - "REMOVE_ME=old_value", - } - - env := &ChainEnv{ - envVars: map[string]string{ - "OVERRIDE_ME": "new_value", - "REMOVE_ME": "", - }, - } - - result := env.ApplyToEnv(originalEnv) - - // Convert result to map for easier testing - resultMap := make(map[string]string) - for _, v := range result { - parts := strings.SplitN(v, "=", 2) - resultMap[parts[0]] = parts[1] - } - - // Test that KEEP_ME was overridden with new value - assert.Equal(t, "old_value", resultMap["KEEP_ME"]) - - // Test that OVERRIDE_ME was overridden with new value - assert.Equal(t, "new_value", resultMap["OVERRIDE_ME"]) - - // Test that REMOVE_ME was removed (not present in result) - _, exists := resultMap["REMOVE_ME"] - assert.False(t, exists, "REMOVE_ME should have been removed") - - // Test that we have exactly 3 variables in the result - assert.Equal(t, 2, len(result), "Result should have exactly 3 variables") -} diff --git a/devnet-sdk/shell/env/file_fetch.go b/devnet-sdk/shell/env/file_fetch.go deleted file mode 100644 index 929331d26659c..0000000000000 --- a/devnet-sdk/shell/env/file_fetch.go +++ /dev/null @@ -1,49 +0,0 @@ -package env - -import ( - "encoding/json" - "fmt" - "net/url" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/spf13/afero" -) - -type fileFetcher struct { - fs afero.Fs -} - -// fetchFileData reads data from a local file -func (f *fileFetcher) fetchFileData(u *url.URL) (*descriptors.DevnetEnvironment, error) { - body, err := afero.ReadFile(f.fs, u.Path) - if err != nil { - return nil, fmt.Errorf("error reading file: %w", err) - } - - basename := u.Path - if lastSlash := strings.LastIndex(basename, "/"); lastSlash >= 0 { - basename = basename[lastSlash+1:] - } - if lastDot := strings.LastIndex(basename, "."); lastDot >= 0 { - basename = basename[:lastDot] - } - - var config descriptors.DevnetEnvironment - if err := json.Unmarshal(body, &config); err != nil { - return nil, fmt.Errorf("error parsing JSON: %w", err) - } - - // If the name is not set, use the basename of the file - if config.Name == "" { - config.Name = basename - } - return &config, nil -} - -func fetchFileData(u *url.URL) (*descriptors.DevnetEnvironment, error) { - fetcher := &fileFetcher{ - fs: afero.NewOsFs(), - } - return fetcher.fetchFileData(u) -} diff --git a/devnet-sdk/shell/env/file_fetch_test.go b/devnet-sdk/shell/env/file_fetch_test.go deleted file mode 100644 index b6e722e0a6684..0000000000000 --- a/devnet-sdk/shell/env/file_fetch_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package env - -import ( - "net/url" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFetchFileDataFromOS(t *testing.T) { - fs := afero.NewMemMapFs() - - var ( - absoluteContent = []byte(`{"name": "absolute"}`) - relativeContent = []byte(`{"name": "relative"}`) - noNameContent = []byte(`{}`) - ) - - err := afero.WriteFile(fs, "/some/absolute/path", absoluteContent, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "some/relative/path", relativeContent, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "some/file/devnet-env.json", noNameContent, 0644) - require.NoError(t, err) - err = afero.WriteFile(fs, "some/file/devnet", noNameContent, 0644) - require.NoError(t, err) - - fetcher := &fileFetcher{ - fs: fs, - } - - tests := []struct { - name string - urlStr string - wantName string - wantContent []byte - wantError bool - }{ - { - name: "file URL", - urlStr: "file:///some/absolute/path", - wantName: "absolute", - wantContent: absoluteContent, - }, - { - name: "absolute path", - urlStr: "/some/absolute/path", - wantName: "absolute", - wantContent: absoluteContent, - }, - { - name: "relative path", - urlStr: "some/relative/path", - wantName: "relative", - wantContent: relativeContent, - }, - { - name: "no name - file with extension", - urlStr: "some/file/devnet-env.json", - wantName: "devnet-env", - wantContent: noNameContent, - }, - { - name: "no name - file without extension", - urlStr: "some/file/devnet", - wantName: "devnet", - wantContent: noNameContent, - }, - { - name: "non-existent file", - urlStr: "file:///nonexistent/path", - wantError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u, err := url.Parse(tt.urlStr) - require.NoError(t, err) - - env, err := fetcher.fetchFileData(u) - if tt.wantError { - assert.Error(t, err) - return - } - - require.NoError(t, err) - assert.Equal(t, tt.wantName, env.Name) - }) - } -} diff --git a/devnet-sdk/shell/env/kt_fetch.go b/devnet-sdk/shell/env/kt_fetch.go deleted file mode 100644 index 2ac5553919d42..0000000000000 --- a/devnet-sdk/shell/env/kt_fetch.go +++ /dev/null @@ -1,69 +0,0 @@ -package env - -import ( - "context" - "fmt" - "net/url" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" -) - -// DevnetFS is an interface that both our mock and the real implementation satisfy -type DevnetFS interface { - GetDevnetDescriptor(ctx context.Context, opts ...ktfs.DevnetFSDescriptorOption) (*descriptors.DevnetEnvironment, error) -} - -type devnetFSFactory func(ctx context.Context, enclave string) (DevnetFS, error) - -type kurtosisFetcher struct { - devnetFSFactory devnetFSFactory -} - -func newDevnetFS(ctx context.Context, enclave string) (DevnetFS, error) { - fs, err := ktfs.NewEnclaveFS(ctx, enclave) - if err != nil { - return nil, err - } - return ktfs.NewDevnetFS(fs), nil -} - -// parseKurtosisURL parses a Kurtosis URL of the form kt://enclave/artifact/file -// If artifact is omitted, it defaults to "" -// If file is omitted, it defaults to "env.json" -func (f *kurtosisFetcher) parseKurtosisURL(u *url.URL) (enclave, artifactName, fileName string) { - enclave = u.Host - artifactName = "" - fileName = ktfs.DevnetEnvArtifactPath - - // Trim both prefix and suffix slashes before splitting - trimmedPath := strings.Trim(u.Path, "/") - parts := strings.Split(trimmedPath, "/") - if len(parts) > 0 && parts[0] != "" { - artifactName = parts[0] - } - if len(parts) > 1 && parts[1] != "" { - fileName = parts[1] - } - - return -} - -// fetchKurtosisData reads data from a Kurtosis artifact -func (f *kurtosisFetcher) fetchKurtosisData(u *url.URL) (*descriptors.DevnetEnvironment, error) { - enclave, artifactName, fileName := f.parseKurtosisURL(u) - - devnetFS, err := f.devnetFSFactory(context.Background(), enclave) - if err != nil { - return nil, fmt.Errorf("error creating enclave fs: %w", err) - } - - env, err := devnetFS.GetDevnetDescriptor(context.Background(), ktfs.WithArtifactName(artifactName), ktfs.WithArtifactPath(fileName)) - if err != nil { - return nil, fmt.Errorf("error getting devnet descriptor: %w", err) - } - - env.Name = enclave - return env, nil -} diff --git a/devnet-sdk/shell/env/kt_fetch_test.go b/devnet-sdk/shell/env/kt_fetch_test.go deleted file mode 100644 index 46da0e091671d..0000000000000 --- a/devnet-sdk/shell/env/kt_fetch_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package env - -import ( - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestParseKurtosisURL(t *testing.T) { - tests := []struct { - name string - urlStr string - wantEnclave string - wantArtifact string - wantFile string - wantParseError bool - }{ - { - name: "basic url", - urlStr: "kt://myenclave", - wantEnclave: "myenclave", - wantArtifact: "", - wantFile: "env.json", - }, - { - name: "with artifact", - urlStr: "kt://myenclave/custom-artifact", - wantEnclave: "myenclave", - wantArtifact: "custom-artifact", - wantFile: "env.json", - }, - { - name: "with artifact and file", - urlStr: "kt://myenclave/custom-artifact/config.json", - wantEnclave: "myenclave", - wantArtifact: "custom-artifact", - wantFile: "config.json", - }, - { - name: "with trailing slash", - urlStr: "kt://enclave/artifact/", - wantEnclave: "enclave", - wantArtifact: "artifact", - wantFile: "env.json", - }, - { - name: "invalid url", - urlStr: "://invalid", - wantParseError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u, err := url.Parse(tt.urlStr) - if tt.wantParseError { - assert.Error(t, err) - return - } - require.NoError(t, err) - - enclave, artifact, file := ktFetcher.parseKurtosisURL(u) - assert.Equal(t, tt.wantEnclave, enclave) - assert.Equal(t, tt.wantArtifact, artifact) - assert.Equal(t, tt.wantFile, file) - }) - } -} diff --git a/devnet-sdk/shell/env/kt_native_fetch.go b/devnet-sdk/shell/env/kt_native_fetch.go deleted file mode 100644 index 3693fa887b569..0000000000000 --- a/devnet-sdk/shell/env/kt_native_fetch.go +++ /dev/null @@ -1,119 +0,0 @@ -package env - -import ( - "context" - "fmt" - "io" - "net/url" - "os" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" -) - -// parseKurtosisNativeURL parses a Kurtosis URL of the form kt://enclave/artifact/file -// If artifact is omitted, it defaults to "devnet" -// If file is omitted, it defaults to "env.json" -func parseKurtosisNativeURL(u *url.URL) (enclave, argsFileName string) { - enclave = u.Host - argsFileName = "/" + strings.Trim(u.Path, "/") - - return -} - -// fetchKurtosisNativeData reads data directly from kurtosis API using default dependency implementations -func fetchKurtosisNativeData(u *url.URL) (*descriptors.DevnetEnvironment, error) { - return fetchKurtosisNativeDataInternal(u, &defaultOSOpenImpl{}, &defaultSpecImpl{}, &defaultKurtosisImpl{}) -} - -// fetchKurtosisNativeDataInternal reads data directly from kurtosis API using provided dependency implementations -func fetchKurtosisNativeDataInternal(u *url.URL, osImpl osOpenInterface, specImpl specInterface, kurtosisImpl kurtosisInterface) (*descriptors.DevnetEnvironment, error) { - // First let's parse the kurtosis URL - enclave, argsFileName := parseKurtosisNativeURL(u) - - // Open the arguments file - argsFile, err := osImpl.Open(argsFileName) - if err != nil { - return nil, fmt.Errorf("error reading arguments file: %w", err) - } - - // Make sure to close the file once we're done reading - defer argsFile.Close() - - // Once we have the arguments file, we can extract the enclave spec - enclaveSpec, err := specImpl.ExtractData(argsFile) - if err != nil { - return nil, fmt.Errorf("error extracting enclave spec: %w", err) - } - - // We'll use the deployer to extract the system spec - deployer, err := kurtosisImpl.NewKurtosisDeployer(kurtosis.WithKurtosisEnclave(enclave)) - if err != nil { - return nil, fmt.Errorf("error creating deployer: %w", err) - } - - // We'll read the environment info from kurtosis directly - ctx := context.Background() - env, err := deployer.GetEnvironmentInfo(ctx, enclaveSpec) - if err != nil { - return nil, fmt.Errorf("error getting environment info: %w", err) - } - - return env.DevnetEnvironment, nil -} - -// osOpenInterface describes a struct that can open filesystem files for reading -// -// osOpenInterface is used when loading kurtosis args files from local filesystem -type osOpenInterface interface { - Open(name string) (fileInterface, error) -} - -// fileInterface describes a subset of os.File struct for ease of testing -type fileInterface interface { - io.Reader - Close() error -} - -// defaultOSOpenImpl implements osOpenInterface -type defaultOSOpenImpl struct{} - -func (d *defaultOSOpenImpl) Open(name string) (fileInterface, error) { - return os.Open(name) -} - -// specInterface describes a subset of functionality required from the spec package -// -// The spec package is responsible for turning a kurtosis args file into an EnclaveSpec -type specInterface interface { - ExtractData(r io.Reader) (*spec.EnclaveSpec, error) -} - -// defaultSpecImpl implements specInterface -type defaultSpecImpl struct{} - -func (d *defaultSpecImpl) ExtractData(r io.Reader) (*spec.EnclaveSpec, error) { - return spec.NewSpec().ExtractData(r) -} - -// kurtosisInterface describes a subset of functionality required from the kurtosis package -// -// kurtosisInterface provides access to a KurtosisDeployer object, an intermediate object that provides -// access to the KurtosisEnvironment object -type kurtosisInterface interface { - NewKurtosisDeployer(opts ...kurtosis.KurtosisDeployerOptions) (kurtosisDeployerInterface, error) -} - -// kurtosisDeployerInterface describes a subset of functionality required from KurtosisDeployer struct -type kurtosisDeployerInterface interface { - GetEnvironmentInfo(ctx context.Context, spec *spec.EnclaveSpec) (*kurtosis.KurtosisEnvironment, error) -} - -// defaultKurtosisImpl implements kurtosisInterface -type defaultKurtosisImpl struct{} - -func (d *defaultKurtosisImpl) NewKurtosisDeployer(opts ...kurtosis.KurtosisDeployerOptions) (kurtosisDeployerInterface, error) { - return kurtosis.NewKurtosisDeployer(opts...) -} diff --git a/devnet-sdk/shell/env/kt_native_fetch_test.go b/devnet-sdk/shell/env/kt_native_fetch_test.go deleted file mode 100644 index 8c23cfb28d4be..0000000000000 --- a/devnet-sdk/shell/env/kt_native_fetch_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package env - -import ( - "context" - "embed" - "encoding/json" - "fmt" - "io" - "net/url" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" - "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - //go:embed testdata/kurtosis - kurtosisTestData embed.FS -) - -func TestParseKurtosisNativeURL(t *testing.T) { - tests := []struct { - name string - urlStr string - wantEnclave string - wantArtifact string - wantFile string - wantParseError bool - }{ - { - name: "absolute file path", - urlStr: "ktnative://myenclave/path/args.yaml", - wantEnclave: "myenclave", - wantFile: "/path/args.yaml", - }, - { - name: "invalid url", - urlStr: "://invalid", - wantParseError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u, err := url.Parse(tt.urlStr) - if tt.wantParseError { - assert.Error(t, err) - return - } - require.NoError(t, err) - - enclave, argsFile := parseKurtosisNativeURL(u) - assert.Equal(t, tt.wantEnclave, enclave) - assert.Equal(t, tt.wantFile, argsFile) - }) - } -} - -func TestFetchKurtosisNativeDataFailures(t *testing.T) { - url, err := url.Parse("ktnative://enclave/file/path") - require.NoError(t, err) - - t.Run("non-existent args file", func(t *testing.T) { - osImpl := &mockOSImpl{ - err: fmt.Errorf("oh no"), - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, &defaultSpecImpl{}, &defaultKurtosisImpl{}) - require.ErrorContains(t, err, "error reading arguments file: oh no") - }) - - t.Run("malformed args file", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--malformed.txt") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, &defaultSpecImpl{}, &defaultKurtosisImpl{}) - require.ErrorContains(t, err, "error extracting enclave spec: failed to decode YAML: yaml: unmarshal errors:") - }) - - t.Run("spec extraction failure", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--simple.yaml") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - specImpl := &mockSpecImpl{ - err: fmt.Errorf("oh no"), - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, specImpl, &defaultKurtosisImpl{}) - require.ErrorContains(t, err, "error extracting enclave spec: oh no") - }) - - t.Run("kurtosis deployer failure", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--simple.yaml") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - kurtosisImpl := &mockKurtosisImpl{ - err: fmt.Errorf("oh no"), - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, &defaultSpecImpl{}, kurtosisImpl) - require.ErrorContains(t, err, "error creating deployer: oh no") - }) - - t.Run("kurtosis info extraction failure", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--simple.yaml") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - kurtosisDeployer := &mockKurtosisDeployerImpl{ - err: fmt.Errorf("oh no"), - } - - kurtosisImpl := &mockKurtosisImpl{ - value: kurtosisDeployer, - } - - _, err = fetchKurtosisNativeDataInternal(url, osImpl, &defaultSpecImpl{}, kurtosisImpl) - require.ErrorContains(t, err, "error getting environment info: oh no") - }) -} - -func TestFetchKurtosisNativeDataSuccess(t *testing.T) { - url, err := url.Parse("ktnative://enclave/file/path") - require.NoError(t, err) - - t.Run("fetching success", func(t *testing.T) { - file, err := kurtosisTestData.Open("testdata/kurtosis/args--simple.yaml") - require.NoError(t, err) - - // We'll prepare a mock spec to be returned - envSpec := &spec.EnclaveSpec{} - env := &kurtosis.KurtosisEnvironment{ - DevnetEnvironment: &descriptors.DevnetEnvironment{ - Name: "enclave", - L2: make([]*descriptors.L2Chain, 0, 1), - Features: envSpec.Features, - }, - } - - // And serialize it so that we can compare values - _, err = json.MarshalIndent(env, "", " ") - require.NoError(t, err) - - osImpl := &mockOSImpl{ - value: file, - } - - specImpl := &mockSpecImpl{ - value: envSpec, - } - - kurtosisDeployer := &mockKurtosisDeployerImpl{ - value: env, - } - - kurtosisImpl := &mockKurtosisImpl{ - value: kurtosisDeployer, - } - - devnetEnv, err := fetchKurtosisNativeDataInternal(url, osImpl, specImpl, kurtosisImpl) - require.NoError(t, err) - require.Equal(t, "enclave", devnetEnv.Name) - }) -} - -var ( - _ osOpenInterface = (*mockOSImpl)(nil) - - _ specInterface = (*mockSpecImpl)(nil) - - _ kurtosisInterface = (*mockKurtosisImpl)(nil) - - _ kurtosisDeployerInterface = (*mockKurtosisDeployerImpl)(nil) -) - -type mockOSImpl struct { - value fileInterface - err error -} - -func (o *mockOSImpl) Open(name string) (fileInterface, error) { - return o.value, o.err -} - -type mockSpecImpl struct { - value *spec.EnclaveSpec - err error -} - -func (o *mockSpecImpl) ExtractData(r io.Reader) (*spec.EnclaveSpec, error) { - return o.value, o.err -} - -type mockKurtosisImpl struct { - value kurtosisDeployerInterface - err error -} - -func (o *mockKurtosisImpl) NewKurtosisDeployer(opts ...kurtosis.KurtosisDeployerOptions) (kurtosisDeployerInterface, error) { - return o.value, o.err -} - -type mockKurtosisDeployerImpl struct { - value *kurtosis.KurtosisEnvironment - err error -} - -func (o *mockKurtosisDeployerImpl) GetEnvironmentInfo(context.Context, *spec.EnclaveSpec) (*kurtosis.KurtosisEnvironment, error) { - return o.value, o.err -} diff --git a/devnet-sdk/shell/env/testdata/kurtosis/args--malformed.txt b/devnet-sdk/shell/env/testdata/kurtosis/args--malformed.txt deleted file mode 100644 index 7b1f8d92fc758..0000000000000 --- a/devnet-sdk/shell/env/testdata/kurtosis/args--malformed.txt +++ /dev/null @@ -1 +0,0 @@ -what is this \ No newline at end of file diff --git a/devnet-sdk/shell/env/testdata/kurtosis/args--simple.yaml b/devnet-sdk/shell/env/testdata/kurtosis/args--simple.yaml deleted file mode 100644 index 28644893b554c..0000000000000 --- a/devnet-sdk/shell/env/testdata/kurtosis/args--simple.yaml +++ /dev/null @@ -1,6 +0,0 @@ -optimism_package: - chains: - op-kurtosis: - whatakey: - - el_type: op-geth - cl_type: op-node \ No newline at end of file diff --git a/devnet-sdk/system/chain.go b/devnet-sdk/system/chain.go deleted file mode 100644 index 20f82796f95a0..0000000000000 --- a/devnet-sdk/system/chain.go +++ /dev/null @@ -1,259 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - "sync" - "time" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/common" - coreTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" -) - -// this is to differentiate between op-geth and go-ethereum -type opBlock interface { - WithdrawalsRoot() *common.Hash -} - -var ( - // This will make sure that we implement the Chain interface - _ Chain = (*chain)(nil) - _ L2Chain = (*l2Chain)(nil) - - // Make sure we're using op-geth in place of go-ethereum. - // If you're wondering why this fails at compile time, - // it's most likely because you're not using a "replace" - // directive in your go.mod file. - _ opBlock = (*coreTypes.Block)(nil) -) - -// clientManager handles ethclient connections -type clientManager struct { - mu sync.RWMutex - clients map[string]*sources.EthClient - gethClients map[string]*ethclient.Client -} - -func newClientManager() *clientManager { - return &clientManager{ - clients: make(map[string]*sources.EthClient), - gethClients: make(map[string]*ethclient.Client), - } -} - -func (m *clientManager) Client(rpcURL string) (*sources.EthClient, error) { - m.mu.RLock() - if client, ok := m.clients[rpcURL]; ok { - m.mu.RUnlock() - return client, nil - } - m.mu.RUnlock() - - m.mu.Lock() - defer m.mu.Unlock() - - // Double-check after acquiring write lock - if client, ok := m.clients[rpcURL]; ok { - return client, nil - } - - ethClCfg := sources.EthClientConfig{ - MaxRequestsPerBatch: 10, - MaxConcurrentRequests: 10, - ReceiptsCacheSize: 10, - TransactionsCacheSize: 10, - HeadersCacheSize: 10, - PayloadsCacheSize: 10, - BlockRefsCacheSize: 10, - TrustRPC: false, - MustBePostMerge: true, - RPCProviderKind: sources.RPCKindStandard, - MethodResetDuration: time.Minute, - } - rpcClient, err := rpc.DialContext(context.Background(), rpcURL) - if err != nil { - return nil, err - } - ethCl, err := sources.NewEthClient(client.NewBaseRPCClient(rpcClient), log.Root(), nil, ðClCfg) - if err != nil { - return nil, err - } - m.clients[rpcURL] = ethCl - return ethCl, nil -} - -func (m *clientManager) GethClient(rpcURL string) (*ethclient.Client, error) { - m.mu.RLock() - if client, ok := m.gethClients[rpcURL]; ok { - m.mu.RUnlock() - return client, nil - } - m.mu.RUnlock() - - m.mu.Lock() - defer m.mu.Unlock() - - // Double-check after acquiring write lock - if client, ok := m.gethClients[rpcURL]; ok { - return client, nil - } - - client, err := ethclient.Dial(rpcURL) - if err != nil { - return nil, err - } - m.gethClients[rpcURL] = client - return client, nil -} - -type chain struct { - id string - wallets WalletMap - nodes []Node - chainConfig *params.ChainConfig - addresses AddressMap -} - -func (c *chain) Nodes() []Node { - return c.nodes -} - -// Wallet returns the first wallet which meets all provided constraints, or an -// error. -// Typically this will be one of the pre-funded wallets associated with -// the deployed system. -func (c *chain) Wallets() WalletMap { - return c.wallets -} - -func (c *chain) ID() types.ChainID { - if c.id == "" { - return types.ChainID(big.NewInt(0)) - } - base := 10 - if len(c.id) >= 2 && c.id[0:2] == "0x" { - c.id = c.id[2:] - base = 16 - } - id, ok := new(big.Int).SetString(c.id, base) - if !ok { - return types.ChainID(big.NewInt(0)) - } - return types.ChainID(id) -} - -func (c *chain) Config() (*params.ChainConfig, error) { - if c.chainConfig == nil { - return nil, fmt.Errorf("chain config is nil") - } - return c.chainConfig, nil -} - -func (c *chain) Addresses() AddressMap { - return c.addresses -} - -// SupportsEIP checks if the chain's first node supports the given EIP -func (c *chain) SupportsEIP(ctx context.Context, eip uint64) bool { - if len(c.nodes) == 0 { - return false - } - return c.nodes[0].SupportsEIP(ctx, eip) -} - -func checkHeader(ctx context.Context, client *sources.EthClient, check func(eth.BlockInfo) bool) bool { - info, err := client.InfoByLabel(ctx, eth.Unsafe) - if err != nil { - return false - } - return check(info) -} - -func newNodesFromDescriptor(d *descriptors.Chain) []Node { - clients := newClientManager() - nodes := make([]Node, len(d.Nodes)) - for i, node := range d.Nodes { - svc := node.Services["el"] - name := svc.Name - rpc := svc.Endpoints["rpc"] - if rpc.Scheme == "" { - rpc.Scheme = "http" - } - nodes[i] = newNode(fmt.Sprintf("%s://%s:%d", rpc.Scheme, rpc.Host, rpc.Port), name, clients) - } - return nodes -} - -func newChainFromDescriptor(d *descriptors.Chain) (*chain, error) { - // TODO: handle incorrect descriptors better. We could panic here. - - nodes := newNodesFromDescriptor(d) - c := newChain(d.ID, nil, d.Config, AddressMap(d.Addresses), nodes) // Create chain first - - wallets, err := newWalletMapFromDescriptorWalletMap(d.Wallets, c) - if err != nil { - return nil, err - } - c.wallets = wallets - - return c, nil -} - -func newChain(chainID string, wallets WalletMap, chainConfig *params.ChainConfig, addresses AddressMap, nodes []Node) *chain { - chain := &chain{ - id: chainID, - wallets: wallets, - nodes: nodes, - chainConfig: chainConfig, - addresses: addresses, - } - return chain -} - -func newL2ChainFromDescriptor(d *descriptors.L2Chain) (*l2Chain, error) { - // TODO: handle incorrect descriptors better. We could panic here. - - nodes := newNodesFromDescriptor(d.Chain) - c := newL2Chain(d.ID, nil, nil, d.Config, AddressMap(d.Addresses), nodes) // Create chain first - - l2Wallets, err := newWalletMapFromDescriptorWalletMap(d.Wallets, c) - if err != nil { - return nil, err - } - c.wallets = l2Wallets - - l1Wallets, err := newWalletMapFromDescriptorWalletMap(d.L1Wallets, c) - if err != nil { - return nil, err - } - c.l1Wallets = l1Wallets - - return c, nil -} - -func newL2Chain(chainID string, l1Wallets WalletMap, l2Wallets WalletMap, chainConfig *params.ChainConfig, l2Addresses AddressMap, nodes []Node) *l2Chain { - chain := &l2Chain{ - chain: newChain(chainID, l2Wallets, chainConfig, l2Addresses, nodes), - l1Wallets: l1Wallets, - } - return chain -} - -type l2Chain struct { - *chain - l1Wallets WalletMap -} - -func (c *l2Chain) L1Wallets() WalletMap { - return c.l1Wallets -} diff --git a/devnet-sdk/system/chain_test.go b/devnet-sdk/system/chain_test.go deleted file mode 100644 index 975f90ea6dc5a..0000000000000 --- a/devnet-sdk/system/chain_test.go +++ /dev/null @@ -1,246 +0,0 @@ -package system - -import ( - "context" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/registry/empty" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" -) - -func TestClientManager(t *testing.T) { - manager := newClientManager() - - t.Run("returns error for invalid URL", func(t *testing.T) { - _, err := manager.Client("invalid://url") - assert.Error(t, err) - }) - - t.Run("caches client for same URL", func(t *testing.T) { - // Use a hostname that's guaranteed to fail DNS resolution - url := "http://this.domain.definitely.does.not.exist:8545" - - // First call should create new client - client1, err1 := manager.Client(url) - // Second call should return cached client - client2, err2 := manager.Client(url) - - // Both calls should succeed in creating a client - assert.NoError(t, err1) - assert.NoError(t, err2) - assert.NotNil(t, client1) - assert.NotNil(t, client2) - - // But the client should fail when used - ctx := context.Background() - _, err := client1.ChainID(ctx) - assert.Error(t, err) - - // And both clients should be the same instance - assert.Same(t, client1, client2) - }) -} - -func TestChainFromDescriptor(t *testing.T) { - descriptor := &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": &descriptors.Service{ - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }, - }, - Wallets: descriptors.WalletMap{ - "user1": &descriptors.Wallet{ - PrivateKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - Address: common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - Addresses: descriptors.AddressMap{ - "user1": common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - } - - chain, err := newChainFromDescriptor(descriptor) - assert.Nil(t, err) - assert.NotNil(t, chain) - assert.Equal(t, "http://localhost:8545", chain.Nodes()[0].RPCURL()) - - // Compare the underlying big.Int values - chainID := chain.ID() - expectedID := big.NewInt(1) - assert.Equal(t, 0, expectedID.Cmp(chainID)) -} - -func TestL2ChainFromDescriptor(t *testing.T) { - descriptor := &descriptors.L2Chain{ - Chain: &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{ - { - Services: descriptors.ServiceMap{ - "el": &descriptors.Service{ - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }, - }, - Wallets: descriptors.WalletMap{ - "user1": &descriptors.Wallet{ - PrivateKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - Address: common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - Addresses: descriptors.AddressMap{ - "user2": common.HexToAddress("0x1234567890123456789012345678901234567891"), - }, - }, - L1Wallets: descriptors.WalletMap{ - "user1": &descriptors.Wallet{ - PrivateKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - Address: common.HexToAddress("0x1234567890123456789012345678901234567890"), - }, - }, - } - - chain, err := newL2ChainFromDescriptor(descriptor) - assert.Nil(t, err) - assert.NotNil(t, chain) - assert.Equal(t, "http://localhost:8545", chain.Nodes()[0].RPCURL()) - - // Compare the underlying big.Int values - chainID := chain.ID() - expectedID := big.NewInt(1) - assert.Equal(t, 0, expectedID.Cmp(chainID)) -} - -func TestChainWallet(t *testing.T) { - testAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") - - wallet, err := NewWallet("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", testAddr, nil) - assert.Nil(t, err) - - l1Chain := newChain("1", WalletMap{"user1": wallet}, nil, map[string]common.Address{}, []Node{}) - - t.Run("finds wallet meeting constraints", func(t *testing.T) { - constraint := &addressConstraint{addr: testAddr} - wallets := l1Chain.Wallets() - - for _, w := range wallets { - if constraint.CheckWallet(w) { - assert.NotNil(t, w) - assert.Equal(t, testAddr, w.Address()) - return - } - } - t.Fatalf("wallet not found") - }) - - t.Run("returns error when no wallet meets constraints", func(t *testing.T) { - wrongAddr := common.HexToAddress("0x0987654321098765432109876543210987654321") - constraint := &addressConstraint{addr: wrongAddr} - wallets := l1Chain.Wallets() - - for _, w := range wallets { - if constraint.CheckWallet(w) { - t.Fatalf("wallet found") - } - } - }) -} - -// addressConstraint implements constraints.WalletConstraint for testing -type addressConstraint struct { - addr common.Address -} - -func (c *addressConstraint) CheckWallet(w Wallet) bool { - return w.Address() == c.addr -} - -func TestChainID(t *testing.T) { - tests := []struct { - name string - idString string - want *big.Int - }{ - { - name: "valid chain ID", - idString: "1", - want: big.NewInt(1), - }, - { - name: "empty chain ID", - idString: "", - want: big.NewInt(0), - }, - { - name: "invalid chain ID", - idString: "not a number", - want: big.NewInt(0), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - chain := newChain(tt.idString, WalletMap{}, nil, AddressMap{}, []Node{}) - got := chain.ID() - // Compare the underlying big.Int values - assert.Equal(t, 0, tt.want.Cmp(got)) - }) - } -} - -func TestSupportsEIP(t *testing.T) { - ctx := context.Background() - chain := newChain("1", WalletMap{}, nil, AddressMap{}, []Node{}) - - // Since we can't reliably test against a live node, we're just testing the error case - t.Run("returns false for connection error", func(t *testing.T) { - assert.False(t, chain.SupportsEIP(ctx, 1559)) - assert.False(t, chain.SupportsEIP(ctx, 4844)) - }) -} - -// mockContractsRegistry extends empty.EmptyRegistry to provide mock contract instances -type mockContractsRegistry struct { - empty.EmptyRegistry -} - -func TestContractsRegistry(t *testing.T) { - node := &mockNode{} - // Create a mock for testing - mockRegistry := &mockContractsRegistry{} - - // Set up the mock to return the registry when ContractsRegistry() is called - node.On("ContractsRegistry").Return(mockRegistry) - - chain := newChain("1", WalletMap{}, nil, AddressMap{}, []Node{node}) - - t.Run("returns empty registry on error", func(t *testing.T) { - registry := chain.Nodes()[0].ContractsRegistry() - assert.NotNil(t, registry) - }) - - t.Run("caches registry", func(t *testing.T) { - registry1 := chain.Nodes()[0].ContractsRegistry() - registry2 := chain.Nodes()[0].ContractsRegistry() - assert.Same(t, registry1, registry2) - }) -} diff --git a/devnet-sdk/system/interfaces.go b/devnet-sdk/system/interfaces.go deleted file mode 100644 index eecb4952fef1a..0000000000000 --- a/devnet-sdk/system/interfaces.go +++ /dev/null @@ -1,150 +0,0 @@ -package system - -import ( - "context" - "crypto/ecdsa" - "math/big" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - supervisorTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - coreTypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/params" -) - -type System interface { - Identifier() string - L1() Chain - L2s() []L2Chain -} - -// Chain represents an Ethereum chain (L1 or L2) -type Chain interface { - ID() types.ChainID - Nodes() []Node // The node at index 0 will always be the sequencer node - Config() (*params.ChainConfig, error) - - // The wallets and addresses below are for use on the chain that the instance represents. - // If the instance also implements L2Chain, then the wallets and addresses below are still for the L2. - Wallets() WalletMap - Addresses() AddressMap -} - -type L2Chain interface { - Chain - - // The wallets below are for use on the L1 chain that this L2Chain instance settles to. - L1Wallets() WalletMap -} - -type Node interface { - Name() string - GasPrice(ctx context.Context) (*big.Int, error) - GasLimit(ctx context.Context, tx TransactionData) (uint64, error) - PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) - BlockByNumber(ctx context.Context, number *big.Int) (eth.BlockInfo, error) - ContractsRegistry() interfaces.ContractsRegistry - SupportsEIP(ctx context.Context, eip uint64) bool - RPCURL() string - Client() (*sources.EthClient, error) - GethClient() (*ethclient.Client, error) -} - -type WalletMap map[string]Wallet -type AddressMap descriptors.AddressMap - -// Wallet represents a chain wallet. -// In particular it can process transactions. -type Wallet interface { - PrivateKey() types.Key - Address() types.Address - SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] - InitiateMessage(chainID types.ChainID, target common.Address, message []byte) types.WriteInvocation[any] - ExecuteMessage(identifier bindings.Identifier, sentMessage []byte) types.WriteInvocation[any] - Balance() types.Balance - Nonce() uint64 - - TransactionProcessor -} - -// WalletV2 is a temporary interface for integrating txplan and txintent -type WalletV2 interface { - PrivateKey() *ecdsa.PrivateKey - Address() common.Address - Client() *sources.EthClient - GethClient() *ethclient.Client - Ctx() context.Context -} - -// TransactionProcessor is a helper interface for signing and sending transactions. -type TransactionProcessor interface { - Sign(tx Transaction) (Transaction, error) - Send(ctx context.Context, tx Transaction) error -} - -// Transaction interfaces: - -// TransactionData is the input for a transaction creation. -type TransactionData interface { - From() common.Address - To() *common.Address - Value() *big.Int - Data() []byte - AccessList() coreTypes.AccessList -} - -// Transaction is the instantiated transaction object. -type Transaction interface { - Type() uint8 - Hash() common.Hash - TransactionData -} - -type Receipt interface { - BlockNumber() *big.Int - Logs() []*coreTypes.Log - TxHash() common.Hash -} - -// RawTransaction is an optional interface that can be implemented by a Transaction -// to provide access to the raw transaction data. -// It is currently necessary to perform processing operations (signing, sending) -// on the transaction. We would need to do better here. -type RawTransaction interface { - Raw() *coreTypes.Transaction -} - -// Specialized interop interfaces: - -// InteropSystem extends System with interoperability features -type InteropSystem interface { - System - InteropSet() InteropSet - Supervisor(context.Context) (Supervisor, error) -} - -// InteropSet provides access to L2 chains in an interop environment -type InteropSet interface { - L2s() []L2Chain -} - -// Supervisor provides access to the query interface of the supervisor -type Supervisor interface { - LocalUnsafe(context.Context, eth.ChainID) (eth.BlockID, error) - CrossSafe(context.Context, eth.ChainID) (supervisorTypes.DerivedIDPair, error) - Finalized(context.Context, eth.ChainID) (eth.BlockID, error) - FinalizedL1(context.Context) (eth.BlockRef, error) - CrossDerivedToSource(context.Context, eth.ChainID, eth.BlockID) (eth.BlockRef, error) - UpdateLocalUnsafe(context.Context, eth.ChainID, eth.BlockRef) error - UpdateLocalSafe(context.Context, eth.ChainID, eth.L1BlockRef, eth.BlockRef) error - SuperRootAtTimestamp(context.Context, hexutil.Uint64) (eth.SuperRootResponse, error) - AllSafeDerivedAt(context.Context, eth.BlockID) (derived map[eth.ChainID]eth.BlockID, err error) - SyncStatus(context.Context) (eth.SupervisorSyncStatus, error) -} diff --git a/devnet-sdk/system/node.go b/devnet-sdk/system/node.go deleted file mode 100644 index 31149d2e76c50..0000000000000 --- a/devnet-sdk/system/node.go +++ /dev/null @@ -1,139 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - "sync" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/op-service/bigs" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" -) - -var ( - // This will make sure that we implement the Node interface - _ Node = (*node)(nil) -) - -type node struct { - rpcUrl string - name string - clients *clientManager - mu sync.Mutex - registry interfaces.ContractsRegistry -} - -func newNode(rpcUrl string, name string, clients *clientManager) *node { - return &node{rpcUrl: rpcUrl, name: name, clients: clients} -} - -func (n *node) GasPrice(ctx context.Context) (*big.Int, error) { - client, err := n.clients.Client(n.rpcUrl) - if err != nil { - return nil, fmt.Errorf("failed to get client: %w", err) - } - return client.SuggestGasPrice(ctx) -} - -func (n *node) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) { - client, err := n.clients.Client(n.rpcUrl) - if err != nil { - return 0, fmt.Errorf("failed to get client: %w", err) - } - - msg := ethereum.CallMsg{ - From: tx.From(), - To: tx.To(), - Value: tx.Value(), - Data: tx.Data(), - AccessList: tx.AccessList(), - } - estimated, err := client.EstimateGas(ctx, msg) - if err != nil { - return 0, fmt.Errorf("failed to estimate gas: %w", err) - } - - return estimated, nil -} - -func (n *node) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) { - client, err := n.clients.Client(n.rpcUrl) - if err != nil { - return 0, fmt.Errorf("failed to get client: %w", err) - } - return client.PendingNonceAt(ctx, address) -} - -func (n *node) BlockByNumber(ctx context.Context, number *big.Int) (eth.BlockInfo, error) { - client, err := n.clients.Client(n.rpcUrl) - if err != nil { - return nil, fmt.Errorf("failed to get client: %w", err) - } - var block eth.BlockInfo - if number != nil { - block, err = client.InfoByNumber(ctx, bigs.Uint64Strict(number)) - } else { - block, err = client.InfoByLabel(ctx, eth.Unsafe) - } - if err != nil { - return nil, err - } - return block, nil -} - -func (n *node) Client() (*sources.EthClient, error) { - return n.clients.Client(n.rpcUrl) -} - -func (n *node) GethClient() (*ethclient.Client, error) { - return n.clients.GethClient(n.rpcUrl) -} - -func (n *node) ContractsRegistry() interfaces.ContractsRegistry { - n.mu.Lock() - defer n.mu.Unlock() - - if n.registry != nil { - return n.registry - } - client, err := n.clients.GethClient(n.rpcUrl) - if err != nil { - return contracts.NewEmptyRegistry() - } - - n.registry = contracts.NewClientRegistry(client) - return n.registry -} - -func (n *node) RPCURL() string { - return n.rpcUrl -} - -func (n *node) SupportsEIP(ctx context.Context, eip uint64) bool { - client, err := n.Client() - if err != nil { - return false - } - - switch eip { - case 1559: - return checkHeader(ctx, client, func(h eth.BlockInfo) bool { - return h.BaseFee() != nil - }) - case 4844: - return checkHeader(ctx, client, func(h eth.BlockInfo) bool { - return h.ExcessBlobGas() != nil - }) - } - return false -} - -func (n *node) Name() string { - return n.name -} diff --git a/devnet-sdk/system/periphery/go-ethereum/fees.go b/devnet-sdk/system/periphery/go-ethereum/fees.go deleted file mode 100644 index 0508d195e7384..0000000000000 --- a/devnet-sdk/system/periphery/go-ethereum/fees.go +++ /dev/null @@ -1,134 +0,0 @@ -package goethereum - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" -) - -var ( - // Ensure that the feeEstimator implements the FeeEstimator interface - _ FeeEstimator = (*EIP1559FeeEstimator)(nil) - - // Ensure that the EIP1159FeeEthClient implements the EIP1159FeeEthClient interface - _ EIP1159FeeEthClient = (*ethclient.Client)(nil) -) - -// FeeEstimator is a generic fee estimation interface (not specific to EIP-1559) -type FeeEstimator interface { - EstimateFees(ctx context.Context, opts *bind.TransactOpts) (*bind.TransactOpts, error) -} - -// EIP1559FeeEstimator is a fee estimator that uses EIP-1559 fee estimation -type EIP1559FeeEstimator struct { - // Access to the Ethereum client is needed to get the fee information from the chain - client EIP1159FeeEthClient - - options eip1559FeeEstimatorOptions -} - -type eip1559FeeEstimatorOptions struct { - // The base multiplier is used to increase the maxFeePerGas (GasFeeCap) by a factor - baseMultiplier float64 - - // The tip multiplier is used to increase the maxPriorityFeePerGas (GasTipCap) by a factor - tipMultiplier float64 -} - -type EIP1559FeeEstimatorOption interface { - apply(*eip1559FeeEstimatorOptions) -} - -type eip1559FeeEstimatorOptionBaseMultiplier float64 - -func (o eip1559FeeEstimatorOptionBaseMultiplier) apply(opts *eip1559FeeEstimatorOptions) { - opts.baseMultiplier = float64(o) -} - -func WithEIP1559BaseMultiplier(multiplier float64) EIP1559FeeEstimatorOption { - return eip1559FeeEstimatorOptionBaseMultiplier(multiplier) -} - -type eip1559FeeEstimatorOptionTipMultiplier float64 - -func (o eip1559FeeEstimatorOptionTipMultiplier) apply(opts *eip1559FeeEstimatorOptions) { - opts.tipMultiplier = float64(o) -} - -func WithEIP1559TipMultiplier(multiplier float64) EIP1559FeeEstimatorOption { - return eip1559FeeEstimatorOptionTipMultiplier(multiplier) -} - -func NewEIP1559FeeEstimator(client EIP1159FeeEthClient, opts ...EIP1559FeeEstimatorOption) *EIP1559FeeEstimator { - options := eip1559FeeEstimatorOptions{ - baseMultiplier: 1.0, - tipMultiplier: 1.0, - } - - for _, o := range opts { - o.apply(&options) - } - - return &EIP1559FeeEstimator{ - client: client, - options: options, - } -} - -func (f *EIP1559FeeEstimator) EstimateFees(ctx context.Context, opts *bind.TransactOpts) (*bind.TransactOpts, error) { - newOpts := *opts - - // Add a gas tip cap if needed - if newOpts.GasTipCap == nil { - tipCap, err := f.client.SuggestGasTipCap(ctx) - - if err != nil { - return nil, err - } - - // GasTipCap represents the maxPriorityFeePerGas - newOpts.GasTipCap = multiplyBigInt(tipCap, f.options.tipMultiplier) - } - - // Add a gas fee cap if needed - if newOpts.GasFeeCap == nil { - block, err := f.client.BlockByNumber(ctx, nil) - if err != nil { - return nil, err - } - - baseFee := block.BaseFee() - if baseFee != nil { - // The adjusted base fee takes the multiplier into account - adjustedBaseFee := multiplyBigInt(baseFee, f.options.baseMultiplier) - - // The total fee (maxFeePerGas) is the sum of the base fee and the tip - newOpts.GasFeeCap = big.NewInt(0).Add(adjustedBaseFee, newOpts.GasTipCap) - } - } - - return &newOpts, nil -} - -// EIP1159FeeEthClient is a subset of the ethclient.Client interface required for fee estimation -type EIP1159FeeEthClient interface { - BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) - SuggestGasTipCap(ctx context.Context) (*big.Int, error) -} - -// multiplyBigInt is a little helper for a messy big.Int x float64 multiplication -// -// It rounds the result away from zero since that's the lower risk behavior for fee estimation -func multiplyBigInt(b *big.Int, m float64) *big.Int { - bFloat := big.NewFloat(0).SetInt(b) - mFloat := big.NewFloat(m) - ceiled, accuracy := big.NewFloat(0).Mul(bFloat, mFloat).Int(nil) - if accuracy == big.Below { - ceiled = ceiled.Add(ceiled, big.NewInt(1)) - } - - return ceiled -} diff --git a/devnet-sdk/system/periphery/go-ethereum/fees_test.go b/devnet-sdk/system/periphery/go-ethereum/fees_test.go deleted file mode 100644 index 302d6f556acc6..0000000000000 --- a/devnet-sdk/system/periphery/go-ethereum/fees_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package goethereum - -import ( - "context" - "fmt" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestMultiplyBigInt(t *testing.T) { - type TestCase struct { - value *big.Int - multiplier float64 - expected *big.Int - } - - testCases := []TestCase{ - { - value: big.NewInt(10), - multiplier: 1.0, - expected: big.NewInt(10), - }, - { - value: big.NewInt(7), - multiplier: 0.0, - expected: big.NewInt(0), - }, - { - value: big.NewInt(10), - multiplier: 1.01, - expected: big.NewInt(11), - }, - { - value: big.NewInt(10), - multiplier: 1.11, - expected: big.NewInt(12), - }, - { - value: big.NewInt(5), - multiplier: 1.2, - expected: big.NewInt(6), - }, - } - - for _, testCase := range testCases { - t.Run(fmt.Sprintf("should return %d for %d multiplied by %f", testCase.expected.Int64(), testCase.value.Int64(), testCase.multiplier), func(t *testing.T) { - result := multiplyBigInt(testCase.value, testCase.multiplier) - require.Equal(t, testCase.expected, result) - }) - } -} - -func TestEstimateEIP1559Fees(t *testing.T) { - t.Run("if GasFeeCap and GasTipCap are not nil", func(t *testing.T) { - opts := &bind.TransactOpts{ - GasFeeCap: big.NewInt(1), - GasTipCap: big.NewInt(2), - } - - t.Run("should not modify the options", func(t *testing.T) { - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{}) - newOpts, err := feeEstimator.EstimateFees(context.Background(), opts) - require.NoError(t, err) - - require.Equal(t, opts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, opts, newOpts) - }) - }) - - t.Run("if the GasTipCap is nil", func(t *testing.T) { - defaultOpts := &bind.TransactOpts{ - GasFeeCap: big.NewInt(1), - From: common.Address{}, - Nonce: big.NewInt(64), - } - - t.Run("should return an error if the client returns an error", func(t *testing.T) { - tipCapErr := fmt.Errorf("tip cap error") - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - tipCapErr: tipCapErr, - }) - _, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.Equal(t, tipCapErr, err) - }) - - t.Run("with default tip multiplier", func(t *testing.T) { - t.Run("should set the GasTipCap to the client's suggested tip cap", func(t *testing.T) { - tipCapValue := big.NewInt(5) - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - tipCapValue: tipCapValue, - }) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected tip cap added - expectedOpts := *defaultOpts - expectedOpts.GasTipCap = tipCapValue - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - }) - - t.Run("with custom tip multiplier", func(t *testing.T) { - t.Run("should set the GasTipCap to the client's suggested tip cap multiplied by the tip multiplier", func(t *testing.T) { - tipCapValue := big.NewInt(5) - tipMultiplier := 10.0 - // The expected tip is a product of the tip cap and the tip multiplier - expectedTip := big.NewInt(50) - - // We create a fee estimator with a custom tip multiplier - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - tipCapValue: tipCapValue, - }, WithEIP1559TipMultiplier(tipMultiplier)) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected tip cap added - expectedOpts := *defaultOpts - expectedOpts.GasTipCap = expectedTip - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - }) - }) - - t.Run("if the GasFeeCap is nil", func(t *testing.T) { - defaultOpts := &bind.TransactOpts{ - GasTipCap: big.NewInt(1), - From: common.Address{}, - Nonce: big.NewInt(64), - } - - t.Run("should return an error if the client returns an error", func(t *testing.T) { - blockErr := fmt.Errorf("tip cap error") - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - blockErr: blockErr, - }) - _, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.Equal(t, blockErr, err) - }) - - t.Run("should set the GasFeeCap to the sum of block base fee and tip", func(t *testing.T) { - baseFeeValue := big.NewInt(5) - blockValue := types.NewBlock(&types.Header{ - BaseFee: baseFeeValue, - Time: 0, - }, nil, nil, nil, &mockBlockType{}) - - // We expect the total gas cap to be the base fee plus the tip cap - expectedGas := big.NewInt(0).Add(baseFeeValue, defaultOpts.GasTipCap) - - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - blockValue: blockValue, - }) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected fee cap added - expectedOpts := *defaultOpts - expectedOpts.GasFeeCap = expectedGas - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - - t.Run("should set the GasFeeCap to nil if the base fee is nil", func(t *testing.T) { - blockValue := types.NewBlock(&types.Header{ - BaseFee: nil, - Time: 0, - }, nil, nil, nil, &mockBlockType{}) - - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - blockValue: blockValue, - }) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected fee cap added - expectedOpts := *defaultOpts - expectedOpts.GasFeeCap = nil - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - - t.Run("with custom base multiplier", func(t *testing.T) { - t.Run("should set the GasFeeCap to the block base fee multiplied by the base multiplier", func(t *testing.T) { - baseMultiplier := 1.2 - baseFeeValue := big.NewInt(9) - blockValue := types.NewBlock(&types.Header{ - BaseFee: baseFeeValue, - Time: 0, - }, nil, nil, nil, &mockBlockType{}) - - // We expect the total gas cap to be the base fee (9) multiplied by 1.2 (= 10.8, rounded up to 11) plus the tip cap (1) - expectedGas := big.NewInt(0).Add(big.NewInt(11), defaultOpts.GasTipCap) - - // We create a fee estimator with a custom tip multiplier - feeEstimator := NewEIP1559FeeEstimator(&mockFeeEthClientImpl{ - blockValue: blockValue, - }, WithEIP1559BaseMultiplier(baseMultiplier)) - - newOpts, err := feeEstimator.EstimateFees(context.Background(), defaultOpts) - require.NoError(t, err) - - // We create a new opts with the expected tip cap added - expectedOpts := *defaultOpts - expectedOpts.GasFeeCap = expectedGas - - // We check that the tip has been added - require.Equal(t, &expectedOpts, newOpts) - - // We make sure that we get a copy of the object to prevent mutating the original - assert.NotSame(t, defaultOpts, newOpts) - }) - }) - }) -} - -var ( - _ EIP1159FeeEthClient = (*mockFeeEthClientImpl)(nil) - - _ types.BlockType = (*mockBlockType)(nil) -) - -type mockFeeEthClientImpl struct { - blockValue *types.Block - blockErr error - - tipCapValue *big.Int - tipCapErr error -} - -func (m *mockFeeEthClientImpl) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - return m.blockValue, m.blockErr -} - -func (m *mockFeeEthClientImpl) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return m.tipCapValue, m.tipCapErr -} - -type mockBlockType struct{} - -func (m *mockBlockType) HasOptimismWithdrawalsRoot(blkTime uint64) bool { - return false -} - -func (m *mockBlockType) IsIsthmus(blkTime uint64) bool { - return false -} diff --git a/devnet-sdk/system/system.go b/devnet-sdk/system/system.go deleted file mode 100644 index d282e9ee01d4d..0000000000000 --- a/devnet-sdk/system/system.go +++ /dev/null @@ -1,110 +0,0 @@ -package system - -import ( - "context" - "fmt" - "slices" - "sync" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" - "github.com/ethereum-optimism/optimism/op-service/dial" -) - -type system struct { - identifier string - l1 Chain - l2s []L2Chain -} - -// system implements System -var _ System = (*system)(nil) - -func NewSystemFromURL(url string) (System, error) { - devnetEnv, err := env.LoadDevnetFromURL(url) - if err != nil { - return nil, fmt.Errorf("failed to load devnet from URL: %w", err) - } - - sys, err := systemFromDevnet(devnetEnv.Env) - if err != nil { - return nil, fmt.Errorf("failed to create system from devnet: %w", err) - } - return sys, nil -} - -func (s *system) L1() Chain { - return s.l1 -} - -func (s *system) L2s() []L2Chain { - return s.l2s -} - -func (s *system) Identifier() string { - return s.identifier -} - -func systemFromDevnet(dn *descriptors.DevnetEnvironment) (System, error) { - l1, err := newChainFromDescriptor(dn.L1) - if err != nil { - return nil, fmt.Errorf("failed to add L1 chain: %w", err) - } - - l2s := make([]L2Chain, len(dn.L2)) - for i, l2 := range dn.L2 { - l2s[i], err = newL2ChainFromDescriptor(l2) - if err != nil { - return nil, fmt.Errorf("failed to add L2 chain: %w", err) - } - } - - sys := &system{ - identifier: dn.Name, - l1: l1, - l2s: l2s, - } - - if slices.Contains(dn.Features, "interop") { - // TODO(14849): this will break as soon as we have a dependency set that - // doesn't include all L2s. - supervisorRPC := dn.L2[0].Services["supervisor"][0].Endpoints["rpc"] - return &interopSystem{ - system: sys, - supervisorRPC: fmt.Sprintf("http://%s:%d", supervisorRPC.Host, supervisorRPC.Port), - }, nil - } - - return sys, nil -} - -type interopSystem struct { - *system - - supervisorRPC string - supervisor Supervisor - mu sync.Mutex -} - -// interopSystem implements InteropSystem -var _ InteropSystem = (*interopSystem)(nil) - -func (i *interopSystem) InteropSet() InteropSet { - return i.system // TODO: the interop set might not contain all L2s -} - -func (i *interopSystem) Supervisor(ctx context.Context) (Supervisor, error) { - i.mu.Lock() - defer i.mu.Unlock() - - if i.supervisor != nil { - return i.supervisor, nil - } - - supervisor, err := dial.DialSupervisorClientWithTimeout(ctx, nil, i.supervisorRPC) - if err != nil { - return nil, fmt.Errorf("failed to dial supervisor RPC: %w", err) - } - i.supervisor = supervisor - return supervisor, nil -} diff --git a/devnet-sdk/system/system_test.go b/devnet-sdk/system/system_test.go deleted file mode 100644 index f97bbdec532b7..0000000000000 --- a/devnet-sdk/system/system_test.go +++ /dev/null @@ -1,273 +0,0 @@ -package system - -import ( - "encoding/json" - "os" - "path/filepath" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewSystemFromEnv(t *testing.T) { - // Create a temporary devnet file - tempDir := t.TempDir() - devnetFile := filepath.Join(tempDir, "devnet.json") - - devnet := &descriptors.DevnetEnvironment{ - L1: &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{{ - Services: map[string]*descriptors.Service{ - "el": { - Name: "geth", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }}, - Wallets: descriptors.WalletMap{ - "default": &descriptors.Wallet{ - Address: common.HexToAddress("0x123"), - PrivateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - }, - }, - Addresses: descriptors.AddressMap{ - "defaultl1": common.HexToAddress("0x123"), - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - ID: "2", - Nodes: []descriptors.Node{{ - Services: map[string]*descriptors.Service{ - "el": { - Name: "geth", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8546, - }, - }, - }, - }, - }}, - Wallets: descriptors.WalletMap{ - "default": &descriptors.Wallet{ - Address: common.HexToAddress("0x123"), - PrivateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - }, - }, - Addresses: descriptors.AddressMap{ - "defaultl2": common.HexToAddress("0x456"), - }, - }, - L1Wallets: descriptors.WalletMap{ - "default": &descriptors.Wallet{ - Address: common.HexToAddress("0x123"), - PrivateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - }, - }, - }, - }, - Features: []string{}, - } - - data, err := json.Marshal(devnet) - require.NoError(t, err) - require.NoError(t, os.WriteFile(devnetFile, data, 0644)) - - sys, err := NewSystemFromURL(devnetFile) - assert.NoError(t, err) - assert.NotNil(t, sys) -} - -func TestSystemFromDevnet(t *testing.T) { - testNode := descriptors.Node{ - Services: map[string]*descriptors.Service{ - "el": { - Name: "geth", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - } - - testWallet := &descriptors.Wallet{ - Address: common.HexToAddress("0x123"), - PrivateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - } - - tests := []struct { - name string - devnet *descriptors.DevnetEnvironment - wantErr bool - isInterop bool - }{ - { - name: "basic system", - devnet: &descriptors.DevnetEnvironment{ - L1: &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{testNode}, - Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - Addresses: descriptors.AddressMap{ - "defaultl1": common.HexToAddress("0x123"), - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - ID: "2", - Nodes: []descriptors.Node{testNode}, - Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - }, - L1Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - }, - }, - }, - wantErr: false, - isInterop: false, - }, - { - name: "interop system", - devnet: &descriptors.DevnetEnvironment{ - L1: &descriptors.Chain{ - ID: "1", - Nodes: []descriptors.Node{testNode}, - Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - Addresses: descriptors.AddressMap{ - "defaultl1": common.HexToAddress("0x123"), - }, - }, - L2: []*descriptors.L2Chain{ - { - Chain: &descriptors.Chain{ - ID: "2", - Nodes: []descriptors.Node{testNode}, - Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - Services: descriptors.RedundantServiceMap{ - "supervisor": []*descriptors.Service{ - &descriptors.Service{ - Name: "supervisor", - Endpoints: descriptors.EndpointMap{ - "rpc": &descriptors.PortInfo{ - Host: "localhost", - Port: 8545, - }, - }, - }, - }, - }, - }, - L1Wallets: descriptors.WalletMap{ - "default": testWallet, - }, - }}, - Features: []string{"interop"}, - }, - wantErr: false, - isInterop: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sys, err := systemFromDevnet(tt.devnet) - if tt.wantErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - assert.NotNil(t, sys) - - _, isInterop := sys.(InteropSystem) - assert.Equal(t, tt.isInterop, isInterop) - }) - } -} - -func TestWallet(t *testing.T) { - chain := newChain("1", WalletMap{}, nil, AddressMap{}, []Node{}) - tests := []struct { - name string - privateKey string - address types.Address - wantAddr types.Address - wantPrivKey types.Key - }{ - { - name: "valid wallet", - privateKey: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - address: common.HexToAddress("0x123"), - wantAddr: common.HexToAddress("0x123"), - }, - { - name: "empty wallet", - privateKey: "", - address: common.HexToAddress("0x123"), - wantAddr: common.HexToAddress("0x123"), - }, - { - name: "only address", - privateKey: "", - address: common.HexToAddress("0x456"), - wantAddr: common.HexToAddress("0x456"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w, err := NewWallet(tt.privateKey, tt.address, chain) - assert.Nil(t, err) - - assert.Equal(t, tt.wantAddr, w.Address()) - }) - } -} - -func TestChainUser(t *testing.T) { - chain := newChain("1", WalletMap{}, nil, AddressMap{}, []Node{}) - - testWallet, err := NewWallet("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", common.HexToAddress("0x123"), chain) - assert.Nil(t, err) - - chain.wallets = WalletMap{ - "l2Faucet": testWallet, - } - - wallets := chain.Wallets() - require.NoError(t, err) - - for _, w := range wallets { - if w.Address() == testWallet.Address() { - assert.Equal(t, testWallet.Address(), w.Address()) - assert.Equal(t, testWallet.PrivateKey(), w.PrivateKey()) - return - } - } - assert.Fail(t, "wallet not found") -} diff --git a/devnet-sdk/system/tx.go b/devnet-sdk/system/tx.go deleted file mode 100644 index 41a661bd9539a..0000000000000 --- a/devnet-sdk/system/tx.go +++ /dev/null @@ -1,227 +0,0 @@ -package system - -import ( - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" -) - -// TxOpts is a struct that holds all transaction options -type TxOpts struct { - from common.Address - to *common.Address - value *big.Int - data []byte - gasLimit uint64 // Optional: if 0, will be estimated - accessList types.AccessList - blobHashes []common.Hash - blobs []kzg4844.Blob - commitments []kzg4844.Commitment - proofs []kzg4844.Proof -} - -var _ TransactionData = (*TxOpts)(nil) - -func (opts *TxOpts) From() common.Address { - return opts.from -} - -func (opts *TxOpts) To() *common.Address { - return opts.to -} - -func (opts *TxOpts) Value() *big.Int { - return opts.value -} - -func (opts *TxOpts) Data() []byte { - return opts.data -} - -func (opts *TxOpts) AccessList() types.AccessList { - return opts.accessList -} - -// Validate checks that all required fields are set and consistent -func (opts *TxOpts) Validate() error { - // Check mandatory fields - if opts.from == (common.Address{}) { - return fmt.Errorf("from address is required") - } - if opts.to == nil { - return fmt.Errorf("to address is required") - } - if opts.value == nil || opts.value.Sign() < 0 { - return fmt.Errorf("value must be non-negative") - } - - // Check blob-related fields consistency - hasBlobs := len(opts.blobs) > 0 - hasCommitments := len(opts.commitments) > 0 - hasProofs := len(opts.proofs) > 0 - hasBlobHashes := len(opts.blobHashes) > 0 - - // If any blob-related field is set, all must be set - if hasBlobs || hasCommitments || hasProofs || hasBlobHashes { - if !hasBlobs { - return fmt.Errorf("blobs are required when other blob fields are set") - } - if !hasCommitments { - return fmt.Errorf("commitments are required when other blob fields are set") - } - if !hasProofs { - return fmt.Errorf("proofs are required when other blob fields are set") - } - if !hasBlobHashes { - return fmt.Errorf("blob hashes are required when other blob fields are set") - } - - // Check that all blob-related fields have the same length - blobCount := len(opts.blobs) - if len(opts.commitments) != blobCount { - return fmt.Errorf("number of commitments (%d) does not match number of blobs (%d)", len(opts.commitments), blobCount) - } - if len(opts.proofs) != blobCount { - return fmt.Errorf("number of proofs (%d) does not match number of blobs (%d)", len(opts.proofs), blobCount) - } - if len(opts.blobHashes) != blobCount { - return fmt.Errorf("number of blob hashes (%d) does not match number of blobs (%d)", len(opts.blobHashes), blobCount) - } - } - - return nil -} - -// TxOption is a function that configures TxOpts -type TxOption func(*TxOpts) - -// WithFrom sets the sender address -func WithFrom(from common.Address) TxOption { - return func(opts *TxOpts) { - opts.from = from - } -} - -// WithTo sets the recipient address -func WithTo(to common.Address) TxOption { - return func(opts *TxOpts) { - opts.to = &to - } -} - -// WithValue sets the transaction value -func WithValue(value *big.Int) TxOption { - return func(opts *TxOpts) { - opts.value = value - } -} - -// WithData sets the transaction data -func WithData(data []byte) TxOption { - return func(opts *TxOpts) { - opts.data = data - } -} - -// WithGasLimit sets an explicit gas limit -func WithGasLimit(gasLimit uint64) TxOption { - return func(opts *TxOpts) { - opts.gasLimit = gasLimit - } -} - -// WithAccessList sets the access list for EIP-2930 transactions -func WithAccessList(accessList types.AccessList) TxOption { - return func(opts *TxOpts) { - opts.accessList = accessList - } -} - -// WithBlobs sets the blob transaction fields -func WithBlobs(blobs []kzg4844.Blob) TxOption { - return func(opts *TxOpts) { - opts.blobs = blobs - } -} - -// WithBlobCommitments sets the blob commitments -func WithBlobCommitments(commitments []kzg4844.Commitment) TxOption { - return func(opts *TxOpts) { - opts.commitments = commitments - } -} - -// WithBlobProofs sets the blob proofs -func WithBlobProofs(proofs []kzg4844.Proof) TxOption { - return func(opts *TxOpts) { - opts.proofs = proofs - } -} - -// WithBlobHashes sets the blob hashes -func WithBlobHashes(hashes []common.Hash) TxOption { - return func(opts *TxOpts) { - opts.blobHashes = hashes - } -} - -// EthTx is the default implementation of Transaction that wraps types.Transaction -type EthTx struct { - tx *types.Transaction - from common.Address - txType uint8 -} - -func (t *EthTx) Hash() common.Hash { - return t.tx.Hash() -} - -func (t *EthTx) From() common.Address { - return t.from -} - -func (t *EthTx) To() *common.Address { - return t.tx.To() -} - -func (t *EthTx) Value() *big.Int { - return t.tx.Value() -} - -func (t *EthTx) Data() []byte { - return t.tx.Data() -} - -func (t *EthTx) AccessList() types.AccessList { - return t.tx.AccessList() -} - -func (t *EthTx) Type() uint8 { - return t.txType -} - -func (t *EthTx) Raw() *types.Transaction { - return t.tx -} - -// EthReceipt is the default implementation of Receipt that wraps types.Receipt -type EthReceipt struct { - blockNumber *big.Int - logs []*types.Log - txHash common.Hash -} - -func (t *EthReceipt) BlockNumber() *big.Int { - return t.blockNumber -} - -func (t *EthReceipt) Logs() []*types.Log { - return t.logs -} - -func (t *EthReceipt) TxHash() common.Hash { - return t.txHash -} diff --git a/devnet-sdk/system/tx_test.go b/devnet-sdk/system/tx_test.go deleted file mode 100644 index e9ec8137b17d2..0000000000000 --- a/devnet-sdk/system/tx_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package system - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" - "github.com/stretchr/testify/assert" -) - -func TestTxOpts_Validate(t *testing.T) { - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - tests := []struct { - name string - opts *TxOpts - wantErr bool - }{ - { - name: "valid basic transaction", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - }, - wantErr: false, - }, - { - name: "missing from address", - opts: &TxOpts{ - to: &addr, - value: big.NewInt(0), - }, - wantErr: true, - }, - { - name: "missing to address", - opts: &TxOpts{ - from: addr, - value: big.NewInt(0), - }, - wantErr: true, - }, - { - name: "negative value", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(-1), - }, - wantErr: true, - }, - { - name: "valid with blobs", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - blobs: []kzg4844.Blob{{1}}, - commitments: []kzg4844.Commitment{{2}}, - proofs: []kzg4844.Proof{{3}}, - blobHashes: []common.Hash{{4}}, - }, - wantErr: false, - }, - { - name: "inconsistent blob fields - missing blobs", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - commitments: []kzg4844.Commitment{{2}}, - proofs: []kzg4844.Proof{{3}}, - blobHashes: []common.Hash{{4}}, - }, - wantErr: true, - }, - { - name: "inconsistent blob fields - mismatched lengths", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - blobs: []kzg4844.Blob{{1}}, - commitments: []kzg4844.Commitment{{2}, {3}}, // Extra commitment - proofs: []kzg4844.Proof{{3}}, - blobHashes: []common.Hash{{4}}, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.opts.Validate() - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestTxOpts_Getters(t *testing.T) { - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - value := big.NewInt(123) - data := []byte{1, 2, 3} - - opts := &TxOpts{ - from: addr, - to: &addr, - value: value, - data: data, - } - - assert.Equal(t, addr, opts.From()) - assert.Equal(t, &addr, opts.To()) - assert.Equal(t, value, opts.Value()) - assert.Equal(t, data, opts.Data()) -} - -func TestEthTx_Methods(t *testing.T) { - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - value := big.NewInt(123) - data := []byte{1, 2, 3} - - // Create a legacy transaction for testing - tx := types.NewTransaction( - 0, // nonce - addr, // to - value, // value - 21000, // gas limit - big.NewInt(1), // gas price - data, // data - ) - - ethTx := &EthTx{ - tx: tx, - from: addr, - txType: uint8(types.LegacyTxType), - } - - assert.Equal(t, tx.Hash(), ethTx.Hash()) - assert.Equal(t, addr, ethTx.From()) - assert.Equal(t, &addr, ethTx.To()) - assert.Equal(t, value, ethTx.Value()) - assert.Equal(t, data, ethTx.Data()) - assert.Equal(t, uint8(types.LegacyTxType), ethTx.Type()) - assert.Equal(t, tx, ethTx.Raw()) -} - -func TestTxOptions(t *testing.T) { - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - value := big.NewInt(123) - data := []byte{1, 2, 3} - gasLimit := uint64(21000) - accessList := types.AccessList{{ - Address: addr, - StorageKeys: []common.Hash{{1}}, - }} - blobs := []kzg4844.Blob{{1}} - commitments := []kzg4844.Commitment{{2}} - proofs := []kzg4844.Proof{{3}} - blobHashes := []common.Hash{{4}} - - opts := &TxOpts{} - - // Apply all options - WithFrom(addr)(opts) - WithTo(addr)(opts) - WithValue(value)(opts) - WithData(data)(opts) - WithGasLimit(gasLimit)(opts) - WithAccessList(accessList)(opts) - WithBlobs(blobs)(opts) - WithBlobCommitments(commitments)(opts) - WithBlobProofs(proofs)(opts) - WithBlobHashes(blobHashes)(opts) - - // Verify all fields were set correctly - assert.Equal(t, addr, opts.from) - assert.Equal(t, &addr, opts.to) - assert.Equal(t, value, opts.value) - assert.Equal(t, data, opts.data) - assert.Equal(t, gasLimit, opts.gasLimit) - assert.Equal(t, accessList, opts.accessList) - assert.Equal(t, blobs, opts.blobs) - assert.Equal(t, commitments, opts.commitments) - assert.Equal(t, proofs, opts.proofs) - assert.Equal(t, blobHashes, opts.blobHashes) -} diff --git a/devnet-sdk/system/txbuilder.go b/devnet-sdk/system/txbuilder.go deleted file mode 100644 index 836210b1fbbb7..0000000000000 --- a/devnet-sdk/system/txbuilder.go +++ /dev/null @@ -1,360 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/holiman/uint256" -) - -// Default values for gas calculations -const ( - DefaultGasLimitMarginPercent = 20 // 20% margin for gas limit - DefaultFeeCapMultiplier = 2 // 2x gas price for fee cap -) - -// TxBuilderOption is a function that configures a TxBuilder -type TxBuilderOption func(*TxBuilder) - -// WithTxType sets the transaction type to use, overriding automatic detection -func WithTxType(txType uint8) TxBuilderOption { - return func(b *TxBuilder) { - b.forcedTxType = &txType - b.supportedTxTypes = []uint8{txType} - } -} - -// WithGasLimitMargin sets the margin percentage to add to estimated gas limit -func WithGasLimitMargin(marginPercent uint64) TxBuilderOption { - return func(b *TxBuilder) { - b.gasLimitMarginPercent = marginPercent - } -} - -// WithFeeCapMultiplier sets the multiplier for calculating fee cap from gas price -func WithFeeCapMultiplier(multiplier uint64) TxBuilderOption { - return func(b *TxBuilder) { - b.feeCapMultiplier = multiplier - } -} - -// TxBuilder helps construct Ethereum transactions -type TxBuilder struct { - ctx context.Context - chain Chain - supportedTxTypes []uint8 - forcedTxType *uint8 // indicates if the tx type was manually set - gasLimitMarginPercent uint64 - feeCapMultiplier uint64 -} - -// NewTxBuilder creates a new transaction builder -func NewTxBuilder(ctx context.Context, chain Chain, opts ...TxBuilderOption) *TxBuilder { - builder := &TxBuilder{ - chain: chain, - ctx: ctx, - supportedTxTypes: []uint8{types.LegacyTxType}, // Legacy is always supported - gasLimitMarginPercent: DefaultGasLimitMarginPercent, - feeCapMultiplier: DefaultFeeCapMultiplier, - } - - // Apply any options provided - for _, opt := range opts { - opt(builder) - } - - // Skip network checks if tx type is forced - if builder.forcedTxType == nil { - if builder.chain.Nodes()[0].SupportsEIP(ctx, 1559) { - builder.supportedTxTypes = append(builder.supportedTxTypes, types.DynamicFeeTxType) - builder.supportedTxTypes = append(builder.supportedTxTypes, types.AccessListTxType) - } - if builder.chain.Nodes()[0].SupportsEIP(ctx, 4844) { - builder.supportedTxTypes = append(builder.supportedTxTypes, types.BlobTxType) - } - } - - log.Info("Instantiated TxBuilder", - "supportedTxTypes", builder.supportedTxTypes, - "forcedTxType", builder.forcedTxType, - "gasLimitMargin", builder.gasLimitMarginPercent, - "feeCapMultiplier", builder.feeCapMultiplier, - ) - return builder -} - -// BuildTx creates a new transaction, using the appropriate type for the network -func (b *TxBuilder) BuildTx(options ...TxOption) (Transaction, error) { - // Apply options to create TxOpts - opts := &TxOpts{} - for _, opt := range options { - opt(opts) - } - - // Check for blob transaction requirements if blobs are provided - if len(opts.blobHashes) > 0 { - if b.forcedTxType != nil && *b.forcedTxType != types.BlobTxType { - return nil, fmt.Errorf("blob transactions not supported with forced transaction type %d", *b.forcedTxType) - } - if !b.supportsType(types.BlobTxType) { - return nil, fmt.Errorf("blob transactions not supported by the network") - } - } - - // Validate all fields - if err := opts.Validate(); err != nil { - return nil, fmt.Errorf("invalid transaction options: %w", err) - } - - var tx *types.Transaction - var err error - - // Choose the most advanced supported transaction type - txType := b.chooseTxType(len(opts.accessList) > 0, len(opts.blobHashes) > 0) - switch txType { - case types.BlobTxType: - if len(opts.blobHashes) > 0 { - tx, err = b.buildBlobTx(opts) - } else { - // If blob tx type is forced but no blobs provided, fall back to dynamic fee tx - tx, err = b.buildDynamicFeeTx(opts) - } - case types.AccessListTxType: - tx, err = b.buildAccessListTx(opts) - case types.DynamicFeeTxType: - tx, err = b.buildDynamicFeeTx(opts) - default: - tx, err = b.buildLegacyTx(opts) - } - - if err != nil { - return nil, err - } - - return &EthTx{ - tx: tx, - from: opts.from, - txType: txType, - }, nil -} - -// supportsType checks if a transaction type is supported -func (b *TxBuilder) supportsType(txType uint8) bool { - for _, t := range b.supportedTxTypes { - if t == txType { - return true - } - } - return false -} - -// chooseTxType selects the most advanced supported transaction type -func (b *TxBuilder) chooseTxType(hasAccessList bool, hasBlobs bool) uint8 { - if b.forcedTxType != nil { - return *b.forcedTxType - } - - // Blob transactions are the most advanced, but only use them if we have blobs - if hasBlobs && b.supportsType(types.BlobTxType) { - return types.BlobTxType - } - - // If we have an access list and support access list transactions, use that - if hasAccessList && b.supportsType(types.AccessListTxType) { - return types.AccessListTxType - } - - // Try dynamic fee transactions next - if b.supportsType(types.DynamicFeeTxType) { - return types.DynamicFeeTxType - } - - // Fall back to legacy - return types.LegacyTxType -} - -// getNonce gets the next nonce for the given address -func (b *TxBuilder) getNonce(from common.Address) (uint64, error) { - nonce, err := b.chain.Nodes()[0].PendingNonceAt(b.ctx, from) - if err != nil { - return 0, fmt.Errorf("failed to get nonce: %w", err) - } - return nonce, nil -} - -// getGasPrice gets the suggested gas price from the network -func (b *TxBuilder) getGasPrice() (*big.Int, error) { - gasPrice, err := b.chain.Nodes()[0].GasPrice(b.ctx) - if err != nil { - return nil, fmt.Errorf("failed to get gas price: %w", err) - } - return gasPrice, nil -} - -// calculateGasLimit calculates the gas limit for a transaction, with a configurable safety buffer -func (b *TxBuilder) calculateGasLimit(opts *TxOpts) (uint64, error) { - if opts.gasLimit != 0 { - return opts.gasLimit, nil - } - - estimated, err := b.chain.Nodes()[0].GasLimit(b.ctx, opts) - if err != nil { - return 0, fmt.Errorf("failed to estimate gas: %w", err) - } - // Add the configured margin to the estimated gas limit - return estimated * (100 + b.gasLimitMarginPercent) / 100, nil -} - -// buildDynamicFeeTx creates a new EIP-1559 transaction with the given parameters -func (b *TxBuilder) buildDynamicFeeTx(opts *TxOpts) (*types.Transaction, error) { - nonce, err := b.getNonce(opts.from) - if err != nil { - return nil, err - } - - gasPrice, err := b.getGasPrice() - if err != nil { - return nil, err - } - - chainID := b.chain.ID() - - gasLimit, err := b.calculateGasLimit(opts) - if err != nil { - return nil, err - } - - return types.NewTx(&types.DynamicFeeTx{ - ChainID: chainID, - Nonce: nonce, - GasTipCap: gasPrice, - GasFeeCap: new(big.Int).Mul(gasPrice, big.NewInt(int64(b.feeCapMultiplier))), - Gas: gasLimit, - To: opts.to, - Value: opts.value, - Data: opts.data, - }), nil -} - -// buildLegacyTx creates a new legacy (pre-EIP-1559) transaction -func (b *TxBuilder) buildLegacyTx(opts *TxOpts) (*types.Transaction, error) { - nonce, err := b.getNonce(opts.from) - if err != nil { - return nil, err - } - - gasPrice, err := b.getGasPrice() - if err != nil { - return nil, err - } - - gasLimit, err := b.calculateGasLimit(opts) - if err != nil { - return nil, err - } - - return types.NewTx(&types.LegacyTx{ - Nonce: nonce, - To: opts.to, - Value: opts.value, - Gas: gasLimit, - GasPrice: gasPrice, - Data: opts.data, - }), nil -} - -// buildAccessListTx creates a new EIP-2930 transaction with access list -func (b *TxBuilder) buildAccessListTx(opts *TxOpts) (*types.Transaction, error) { - nonce, err := b.getNonce(opts.from) - if err != nil { - return nil, err - } - - gasPrice, err := b.getGasPrice() - if err != nil { - return nil, err - } - - chainID := b.chain.ID() - - gasLimit, err := b.calculateGasLimit(opts) - if err != nil { - return nil, err - } - - return types.NewTx(&types.AccessListTx{ - ChainID: chainID, - Nonce: nonce, - GasPrice: gasPrice, - Gas: gasLimit, - To: opts.to, - Value: opts.value, - Data: opts.data, - AccessList: opts.accessList, - }), nil -} - -// buildBlobTx creates a new EIP-4844 blob transaction -func (b *TxBuilder) buildBlobTx(opts *TxOpts) (*types.Transaction, error) { - nonce, err := b.getNonce(opts.from) - if err != nil { - return nil, err - } - - gasPrice, err := b.getGasPrice() - if err != nil { - return nil, err - } - - chainID := b.chain.ID() - - gasLimit, err := b.calculateGasLimit(opts) - if err != nil { - return nil, err - } - - // Validate blob transaction requirements - if opts.to == nil { - return nil, fmt.Errorf("blob transactions must have a recipient") - } - - if len(opts.blobHashes) == 0 { - return nil, fmt.Errorf("blob transactions must have at least one blob hash") - } - - if len(opts.blobs) != len(opts.commitments) || len(opts.blobs) != len(opts.proofs) { - return nil, fmt.Errorf("mismatched number of blobs, commitments, and proofs") - } - - // Convert big.Int values to uint256.Int - chainIDU256, _ := uint256.FromBig(chainID) - gasTipCapU256, _ := uint256.FromBig(gasPrice) - gasFeeCapU256, _ := uint256.FromBig(new(big.Int).Mul(gasPrice, big.NewInt(int64(b.feeCapMultiplier)))) - valueU256, _ := uint256.FromBig(opts.value) - // For blob transactions, we'll use the same gas price for blob fee cap - blobFeeCapU256, _ := uint256.FromBig(gasPrice) - - return types.NewTx(&types.BlobTx{ - ChainID: chainIDU256, - Nonce: nonce, - GasTipCap: gasTipCapU256, - GasFeeCap: gasFeeCapU256, - Gas: gasLimit, - To: *opts.to, - Value: valueU256, - Data: opts.data, - AccessList: opts.accessList, - BlobFeeCap: blobFeeCapU256, - BlobHashes: opts.blobHashes, - Sidecar: &types.BlobTxSidecar{ - Blobs: opts.blobs, - Commitments: opts.commitments, - Proofs: opts.proofs, - }, - }), nil -} diff --git a/devnet-sdk/system/txbuilder_test.go b/devnet-sdk/system/txbuilder_test.go deleted file mode 100644 index f82a55537ae5e..0000000000000 --- a/devnet-sdk/system/txbuilder_test.go +++ /dev/null @@ -1,475 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/params" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var ( - _ Chain = (*mockChain)(nil) - _ Node = (*mockNode)(nil) - _ Wallet = (*mockWallet)(nil) -) - -// mockWallet implements types.Wallet for testing -type mockWallet struct { - mock.Mock -} - -func (m *mockWallet) PrivateKey() types.Key { - args := m.Called() - return args.Get(0).(types.Key) -} - -func (m *mockWallet) Address() types.Address { - args := m.Called() - return args.Get(0).(common.Address) -} - -func (m *mockWallet) Send(ctx context.Context, tx Transaction) error { - return nil -} - -func (m *mockWallet) Sign(tx Transaction) (Transaction, error) { - return tx, nil -} - -func (m *mockWallet) SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] { - args := m.Called(to, amount) - return args.Get(0).(types.WriteInvocation[any]) -} - -func (m *mockWallet) InitiateMessage(chainID types.ChainID, target common.Address, message []byte) types.WriteInvocation[any] { - args := m.Called(chainID, target, message) - return args.Get(0).(types.WriteInvocation[any]) -} - -func (m *mockWallet) ExecuteMessage(identifier bindings.Identifier, sentMessage []byte) types.WriteInvocation[any] { - args := m.Called(identifier, sentMessage) - return args.Get(0).(types.WriteInvocation[any]) -} - -func (m *mockWallet) Balance() types.Balance { - args := m.Called() - return args.Get(0).(types.Balance) -} - -func (m *mockWallet) Nonce() uint64 { - args := m.Called() - return args.Get(0).(uint64) -} - -func (m *mockWallet) Transactor() *bind.TransactOpts { - return nil -} - -// mockChain implements the Chain interface for testing -type mockChain struct { - mock.Mock - wallet *mockWallet -} - -func newMockChain() *mockChain { - return &mockChain{ - wallet: new(mockWallet), - } -} - -func (m *mockChain) Nodes() []Node { - args := m.Called() - return args.Get(0).([]Node) -} - -func (m *mockChain) ID() types.ChainID { - args := m.Called() - return args.Get(0).(types.ChainID) -} - -func (m *mockChain) SupportsEIP(ctx context.Context, eip uint64) bool { - args := m.Called(ctx, eip) - return args.Bool(0) -} - -func (m *mockChain) ContractsRegistry() interfaces.ContractsRegistry { - args := m.Called() - return args.Get(0).(interfaces.ContractsRegistry) -} - -func (m *mockChain) RPCURL() string { - args := m.Called() - return args.String(0) -} - -func (m *mockChain) Client() (*sources.EthClient, error) { - args := m.Called() - return args.Get(0).(*sources.EthClient), nil -} - -func (m *mockChain) Wallets() WalletMap { - return nil -} - -func (m *mockChain) Config() (*params.ChainConfig, error) { - return nil, fmt.Errorf("not implemented for mock chain") -} - -func (m *mockChain) Addresses() AddressMap { - args := m.Called() - return args.Get(0).(AddressMap) -} - -type mockNode struct { - mock.Mock -} - -func newMockNode() *mockNode { - return &mockNode{} -} - -func (m *mockNode) GasPrice(ctx context.Context) (*big.Int, error) { - args := m.Called(ctx) - return args.Get(0).(*big.Int), args.Error(1) -} - -func (m *mockNode) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) { - args := m.Called(ctx, tx) - return args.Get(0).(uint64), args.Error(1) -} - -func (m *mockNode) PendingNonceAt(ctx context.Context, addr common.Address) (uint64, error) { - args := m.Called(ctx, addr) - return args.Get(0).(uint64), args.Error(1) -} - -func (m *mockNode) BlockByNumber(ctx context.Context, number *big.Int) (eth.BlockInfo, error) { - args := m.Called(ctx, number) - return args.Get(0).(eth.BlockInfo), args.Error(1) -} - -func (m *mockNode) Client() (*sources.EthClient, error) { - args := m.Called() - return args.Get(0).(*sources.EthClient), args.Error(1) -} - -func (m *mockNode) ContractsRegistry() interfaces.ContractsRegistry { - args := m.Called() - return args.Get(0).(interfaces.ContractsRegistry) -} - -func (m *mockNode) GethClient() (*ethclient.Client, error) { - args := m.Called() - return args.Get(0).(*ethclient.Client), args.Error(1) -} - -func (m *mockNode) RPCURL() string { - args := m.Called() - return args.Get(0).(string) -} - -func (m *mockNode) SupportsEIP(ctx context.Context, eip uint64) bool { - args := m.Called(ctx, eip) - return args.Bool(0) -} - -func (m *mockNode) Name() string { - args := m.Called() - return args.String(0) -} - -func TestNewTxBuilder(t *testing.T) { - ctx := context.Background() - - var node *mockNode - var chain *mockChain - tests := []struct { - name string - setupMock func() - opts []TxBuilderOption - expectedTypes []uint8 - expectedMargin uint64 - }{ - { - name: "legacy only", - setupMock: func() { - chain = newMockChain() - node = newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(false).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - }, - opts: nil, - expectedTypes: []uint8{ethtypes.LegacyTxType}, - expectedMargin: DefaultGasLimitMarginPercent, - }, - { - name: "with EIP-1559", - setupMock: func() { - chain = newMockChain() - node = newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - }, - opts: nil, - expectedTypes: []uint8{ethtypes.LegacyTxType, ethtypes.DynamicFeeTxType, ethtypes.AccessListTxType}, - expectedMargin: DefaultGasLimitMarginPercent, - }, - { - name: "with EIP-4844", - setupMock: func() { - chain = newMockChain() - node = newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(true).Once() - }, - opts: nil, - expectedTypes: []uint8{ethtypes.LegacyTxType, ethtypes.DynamicFeeTxType, ethtypes.AccessListTxType, ethtypes.BlobTxType}, - expectedMargin: DefaultGasLimitMarginPercent, - }, - { - name: "forced tx type", - setupMock: func() { - // No EIP checks needed when type is forced - }, - opts: []TxBuilderOption{ - WithTxType(ethtypes.DynamicFeeTxType), - }, - expectedTypes: []uint8{ethtypes.DynamicFeeTxType}, - expectedMargin: DefaultGasLimitMarginPercent, - }, - { - name: "custom margin", - setupMock: func() { - chain = newMockChain() - node = newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(false).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - }, - opts: []TxBuilderOption{ - WithGasLimitMargin(50), - }, - expectedTypes: []uint8{ethtypes.LegacyTxType}, - expectedMargin: 50, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMock() - builder := NewTxBuilder(ctx, chain, tt.opts...) - - assert.Equal(t, tt.expectedTypes, builder.supportedTxTypes) - assert.Equal(t, tt.expectedMargin, builder.gasLimitMarginPercent) - chain.AssertExpectations(t) - }) - } -} - -func TestBuildTx(t *testing.T) { - ctx := context.Background() - chain := newMockChain() - node := newMockNode() - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - to := common.HexToAddress("0x0987654321098765432109876543210987654321") - chainID := big.NewInt(1) - gasPrice := big.NewInt(1000000000) // 1 gwei - nonce := uint64(1) - - tests := []struct { - name string - setupMock func() - opts []TxOption - wantType uint8 - wantErr bool - }{ - { - name: "legacy tx", - setupMock: func() { - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(false).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - node.On("GasPrice", ctx).Return(gasPrice, nil).Once() - node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() - }, - opts: []TxOption{ - WithFrom(addr), - WithTo(to), - WithValue(big.NewInt(100000000000000000)), // 0.1 ETH - }, - wantType: ethtypes.LegacyTxType, - wantErr: false, - }, - { - name: "dynamic fee tx", - setupMock: func() { - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - node.On("GasPrice", ctx).Return(gasPrice, nil).Once() - chain.On("ID").Return(chainID).Once() - node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() - }, - opts: []TxOption{ - WithFrom(addr), - WithTo(to), - WithValue(big.NewInt(100000000000000000)), // 0.1 ETH - }, - wantType: ethtypes.DynamicFeeTxType, - wantErr: false, - }, - { - name: "access list tx", - setupMock: func() { - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - node.On("GasPrice", ctx).Return(gasPrice, nil).Once() - chain.On("ID").Return(chainID).Once() - node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() - }, - opts: []TxOption{ - WithFrom(addr), - WithTo(to), - WithValue(big.NewInt(100000000000000000)), // 0.1 ETH - WithAccessList(ethtypes.AccessList{ - { - Address: common.HexToAddress("0x1234567890123456789012345678901234567890"), - StorageKeys: []common.Hash{ - common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), - }, - }, - }), - }, - wantType: ethtypes.AccessListTxType, - wantErr: false, - }, - { - name: "blob tx", - setupMock: func() { - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() - node.On("SupportsEIP", ctx, uint64(4844)).Return(true).Once() - node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - node.On("GasPrice", ctx).Return(gasPrice, nil).Once() - chain.On("ID").Return(chainID).Once() - node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() - }, - opts: []TxOption{ - WithFrom(addr), - WithTo(to), - WithValue(big.NewInt(100000000000000000)), // 0.1 ETH - WithBlobs([]kzg4844.Blob{{}}), - WithBlobCommitments([]kzg4844.Commitment{{}}), - WithBlobProofs([]kzg4844.Proof{{}}), - WithBlobHashes([]common.Hash{{}}), - }, - wantType: ethtypes.BlobTxType, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMock() - builder := NewTxBuilder(ctx, chain) - tx, err := builder.BuildTx(tt.opts...) - - if tt.wantErr { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - assert.Equal(t, tt.wantType, tx.Type()) - chain.AssertExpectations(t) - }) - } -} - -func TestCalculateGasLimit(t *testing.T) { - ctx := context.Background() - addr := common.HexToAddress("0x1234567890123456789012345678901234567890") - - tests := []struct { - name string - opts *TxOpts - margin uint64 - estimatedGas uint64 - expectedLimit uint64 - expectEstimate bool - wantErr bool - }{ - { - name: "explicit gas limit", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - gasLimit: 21000, - }, - margin: 20, - estimatedGas: 0, - expectedLimit: 21000, - expectEstimate: false, - wantErr: false, - }, - { - name: "estimated with margin", - opts: &TxOpts{ - from: addr, - to: &addr, - value: big.NewInt(0), - }, - margin: 20, - estimatedGas: 21000, - expectedLimit: 25200, // 21000 * 1.2 - expectEstimate: true, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Set up EIP support expectations for NewTxBuilder - chain := newMockChain() - node := newMockNode() - chain.On("Nodes").Return([]Node{node}) - node.On("SupportsEIP", ctx, uint64(1559)).Return(false) - node.On("SupportsEIP", ctx, uint64(4844)).Return(false) - node.On("GasLimit", ctx, tt.opts).Return(tt.estimatedGas, nil).Once() - - builder := NewTxBuilder(ctx, chain, WithGasLimitMargin(tt.margin)) - limit, err := builder.calculateGasLimit(tt.opts) - - if tt.wantErr { - assert.Error(t, err) - return - } - - assert.NoError(t, err) - assert.Equal(t, tt.expectedLimit, limit) - chain.AssertExpectations(t) - }) - } -} diff --git a/devnet-sdk/system/txprocessor.go b/devnet-sdk/system/txprocessor.go deleted file mode 100644 index 1279aadffc1b6..0000000000000 --- a/devnet-sdk/system/txprocessor.go +++ /dev/null @@ -1,83 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - - sdkTypes "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" -) - -// EthClient defines the interface for interacting with Ethereum node -type EthClient interface { - SendTransaction(ctx context.Context, tx *types.Transaction) error -} - -// TransactionProcessor handles signing and sending transactions -type transactionProcessor struct { - client EthClient - chainID *big.Int - privateKey sdkTypes.Key -} - -// NewTransactionProcessor creates a new transaction processor -func NewTransactionProcessor(client EthClient, chainID *big.Int) TransactionProcessor { - return &transactionProcessor{ - client: client, - chainID: chainID, - } -} - -// NewEthTransactionProcessor creates a new transaction processor with an ethclient -func NewEthTransactionProcessor(client *ethclient.Client, chainID *big.Int) TransactionProcessor { - return NewTransactionProcessor(client, chainID) -} - -// Sign signs a transaction with the given private key -func (p *transactionProcessor) Sign(tx Transaction) (Transaction, error) { - pk := p.privateKey - if pk == nil { - return nil, fmt.Errorf("private key is nil") - } - - var signer types.Signer - switch tx.Type() { - case types.SetCodeTxType: - signer = types.NewIsthmusSigner(p.chainID) - case types.DynamicFeeTxType: - signer = types.NewLondonSigner(p.chainID) - case types.AccessListTxType: - signer = types.NewEIP2930Signer(p.chainID) - default: - signer = types.NewEIP155Signer(p.chainID) - } - - if rt, ok := tx.(RawTransaction); ok { - signedTx, err := types.SignTx(rt.Raw(), signer, pk) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - - return &EthTx{ - tx: signedTx, - from: tx.From(), - txType: tx.Type(), - }, nil - } - - return nil, fmt.Errorf("transaction does not support signing") -} - -// Send sends a signed transaction to the network -func (p *transactionProcessor) Send(ctx context.Context, tx Transaction) error { - if st, ok := tx.(RawTransaction); ok { - if err := p.client.SendTransaction(ctx, st.Raw()); err != nil { - return fmt.Errorf("failed to send transaction: %w", err) - } - return nil - } - - return fmt.Errorf("transaction is not signed") -} diff --git a/devnet-sdk/system/txprocessor_test.go b/devnet-sdk/system/txprocessor_test.go deleted file mode 100644 index c69776d53a7cf..0000000000000 --- a/devnet-sdk/system/txprocessor_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package system - -import ( - "context" - "fmt" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/assert" -) - -func (m *mockEthClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { - args := m.Called(ctx, tx) - return args.Error(0) -} - -func TestTransactionProcessor_Sign(t *testing.T) { - // Test private key and corresponding address - // DO NOT use this key for anything other than testing - testKey := "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - testAddr := common.HexToAddress("0x96216849c49358B10257cb55b28eA603c874b05E") - - chainID := big.NewInt(1) - client := new(mockEthClient) - - // Create a wallet with the test key - chain := newChain(chainID.String(), WalletMap{}, nil, AddressMap{}, []Node{}) - wallet, err := NewWallet(testKey, testAddr, chain) - assert.NoError(t, err) - - processor := &transactionProcessor{ - client: client, - chainID: chainID, - privateKey: wallet.PrivateKey(), - } - - invalidProcessor := &transactionProcessor{ - client: client, - chainID: chainID, - // No private key set - } - - tests := []struct { - name string - processor *transactionProcessor - tx Transaction - wantType uint8 - wantErr bool - errMessage string - }{ - { - name: "legacy tx", - processor: processor, - tx: &EthTx{ - tx: types.NewTransaction( - 0, - testAddr, - big.NewInt(1), - 21000, - big.NewInt(1), - nil, - ), - from: testAddr, - txType: types.LegacyTxType, - }, - wantType: types.LegacyTxType, - wantErr: false, - }, - { - name: "dynamic fee tx", - processor: processor, - tx: &EthTx{ - tx: types.NewTx(&types.DynamicFeeTx{ - ChainID: chainID, - Nonce: 0, - GasTipCap: big.NewInt(1), - GasFeeCap: big.NewInt(1), - Gas: 21000, - To: &testAddr, - Value: big.NewInt(1), - Data: nil, - }), - from: testAddr, - txType: types.DynamicFeeTxType, - }, - wantType: types.DynamicFeeTxType, - wantErr: false, - }, - { - name: "invalid private key", - processor: invalidProcessor, - tx: &EthTx{ - tx: types.NewTransaction( - 0, - testAddr, - big.NewInt(1), - 21000, - big.NewInt(1), - nil, - ), - from: testAddr, - txType: types.LegacyTxType, - }, - wantErr: true, - errMessage: "private key is nil", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - signedTx, err := tt.processor.Sign(tt.tx) - if tt.wantErr { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.errMessage) - return - } - - assert.NoError(t, err) - assert.NotNil(t, signedTx) - assert.Equal(t, tt.wantType, signedTx.Type()) - assert.Equal(t, tt.tx.From(), signedTx.From()) - }) - } -} - -func TestTransactionProcessor_Send(t *testing.T) { - chainID := big.NewInt(1) - client := new(mockEthClient) - processor := NewTransactionProcessor(client, chainID) - ctx := context.Background() - - testAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") - tx := types.NewTransaction( - 0, - testAddr, - big.NewInt(1), - 21000, - big.NewInt(1), - nil, - ) - - tests := []struct { - name string - tx Transaction - setupMock func() - wantErr bool - errMessage string - }{ - { - name: "successful send", - tx: &EthTx{ - tx: tx, - from: testAddr, - txType: types.LegacyTxType, - }, - setupMock: func() { - client.On("SendTransaction", ctx, tx).Return(nil).Once() - }, - wantErr: false, - }, - { - name: "send error", - tx: &EthTx{ - tx: tx, - from: testAddr, - txType: types.LegacyTxType, - }, - setupMock: func() { - client.On("SendTransaction", ctx, tx).Return(fmt.Errorf("send failed")).Once() - }, - wantErr: true, - errMessage: "failed to send transaction", - }, - { - name: "not a raw transaction", - tx: &mockTransaction{ - from: testAddr, - }, - setupMock: func() {}, - wantErr: true, - errMessage: "transaction is not signed", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.setupMock() - - err := processor.Send(ctx, tt.tx) - if tt.wantErr { - assert.Error(t, err) - assert.Contains(t, err.Error(), tt.errMessage) - return - } - - assert.NoError(t, err) - client.AssertExpectations(t) - }) - } -} - -// mockTransaction implements Transaction for testing -type mockTransaction struct { - from common.Address -} - -func (m *mockTransaction) Hash() common.Hash { return common.Hash{} } -func (m *mockTransaction) From() common.Address { return m.from } -func (m *mockTransaction) To() *common.Address { return nil } -func (m *mockTransaction) Value() *big.Int { return nil } -func (m *mockTransaction) Data() []byte { return nil } -func (m *mockTransaction) AccessList() types.AccessList { return nil } -func (m *mockTransaction) Type() uint8 { return 0 } diff --git a/devnet-sdk/system/wallet.go b/devnet-sdk/system/wallet.go deleted file mode 100644 index 7d5d14757e249..0000000000000 --- a/devnet-sdk/system/wallet.go +++ /dev/null @@ -1,422 +0,0 @@ -package system - -import ( - "context" - "encoding/hex" - "fmt" - "math/big" - "strings" - - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/bindings" - "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" - "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - "github.com/ethereum-optimism/optimism/op-service/eth" - supervisorTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/ethereum-optimism/optimism/op-service/bigs" - coreTypes "github.com/ethereum/go-ethereum/core/types" -) - -var ( - // This will make sure that we implement the Chain interface - _ Wallet = (*wallet)(nil) -) - -type wallet struct { - privateKey types.Key - address types.Address - chain Chain -} - -func newWalletMapFromDescriptorWalletMap(descriptorWalletMap descriptors.WalletMap, chain Chain) (WalletMap, error) { - result := WalletMap{} - for k, v := range descriptorWalletMap { - wallet, err := NewWallet(v.PrivateKey, v.Address, chain) - if err != nil { - return nil, err - } - result[k] = wallet - } - return result, nil -} - -func NewWallet(pk string, addr types.Address, chain Chain) (*wallet, error) { - privateKey, err := privateKeyFromString(pk) - if err != nil { - return nil, fmt.Errorf("failed to convert private from string: %w", err) - } - - return &wallet{ - privateKey: privateKey, - address: addr, - chain: chain, - }, nil -} - -func privateKeyFromString(pk string) (types.Key, error) { - var privateKey types.Key - if pk != "" { - pk = strings.TrimPrefix(pk, "0x") - if len(pk)%2 == 1 { - pk = "0" + pk - } - pkBytes, err := hex.DecodeString(pk) - if err != nil { - return nil, fmt.Errorf("failed to decode private key: %w", err) - } - key, err := crypto.ToECDSA(pkBytes) - if err != nil { - return nil, fmt.Errorf("failed to convert private key to ECDSA: %w", err) - } - privateKey = key - } - - return privateKey, nil -} - -func (w *wallet) PrivateKey() types.Key { - return w.privateKey -} - -func (w *wallet) Address() types.Address { - return w.address -} - -func (w *wallet) SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] { - return &sendImpl{ - chain: w.chain, - processor: w, - from: w.address, - to: to, - amount: amount, - } -} - -func (w *wallet) Balance() types.Balance { - client, err := w.chain.Nodes()[0].Client() - if err != nil { - return types.Balance{} - } - - balance, err := client.BalanceAt(context.Background(), w.address, nil) - if err != nil { - return types.Balance{} - } - - return types.NewBalance(balance) -} - -func (w *wallet) InitiateMessage(chainID types.ChainID, target common.Address, message []byte) types.WriteInvocation[any] { - return &initiateMessageImpl{ - chain: w.chain, - processor: w, - from: w.address, - target: target, - chainID: chainID, - message: message, - } -} - -func (w *wallet) ExecuteMessage(identifier bindings.Identifier, sentMessage []byte) types.WriteInvocation[any] { - return &executeMessageImpl{ - chain: w.chain, - processor: w, - from: w.address, - identifier: identifier, - sentMessage: sentMessage, - } -} - -type initiateMessageImpl struct { - chain Chain - processor TransactionProcessor - from types.Address - - target types.Address - chainID types.ChainID - message []byte -} - -func (i *initiateMessageImpl) Call(ctx context.Context) (any, error) { - builder := NewTxBuilder(ctx, i.chain) - messenger, err := i.chain.Nodes()[0].ContractsRegistry().L2ToL2CrossDomainMessenger(constants.L2ToL2CrossDomainMessenger) - if err != nil { - return nil, fmt.Errorf("failed to init transaction: %w", err) - } - data, err := messenger.ABI().Pack("sendMessage", i.chainID, i.target, i.message) - if err != nil { - return nil, fmt.Errorf("failed to build calldata: %w", err) - } - tx, err := builder.BuildTx( - WithFrom(i.from), - WithTo(constants.L2ToL2CrossDomainMessenger), - WithValue(big.NewInt(0)), - WithData(data), - ) - if err != nil { - return nil, fmt.Errorf("failed to build transaction: %w", err) - } - - tx, err = i.processor.Sign(tx) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - - return tx, nil -} - -func (i *initiateMessageImpl) Send(ctx context.Context) types.InvocationResult { - result, err := i.Call(ctx) - if err != nil { - return &sendResult{chain: i.chain, tx: nil, err: err} - } - tx, ok := result.(Transaction) - if !ok { - return &sendResult{chain: i.chain, tx: nil, err: fmt.Errorf("unexpected return type")} - } - err = i.processor.Send(ctx, tx) - return &sendResult{ - chain: i.chain, - tx: tx, - err: err, - } -} - -type executeMessageImpl struct { - chain Chain - processor TransactionProcessor - from types.Address - - identifier bindings.Identifier - sentMessage []byte -} - -func (i *executeMessageImpl) Call(ctx context.Context) (any, error) { - builder := NewTxBuilder(ctx, i.chain) - messenger, err := i.chain.Nodes()[0].ContractsRegistry().L2ToL2CrossDomainMessenger(constants.L2ToL2CrossDomainMessenger) - if err != nil { - return nil, fmt.Errorf("failed to init transaction: %w", err) - } - data, err := messenger.ABI().Pack("relayMessage", i.identifier, i.sentMessage) - if err != nil { - return nil, fmt.Errorf("failed to build calldata: %w", err) - } - // Wrapper to use Access implementation - msg := supervisorTypes.Message{ - Identifier: supervisorTypes.Identifier{ - Origin: i.identifier.Origin, - BlockNumber: bigs.Uint64Strict(i.identifier.BlockNumber), - LogIndex: uint32(bigs.Uint64Strict(i.identifier.LogIndex)), - Timestamp: bigs.Uint64Strict(i.identifier.Timestamp), - ChainID: eth.ChainIDFromBig(i.identifier.ChainId), - }, - PayloadHash: crypto.Keccak256Hash(i.sentMessage), - } - access := msg.Access() - accessList := coreTypes.AccessList{{ - Address: constants.CrossL2Inbox, - StorageKeys: supervisorTypes.EncodeAccessList([]supervisorTypes.Access{access}), - }} - tx, err := builder.BuildTx( - WithFrom(i.from), - WithTo(constants.L2ToL2CrossDomainMessenger), - WithValue(big.NewInt(0)), - WithData(data), - WithAccessList(accessList), - ) - if err != nil { - return nil, fmt.Errorf("failed to build transaction: %w", err) - } - tx, err = i.processor.Sign(tx) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - return tx, nil -} - -func (i *executeMessageImpl) Send(ctx context.Context) types.InvocationResult { - result, err := i.Call(ctx) - if err != nil { - return &sendResult{chain: i.chain, tx: nil, err: err} - } - tx, ok := result.(Transaction) - if !ok { - return &sendResult{chain: i.chain, tx: nil, err: fmt.Errorf("unexpected return type")} - } - err = i.processor.Send(ctx, tx) - return &sendResult{ - chain: i.chain, - tx: tx, - err: err, - } -} - -func (w *wallet) Nonce() uint64 { - client, err := w.chain.Nodes()[0].Client() - if err != nil { - return 0 - } - - nonce, err := client.PendingNonceAt(context.Background(), w.address) - if err != nil { - return 0 - } - - return nonce -} - -func (w *wallet) Transactor() *bind.TransactOpts { - transactor, err := bind.NewKeyedTransactorWithChainID(w.PrivateKey(), w.chain.ID()) - if err != nil { - panic(fmt.Sprintf("could not create transactor for address %s and chainID %v", w.Address(), w.chain.ID())) - } - - return transactor -} - -func (w *wallet) Sign(tx Transaction) (Transaction, error) { - pk := w.privateKey - - var signer coreTypes.Signer - switch tx.Type() { - case coreTypes.SetCodeTxType: - signer = coreTypes.NewIsthmusSigner(w.chain.ID()) - case coreTypes.DynamicFeeTxType: - signer = coreTypes.NewLondonSigner(w.chain.ID()) - case coreTypes.AccessListTxType: - signer = coreTypes.NewEIP2930Signer(w.chain.ID()) - default: - signer = coreTypes.NewEIP155Signer(w.chain.ID()) - } - - if rt, ok := tx.(RawTransaction); ok { - signedTx, err := coreTypes.SignTx(rt.Raw(), signer, pk) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - - return &EthTx{ - tx: signedTx, - from: tx.From(), - txType: tx.Type(), - }, nil - } - - return nil, fmt.Errorf("transaction does not support signing") -} - -func (w *wallet) Send(ctx context.Context, tx Transaction) error { - if st, ok := tx.(RawTransaction); ok { - client, err := w.chain.Nodes()[0].Client() - if err != nil { - return fmt.Errorf("failed to get client: %w", err) - } - if err := client.SendTransaction(ctx, st.Raw()); err != nil { - return fmt.Errorf("failed to send transaction: %w", err) - } - return nil - } - - return fmt.Errorf("transaction is not signed") -} - -type sendImpl struct { - chain Chain - processor TransactionProcessor - from types.Address - to types.Address - amount types.Balance -} - -func (i *sendImpl) Call(ctx context.Context) (any, error) { - builder := NewTxBuilder(ctx, i.chain) - tx, err := builder.BuildTx( - WithFrom(i.from), - WithTo(i.to), - WithValue(i.amount.Int), - WithData(nil), - ) - if err != nil { - return nil, fmt.Errorf("failed to build transaction: %w", err) - } - - tx, err = i.processor.Sign(tx) - if err != nil { - return nil, fmt.Errorf("failed to sign transaction: %w", err) - } - - return tx, nil -} - -func (i *sendImpl) Send(ctx context.Context) types.InvocationResult { - builder := NewTxBuilder(ctx, i.chain) - tx, err := builder.BuildTx( - WithFrom(i.from), - WithTo(i.to), - WithValue(i.amount.Int), - WithData(nil), - ) - - // Sign the transaction if it's built okay - if err == nil { - tx, err = i.processor.Sign(tx) - } - - // Send the transaction if it's signed okay - if err == nil { - err = i.processor.Send(ctx, tx) - } - - return &sendResult{ - chain: i.chain, - tx: tx, - err: err, - } -} - -type sendResult struct { - chain Chain - tx Transaction - receipt Receipt - err error -} - -func (r *sendResult) Error() error { - return r.err -} - -func (r *sendResult) Wait() error { - client, err := r.chain.Nodes()[0].GethClient() - if err != nil { - return fmt.Errorf("failed to get client: %w", err) - } - - if r.err != nil { - return r.err - } - if r.tx == nil { - return fmt.Errorf("no transaction to wait for") - } - - if tx, ok := r.tx.(RawTransaction); ok { - receipt, err := wait.ForReceiptOK(context.Background(), client, tx.Raw().Hash()) - if err != nil { - return fmt.Errorf("failed waiting for transaction confirmation: %w", err) - } - r.receipt = &EthReceipt{blockNumber: receipt.BlockNumber, logs: receipt.Logs, txHash: receipt.TxHash} - if receipt.Status == 0 { - return fmt.Errorf("transaction failed") - } - } - - return nil -} - -func (r *sendResult) Info() any { - return r.receipt -} diff --git a/devnet-sdk/system/walletV2.go b/devnet-sdk/system/walletV2.go deleted file mode 100644 index 9e2d38c208861..0000000000000 --- a/devnet-sdk/system/walletV2.go +++ /dev/null @@ -1,93 +0,0 @@ -package system - -import ( - "context" - "crypto/ecdsa" - "fmt" - - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" -) - -var ( - _ WalletV2 = (*walletV2)(nil) -) - -type walletV2 struct { - address common.Address - priv *ecdsa.PrivateKey - client *sources.EthClient - gethClient *ethclient.Client - ctx context.Context -} - -func NewWalletV2FromWalletAndChain(ctx context.Context, wallet Wallet, chain Chain) (WalletV2, error) { - if len(chain.Nodes()) == 0 { - return nil, fmt.Errorf("failed to init walletV2: chain has zero nodes") - } - client, err := chain.Nodes()[0].Client() - if err != nil { - return nil, err - } - gethClient, err := chain.Nodes()[0].GethClient() - if err != nil { - return nil, err - } - return &walletV2{ - address: wallet.Address(), - priv: wallet.PrivateKey(), - client: client, - gethClient: gethClient, - ctx: ctx, - }, nil -} - -func NewWalletV2(ctx context.Context, rpcURL string, priv *ecdsa.PrivateKey, clCfg *sources.EthClientConfig, log log.Logger) (*walletV2, error) { - if clCfg == nil { - clCfg = sources.DefaultEthClientConfig(10) - } - rpcClient, err := rpc.DialContext(ctx, rpcURL) - if err != nil { - return nil, err - } - cl, err := sources.NewEthClient(client.NewBaseRPCClient(rpcClient), log, nil, clCfg) - if err != nil { - return nil, err - } - pubkeyECDSA, ok := priv.Public().(*ecdsa.PublicKey) - if !ok { - return nil, fmt.Errorf("Failed to assert type: publicKey is not of type *ecdsa.PublicKey") - } - address := crypto.PubkeyToAddress(*pubkeyECDSA) - return &walletV2{ - address: address, - client: cl, - priv: priv, - ctx: ctx, - }, nil -} - -func (w *walletV2) PrivateKey() *ecdsa.PrivateKey { - return w.priv -} - -func (w *walletV2) Client() *sources.EthClient { - return w.client -} - -func (w *walletV2) Ctx() context.Context { - return w.ctx -} - -func (w *walletV2) Address() common.Address { - return w.address -} - -func (w *walletV2) GethClient() *ethclient.Client { - return w.gethClient -} diff --git a/devnet-sdk/system/wallet_test.go b/devnet-sdk/system/wallet_test.go deleted file mode 100644 index 294869af1a6d5..0000000000000 --- a/devnet-sdk/system/wallet_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package system - -import ( - "context" - "math/big" - "testing" - - "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/sources" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -// testWallet is a minimal wallet implementation for testing balance functionality -type testWallet struct { - privateKey types.Key - address types.Address - chain *mockChainForBalance // Use concrete type to access mock client directly -} - -func (w *testWallet) Balance() types.Balance { - // Use the mock client directly instead of going through Client() - balance, err := w.chain.client.BalanceAt(context.Background(), w.address, nil) - if err != nil { - return types.NewBalance(new(big.Int)) - } - - return types.NewBalance(balance) -} - -// mockEthClient implements a mock ethereum client for testing -type mockEthClient struct { - mock.Mock -} - -func (m *mockEthClient) BalanceAt(ctx context.Context, account types.Address, blockNumber *big.Int) (*big.Int, error) { - args := m.Called(ctx, account, blockNumber) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*big.Int), args.Error(1) -} - -func (m *mockEthClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - args := m.Called(ctx, account) - return args.Get(0).(uint64), args.Error(1) -} - -// mockChainForBalance implements just enough of the chain interface for balance testing -type mockChainForBalance struct { - mock.Mock - client *mockEthClient -} - -func TestWalletBalance(t *testing.T) { - tests := []struct { - name string - setupMock func(*mockChainForBalance) - expectedValue *big.Int - }{ - { - name: "successful balance fetch", - setupMock: func(m *mockChainForBalance) { - balance := big.NewInt(1000000000000000000) // 1 ETH - m.client.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(balance, nil) - }, - expectedValue: big.NewInt(1000000000000000000), - }, - { - name: "balance fetch error returns zero", - setupMock: func(m *mockChainForBalance) { - m.client.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(nil, assert.AnError) - }, - expectedValue: new(big.Int), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockChain := &mockChainForBalance{ - client: new(mockEthClient), - } - tt.setupMock(mockChain) - - w := &testWallet{ - privateKey: crypto.ToECDSAUnsafe(common.FromHex("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")), - address: types.Address{}, - chain: mockChain, - } - - balance := w.Balance() - assert.Equal(t, 0, balance.Int.Cmp(tt.expectedValue)) - - mockChain.AssertExpectations(t) - mockChain.client.AssertExpectations(t) - }) - } -} - -type internalMockChain struct { - *mockChain -} - -func (m *internalMockChain) Client() (*sources.EthClient, error) { - args := m.Called() - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*sources.EthClient), args.Error(1) -} - -func (m *internalMockChain) GethClient() (*ethclient.Client, error) { - args := m.Called() - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(*ethclient.Client), args.Error(1) -} - -func TestNewWallet(t *testing.T) { - pk := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - addr := types.Address(common.HexToAddress("0x5678")) - chain := &chain{} - - w, err := NewWallet(pk, addr, chain) - assert.NoError(t, err) - - // The private key is converted to ECDSA, so we can't compare directly with the input string - assert.NotNil(t, w.privateKey) - assert.Equal(t, addr, w.address) - assert.Equal(t, chain, w.chain) -} - -func TestWallet_Address(t *testing.T) { - addr := types.Address(common.HexToAddress("0x5678")) - w := &wallet{address: addr} - - assert.Equal(t, addr, w.Address()) -} - -func TestWallet_SendETH(t *testing.T) { - ctx := context.Background() - mockChain := newMockChain() - mockNode := newMockNode() - internalChain := &internalMockChain{mockChain} - - // Use a valid 256-bit private key (32 bytes) - testPrivateKey := crypto.ToECDSAUnsafe(common.FromHex("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")) - - // Derive the address from the private key - fromAddr := crypto.PubkeyToAddress(testPrivateKey.PublicKey) - - w := &wallet{ - privateKey: testPrivateKey, - address: types.Address(fromAddr), - chain: internalChain, - } - - toAddr := types.Address(common.HexToAddress("0x5678")) - amount := types.NewBalance(big.NewInt(1000000)) - - chainID := big.NewInt(1) - - // Mock chain ID for all calls - mockChain.On("ID").Return(types.ChainID(chainID)).Maybe() - - // Mock EIP support checks - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("SupportsEIP", ctx, uint64(1559)).Return(false) - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("SupportsEIP", ctx, uint64(4844)).Return(false) - - // Mock gas price and limit - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("GasPrice", ctx).Return(big.NewInt(1000000000), nil) - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil) - - // Mock nonce retrieval - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("PendingNonceAt", ctx, fromAddr).Return(uint64(0), nil) - - // Mock client access - rpcClient, err := rpc.DialContext(context.Background(), "http://this.domain.definitely.does.not.exist:8545") - assert.NoError(t, err) - ethClCfg := sources.EthClientConfig{MaxConcurrentRequests: 1, MaxRequestsPerBatch: 1, RPCProviderKind: sources.RPCKindStandard} - ethCl, err := sources.NewEthClient(client.NewBaseRPCClient(rpcClient), log.Root(), nil, ðClCfg) - assert.NoError(t, err) - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - mockNode.On("Client").Return(ethCl, nil) - - // Create the send invocation - invocation := w.SendETH(toAddr, amount) - assert.NotNil(t, invocation) - - // Send the transaction - result := invocation.Send(ctx) - assert.Error(t, result.Error()) // We expect an error since the client can't connect - - mockChain.AssertExpectations(t) -} - -func TestWallet_Balance(t *testing.T) { - mockChain := newMockChain() - mockNode := newMockNode() - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - internalChain := &internalMockChain{mockChain} - w := &wallet{ - chain: internalChain, - } - - // Test error case when client is not available - mockNode.On("Client").Return((*sources.EthClient)(nil), assert.AnError).Once() - balance := w.Balance() - assert.Equal(t, types.Balance{}, balance) -} - -func TestWallet_Nonce(t *testing.T) { - mockChain := newMockChain() - mockNode := newMockNode() - mockChain.On("Nodes").Return([]Node{mockNode}).Once() - internalChain := &internalMockChain{mockChain} - w := &wallet{ - chain: internalChain, - } - - // Test error case when client is not available - mockNode.On("Client").Return((*sources.EthClient)(nil), assert.AnError).Once() - nonce := w.Nonce() - assert.Equal(t, uint64(0), nonce) -} diff --git a/devnet-sdk/telemetry/carrier.go b/devnet-sdk/telemetry/carrier.go deleted file mode 100644 index dfbbdcfbee5d6..0000000000000 --- a/devnet-sdk/telemetry/carrier.go +++ /dev/null @@ -1,55 +0,0 @@ -package telemetry - -import ( - "context" - "fmt" - "strings" - - "go.opentelemetry.io/otel/propagation" -) - -const CarrierEnvVarPrefix = "OTEL_DEVSTACK_PROPAGATOR_CARRIER_" - -// keep in sync with textPropagator() below -var defaultPropagators = []string{ - "tracecontext", - "baggage", -} - -func textPropagator() propagation.TextMapPropagator { - return propagation.NewCompositeTextMapPropagator( - // keep in sync with propagators above - propagation.TraceContext{}, - propagation.Baggage{}, - ) -} - -func InstrumentEnvironment(ctx context.Context, env []string) []string { - propagator := textPropagator() - carrier := propagation.MapCarrier{} - propagator.Inject(ctx, carrier) - - for k, v := range carrier { - env = append(env, fmt.Sprintf("%s%s=%s", CarrierEnvVarPrefix, k, v)) - } - - return env -} - -func ExtractEnvironment(ctx context.Context, env []string) (context.Context, error) { - carrier := propagation.MapCarrier{} - // Reconstruct the carrier from the environment variables - for _, e := range env { - if strings.HasPrefix(e, CarrierEnvVarPrefix) { - parts := strings.SplitN(e, "=", 2) - if len(parts) == 2 { - key := strings.TrimPrefix(parts[0], CarrierEnvVarPrefix) - value := parts[1] - carrier.Set(key, value) - } - } - } - - ctx = textPropagator().Extract(ctx, carrier) - return ctx, nil -} diff --git a/devnet-sdk/telemetry/init.go b/devnet-sdk/telemetry/init.go deleted file mode 100644 index 52ffe4bc1fa67..0000000000000 --- a/devnet-sdk/telemetry/init.go +++ /dev/null @@ -1,58 +0,0 @@ -package telemetry - -import ( - "context" - "os" - - "github.com/honeycombio/otel-config-go/otelconfig" -) - -const ( - serviceNameEnvVar = "OTEL_SERVICE_NAME" - serviceVersionEnvVar = "OTEL_SERVICE_VERSION" - tracesEndpointEnvVar = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" - metricsEndpointEnvVar = "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT" - - defaultServiceName = "devstack" - defaultServiceVersion = "0.0.0" -) - -func envOrDefault(key, def string) string { - if v, ok := os.LookupEnv(key); ok { - return v - } - return def -} - -func SetupOpenTelemetry(ctx context.Context, opts ...otelconfig.Option) (context.Context, func(), error) { - defaultOpts := []otelconfig.Option{ - otelconfig.WithServiceName(envOrDefault(serviceNameEnvVar, defaultServiceName)), - otelconfig.WithServiceVersion(envOrDefault(serviceVersionEnvVar, defaultServiceVersion)), - otelconfig.WithPropagators(defaultPropagators), - } - - // do not use localhost:4317 by default, we want telemetry to be opt-in and - // explicit. - // The caller is still able to override this by passing in their own opts. - if os.Getenv(tracesEndpointEnvVar) == "" { - defaultOpts = append(defaultOpts, otelconfig.WithTracesEnabled(false)) - } - if os.Getenv(metricsEndpointEnvVar) == "" { - defaultOpts = append(defaultOpts, otelconfig.WithMetricsEnabled(false)) - } - - opts = append(defaultOpts, opts...) - otelShutdown, err := otelconfig.ConfigureOpenTelemetry(opts...) - if err != nil { - return ctx, nil, err - } - - // If the environment contains carrier information, extract it. - // This is useful for test runner / test communication for example. - ctx, err = ExtractEnvironment(ctx, os.Environ()) - if err != nil { - return ctx, nil, err - } - - return ctx, otelShutdown, nil -} diff --git a/devnet-sdk/telemetry/slog.go b/devnet-sdk/telemetry/slog.go deleted file mode 100644 index ce7429a053016..0000000000000 --- a/devnet-sdk/telemetry/slog.go +++ /dev/null @@ -1,98 +0,0 @@ -package telemetry - -import ( - "context" - "fmt" - "log/slog" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - - "github.com/ethereum-optimism/optimism/op-service/logmods" -) - -func WrapHandler(h slog.Handler) slog.Handler { - return &tracingHandler{ - Handler: h, - } -} - -type tracingHandler struct { - slog.Handler -} - -var _ logmods.Handler = (*tracingHandler)(nil) - -func (h *tracingHandler) Unwrap() slog.Handler { - return h.Handler -} - -func (h *tracingHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - return &tracingHandler{Handler: h.Handler.WithAttrs(attrs)} -} - -func (h *tracingHandler) WithGroup(name string) slog.Handler { - return &tracingHandler{Handler: h.Handler.WithGroup(name)} -} - -func (h *tracingHandler) Handle(ctx context.Context, record slog.Record) error { - // Send log entries as events to the tracer - span := trace.SpanFromContext(ctx) - if span.IsRecording() { - attrRecorder := &attrAccumulator{} - record.Attrs(func(a slog.Attr) bool { - attrRecorder.register(a) - return true - }) - span.AddEvent(record.Message, trace.WithAttributes(attrRecorder.kv...)) - } - - // Conversely add tracing data to the local logs - spanCtx := trace.SpanContextFromContext(ctx) - if spanCtx.HasTraceID() { - record.AddAttrs(slog.String("trace_id", spanCtx.TraceID().String())) - } - if spanCtx.HasSpanID() { - record.AddAttrs(slog.String("span_id", spanCtx.SpanID().String())) - } - return h.Handler.Handle(ctx, record) -} - -type attrAccumulator struct { - kv []attribute.KeyValue -} - -func (ac *attrAccumulator) register(a slog.Attr) { - switch a.Value.Kind() { - case slog.KindAny: - ac.kv = append(ac.kv, attribute.String(a.Key, fmt.Sprintf("%v", a.Value.Any()))) - case slog.KindBool: - ac.kv = append(ac.kv, attribute.Bool(a.Key, a.Value.Bool())) - case slog.KindDuration: - ac.kv = append(ac.kv, attribute.String(a.Key, a.Value.Duration().String())) - case slog.KindFloat64: - ac.kv = append(ac.kv, attribute.Float64(a.Key, a.Value.Float64())) - case slog.KindInt64: - ac.kv = append(ac.kv, attribute.Int64(a.Key, a.Value.Int64())) - case slog.KindString: - ac.kv = append(ac.kv, attribute.String(a.Key, a.Value.String())) - case slog.KindTime: - ac.kv = append(ac.kv, attribute.String(a.Key, a.Value.Time().String())) - case slog.KindUint64: - val := a.Value.Uint64() - ac.kv = append(ac.kv, attribute.Int64(a.Key, int64(val))) - // detect overflows - if val > uint64(1<<63-1) { - // Value doesn't properly fit in int64 - ac.kv = append(ac.kv, attribute.Bool(a.Key+".overflow", true)) - ac.kv = append(ac.kv, attribute.String(a.Key+".actual", fmt.Sprintf("%d", val))) - } - case slog.KindGroup: - for _, attr := range a.Value.Group() { - ac.register(attr) - } - case slog.KindLogValuer: - value := a.Value.LogValuer().LogValue() - ac.register(slog.Attr{Key: a.Key, Value: value}) - } -} diff --git a/devnet-sdk/types/balance.go b/devnet-sdk/types/balance.go deleted file mode 100644 index bc2e4aa5942ea..0000000000000 --- a/devnet-sdk/types/balance.go +++ /dev/null @@ -1,92 +0,0 @@ -package types - -import ( - "fmt" - "math/big" -) - -type Balance struct { - *big.Int -} - -// NewBalance creates a new Balance from a big.Int -func NewBalance(i *big.Int) Balance { - return Balance{Int: new(big.Int).Set(i)} -} - -// Add returns a new Balance with other added to it -func (b Balance) Add(other Balance) Balance { - return Balance{Int: new(big.Int).Add(b.Int, other.Int)} -} - -// Sub returns a new Balance with other subtracted from it -func (b Balance) Sub(other Balance) Balance { - return Balance{Int: new(big.Int).Sub(b.Int, other.Int)} -} - -// Mul returns a new Balance multiplied by a float64 -func (b Balance) Mul(f float64) Balance { - floatResult := new(big.Float).Mul(new(big.Float).SetInt(b.Int), new(big.Float).SetFloat64(f)) - result := new(big.Int) - floatResult.Int(result) - return Balance{Int: result} -} - -// GreaterThan returns true if this balance is greater than other -func (b Balance) GreaterThan(other Balance) bool { - if b.Int == nil { - return false - } - if other.Int == nil { - return true - } - return b.Int.Cmp(other.Int) > 0 -} - -// LessThan returns true if this balance is less than other -func (b Balance) LessThan(other Balance) bool { - if b.Int == nil { - return other.Int != nil - } - if other.Int == nil { - return false - } - return b.Int.Cmp(other.Int) < 0 -} - -// Equal returns true if this balance equals other -func (b Balance) Equal(other Balance) bool { - if b.Int == nil { - return other.Int == nil - } - if other.Int == nil { - return false - } - return b.Int.Cmp(other.Int) == 0 -} - -// String implements fmt.Stringer to format Balance in the most readable unit -func (b Balance) String() string { - if b.Int == nil { - return "0 ETH" - } - - val := new(big.Float).SetInt(b.Int) - eth := new(big.Float).Quo(val, new(big.Float).SetInt64(1e18)) - - // 1 ETH = 1e18 Wei - if eth.Cmp(new(big.Float).SetFloat64(0.001)) >= 0 { - str := eth.Text('f', 0) - return fmt.Sprintf("%s ETH", str) - } - - // 1 Gwei = 1e9 Wei - gwei := new(big.Float).Quo(val, new(big.Float).SetInt64(1e9)) - if gwei.Cmp(new(big.Float).SetFloat64(0.001)) >= 0 { - str := gwei.Text('g', 3) - return fmt.Sprintf("%s Gwei", str) - } - - // Wei - return fmt.Sprintf("%s Wei", b.Text(10)) -} diff --git a/devnet-sdk/types/balance_test.go b/devnet-sdk/types/balance_test.go deleted file mode 100644 index 09b880f60622f..0000000000000 --- a/devnet-sdk/types/balance_test.go +++ /dev/null @@ -1,267 +0,0 @@ -package types - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewBalance(t *testing.T) { - i := big.NewInt(100) - b := NewBalance(i) - if b.Int.Cmp(i) != 0 { - t.Errorf("NewBalance failed, got %v, want %v", b.Int, i) - } - - // Verify that modifying the input doesn't affect the Balance - i.SetInt64(200) - if b.Int.Cmp(big.NewInt(100)) != 0 { - t.Error("NewBalance did not create a copy of the input") - } -} - -func TestBalance_Add(t *testing.T) { - tests := []struct { - a, b, want int64 - }{ - {100, 200, 300}, - {0, 100, 100}, - {-100, 100, 0}, - {1000000, 2000000, 3000000}, - } - - for _, tt := range tests { - a := NewBalance(big.NewInt(tt.a)) - b := NewBalance(big.NewInt(tt.b)) - got := a.Add(b) - want := NewBalance(big.NewInt(tt.want)) - if !got.Equal(want) { - t.Errorf("Add(%v, %v) = %v, want %v", tt.a, tt.b, got, want) - } - // Verify original balances weren't modified - if !a.Equal(NewBalance(big.NewInt(tt.a))) { - t.Error("Add modified original balance") - } - } -} - -func TestBalance_Sub(t *testing.T) { - tests := []struct { - a, b, want int64 - }{ - {300, 200, 100}, - {100, 100, 0}, - {0, 100, -100}, - {3000000, 2000000, 1000000}, - } - - for _, tt := range tests { - a := NewBalance(big.NewInt(tt.a)) - b := NewBalance(big.NewInt(tt.b)) - got := a.Sub(b) - want := NewBalance(big.NewInt(tt.want)) - if !got.Equal(want) { - t.Errorf("Sub(%v, %v) = %v, want %v", tt.a, tt.b, got, want) - } - } -} - -func TestBalance_Mul(t *testing.T) { - tests := []struct { - a int64 - mul float64 - want int64 - }{ - {100, 2.0, 200}, - {100, 0.5, 50}, - {100, 0.0, 0}, - {1000, 1.5, 1500}, - } - - for _, tt := range tests { - a := NewBalance(big.NewInt(tt.a)) - got := a.Mul(tt.mul) - want := NewBalance(big.NewInt(tt.want)) - if !got.Equal(want) { - t.Errorf("Mul(%v, %v) = %v, want %v", tt.a, tt.mul, got, want) - } - } -} - -func TestBalanceComparisons(t *testing.T) { - tests := []struct { - name string - balance1 Balance - balance2 Balance - greater bool - less bool - equal bool - }{ - { - name: "both nil", - balance1: Balance{}, - balance2: Balance{}, - greater: false, - less: false, - equal: true, - }, - { - name: "first nil", - balance1: Balance{}, - balance2: NewBalance(big.NewInt(100)), - greater: false, - less: true, - equal: false, - }, - { - name: "second nil", - balance1: NewBalance(big.NewInt(100)), - balance2: Balance{}, - greater: true, - less: false, - equal: false, - }, - { - name: "first greater", - balance1: NewBalance(big.NewInt(200)), - balance2: NewBalance(big.NewInt(100)), - greater: true, - less: false, - equal: false, - }, - { - name: "second greater", - balance1: NewBalance(big.NewInt(100)), - balance2: NewBalance(big.NewInt(200)), - greater: false, - less: true, - equal: false, - }, - { - name: "equal values", - balance1: NewBalance(big.NewInt(100)), - balance2: NewBalance(big.NewInt(100)), - greater: false, - less: false, - equal: true, - }, - { - name: "zero values", - balance1: NewBalance(new(big.Int)), - balance2: NewBalance(new(big.Int)), - greater: false, - less: false, - equal: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.greater, tt.balance1.GreaterThan(tt.balance2), "GreaterThan check failed") - assert.Equal(t, tt.less, tt.balance1.LessThan(tt.balance2), "LessThan check failed") - assert.Equal(t, tt.equal, tt.balance1.Equal(tt.balance2), "Equal check failed") - }) - } -} - -func TestBalanceArithmetic(t *testing.T) { - tests := []struct { - name string - balance1 Balance - balance2 Balance - add *big.Int - sub *big.Int - mul float64 - mulRes *big.Int - }{ - { - name: "basic arithmetic", - balance1: NewBalance(big.NewInt(100)), - balance2: NewBalance(big.NewInt(50)), - add: big.NewInt(150), - sub: big.NewInt(50), - mul: 2.5, - mulRes: big.NewInt(250), - }, - { - name: "zero values", - balance1: NewBalance(new(big.Int)), - balance2: NewBalance(new(big.Int)), - add: new(big.Int), - sub: new(big.Int), - mul: 1.0, - mulRes: new(big.Int), - }, - { - name: "large numbers", - balance1: NewBalance(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(100))), // 100 ETH - balance2: NewBalance(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(50))), // 50 ETH - add: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(150)), // 150 ETH - sub: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(50)), // 50 ETH - mul: 0.5, - mulRes: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(50)), // 50 ETH - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Test Add - sum := tt.balance1.Add(tt.balance2) - assert.Equal(t, 0, sum.Int.Cmp(tt.add), "Add result mismatch") - - // Test Sub - diff := tt.balance1.Sub(tt.balance2) - assert.Equal(t, 0, diff.Int.Cmp(tt.sub), "Sub result mismatch") - - // Test Mul - product := tt.balance1.Mul(tt.mul) - assert.Equal(t, 0, product.Int.Cmp(tt.mulRes), "Mul result mismatch") - }) - } -} - -func TestBalanceLogValue(t *testing.T) { - tests := []struct { - name string - balance Balance - expected string - }{ - { - name: "nil balance", - balance: Balance{}, - expected: "0 ETH", - }, - { - name: "zero balance", - balance: NewBalance(new(big.Int)), - expected: "0 Wei", - }, - { - name: "small wei amount", - balance: NewBalance(big.NewInt(100)), - expected: "100 Wei", - }, - { - name: "gwei amount", - balance: NewBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(1e9))), - expected: "1 Gwei", - }, - { - name: "eth amount", - balance: NewBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(1e18))), - expected: "1 ETH", - }, - { - name: "large eth amount", - balance: NewBalance(new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18))), - expected: "1000 ETH", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expected, tt.balance.String()) - }) - } -} diff --git a/devnet-sdk/types/types.go b/devnet-sdk/types/types.go deleted file mode 100644 index e251710ae7193..0000000000000 --- a/devnet-sdk/types/types.go +++ /dev/null @@ -1,30 +0,0 @@ -package types - -import ( - "context" - "crypto/ecdsa" - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -type Address = common.Address - -type ChainID = *big.Int - -type ReadInvocation[T any] interface { - Call(ctx context.Context) (T, error) -} - -type WriteInvocation[T any] interface { - ReadInvocation[T] - Send(ctx context.Context) InvocationResult -} - -type InvocationResult interface { - Error() error - Wait() error - Info() any -} - -type Key = *ecdsa.PrivateKey diff --git a/docker-bake.hcl b/docker-bake.hcl index a1165148c2333..17e084d17079d 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -389,3 +389,10 @@ target "op-reth" { platforms = split(",", PLATFORMS) tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/op-reth:${tag}"] } + +target "cannon-builder" { + dockerfile = "cannon.dockerfile" + context = "rust/kona/docker/cannon" + platforms = split(",", PLATFORMS) + tags = [for tag in split(",", IMAGE_TAGS) : "${REGISTRY}/${REPOSITORY}/cannon-builder:${tag}"] +} diff --git a/docs/ai/contract-dev.md b/docs/ai/contract-dev.md index 6b9c6265594f2..e76c7c6095d3c 100644 --- a/docs/ai/contract-dev.md +++ b/docs/ai/contract-dev.md @@ -2,4 +2,40 @@ This document provides guidance for AI agents working with smart contracts in the OP Stack. - +## Non-Idempotent Initializers + +When reviewing `initialize()` or `reinitializer` functions, check whether the function is **idempotent** — calling it multiple times with the same arguments should produce the same end state as calling it once. + +### The Risk + +Proxied contracts in the OP Stack can be re-initialized during upgrades (via `reinitializer(version)`). Orchestrators like `OPContractsManagerV2._apply()` call `initialize()` on contracts that may already hold state from a previous initialization. If the initializer is not idempotent, re-initialization can corrupt state. + +**Example**: `ETHLockbox.initialize()` calls `_authorizePortal()` for each portal passed in. Currently safe because `_authorizePortal()` is idempotent — setting `authorizedPortals[portal] = true` twice has the same effect as once. But if someone later added a portal count that increments on each authorization, re-initialization would double-count portals. + +### What Makes an Initializer Non-Idempotent + +- Incrementing counters or nonces +- Appending to arrays (creates duplicates on re-init) +- External calls with lasting side-effects (e.g., minting tokens, sending ETH) +- Operations that depend on prior state (e.g., "add 10 to balance" vs "set balance to 10") + + +### Other Reasons an Initializer may be Unsafe to Re-Run + +- Emitting events that trigger off-chain actions (e.g., indexers that process each event exactly once) +- Overwriting a variable that other contracts or off-chain systems already depend on (e.g., resetting a registry address that live contracts are pointing to, or changing a config value that should be immutable after first init) + +### Rule + +Non-idempotent or unsafe-to-rerun behavior in `initialize()` / `reinitializer` functions is **disallowed** unless the consequences are explicitly acknowledged in a `@notice` comment on the function. The comment must explain why the non-idempotent behavior is safe given how callers use the function. + +Without this comment, the code must not be approved. + +### Review Checklist + +When reviewing changes to `initialize()` or its callers: + +1. **Is every operation in this initializer idempotent?** Assigning a variable to a fixed value is idempotent. Incrementing, appending, or calling external contracts may not be. +2. **Could overwriting any variable be unsafe?** Some values should only be set once — overwriting them during re-initialization could break other contracts or systems that depend on the original value. +3. **Can this contract be re-initialized?** Check for `reinitializer` modifier. If it only uses `initializer` (one-shot), the risk does not apply. +4. **If non-idempotent or unsafe behavior exists, is there a `@notice` comment acknowledging it?** The comment must explain why it's safe. If the comment is missing, flag it as a blocking issue. diff --git a/docs/ai/dev-workflow.md b/docs/ai/dev-workflow.md new file mode 100644 index 0000000000000..c2b6005fd1fc1 --- /dev/null +++ b/docs/ai/dev-workflow.md @@ -0,0 +1,31 @@ +# Development Workflow + +Common workflow guidance for AI agents working in the Optimism monorepo. Language-specific details are in [go-dev.md](go-dev.md) and [rust-dev.md](rust-dev.md). + +## Tool Versions + +All tool versions are pinned in `mise.toml` at the repo root. Always access tools through mise — never install or invoke system-global versions directly. Check `mise.toml` for current pinned versions when you need to know what's available. + +If mise reports the repo isn't trusted, ask the user to run `mise trust` — never trust it automatically. + +## Build System + +The repo uses [Just](https://github.com/casey/just) as its build system. Shared justfile infrastructure lives in `justfiles/`. Each component has its own justfile — run `just --list` in any directory to see available targets. + +## Before Every PR + +After running language-specific commit checks (lint, test): + +1. **Run affected tests broadly** — don't just test the package/crate you changed. Test packages that depend on it too. + +2. **Rebase on `develop`** — this is the default branch, not `main`: + ```bash + git fetch origin develop + git rebase origin/develop + ``` + +3. **Follow PR guidelines** — see `docs/handbook/pr-guidelines.md`. + +## CI + +Some tests require CI-only environment variables and are skipped locally. Check the test code for environment variable guards if a test behaves differently than expected. diff --git a/docs/ai/go-dev.md b/docs/ai/go-dev.md index 34a83a720d974..a271949e49315 100644 --- a/docs/ai/go-dev.md +++ b/docs/ai/go-dev.md @@ -1,5 +1,64 @@ # Go Service Development -This document provides guidance for AI agents working with Go services in the OP Stack. +Guidance for AI agents working with Go code in the Optimism monorepo. See [dev-workflow.md](dev-workflow.md) for tool versions, PR workflow, and other cross-language guidance. - +## Build System + +Each Go service has its own justfile — run `just --list` in any service directory to see available targets. + +```bash +# Build a single service (pattern: just .//) +just ./op-node/op-node + +# Build all Go components +just build-go +``` + +### Running Tests + +```bash +# Test a single service +cd && just test + +# Test specific packages +go test ./op-node/rollup/derive/... + +# Run the full test suite from the repo root +just go-tests +``` + +### Generating Mocks + +Each service justfile has a `generate-mocks` target: + +```bash +cd && just generate-mocks +``` + +## Linting + +The repo uses a **custom golangci-lint build** with additional analyzer plugins. The standard `golangci-lint` binary will not catch all issues — always lint through `just`. + +```bash +# Lint (also verifies compilation and module tidiness) +just lint-go + +# Lint with auto-fix +just lint-go-fix +``` + +The linter configuration is in `.golangci.yaml` — read it when you need specifics on which linters are enabled and how they're scoped. + +## Before Every Commit + +Run these checks before committing Go changes. Fix all issues — CI enforces zero warnings. + +1. **Lint** — this also verifies the code compiles and modules are tidy: + ```bash + just lint-go + ``` + +2. **Test** — run tests for changed packages: + ```bash + cd && just test + ``` diff --git a/docs/ai/rust-dev.md b/docs/ai/rust-dev.md index 881d73191ea3a..d3b47e2212ccf 100644 --- a/docs/ai/rust-dev.md +++ b/docs/ai/rust-dev.md @@ -1,5 +1,131 @@ # Rust Development -This document provides guidance for AI agents working with Rust components in the OP Stack. +Guidance for AI agents working with Rust code in the Optimism monorepo. See [dev-workflow.md](dev-workflow.md) for tool versions, PR workflow, and other cross-language guidance. - +## Workspace Layout + +All Rust code lives under `rust/`. This is a unified Cargo workspace — always run Rust commands from this directory. The workspace contains three main component groups: + +- **Kona** — Proof system and rollup node (`rust/kona/`) +- **Op-Reth** — OP Stack execution client built on reth (`rust/op-reth/`) +- **Op-Alloy / Alloy extensions** — OP Stack types and providers + +Check `rust/Cargo.toml` for the full workspace member list, dependency versions, and lint configuration. The Rust toolchain version is pinned in `rust/rust-toolchain.toml`. + +## Build System + +Run `just --list` in `rust/` to see all available targets. The key ones: + +```bash +cd rust + +# Build the workspace +just build + +# Build in release mode +just build-release + +# Build specific binaries +just build-node # kona-node +just build-op-reth # op-reth +``` + +### Running Tests + +Tests use `cargo-nextest` (not `cargo test`) for unit tests: + +```bash +cd rust + +# Run all tests (unit + doc tests) +just test + +# Unit tests only (excludes online tests) +just test-unit + +# Doc tests only +just test-docs +``` + +### Generating Prestates + +Kona prestates are built via Docker: + +```bash +cd rust +just build-kona-prestates +``` + +## Linting + +```bash +cd rust + +# Run all lints (format check + clippy + doc lints) +just lint + +# Individual lint steps +just fmt-check # formatting (requires nightly) +just lint-clippy # clippy with all features, -D warnings +just lint-docs # rustdoc warnings +``` + +Lint configuration lives in `rust/Cargo.toml` (workspace lints section), `rust/clippy.toml`, and `rust/rustfmt.toml`. + +### Formatting Requires Nightly + +Formatting uses a pinned nightly toolchain (defined as `NIGHTLY` in `rust/justfile`). If the nightly isn't installed: + +```bash +cd rust +just install-nightly +``` + +Then use `just fmt-fix` to auto-format, or `just fmt-check` to verify. + +### no_std Compatibility + +Many kona and alloy crates must compile without the standard library (for the fault proof VM). If you modify these crates, verify no_std builds: + +```bash +cd rust +just check-no-std +``` + +This builds affected crates for the `riscv32imac-unknown-none-elf` target. + +## Dependency Auditing + +The workspace uses `cargo-deny` for license, advisory, and dependency checks. Configuration is in `rust/deny.toml`. + +```bash +cd rust +just deny +``` + +## Before Every Commit + +Run these checks from `rust/`. Fix all issues — CI enforces zero warnings. + +1. **Lint** — this checks formatting, clippy, and doc lints: + ```bash + just lint + ``` + +2. **Test** — run tests for changed packages: + ```bash + just test-unit + ``` + +3. **no_std** — if you changed any proof, protocol, or alloy crate: + ```bash + just check-no-std + ``` + +## CI + +Op-reth requires `clang` / `libclang-dev` for reth-mdbx-sys bindgen. CI installs this automatically — if you see bindgen errors locally, install clang. + +## Skills + +- **Fix Rust Formatting** ([`.claude/skills/fix-rust-fmt/SKILL.md`](../../.claude/skills/fix-rust-fmt/SKILL.md)): Fixes `rust-fmt` CI failures by installing the pinned nightly toolchain and running `just fmt-fix`. Invoke with `/fix-rust-fmt`. diff --git a/docs/public-docs/.eslintignore b/docs/public-docs/.eslintignore new file mode 100644 index 0000000000000..d5ec38eaf732a --- /dev/null +++ b/docs/public-docs/.eslintignore @@ -0,0 +1 @@ +pages/_app.mdx diff --git a/docs/public-docs/.eslintrc.cjs b/docs/public-docs/.eslintrc.cjs new file mode 100644 index 0000000000000..93dde77570306 --- /dev/null +++ b/docs/public-docs/.eslintrc.cjs @@ -0,0 +1,26 @@ +module.exports = { + parserOptions: { + ecmaVersion: 'latest' + }, + extends: [ + 'plugin:mdx/recommended', + ], + rules: { + 'semi': ['error', 'never'] + }, + overrides: [ + { + files: ['pages/**/*.mdx'], + extends: [ + 'plugin:mdx/recommended' + ], + settings: { + 'mdx/code-blocks': true + }, + rules: { + 'no-unused-expressions': 'off', + 'semi': ['error', 'never'] + } + } + ] +} diff --git a/docs/public-docs/.node-version b/docs/public-docs/.node-version new file mode 100644 index 0000000000000..18c284172339c --- /dev/null +++ b/docs/public-docs/.node-version @@ -0,0 +1 @@ +20.11.0 \ No newline at end of file diff --git a/docs/public-docs/.npmrc b/docs/public-docs/.npmrc new file mode 100644 index 0000000000000..d6fbf514339d4 --- /dev/null +++ b/docs/public-docs/.npmrc @@ -0,0 +1,3 @@ +enable-pre-post-scripts=true +use-node-version=20.11.0 +use-pnpm-version=10.2.0 \ No newline at end of file diff --git a/docs/public-docs/.nvmrc b/docs/public-docs/.nvmrc new file mode 100644 index 0000000000000..18c284172339c --- /dev/null +++ b/docs/public-docs/.nvmrc @@ -0,0 +1 @@ +20.11.0 \ No newline at end of file diff --git a/docs/public-docs/.vale.ini b/docs/public-docs/.vale.ini new file mode 100644 index 0000000000000..8225a4bffe4f4 --- /dev/null +++ b/docs/public-docs/.vale.ini @@ -0,0 +1,32 @@ +# Top level styles +StylesPath = .vale/styles +MinAlertLevel = suggestion +IgnoredScopes = code, tt, img, url, a +SkippedScopes = script, style, pre, figure, code + +# Vocabularies +Vocab = Optimism + +# This is required since Vale doesn't officially support MDX +[formats] +mdx = md + +# MDX support +[*.mdx] +BasedOnStyles = Vale +Vale.Terms = NO # Enforces really harsh capitalization rules, keep off + +# `import ...`, `export ...` +# `` +# `...` +# `{ ... }` +TokenIgnores = (?sm)((?:import|export) .+?$), \ +(?)(?!`), \ +(<[A-Z]\w+>.+?<\/[A-Z]\w+>) + +# Exclude: +# `` +BlockIgnores = (?sm)^(<\w+\n .*\s\/>)$, \ +(?sm)^({.+.*}) + +CommentDelimiters = {/*, */} \ No newline at end of file diff --git a/docs/public-docs/.vale/styles/config/vocabularies/Optimism/accept.txt b/docs/public-docs/.vale/styles/config/vocabularies/Optimism/accept.txt new file mode 100644 index 0000000000000..f9fd90a3cb46c --- /dev/null +++ b/docs/public-docs/.vale/styles/config/vocabularies/Optimism/accept.txt @@ -0,0 +1,643 @@ +ACCOUNTQUEUE +ACCOUNTSLOTS +ACDC +ADDI +ADDIU +ADDU +ANDI +API's +APIs +Allnodes +Alphanet +Alphanets +Ankr +Apeworx +Arweave +BGEZ +BGTZ +BLEZ +BLOBPOOL +BLOCKLOGS +BLOCKPROFILERATE +BLOOMFILTER +BLTZ +BOOTNODES +Backports +Betanet +Betanets +Biconomy +Binance +Blockdaemon +Blockscout +Blockspace +Blocktimes +Bootcamp +Bootnodes +Brotli +Bundler +Bundlers +CCIP +COMPUTEPENDINGBLOCK +CPUs +Callouts +Celestia +Celestia's +Chainlink +Chainlink's +Chainstack +Chugsplash +Clabby +Coinbase +Collateralized +Comprensive +Config +Configurability +Crosschain +Crossmint +Cyber +DATACAP +DATADIR +DEXs +DISABLETXPOOLGOSSIP +DIVU +Defi +Defillama's +Devnet +Devnets +Discv +Drand +Drippie +ENRs +EIPs +ENABLEDEPRECATEDPERSONAL +EOAs +ETFs +ETHERBASE +ETHSTATS +EVM's +EVMTIMEOUT +EXITWHENSYNCED +EXTRADATA +Eigen +Endianness +Erigon +Ethereum +Ethernity +Ethernow +Etherscan +FLASHBLOCKS +FPVM +FPVMs +Farcaster +Faultproof +Flashblocks +Flashbots +Fraxtal +Funct +Fusaka +GASCAP +GCMODE +GLOBALQUEUE +GLOBALSLOTS +GWei +Gelato +Geth +Gitcoin +Grafana +HDDs +HEALTHCHECK +HHDs +HISTORICALRPC +HISTORICALRPCTIMEOUT +HOLESKY +HSMs +Holesky +Hoodi +IERC +IGNOREPRICE +INFLUXDBV +IPCDISABLE +IPCPATH +IPFS +Immunefi +Inator +JALR +JOURNALREMOTES +JSPATH +Jito +Keccak +Kona +LPs +Lisk +MAXAGE +MAXBACKUPS +MAXPEERS +MAXPENDPEERS +MAXPRICE +MBs +MEMPROFILERATE +MFHI +MFLO +MINFREEDISK +MINSUGGESTEDPRIORITYFEE +MIPSEVM +MOVN +MOVZ +MTHI +MTLO +MULT +MULTU +Mainnet +Mainnets +Merkle +Mgas +Minato +Mintable +Mintplex +Mips +Mitigations +Monitorism +Moralis +Mordor +Multichain +NETRESTRICT +NETWORKID +NEWPAYLOAD +NFTs +NOCOMPACTION +NODEKEY +NODEKEYHEX +NODISCOVER +NOLOCALS +NOPREFETCH +NOPRUNING +NOSYNCSERVE +NVME +Nodies +Numba +OPCM +Openfort +Ownable +PGAs +POAP +POAPs +PPROF +PREIMAGES +PREVRANDAO +PRICEBUMP +PRICELIMIT +Pectra +Pectra's +Peerstore +Permissioned +Permissionless +Perps +Peta +Pimlico +Precommitments +Predeployed +Predeploys +Preimage +Preinstalls +Prestate +Protip +Proxied +Proxyd +Prysm +Pyth +Pyth's +QRNG +Quicknode +REJOURNAL +REMOTEDB +REQUIREDBLOCKS +RPCPREFIX +RPGF +RWAs +Reemitting +Regenesis +Reimagine +Relayer +Reown +Reown's +Reth +Rollup +Rollups +Routescan +Runbooks +SEPOLIA +SEQUENCERHTTP +SLLV +SLTI +SLTIU +SLTU +SRAV +SRLV +SSDs +SUBU +SUPERCHAIN +SYNCMODE +SYNCTARGET +SYSCON +Schnorr +Sepolia +Servicer +Solana +Soneium +Spearbit +Stablecoins +Sunnyside +Superchain +Superchain's +Superlend +Superloans +Superscan +Superseed +Supersim +TXPOOL +Teku +Tenderly's +Testnet +Testnets +Timeboost +Twei +UPNP +Unichain +Unprotect +Upon's +VERKLE +VHOSTS +VMDEBUG +VMODULE +VMs +Viem +Viem's +XORI +ZKPs +ZKVM +Zora +absolutePrestate +accountqueue +accountslots +airgap +allocs +allowlist +alphanet +alphanets +altda +architected +archiver +arg +async +attestors +authrpc +autorelay +autorelayer +backport +backporting +basefee +batch_inbox_address +batcherAddr +batcher +bcde +betanet +betanets +blobpool +blobspace +block_time +blockchain +blockchains +blockhash +blocklists +blocklogs +blockprofilerate +blockspace +blocktime +blocktimes +bloomfilter +bool +boolean +bootnode +bootnodes +bottlenecked +brotli +bundler +bundlers +calldata +callout +callouts +canyon_time +cdef +chainConfigs +chaindata +channel_timeout +channelTimeout +chaosnet +cheatcodes +ckzg +codebases +collateralized +composability +composable +compr +computependingblock +conductor_leader +confs +const +corsdomain +counterfactually +cpu +crosschain +crypto +cryptocurrencies +da_challenge_address +da_challenge_window +da_resolve_window +daserver +datacap +datadir +delegatecall +delta_time +deletable +deposit_contract_address +deployer +dev +devnet +devnets +devs +direnv +disabletxpoolgossip +discv +dripcheck +ecotone_time +enabledeprecatedpersonal +enablement +endianness +enginekind +enode +enum +env +erigon +eth +etherbase +ethstats +evm +evmtimeout +executability +exfiltrate +exitwhensynced +extensibly +extradata +fd +fdlimit +filepath +flashblock +flashblock's +flashblocks +forkchoice +formatEther +fulfillRandomness +gascap +gasLimit +gasprice +gaslessly +gcmode +getters +gifs +globalqueue +globalslots +gokzg +golang +goroutine +graphql +growthepie +hardcoded +hardfork +hardforks +healthcheck +healthchecks +historicalrpc +historicalrpctimeout +holocene +holesky +hostnames +http +ignoreprice +infura +inator +influxdbv +initcode +interop +ipc +ipcdisable +ipcfile +ipcpath +journalremotes +json +jspath +jwtsecret +keystore +keystores +leveldb +lightkdf +linux +liveness +logfile +logfmt +lookups +max_sequencer_drift +maxage +maxbackups +maxpeers +maxpendpeers +maxprice +mempool +mempools +memprofilerate +merkle +minfreedisk +minsuggestedpriorityfee +monitorism +monorepo +multiaddr +multichain +multiclient +multisig +multisigs +mutex +namespace +nat +nethermind +netrestrict +networkid +newpayload +npm +nocompaction +nodekey +nodekeyhex +nodename +nodiscover +nolocals +noprefetch +nopruning +nosyncserve +offchain +onboarding +onchain +onlyreqtostatic +opchaina +opchainb +opcm +opcmAddress +oplabs +opnode's +optimismSepolia +outfile +outperformance +ownable +parseEther +patricia +pcscd +pcscdpath +peerstore +peerstores +performant +permissioned +permissioning +permissionless +permissionlessly +pid +postmerge +pprof +pragma +precommitments +precompiled +preconfigured +predeploy +predeployed +predeploys +prefetch +prefunded +preimage +preimages +preinstall +preinstalls +preload +prestate +prestates +pricebump +pricelimit +productionize +productionized +protocol_versions_address +proveHash +proveReceipt +proxied +proxyAdmin +proxyd +quicknode +quickstarts +rebalancing +reemit +regenesis +regolith_time +rejournal +remotedb +replayability +replayor +reposts +reproven +requiredblocks +resync +reth +rollouts +rollups +rpc +rpckind +rpcprefix +rpcs +runbooks +safedb +semver +sepolia +seq_window_size +seqnr +sequencer's +sequencerhttp +serv +signup +smartcard +snapshotlog +snapsync +solady +soyboy +stablecoins +startin +statefulset +structs +subcommand +subcommands +subcomponents +subgame +subheaders +subsecond +subtree +subtrees +superchain +superchains +supersim +syncmode +synctarget +syscall +syscalls +system_config +targetChain +testnet +thirdweb +threadcreate +timeseries +tls +topline +trie +triggerable +trustable +trustlessly +trustrpc +tx +txfeecap +txmgr +txns +txpool +txproxy +uncensorable +uncountered +undercollateralize +unsubmitted +upgradeability +url +usb +use_plasma +useInterop +validator +validators +verifier's +verkle +vhosts +viem +viem's +vmdebug +vmtrace +vmodule +websocket +websockets +wei +withdrawal_storage_root +withdrawalHash +withdrawalReceipt +ws +xlarge +zlib +zora +Uniswap +uniswap +stablecoin \ No newline at end of file diff --git a/docs/public-docs/DOCS_CONTRIBUTING.md b/docs/public-docs/DOCS_CONTRIBUTING.md new file mode 100644 index 0000000000000..1f9f5a4c8747a --- /dev/null +++ b/docs/public-docs/DOCS_CONTRIBUTING.md @@ -0,0 +1,104 @@ +# Contributing to Optimism Docs + +Thanks for taking the time to contribute! ❤️ + +## Table of Contents +- [Contributing to Optimism Docs](#contributing-to-optimism-docs) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Development Setup](#development-setup) + - [Contributing Process](#contributing-process) + - [File Architecture](#file-architecture) + - [Content Guidelines](#content-guidelines) + - [Local Testing](#local-testing) + - [Pull Request Process](#pull-request-process) + - [Before Submitting](#before-submitting) + - [Submission Guidelines](#submission-guidelines) + - [Review Process](#review-process) + - [Code of Conduct](#code-of-conduct) + - [Additional Ways to Contribute](#additional-ways-to-contribute) + +## Overview + +Optimism's documentation is open-source and hosted on GitHub in the `ethereum-optimism/docs` repository. The documentation is rendered at [docs.optimism.io](https://docs.optimism.io). You can contribute either by: +- Forking the `docs` repository and working locally +- Using the "Suggest edits" button on any documentation page for smaller updates + +All contributions, pull requests, and issues should be in English at this time. + +## Getting Started + +### Prerequisites +- Basic knowledge of Git and GitHub +- Familiarity with Markdown +- Understanding of technical documentation principles +- Node.js and npm installed + +### Development Setup +1. Install [pnpm](https://pnpm.io/installation) +2. Run `pnpm i` to install dependencies +3. Run `pnpm dev` to start development server +4. Visit [localhost:3000](http://localhost:3000) + +You can now start changing content and see the website updated live each time you save a new file. 🤓 + +## Contributing Process + +### File Architecture + +See the [mintlify docs](https://www.mintlify.com/docs/organize/navigation). + +**Warning**: The `public` folder contains `robots.txt` and `sitemap.xml` for SEO purposes. These files are maintained by the Documentation team only. + +### Content Guidelines +We use [mintlify](https://www.mintlify.com/docs) to power our docs. + +Please refer to our comprehensive [Style Guide](STYLE_GUIDE.md) for detailed formatting instructions. + +### Local Testing + +Follow these [docs](https://www.mintlify.com/docs/installation) for local changes. + +## Pull Request Process + +### Before Submitting +- Fix any reported issues +- Verify content accuracy +- Test all links and references +- Target the `mintlify` branch (`main` needs to be cleaned up) + +### Submission Guidelines +1. Create a [new pull request](https://github.com/ethereum-optimism/docs/issues/new/choose) +2. Choose appropriate PR type or use blank template +3. Provide clear title and accurate description +4. Add labels + +> **Important**: Add `flag:merge-pending-release` label if the PR content should only be released publicly in sync with a product release. + +> **Tip**: Use "[Create draft pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)" if your work is still in progress. + +### Review Process +1. Assignment to Documentation team member +2. Technical review for accuracy +3. Quality and scope alignment check +4. Minimum 1 reviewer approval required +5. Reviewers will either approve, request changes, or close the pull request with comments +6. Automatic deployment after merge to [docs.optimism.io](https://docs.optimism.io) + +## Code of Conduct +- Be respectful and inclusive +- Follow project guidelines +- Provide constructive feedback +- Maintain professional communication +- Report inappropriate behavior + +## Additional Ways to Contribute +Even without direct code contributions, you can support us by: +- ⭐ Starring the project +- 🐦 Sharing on social media +- 📝 Mentioning us in your projects +- 🗣️ Spreading the word in your community + +Thank you for contributing to Optimism Docs! 🎉 diff --git a/docs/public-docs/README.md b/docs/public-docs/README.md new file mode 100644 index 0000000000000..b27d240915792 --- /dev/null +++ b/docs/public-docs/README.md @@ -0,0 +1,11 @@ +# Optimism Docs + +This repo houses the Optimism Docs located at [docs.optimism.io](https://docs.optimism.io/). All documentation-related updates and new content will be tracked and maintained in this repo. + +## Local Development + +Follow these [mintlify docs](https://www.mintlify.com/docs/installation) to preview and edit the documentation page locally. + +## License + +This project is licensed under the [MIT License](https://github.com/ethereum-optimism/optimism/blob/develop/LICENSE). diff --git a/docs/public-docs/REDIRECTS_GUIDE.md b/docs/public-docs/REDIRECTS_GUIDE.md new file mode 100644 index 0000000000000..eb1fb5a58b926 --- /dev/null +++ b/docs/public-docs/REDIRECTS_GUIDE.md @@ -0,0 +1,178 @@ +# Mintlify Redirects Guide + +A comprehensive guide for managing redirects in Mintlify documentation (migrated from Nextra). + +--- + +## Table of Contents + +- [Overview](#overview) +- [How to add redirects](#how-to-add-redirects) +- [Examples](#examples) +- [Limitations](#limitations) + +--- + +## Overview + +Redirects are essential when restructuring documentation (IA refactor) to ensure: + +- Old links don't break +- Search engine rankings are preserved +- User bookmarks continue to work +- External links remain valid + +**Location**: All Mintlify redirects are configured in `/docs.json` + +--- + +## How to add redirects + +### Step 1: Open `docs.json` + +Navigate to the root of the project and open `/docs.json`. + +### Step 2: Find the `redirects` array + +Look for the `redirects` section (around line 33): + +```json +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "mint", + "name": "Optimism Documentation", + ... + "redirects": [ + // Redirects go here + ], + ... +} +``` + +### Step 3: Add your redirect + +Add a new object to the array: + +```json +{ + "redirects": [ + { + "source": "/old-page-path", + "destination": "/new-page-path" + } + ] +} +``` + +### Step 4: Test locally + +```bash +# Run the dev server +mint dev + +# Test the redirect +# Navigate to http://localhost:3000/old-page-path +# Should redirect to /new-page-path +``` + +--- + +## Examples + +### Example 1: Simple page redirect + +**Scenario**: You renamed `getting-started.mdx` to `quickstart.mdx` + +```json +{ + "source": "/getting-started", + "destination": "/quickstart" +} +``` + +### Example 2: Section restructure + +**Scenario**: Moved all tutorials from `/docs/` to `/tutorials/` + +```json +{ + "source": "/docs/deploy-contract", + "destination": "/tutorials/deploy-contract" +}, +{ + "source": "/docs/setup-wallet", + "destination": "/tutorials/setup-wallet" +}, +{ + "source": "/docs/bridge-tokens", + "destination": "/tutorials/bridge-tokens" +} +``` + +**Note**: You need one redirect per page (no wildcards!) + +### Example 3: Deep restructure + +**Scenario**: Moved `app-developers/tools/supersim.mdx` to `interop/tools/supersim.mdx` + +```json +{ + "source": "/app-developers/tools/supersim", + "destination": "/interop/tools/supersim" +} +``` + +### Example 4: Deleted page + +**Scenario**: Deleted a page and want to redirect to a related page + +```json +{ + "source": "/deprecated-feature", + "destination": "/new-feature-overview" +} +``` + +### Example 5: External redirect + +**Scenario**: Moved content to external documentation + +```json +{ + "source": "/old-specs", + "destination": "https://specs.optimism.io" +} +``` + +--- + +## Limitations + +### No Wildcard support + +**Nextra** (supported): + +``` +/docs/* /tutorials/:splat 301 +``` + +**Mintlify** (NOT supported): + +```json +{ + "source": "/docs/*", + "destination": "/tutorials/*" +} +``` + +**Workaround**: Create individual redirects for each page: + +```json +{ + "redirects": [ + { "source": "/docs/page1", "destination": "/tutorials/page1" }, + { "source": "/docs/page2", "destination": "/tutorials/page2" }, + { "source": "/docs/page3", "destination": "/tutorials/page3" } + ] +} +``` diff --git a/docs/public-docs/STYLE_GUIDE.md b/docs/public-docs/STYLE_GUIDE.md new file mode 100644 index 0000000000000..c52315d01d220 --- /dev/null +++ b/docs/public-docs/STYLE_GUIDE.md @@ -0,0 +1,443 @@ +# Docs Style Guide + +This guide explains how to write technical content for Optimism Docs using a consistent voice, tone, and style. + +This Style Guide aims to assist Optimists in writing technical content with a consistent voice, tone, and style. See the [glossary](https://docs.optimism.io/op-stack/reference/glossary) for an alphabetical listing of commonly used words, terms, and concepts used throughout the technical docs and across the OP Collective. + +This doc doesn't cover all questions or use-cases. Our guide is based on the [Microsoft Writing Style Guide](https://learn.microsoft.com/en-us/style-guide/welcome/). Please reference their guide for any use-case or situation we do not cover here. + +* For docs-related questions or comments, create an issue in the [docs repo](https://github.com/ethereum-optimism/docs/issues). +* For support-related questions or comments, create an issue in the [developers repo](https://github.com/ethereum-optimism/developers/issues). + +## Table of Contents + +* [Files, Folders, and Naming Conventions](#files-folders-and-naming-conventions) +* [Writing Style](#writing-style) +* [Accessibility](#accessibility) +* [Content Organization](#content-organization) +* [Links](#links) +* [Content Types](#content-types) +* [General Formatting](#general-formatting) + +## Files, folders, and naming conventions + +### Folder structure + +The folder structure for the [docs.optimism.io](https://github.com/ethereum-optimism/docs) repository is organized into several high-level categories. + +The left sidebar (side navigation) is managed in the `docs.json` file, which should be edited only for adding or deleting pages. Frequent edits may lead to merge conflicts due to ongoing content updates. Accept changes from others when committing a PR. + +Don't worry if you're not sure where in the left sidebar a new topic belongs. Do your best and when you submit your PR, the Developer Relations team will edit the `docs.json` file and determine the right placement. + +The right sidebar (page TOC) is created automatically for all the H2 and H3 headings on a page. + +### Filenames + +In general, filenames should be as short as possible (~2 to 4 words) and all lower-case. Add a hyphen (-) between each word. + +**Example:** `writing-a-guide.mdx` or `run-node.mdx` + +### File paths + +File paths, when mentioned **within** a docs page, should be formatted as code snippets for readability and wrapped in backticks. + +**Example**: `/source/docs/assets/images/` + +## Writing style + +### Voice and tone + +Write in a friendly, yet professional tone. We are upbeat, knowledgeable, and **optimistic** about the development of the Optimism Collective, which we try our best to convey in our technical documentation. + +### Clear and concise language + +* Be consistent. Use the same terminology, voice, and tone throughout the documentation. +* Be concise. Focus on providing key information and avoid including superfluous information. +* Use language the audience understands. Although it's challenging to avoid jargon in the web3 space, several strategies can enhance language comprehension, such as: + * checking the glossary to ensure terminology is clearly defined before using it; and + * regularly updating the glossary +* Write in an action-oriented style. Focus on helping the user complete the task at hand by writing in the active voice. An active voice is more direct and reduces ambiguity. Avoid passive voice as it is often vague and creates awkward sentences. +* Avoid gender-specific language. Use the imperative. This form of a verb lets you use the second person (you, your) rather than the third person (him, her, she, his). + +### Capitalization + +See below for when to use title or sentence case. + +* Avoid using all caps as it slows down reading comprehension. + +* Capitalize proper nouns in sentences. + **Example**: I use Visual Studio on my local machine. + +* Use title case for all headers (H1, H2, H3) and when referring to buttons, tab names, page names, and links within the documentation. Note: The actual text on buttons, links, pages, or tabs need not be in title case—only the references within the docs. + + > **Examples of Title Case:** + > * Domains For the Development Environment (header) + > * Select **Make Owner**. + > * Click **Clear Caches**. + > * Select the **Settings** tab. + +* Use sentence case for body content and short phrases, even when the content is a link. Sentence case means you only capitalize the first letter of the sentence. + **Example:** If you're trying to figure out how to do something specific as a node operator, you might search our collection of tutorials or [suggest a new one](https://github.com/ethereum-optimism/docs/issues). + +* Use lowercase in code snippets by default, unless the code block uses capitalization (e.g., for the name of a function or variable) and you are referring to the function or variable elsewhere within the technical documentation. + **Examples**: Run `git add` or Import `useState` + + > When in doubt, follow the code base because exact capitalization is necessary in order for the code to compile. + +## Accessibility + +When creating content, ensure it is accessible to screen-reader users, visually impaired individuals, and those using a mouse or keyboard. For more information regarding accessibility guidelines, see [W3C Web Content Accessibility Guidelines 2.0](http://www.w3.org/TR/UNDERSTANDING-WCAG20/Overview.html#contents). + +### Alt-text + +* Provide alt text for all images so that the screen reader can interpret the purpose of the image and convey that to the user. The alt text should include any content shown in the image so that screen reader can read it to the user. +* Provide alt text for videos by modifying the title attribute of any embedded video content or iframe. The title attribute serves as the alt text for screen readers to provide descriptive information. Video content with speaking or narration must include closed captions. + +### Captions + +* Provide captions for visual elements/objects whenever possible, so visuals are accessible to all users, regardless of ability. This includes images or screenshots, animated GIFs, promo and tutorial videos, tables, charts, mermaid diagrams, and code blocks. +* Ensure that captions can be translated into major languages. + +### Images + +* Don't use images of text, code samples, or terminal output. Use actual text. +* Use SVG instead of PNG if available. SVGs stay sharp when users zoom in on the image. + +## Content organization + +We aim to use consistent organization that is also user-centered and accessible. This requires intentional work and planning to group technical content into sections instead of using long, dense paragraphs. This also gives readers a visual rest from a usability perspective and improves reading comprehension. + +* Use structured headings (H1, H2 or #, ##) to guide readers through the technical documentation. +* Use numbered lists for chronological steps. +* Use bulleted lists to visually separate content that does not require a specific order. +* Format text for optimal readability (bold vs. italics). Avoid using italics in web content as it decreases readability. For instance, **bold** is appropriate to use when referring to a specific button or page name in technical documentation. +* Organize technical content to cover only one major concept or task at a time. + * **General rule of thumb**: documents with more than 3 levels of structured headings (H4 or ####) and/or more than 20 minutes estimated reading time (ERT) need revisions will typically involve editing for conciseness, splitting the document into multiple pages, or both. + * Revisions will usually require editing for concision, breaking the document apart into multiple pages, or some combination of the two. +* Organize content based on the audience's varying needs and prior knowledge. If pre-requisite knowledge is necessary to complete a task or understand a concept, then share it with users (including links to learn more), so they can easily get up to speed. + +### Meta tags + +* Define the meta **title**, **language**, and **description** for each page to improve SEO ranking of the docs. Place meta tags at the top of the page before the H1 tag, with 3 dashes surrounding the block of text on either side. + +* Set the meta title by reusing the H1 page title, but since meta titles are not visually displayed on the page to users, the H1 page title is also required. + + > **NOTE:** SEO guidelines suggest that meta page titles differ slightly from H1 page titles, but this is handled automatically by our site platform. So, please match H1 page titles to meta page titles for ease of documentation. + +* Set the language attribute for accessibility and usability (for screen readers) but also to make it easier to enable language localization support in the future. + +* Write meta descriptions as concise overviews (100-150 characters) of the most relevant content of the page. + +> **Example Meta section of docs:** +> ``` +> --- +> title: Supercharge Your App with Account Abstraction +> lang: en-US +> description: This guide explains how account abstraction enables users to utilize smart contracts to build, onboard, and scale apps. +> --- +> ``` + +### Page titles (H1) + +* Create concise page titles and format as H1. The title should be able to fit on 1-2 lines. +* Every page must have an H1 heading, in addition to the page title being defined in the SEO meta tags. +* H1 heading is reserved for page titles, so avoid using H1 in any other place on the page. +* For tutorials and quick starts, use task-based page titles starting with gerunds (verb ending in "ing"). + **Examples**: `Creating Your Own L2 Rollup` or `Running a Node` + +### Headings and TOC + +* Use an imperative verb for headings or subheadings in a document, and not a gerund (a verb ending in "ing"). Headings should be shown in H2 tags, and subheadings should be shown in H3 tags. + +* Outline the page content according to main topics first. Those will be your headings (tagged as H2). If there are subtopics that belong under a category, display those as subheaders (tagged as H3). + + > **Example:** + > * (H1) Supporting OP Mainnet in Your Wallet (H1 page title uses "ing" verb ending) + > * (H2) Connect to OP Mainnet (H2 does not use "ing" verb ending) + > * (H2) Locate Canonical Token Addresses (second H2 does not use "ing" verb ending) + +* Use headings in a logical manner, and the site will automatically generate anchor links for H2 and H3 tags and place them in a Table of Contents (TOC) in the right column. + +* Avoid H4 levels and above within guide and template pages. As stated elsewhere in this style guide, technical documents with more than 3 levels of structured headings (H4 or ####) usually indicates clarity, organization, or structural issues and should be revised. + +* H4 headings are reserved (at this time) for glossary terms. This standardization will make it easier for us to extend glossary functionality across the docs in the future, such as tool tips. + +### Listing prerequisites (before you begin) + +* Add a "Before You Begin" section at the top of the document if there are tasks a user needs to complete before continuing with the current task, e.g. installing a module, downloading a software update, etc. +* Use the title "Before You Begin" and format as H2. It should follow the page overview/intro section or table of contents. +* Include all the tasks the user needs to complete, including links to aid in usability. Use a bulleted list for more than 2 prerequisite items. + + > **Example:** + > (H2) Before You Begin + > You'll need to enable the ApacheSolr module. Visit the [ApacheSolr](https://drupal.org/project/apachesolr) page on [Drupal.org](http://drupal.org/) for more information. + +### Callouts + +* Use callouts to direct users to information necessary to complete a task or information of special importance. When adding a callout to a document, use sentence case. +* Use the correct callout type based on the type of issue: a) info/general, b) warning, c) error. Our documentation platform supports 4 different callout types. + * The default and info callouts are used to share non-sensitive, non-breaking info with users, such as suggestions or best practices that might make the installation or deployment easier. + * Warning callouts should be used to indicate important info, such as when a product or code will be deprecated. + * Error callouts are reserved for critical issues that cannot be undone or can result in breaking changes, such as when data might be permanently deleted or lost. +* Use callouts sparingly as too many can be confusing to readers. **As a general rule of thumb:** pages with more than 2 callouts likely needs revision and/or a discussion with an Optimism developer or product manager to ensure guide content accuracy. + +### Code samples + +* Use code samples as often as possible to help explain concepts. Can be used in guides or tutorials, but every tutorial should have at least one code sample to be useful to developers. +* Any bits of code should be wrapped in backticks and use built-in syntax highlighting. Most documentation platforms automatically apply syntax highlighting when properly defined inside the code block. + +**Example**: This markdown or MDX file + +```js +console.log('hello, world') +``` + +will render this code snippet in `javascript` with proper syntax highlighting. + +* To improve readability and accessibility even further, consider the following user-centered options for code blocks: + * adding a filename or title to the code block + * adding a caption or description (shown above) + * adding or showing the line numbers within the code block (easily refer to a certain code lines within the documentation) + * highlighting lines or strings in the code block to draw user's attention to specific areas + +### Images, screenshots, & icons + +* Images, screenshots, and icons are stored in the `public/img` directory in the root folder. +* Every image and screenshot should have descriptive alt text. +* Screenshots should clearly capture the content being discussed in the guide or tutorial. + * Use more than one screenshot if space is an issue and/or to better coordinate screenshots with a particular location or tutorial step in the technical documentation. + * Use arrows and callouts to help explain the elements in the image that are not already highlighted by the interface. Do not use callouts to highlight the environment and tool, as they are apparent. +* Images can be inserted two ways: embedded in the `.mdx` file or imported. Use the latter option when you need to add styling to the image, such as a specific height or width, but note that the file path changes when the image is imported. +* File paths to images will vary based on where the image is located and how the image is used (e.g., embedded vs imported into the mdx page — see below for an example). +* **Example (embedded)**: `![Deposit Flow Diagram](/public/img/op-stack/protocol/deposit-flow.png)` +* **Example (imported)**: + +```jsx +Deposit Flow Diagram +``` + +* Icons come from [Remix](https://remixicon.com/) to maintain consistency across the docs. Use Optimism Red `FF0420` to color icons before downloading and store icons in `public/img/icons` directory. + +### Videos + +* Use videos sparingly and only for more complex tasks. +* Write meaningful alt text for videos and animations. Include a caption too, if possible. +* Promo videos should be as short as possible, typically no more than 30 seconds. +* Animated gifs should also be short, generally between 10-60 seconds. +* Tutorial videos are considered educational content and should not exceed 10 minutes, based on instructional design best practices. +* Tutorial videos must be hosted by a third party (YouTube, Vimeo, etc.) and include closed captions for accessibility. +* Embed videos with an iframe and add/modify the `title` attribute as needed to make more meaningful `alt-text`, which improves accessibility for screen readers. +* **Example of video with title for alt-text:** + +```html + +``` + +## Links + +Developers trust that we will lead them to sites or pages related to their reading content. In order to maintain that trust, it's important that links are transparent, up-to-date, and lead to legitimate resources. + +### Internal links + +* Internal links are automatically generated based on the H2 and H3 title tags. + +* When linking to a specific H2 or H3 section of a page, the anchor links are always lowercase with dashes taking the place of spaces. + **Example**: `(H3) Test your application` is converted to `test-your-application` as an anchor link. + +* Use anchor links, whenever possible, to guide users to a specific page and location in the technical documentation. This reduces cognitive load and improves overall usability. + +* To link to an anchor, such as an H3 tag within a page, you need to append it to the page name preceded by `#`, like this **example**: `[any descriptive text](/app-developers/tutorials/tokens/deploy-superchain-erc20)`. + +### Linking across pages + +* Use absolute or relative links when linking across pages in the site. Absolute links are cleaner and easier to work with when pages are nested, so they are the recommended option. + **Examples**: absolute link `(/op-stack/protocol/bridging/deposit-flow)` versus relative link `(../../protocol/deposit-flow)` + +* Use the exact title of the page when linking to it in a sentence, whenever possible, and display it in title case. The link should use default styling with no other formatting (bold, italics, quotations). + **Example**: Be sure to check out the [OP Stack Getting Started Guide](https://docs.optimism.io/op-stack/protocol/getting-started) to get up to speed. + +* Use sentence case when linking to an article without using the exact article title. This causes minimal disruption to the flow of the sentence. The link should use default styling. + **Example**: For something more advanced, we recommend reading through our page on [sending data between L1 and L2](https://docs.optimism.io/app-developers/bridging/messaging). + +* Use `detailed instructions` link format to refer users to another article with detailed instructions that are important for completing the current task. + **Example**: For detailed instructions, see [Article Title](https://docs.optimism.io/app-developers/bridging/standard-bridge). + +* Use the `more information` link format to guide users to a suggested reading that the user may find helpful because it is related to the task/topic, but not essential for completing the current task. + **Example**: For more information, see [Article Title](https://docs.optimism.io/app-developers/bridging/standard-bridge). + +## Content types + +Content types help manage technical content by defining the purpose and common structure for each file type. All content types used in these technical docs have attributes or properties, as defined below. + +| Document type | Purpose | Examples | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | +| Overviews or Explainers | General introduction to a product or feature, provides a happy-path for readers | [Superchain Explainer](https://docs.optimism.io/superchain/superchain-explainer) | +| Guides | Explain what things are and how they work | [Standard Bridge Guide](https://docs.optimism.io/app-developers/bridging/standard-bridge) | +| Tutorials | Provide task-oriented guidance with step-by-step "learn by doing" instructions | [Bridging ERC-20 tokens with viem](https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-bridge-erc20) | +| FAQs | Address frequently asked questions | [FAQ: OP Mainnet Security Model](https://docs.optimism.io/op-stack/security/faq-sec-model) | +| Troubleshooting | List common troubleshooting scenarios and solutions | [Troubleshooting: Run a Node](https://docs.optimism.io/operators/node-operators/management/troubleshooting) | +| Reference | Provide deep, theoretical knowledge of the internal workings of a system, such as API endpoints and specifications | [Node and RPC Providers](https://docs.optimism.io/app-developers/tools/connect/rpc-providers) | + +### Overviews (or Explainers) + +Overviews or explainers provide a general introduction to a product or service and a happy path for readers on how to navigate a particular set of docs or related features. When done well, overviews and explainers accomplish two essential tasks for users: + +1. **organize** the related set of documentation pages to keep developers from getting overloaded by too much information, and +2. **establish a 'happy path'** to direct developers to the right pages in the documentation set based on their user scenario or use case. + +Tables work really well for overview pages, which can both organize page content and establish the happy path for specific developer use cases. Alternatively, headings (H2) can be used, organized by use case, user scenario, or directory page titles. + +To maintain consistency, overviews should include all these items: + +* overview of what the page will cover +* content organized into discrete sections, by use case, user scenario, or page titles +* clear headings for each section written in parallel style +* next steps section: links to related guides, tutorials, etc. + +### Guides + +Guides use simple language to explain concepts or complex features, such as what things are and how they work. They are great for on-boarding beginning or novice developers. For developer-focused guides, it is best practice to include illustrations, diagrams, and code samples whenever possible to help explain concepts. + +To maintain consistency, guides should include all these items: + +* overview of what the guide will cover +* guide content organized into discrete sections +* clear headings for each section written in parallel style +* one or more visual elements (e.g., images, screenshots, illustrations, and/or code samples) +* next steps section: links to related tutorials, other guides, troubleshooting, etc. + +### Tutorials + +Tutorials are task-oriented pages or videos that include practical, step-by-step instructions for completing a task, activity, or objective. Tutorials are more interactive and hands-on than other technical documentation, often including practical examples, exercises, and demonstrations to help developers learn by doing. + +To maintain consistency, tutorials should include all of these items: + +* overview of what the tutorial will cover, including context for what problem is being solved +* goals of the tutorial (i.e., what the developer will learn or the finish state) +* before you begin section (also known as prerequisites) +* list of steps involved in the task +* images, screenshots, or code samples (ideally, at least one of these for each step) +* next steps section: links to other tutorials, related guides, troubleshooting, etc. + +When writing tutorials or quick starts, steps should be written in parallel style, as sentences with imperative verbs and not gerunds (ending in "ing"). + +> **Example:** +> * Step 1: **Create** Your Site +> * Step 2: **Choose** Your Framework +> * Step 3: **Visit** the Dev Environment + +### FAQs + +Whenever possible, we should avoid using FAQs due to their lack of readability and difficulty of maintenance. + +FAQs address common questions developers have/might ask about a product, feature, or service. FAQs should be written from the developer's perspective. If there are more than two steps in the answer, use a numbered list. Place FAQs onto their own separate pages, whenever possible, and link to the FAQ from within the respective guide or tutorial. + +To maintain consistency, FAQs should include all of these items: + +* overview of what product or service the FAQ is about +* questions (usually H2 or H3, depending on page) +* answers (not a heading, usually normal paragraph text beneath the question) + +The heading level for FAQs will vary based on if it's an FAQ-only doc or if FAQs are included as part of a larger document. In either case, try not to exceed H3 level when organizing the document. + +**Example of a simple FAQ** + +> **Does Optimism Support ERC-721?** +> Yes. We have complete and total support for ERC-721 standard. + +**Example of an instructional FAQ** + +> **How do I change my Optimism password?** +> 1. Select **Sites & Accounts** from the user account drop-down menu. +> 2. Click the **Account** tab. +> 3. Select **Change Password**. +> 4. Enter the information, and click **Save**. + +Include a category heading when you need to group related FAQ content. Category headings are optional, but helpful, for longer FAQs. + +### Troubleshooting guides + +Troubleshooting guides list common problems a developer might encounter while using a product or service, identifies the symptoms, and offers solutions to these problems. It is important to accurately capture symptoms produced by the system or interface (e.g., error messages, unexpected page refresh/reload, spinning wheel, etc.), so developers know if their system response aligns with one of the common problems identified in the troubleshooting guide. + +A troubleshooting guide should be limited to identifying common problems for one particular product or service. + +To maintain consistency, troubleshooting guides should include all of these items: + +* overview of what product or service the troubleshooting guide will cover +* common problem (usually H2 or H3, depending on page) + * cause of problem + * symptom of problem (system response, captured as image/screenshot) +* solution (step-by-step) +* next steps section: links to related tutorials, other guides, etc. + +### Technical reference + +Technical references provide deep, theoretical knowledge of the internal workings of a system. These often come in the form of requirements or system specifications developers need to run the product efficiently, so lists and tables, such as API endpoints and error codes are commonplace. +A technical reference page is usually quite long, so it is best practice to embed a table of contents (TOC) at the top of the page to help organize material for developers. From a usability perspective, this practice shows developers what will be covered in the reference in advance, and allows them to jump to a specific section, if desired. + +To maintain consistency, technical references should include all these items: + +* overview of what the reference will cover +* table of contents +* reference content organized into discrete sections, with parallel headings +* one or more visual elements (e.g., flow diagrams, illustrations, and/or code samples) +* suggestions for further reading (links spread throughout the reference doc) + +Technical references often include more links throughout the document than other content types, often linking to other technical references, guides, tutorials, glossary definitions, etc. Since the purpose of technical reference material is to educate developers on a deeper level about the topic of their choosing, this is a common and expected practice and is a good indication of a strong technical reference. + +## General formatting + +### Fonts + +Fonts in Optimism technical documentation are setup to follow brand guidelines established by marketing (e.g., heading fonts are different than body or paragraph font). Please do not change them. + +### Bullets & unordered lists + +Please use `*` instead of `-` for items in a list. This maintains consistency across the docs. + +### Date & numbers + +* Use the full month, day, year format for dates whenever possible. Do not abbreviate the month. In a form or when space is limited, use slashes in the format of month/day/year without any leading zeros. + **Examples**: `January 10, 2014` or `2024/01/10` + +* Spell out all numbers under 10. For numbers 10 and above, use the numeral. + **Example**: CompanyX operates five nodes and plans to add 12 more. + +### Abbreviations + +* Use contractions with intention. Contractions can be used to create a conversational, informal tone, such as in FAQs or Tutorials. Avoid using contractions in UI labels (i.e. button names, page headers, etc), error messages/error codes, or interactive page elements. + +* Avoid abbreviating common words. It is preferable to spell it out unless there are major space limitations, such as in a table. + **Example**: `Use account` (not acct.) and `number` (not no.) + +* Spell out acronyms the first time used on any given page. Then, abbreviate in parentheses afterward. Link users to the glossary, when applicable. + **Example**: `Externally Owned Account (EOA)` + +### Punctuation + +* **Ampersand (&)** Only use "&" in headings where items are grouped or in proper names. Do not use in sentences. + +* **Colon (:)** Use to introduce a list or series. + +* **Commas (,)** Use a serial comma in lists of three or more items and use the oxford comma preceding the "and" before the last element in a list. + **Example**: The developer built a node, social app, and DeFi app for the Optimism Collective. + +* **Em dash (—)** Use to indicate a break in thought or a parenthetical comment. Do not add spaces around the em dash. + **Example**: The developer graduated—with honors—from Optimism Bootcamp. + +* **En dash (–)** Use to indicate a range or a continuation of a series. Use spaces on each side of the en dash. + **Example**: `Pages 11 – 19` or `Mon – Fri` or `Nov 1 – 17` + +* **Exclamation point (!)** Avoid. We do not use exclamation points in user assistance content. It is more appropriate for marketing and sales content, but not in the UI or technical documentation. + +* **Hyphen (-)** Use to connect two words. No spaces are needed around the hyphen. + **Example**: `developer-focused product` or `company-wide holiday` + +* **Slash (/)** Avoid using as the slash is reserved for file names (see above). Use "or," "and," or "both" instead. diff --git a/docs/public-docs/app-developers/guides/bridging/basics.mdx b/docs/public-docs/app-developers/guides/bridging/basics.mdx new file mode 100644 index 0000000000000..ee91b9abae8a6 --- /dev/null +++ b/docs/public-docs/app-developers/guides/bridging/basics.mdx @@ -0,0 +1,30 @@ +--- +title: Bridging basics +description: Learn about the fundamentals of sending data and tokens between Ethereum and OP Mainnet. +--- +OP Mainnet is a "Layer 2" system and is fundamentally connected to Ethereum. +However, OP Mainnet is also a distinct blockchain with its own blocks and transactions. +App developers commonly need to move data and tokens between OP Mainnet and Ethereum. +This process of moving data and tokens between the two networks is called "bridging". + +## Sending tokens + +One of the most common use cases for bridging is the need to send ETH or ERC-20 tokens between OP Mainnet and Ethereum. +OP Mainnet has a system called the [Standard Bridge](./standard-bridge) that makes it easy to move tokens in both directions. +If you mostly need to bridge tokens, make sure to check out the [Standard Bridge](./standard-bridge) guide. + +## Sending data + +Under the hood, the Standard Bridge is just an application that uses the OP Mainnet [message passing system to send arbitrary data between Ethereum and OP Mainnet](./messaging). +Applications can use this system to have a contract on Ethereum interact with a contract on OP Mainnet, and vice versa. +All of this is easily accessible with a simple, clean API. + +## Next steps + +Ready to start bridging? +Check out these tutorials to get up to speed fast. + +* [Learn how to bridge ERC-20 tokens with viem](/app-developers/tutorials/bridging/cross-dom-bridge-erc20) +* [Learn how to create a standard bridged token](/app-developers/tutorials/bridging/standard-bridge-standard-token) +* [Learn how to create a custom bridged token](/app-developers/tutorials/bridging/standard-bridge-custom-token) +* [Learn how to submit transactions from L1](/app-developers/tutorials/bridging/cross-dom-bridge-eth) diff --git a/docs/public-docs/app-developers/guides/bridging/custom-bridge.mdx b/docs/public-docs/app-developers/guides/bridging/custom-bridge.mdx new file mode 100644 index 0000000000000..abca069601afc --- /dev/null +++ b/docs/public-docs/app-developers/guides/bridging/custom-bridge.mdx @@ -0,0 +1,39 @@ +--- +title: Custom bridges +description: Important considerations when building custom bridges for OP Mainnet. +--- + +Custom token bridges are any bridges other than the [Standard Bridge](./standard-bridge). +You may find yourself in a position where you need to build a custom token bridge because the Standard Bridge doesn't completely support your use case. +This guide provides important information you should be aware of when building a custom bridge. + + + Custom bridges can bring a significant amount of complexity and risk to any project. + Before you commit to a custom bridge, be sure that the [Standard Bridge](./standard-bridge) definitely does not support your use case. + [Building a custom bridged token](/app-developers/tutorials/bridging/standard-bridge-custom-token) is often sufficient for projects that need more flexibility. + + +## Guidelines + +Custom bridges can use any design pattern you can think of. +However, with increased complexity comes increased risk. +Consider directly extending or modifying the [`StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/87dd5a4743380b717dac44a4e55c5e6e60e32684/packages/contracts-bedrock/src/universal/StandardBridge.sol) contract before building your own bridge contracts from scratch. +Doing so will provide you with an audited foundation upon which you can add extra logic. + +If you choose not to extend the `StandardBridge` contract, you may still want to follow the interface that the `StandardBridge` provides. +Bridges that extend this interface will be compatible with the [Superchain Bridges UI](https://app.optimism.io/bridge?utm_source=op-docs&utm_medium=docs). +You can read more about the design of the Standard Bridge in the guide on [Using the Standard Bridge](./standard-bridge). + +## The Superchain Token List + +The [Superchain Token List](/app-developers/reference/tokens/tokenlist) exists to help users and developers find the right bridged representations of tokens native to another blockchain. +Once you've built and tested your custom bridge, make sure to register any tokens meant to flow through this bridge by [making a pull request against the Superchain Token List repository](https://github.com/ethereum-optimism/ethereum-optimism.github.io#adding-a-token-to-the-list). +You **must** deploy your bridge to OP Sepolia before it can be added to the Superchain Token List. + +## Next steps + +You can explore several examples of custom bridges for OP Mainnet: + +* [NFT Bridge](https://github.com/ethereum-optimism/optimism/blob/v1.1.4/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol) +* [L2 DAI Token Bridge](https://optimistic.etherscan.io/address/0x467194771dae2967aef3ecbedd3bf9a310c76c65#code) and [deployed addresses](https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/data/DAI/data.json) +* [SNX Bridge](https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/data/SNX/data.json) diff --git a/docs/public-docs/app-developers/guides/bridging/messaging.mdx b/docs/public-docs/app-developers/guides/bridging/messaging.mdx new file mode 100644 index 0000000000000..330f2f272d267 --- /dev/null +++ b/docs/public-docs/app-developers/guides/bridging/messaging.mdx @@ -0,0 +1,228 @@ +--- +title: Sending data between L1 and L2 +description: Learn how bridging works between L1 and L2, how to use it, and what to watch out for. +--- + +Smart contracts on L1 (Ethereum) can interact with smart contracts on L2 (OP Mainnet) through a process called "bridging". +This page explains how bridging works, how to use it, and what to watch out for. + + + This is a high-level overview of the bridging process. + For a step-by-step tutorial on how to send data between L1 and L2, check out the [Solidity tutorial](/app-developers/tutorials/bridging/cross-dom-solidity). + + +## Understanding contract calls + +It can be easier to understand bridging if you first have a basic understanding of how contracts on EVM-based blockchains like OP Mainnet and Ethereum communicate within the *same* network. +The interface for sending messages *between* Ethereum and OP Mainnet is designed to mimic the standard contract communication interface as much as possible. + +Here's how a contract on Ethereum might trigger a function within another contract on Ethereum: + +```solidity +contract MyContract { + function doTheThing(address myContractAddress, uint256 myFunctionParam) public { + MyOtherContract(myContractAddress).doSomething(myFunctionParam); + } +} +``` + +Here, `MyContract.doTheThing` triggers a call to `MyOtherContract.doSomething`. +Under the hood, Solidity is triggering the code for `MyOtherContract` by sending an [ABI encoded](https://docs.soliditylang.org/en/v0.8.23/abi-spec.html) call for the `doSomething` function. +A lot of this complexity is abstracted away to simplify the developer experience. +Solidity also has manual encoding tools that allow us to demonstrate the same process in a more verbose way. + +Here's how you might manually encode the same call: + +```solidity +contract MyContract { + function doTheThing(address myContractAddress, uint256 myFunctionParam) public { + myContractAddress.call( + abi.encodeCall( + MyOtherContract.doSomething, + ( + myFunctionParam + ) + ) + ); + } +} +``` + +Here you're using the [low-level "call" function](https://docs.soliditylang.org/en/v0.8.23/units-and-global-variables.html#members-of-address-types) and one of the [ABI encoding functions built into Solidity](https://docs.soliditylang.org/en/v0.8.23/units-and-global-variables.html#abi-encoding-and-decoding-functions). +Although these two code snippets look a bit different, they're doing the exact same thing. +Because of limitations of Solidity, **the OP Stack's bridging interface is designed to look like the second code snippet**. + +## Basics of communication between layers + +At a high level, the process for sending data between L1 and L2 is pretty similar to the process for sending data between two contracts on Ethereum (with a few caveats). +Communication between L1 and L2 is made possible by a pair of special smart contracts called the "messenger" contracts. +Each layer has its own messenger contract, which serves to abstract away some lower-level communication details, a lot like how HTTP libraries abstract away physical network connections. + +We won't get into *too* much detail about these contracts here. +The most important thing that you need to know is that each messenger contract has a `sendMessage` function that allows you to send a message to a contract on the other layer. + +```solidity +function sendMessage( + address _target, + bytes memory _message, + uint32 _minGasLimit +) public; +``` + +The `sendMessage` function has three parameters: + +1. The `address _target` of the contract to call on the other layer. +2. The `bytes memory _message` calldata to send to the contract on the other layer. +3. The `uint32 _minGasLimit` minimum gas limit that can be used when executing the message on the other layer. + +This is basically equivalent to: + +```solidity +address(_target).call{gas: _minGasLimit}(_message); +``` + +Except, of course, that you're calling a contract on a completely different network. + +This is glossing over a lot of the technical details that make this whole thing work under the hood, but this should be enough to get you started. +Want to call a contract on OP Mainnet from a contract on Ethereum? +It's dead simple: + +```solidity +// Pretend this is on L2 +contract MyOptimisticContract { + function doSomething(uint256 myFunctionParam) public { + // ... some sort of code goes here + } +} + +// And pretend this is on L1 +contract MyContract { + function doTheThing(address myOptimisticContractAddress, uint256 myFunctionParam) public { + messenger.sendMessage( + myOptimisticContractAddress, + abi.encodeCall( + MyOptimisticContract.doSomething, + ( + myFunctionParam + ) + ), + 1000000 // or use whatever gas limit you want + ) + } +} +``` + + + You can find the addresses of the `L1CrossDomainMessenger` and the `L2CrossDomainMessenger` contracts on OP Mainnet and OP Sepolia on the [Contract Addresses](/op-mainnet/network-information/op-addresses) page. + + +## Communication speed + +Unlike calls between contracts on the same blockchain, calls between Ethereum and OP Mainnet are *not* instantaneous. +The speed of a cross-chain transaction depends on the direction in which the transaction is sent. + +### For L1 to L2 transactions + +Transactions sent from L1 to L2 take **approximately 1-3 minutes** to get from Ethereum to OP Mainnet, or from Sepolia to OP Sepolia. +This is because the Sequencer waits for a certain number of L1 blocks to be created before including L1 to L2 transactions to avoid potentially annoying [reorgs](https://www.alchemy.com/overviews/what-is-a-reorg). + +### For L2 to L1 transactions + +Transactions sent from L2 to L1 take **approximately 7 days** to get from OP Mainnet to Ethereum, or from OP Sepolia to Sepolia. +This is because the bridge contract on L1 must wait for the L2 state to be *proven* to the L1 chain before it can relay the message. + +The process of sending transactions from L2 to L1 involves four distinct steps: + +1. The L2 transaction that sends a message to L1 is sent to the Sequencer. + This is just like any other L2 transaction and takes just a few seconds to be confirmed by the Sequencer. + +2. The block containing the L2 transaction is proposed to the L1. + This typically takes approximately 20 minutes. + +3. A proof of the transaction is submitted to the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/111f3f3a3a2881899662e53e0f1b2f845b188a38/packages/contracts-bedrock/src/L1/OptimismPortal.sol#L209) contract on L1. + This can be done any time after step 2 is complete. + +4. The transaction is finalized on L1. + This can *only* be done after the [fault challenge period](#understanding-the-challenge-period) has elapsed. + This period is 7 days on Ethereum and a few seconds on Sepolia. + This waiting period is a core part of the security model of the OP Stack and cannot be circumvented. + +## Accessing `msg.sender` + +Contracts frequently make use of `msg.sender` to make decisions based on the calling address. +For example, many contracts will use the [Ownable](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol) pattern to selectively restrict access to certain functions. +Because messages are essentially shuttled between L1 and L2 by the messenger contracts, **the `msg.sender` you'll see when receiving one of these messages will be the messenger contract** corresponding to the layer you're on. + +In order to get around this, you can find a `xDomainMessageSender` function to each messenger: + +```solidity +function xDomainMessageSender() public returns (address); +``` + +If your contract has been called by one of the messenger contracts, you can use this function to see who's *actually* sending this message. +Here's how you might implement an `onlyOwner` modifier on L2: + +```solidity +modifier onlyOwner() { + require( + msg.sender == address(messenger) + && messenger.xDomainMessageSender() == owner + ); + _; +} +``` + +## Fees for sending data between L1 and L2 + +### For L1 to L2 transactions + +The majority of the cost of an L1 to L2 transaction comes from the smart contract execution on L1. +When sending an L1 to L2 transaction, you send to the [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/111f3f3a3a2881899662e53e0f1b2f845b188a38/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol) contract, which then sends a call to the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/111f3f3a3a2881899662e53e0f1b2f845b188a38/packages/contracts-bedrock/src/L1/OptimismPortal.sol) contract. +This involves some execution on L1, which costs gas. +The total cost is ultimately determined by gas prices on Ethereum when you're sending the cross-chain transaction. + +L1 to L2 execution also triggers contract execution on L2. +The `OptimismPortal` contract charges you for this L2 execution by burning a dynamic amount of L1 gas during your L1 to L2 transaction, depending on the gas limit you requested on L2. +The amount of L1 gas charged increases when more people are sending L1 to L2 transactions (and decreases when fewer people are sending L1 to L2 transactions). + + + Since the gas amount charged is dynamic, the gas burn can change from block to block. + You should always add a buffer of at least 20% to the gas limit for your L1 to L2 transaction to avoid running out of gas. + + +### For L2 to L1 transactions + +Each message from L2 to L1 requires three transactions: + +1. An L2 transaction that *initiates* the transaction, which is priced the same as any other transaction made on OP Mainnet. + +2. An L1 transaction that *proves* the transaction. + This transaction can only be submitted after the L2 block, including your L2 transaction, is proposed on L1. + This transaction is expensive because it includes verifying a [Merkle trie](/connect/resources/glossary#merkle-patricia-trie) inclusion proof on L1. + +3. An L1 transaction that *finalizes* the transaction. + This transaction can only be submitted after the transaction challenge period (7 days on mainnet) has passed. + +The total cost of an L2 to L1 transaction is therefore the combined cost of the L2 initialization transaction and the two L1 transactions. +The L1 proof and finalization transactions are typically significantly more expensive than the L2 initialization transaction. + +## Understanding the challenge period + +One of the most important things to understand about L1 ⇔ L2 interaction is that **mainnet messages sent from Layer 2 to Layer 1 cannot be relayed for at least 7 days**. +This means that any messages you send from Layer 2 will only be received on Layer 1 after this one-week period has elapsed. +We call this period of time the "challenge period" because it is the time during which a transaction can be challenged with a [fault proof](/op-stack/protocol/overview#fault-proofs). + +Optimistic Rollups are "optimistic" because they're based around the idea of publishing the *result* of a transaction to Ethereum without actually executing the transaction on Ethereum. +In the "optimistic" case, this transaction result is correct and one can completely avoid the need to perform complicated (and expensive) logic on Ethereum. + +However, one still needs to find a way to prevent incorrect transaction results from being published in place of correct ones. +Here's where the "fault proof" comes into play. +Whenever a transaction result is published, it's considered "pending" for a period of time known as the challenge period. +During this period of time, anyone may re-execute the transaction *on Ethereum* in an attempt to demonstrate that the published result was incorrect. + +If someone can prove that a transaction result is faulty, then the result is scrubbed from existence and anyone can publish another result in its place (hopefully the correct one this time, financial punishments make faulty results *very* costly for their publishers). +Once the window for a given transaction result has fully passed without a challenge, the result can be considered fully valid (or else someone would've challenged it). + +Anyway, the point here is that **you don't want to be making decisions about Layer 2 transaction results from inside a smart contract on Layer 1 until this challenge period has elapsed**. +Otherwise you might be making decisions based on an invalid transaction result. +As a result, L2 ⇒ L1 messages sent using the standard messenger contracts cannot be relayed until they've waited out the full challenge period. diff --git a/docs/public-docs/app-developers/guides/bridging/standard-bridge.mdx b/docs/public-docs/app-developers/guides/bridging/standard-bridge.mdx new file mode 100644 index 0000000000000..7466463830872 --- /dev/null +++ b/docs/public-docs/app-developers/guides/bridging/standard-bridge.mdx @@ -0,0 +1,263 @@ +--- +title: Using the Standard Bridge +description: Learn the basics of using the Standard Bridge to move tokens between Layer 1 and Layer 2. +--- + +The Standard Bridge is a basic token bridging system available on OP Mainnet and all other standard OP Stack chains. +The Standard Bridge allows you to easily move ETH and most ERC-20 tokens between Ethereum and OP Mainnet. +Transfers from Ethereum to OP Mainnet via the Standard Bridge are usually completed within 1-3 minutes. +Transfers from OP Mainnet to Ethereum are completed in 7 days as a result of the [withdrawal challenge period](./messaging#understanding-the-challenge-period). + +The Standard Bridge is fully permissionless and supports standard ERC-20 tokens. +Other bridging systems also exist that provide different features and security properties. +You may wish to explore some of these options to find the bridge that works best for you and your application. + + + The Standard Bridge **does not** support [**fee on transfer tokens**](https://github.com/d-xo/weird-erc20#fee-on-transfer) or [**rebasing tokens**](https://github.com/d-xo/weird-erc20#balance-modifications-outside-of-transfers-rebasingairdrops) because these types of tokens may cause bridge accounting errors. + + +## Design + +The Standard Bridge allows users to convert tokens that are native to one chain (like Ethereum) into a representation of those tokens on the other chain (like OP Mainnet). +Users can then convert these bridged representations back into their original native tokens at any time. + +This bridging mechanism functions identically in both directions — tokens native to OP Mainnet can be bridged to Ethereum, just like tokens native to Ethereum can be bridged to OP Mainnet. +Here you'll get to understand how the Standard Bridge works when moving tokens from Ethereum to OP Mainnet. +Since the bridging mechanism is mirrored on both sides, this will also explain how the bridge works in the opposite direction. + +### Architecture + +The Standard Bridge is composed of two contracts, the [`L1StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/65ec61dde94ffa93342728d324fecf474d228e1f/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol) (on `Ethereum`) and the [`L2StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/65ec61dde94ffa93342728d324fecf474d228e1f/packages/contracts-bedrock/contracts/L2/L2StandardBridge.sol) (on `OP Mainnet`). +These two contracts interact with one another via the `CrossDomainMessenger` system for sending messages between Ethereum and OP Mainnet. +You can read more about the `CrossDomainMessenger` in the guide on [Sending Data Between L1 and L2](./messaging). + +### Bridged tokens + +The Standard Bridge utilizes bridged representations of tokens that are native to another blockchain. +Before a token native to one chain can be bridged to the other chain, a bridged representation of that token must be created on the receiving side. + +A bridged representation of a token is an ERC-20 token that implements the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/IOptimismMintableERC20.sol#L6-L18) interface. +This interface includes a few functions that the `StandardBridge` contracts use to manage the bridging process. +All bridged versions of tokens **must** implement this interface to be used with the `StandardBridge`. +Native tokens do not need to implement this interface. + +A native token may have more than one bridged representation at the same time. +Users must always specify which bridged token they wish to use when using the bridge. +Different bridged representations of the same native token are considered entirely independent tokens. + +### Bridging native tokens + +The Standard Bridge uses a "lock-and-mint" mechanism to convert native tokens into their bridged representations. +This means that **native tokens are locked** into the Standard Bridge on one side, after which **bridged tokens are minted** on the other side. +The process for bridging a native token involves a few steps. + + + +The Standard Bridge must be able to pull tokens from the user to lock them into the bridge contract. + To do this, the user must first give the bridge an [allowance](https://eips.ethereum.org/EIPS/eip-20#approve) to transfer the number of tokens that the user wishes to convert into a bridged representation. + + + + +After providing a sufficient allowance, the user calls the [`bridgeERC20To`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L193-L217) function on the `StandardBridge` contract on the chain where the native token lives (e.g., the `L1StandardBridge` contract if the token is native to Ethereum). + + The user must provide the following parameters to this function call: + + * `address _localToken`: Address of the native token on the sending side. + * `address _remoteToken`: Address of the bridged representation on the receiving side. + * `address _to`: Address of the recipient of these tokens, usually the sender's address. + * `uint256 _amount`: Number of tokens to transfer. + * `uint32 _minGasLimit`: Gas to use to complete the transfer on the receiving side. + * `bytes calldata _extraData`: Optional identity extra data. + + + Users can also trigger the [`bridgeERC20`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L168-L191) function instead of `bridgeERC20To` to avoid needing to specify the `address _to` parameter. + Doing so will automatically set the `address _to` parameter to the `msg.sender`. + + **The `bridgeERC20` function can be potentially dangerous for users with [smart contract wallets](https://web.archive.org/web/20231012141406/https://blockworks.co/news/what-are-smart-contract-wallets) as some smart contract wallets cannot be deployed at the same address on every blockchain.** + To help users avoid potentially losing access to tokens by accident, the `bridgeERC20` function will always revert when triggered from a smart contract. + Smart contract wallet users and other smart contracts should therefore use the `bridgeERC20To` function instead. + + + + + + +When the user triggers the `bridgeERC20To` function while transferring a native token, the Standard Bridge will pull the `_amount` of `_localToken` tokens from the user's address and lock them inside of the bridge contract. + A record of all locked tokens is stored within a [`deposits` mapping](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L41-L42) that keeps track of the total number of tokens deposited for a given `_localToken` and `_remoteToken` pair. + + Since a native token may have more than one bridged representation, the `deposits` token must keep track of the deposit pools for each `_localToken`/`_remoteToken` pair independently. + + To illustrate, suppose that two users deposit 100 units of the same native token, `Token A`, but wish to receive two different bridged tokens, `Token B` and `Token C`. + Although the Standard Bridge would now have a total balance of 200 units of `Token A`, the mapping would show that the `Token A`/`Token B` pool and the `Token A`/`Token C` pool both have only 100 units. + + + + + +After locking the native tokens, the Standard Bridge contract on the sending side will trigger a cross-chain message to the Standard Bridge contract on the receiving side via the [`CrossDomainMessenger`](./messaging) system. + This message tells the receiving side to **mint** tokens according to the parameters specified by the user. + Specifically, this message is an encoded call to the [`finalizeBridgeERC20`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L250-L287) function on the other Standard Bridge contract. + At this point, execution ends on the sending side. + + + + + +Once the minting message is sent, it must be relayed to the receiving side. + Message relaying is automatic when sending from Ethereum to OP Mainnet but requires additional user transactions when sending from OP Mainnet to Ethereum. + Read more about the message relaying process in the guide to [Sending Data Between L1 and L2](./messaging#communication-speed). + + When the message is relayed, the `finalizeBridgeERC20` function will be triggered on the receiving Standard Bridge contract. + This function will receive the `_minGasLimit` gas defined by the user to execute to completion. + + + + + +Upon execution, `finalizeBridgeERC20` verifies a number of things about the incoming request: + + * [The request must have originated from the Standard Bridge contract on the other blockchain](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L269). + * [The Standard Bridge must not be in an emergency paused state](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L271). + * [The bridged token must properly implement the `IOptimismMintableERC20` interface](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L272). + * [The bridged token must recognize the original native token as its `remoteToken()`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L273-L276). + + + + + +If the minting message is fully verified, `finalizeBridgeERC20` will [mint tokens to the recipient](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L278) equal to the number of tokens originally deposited on the other blockchain. + For this to work properly, the bridged representation of the native token must correctly implement a `mint` function that allows the Standard Bridge to mint tokens arbitrarily. + This is part of the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/8ed8be8806208976e63421bd68779477d12295b5/packages/contracts-bedrock/src/universal/IOptimismMintableERC20.sol) interface. + + This completes the process of bridging native tokens. + This process is identical in both the Ethereum to OP Mainnet and OP Mainnet to Ethereum directions. + + + + +### Bridging non-native tokens + +The Standard Bridge uses a "burn-and-unlock" mechanism to convert bridged representations of tokens back into their native tokens. +This means that **bridged tokens are burned** on the Standard Bridge on one side, after which **native tokens are unlocked** on the other side. +The process for bridging a non-native, bridged representation of a token involves a few steps. + + + +Unlike when bridging native tokens, users do not need to provide an approval to trigger a transfer of a bridged token because the Standard Bridge should already have the ability to `burn` these tokens. + Here, the user calls the [`bridgeERC20To`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L193-L217) function on the `StandardBridge` contract on the chain where the bridged token lives (e.g., the `L2StandardBridge` contract if the token is bridged to OP Mainnet). + + The user must provide the following parameters to this function call: + + * `address _localToken`: Address of the bridged token on the sending side. + * `address _remoteToken`: Address of the native token on the receiving side. + * `address _to`: Address of the recipient of these tokens, usually the sender's address. + * `uint256 _amount`: Number of tokens to transfer. + * `uint32 _minGasLimit`: Gas to use to complete the transfer on the receiving side. + * `bytes calldata _extraData`: Optional identity extra data. + + + + +When the user triggers the `bridgeERC20To` function while transferring a bridge token, [the Standard Bridge will burn the corresponding `_amount` of tokens from the sender's address](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L345). + + + + + +After burning the bridged tokens, the Standard Bridge contract on the sending side will trigger a cross-chain message to the Standard Bridge contract on the receiving side via the [`CrossDomainMessenger`](./messaging) system. + This message tells the receiving side to **unlock** tokens according to the parameters specified by the user. + Specifically, this message is an encoded call to the [`finalizeBridgeERC20`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L250-L287) function on the other Standard Bridge contract. + At this point, execution ends on the sending side. + + + + + +Once the unlock message is sent, it must be relayed to the receiving side. + Message relaying is automatic when sending from Ethereum to OP Mainnet but requires additional user transactions when sending from OP Mainnet to Ethereum. + Read more about the message relaying process in the guide to [Sending Data Between L1 and L2](./messaging#communication-speed). + + When the message is relayed, the `finalizeBridgeERC20` function will be triggered on the receiving Standard Bridge contract. + This function will receive the `_minGasLimit` gas defined by the user to execute to completion. + + + + + +Upon execution, `finalizeBridgeERC20` verifies a number of things about the incoming request: + + * [The request must have originated from the Standard Bridge contract on the other blockchain](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L269). + * [The Standard Bridge must not be in an emergency paused state](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L271). + + + + + +If the unlock message is fully verified, `finalizeBridgeERC20` will [unlock and transfer tokens to the recipient](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L280-L281) equal to the number of tokens originally burned on the other blockchain. + + This completes the process of bridging native tokens. + This process is identical in both the Ethereum to OP Mainnet and OP Mainnet to Ethereum directions. + + + + +### Bridging ETH + +The Standard Bridge contracts can also be used to bridge ETH from Ethereum to OP Mainnet and vice versa. +The ETH bridging process is generally less complex than the ERC-20 bridging process. +Users simply need to trigger and send ETH to the [`bridgeETH`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L143-L150) or [`bridgeETHTo`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L152-L166) functions on either blockchain. + + + Users can also deposit ETH from Ethereum to OP Mainnet by sending a basic ETH transfer from an EOA to the `L1StandardBridgeProxy`. + This works because the `L1StandardBridgeProxy` contains a [`receive`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L119-L121) function. + You can find the mainnet and testnet addresses on the [Contract Addresses](/op-mainnet/network-information/op-addresses) page. + + +## Tutorials + +* [Learn how to bridge ERC-20 tokens with viem](/app-developers/tutorials/bridging/cross-dom-bridge-erc20) +* [Learn how to create a standard bridged token](/app-developers/tutorials/bridging/standard-bridge-standard-token) +* [Learn how to create a custom bridged token](/app-developers/tutorials/bridging/standard-bridge-custom-token) +* [Learn how to submit transactions from L1](/app-developers/tutorials/bridging/cross-dom-bridge-eth) + +## Superchain Token List + +The [Superchain Token List](/app-developers/reference/tokens/tokenlist) exists to help users discover the right bridged token addresses for any given native token. +Consider checking this list to make sure that you're not using the wrong bridged representation of a token when bridging a native token. + +Developers who are creating their own bridged tokens should consider [adding their token](https://github.com/ethereum-optimism/ethereum-optimism.github.io#adding-a-token-to-the-list) to the Superchain Token List. +Tokens on the Superchain Token List will automatically appear on certain tools like the [Superchain Bridges UI](https://app.optimism.io/bridge?utm_source=op-docs&utm_medium=docs). + +### Searching the Token List + +You should strongly consider using the Superchain Token List to verify that you're using the correct bridged representation of a token when bridging a native token. +Doing so can help you avoid accidentally bridging to the wrong token and locking up your native token permanently. + +You can easily find the bridged representation of a token for OP Mainnet on the [Bridged Token Addresses](/app-developers/reference/tokens/tokenlist) page. +If you want to find the bridged representation of a token for another chain, use the following steps. + + + +The Superchain Token List is organized by the token's address and native blockchain. + [Search the token list](https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/optimism.tokenlist.json) for the token you want to bridge to confirm that it's included in the list. + Make sure that the chain ID in the entry matches the chain ID of the blockchain you're bridging from. + Retrieve the token's name and symbol from the list. + + + + +Once you've found the token you want to bridge, look for the token's name and symbol in the list. + Find the entry that matches the name and symbol of the token you want to bridge and where the chain ID matches the chain ID of the blockchain you're bridging to. + The address of this entry is the address of the bridged representation of the token you want to bridge. + + + + +Identify the cross-chain bridge contract address used by the token pair. +You can observe that some token pairs in the list utilize **custom bridge contracts** instead of the default Standard Bridge. +Verify the bridge contract specified in the token list entry to ensure you are interacting with the correct bridge implementation for that token. + + + + diff --git a/docs/public-docs/app-developers/guides/building-apps.mdx b/docs/public-docs/app-developers/guides/building-apps.mdx new file mode 100644 index 0000000000000..20f275f77bfb0 --- /dev/null +++ b/docs/public-docs/app-developers/guides/building-apps.mdx @@ -0,0 +1,59 @@ +--- +title: Building apps on OP Stack chains +description: Learn the basics of building apps on OP Stack chains. +--- + +This guide explains the basics of OP Stack development. +OP Stack chains are [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), meaning they run a slightly modified version of the same `geth` you run on mainnet. +Therefore, the differences between OP Stack development and Ethereum development are minor. +But a few differences [do exist](/op-stack/protocol/differences). + +## OP Stack chains endpoint URLs + +To access any Ethereum type network you need an endpoint. [These providers](/app-developers/reference/rpc-providers) support our networks. + +### Network choice + +For development purposes we recommend you use either a local development network or [OP Sepolia](https://sepolia-optimism.etherscan.io). +That way you don't need to spend real money. +If you need ETH on OP Sepolia for testing purposes, [you can use this faucet](https://console.optimism.io/faucet?utm_source=op-docs&utm_medium=docs). + +## Interacting with contracts on OP Stack chains + +We have Hardhat's Greeter contract on OP Sepolia at address [0x9d334aFBa83865E67a9219830ADA57aaA9406681](https://testnet-explorer.optimism.io/address/0x9d334aFBa83865E67a9219830ADA57aaA9406681#code). +You can verify your development stack configuration by interacting with it. + +## Development stacks + +As you can see in the different development stacks below, the way you deploy contracts and interact with them on OP Stack chains is almost identical to the way you do it with L1 Ethereum. +The most visible difference is that you have to specify a different endpoint (of course). +For more detail, see the guide on [Differences between Ethereum and OP Stack Chains](/op-stack/protocol/differences). + +* [Foundry](https://getfoundry.sh/) +* [Hardhat](https://hardhat.org/) +* [Apeworx](https://www.apeworx.io/) +* [Brownie](https://eth-brownie.readthedocs.io/en/stable/install.html) +* [Remix](https://remix.ethereum.org) +* [Waffle](https://getwaffle.io/) + +## Best practices + +### Use provided EVM + +It is best to start development with the EVM provided by the development stack. +Not only is it faster, but such EVMs often have extra features, such as the [ability to log messages from Solidity](https://hardhat.org/tutorial/debugging-with-hardhat-network.html) or a [graphical user interface](https://trufflesuite.com/ganache/). + +### Debug before deploying + +After you are done with that development, debug your decentralized application locally and then on a [Sepolia test network](/op-mainnet/network-information/connecting-to-op). +This lets you debug parts that are OP Stack chains specific such as calls to bridges to transfer ETH or tokens between layers. + +Only when you have a version that works well on a test network should you deploy to the production network, where every transaction has a cost. + +### Contract source verification + +You don't have to upload your source code to [block explorers](/app-developers/tools/infrastructure/block-explorers), but it is a good idea. +On the test network, it lets you issue queries and transactions from the explorer's user interface. +On the production network, it lets users know exactly what your contract does, which is conducive to trust. + +Just remember, if you use [the Etherscan API](https://explorer.optimism.io/apis?utm_source=op-docs&utm_medium=docs), you need one API key for OP Stack chains and a separate one for OP Sepolia. diff --git a/docs/public-docs/app-developers/guides/configuring-actions.mdx b/docs/public-docs/app-developers/guides/configuring-actions.mdx new file mode 100644 index 0000000000000..5284e021683b5 --- /dev/null +++ b/docs/public-docs/app-developers/guides/configuring-actions.mdx @@ -0,0 +1,284 @@ +--- +title: Configuring Actions SDK +description: Learn how to configure Actions SDK for your application. +--- + +Actions SDK lets you choose which assets, markets, chains, protocols, and providers you want to support in your application via configuration file. + + + + Follow the + [quickstart](/app-developers/quickstarts/actions) guide to + add Actions SDK as a dependency in your app. + + + Follow the [integrating + wallets](/app-developers/quickstarts/actions#choose-a-wallet-provider) guide, + choose and install a Wallet Provider. + + + `actions.ts` - An accessible file that holds all of your configuration preference. + + + Let Actions SDK know which Wallet Provider you've chosen: + + + + Select a wallet provider: + + + + ```typescript title="actions.ts" + const walletConfig = { + hostedWalletConfig: { + provider: { + type: "privy" as const, + }, + }, + smartWalletConfig: { + provider: { + type: "default" as const, + attributionSuffix: "actions", + }, + }, + }; + ``` + + + ```typescript title="actions.ts" + const walletConfig = { + hostedWalletConfig: { + provider: { + type: "turnkey" as const, + }, + }, + smartWalletConfig: { + provider: { + type: "default" as const, + attributionSuffix: "actions", + }, + }, + }; + ``` + + + ```typescript title="actions.ts" + const walletConfig = { + hostedWalletConfig: { + provider: { + type: "dynamic" as const, + }, + }, + smartWalletConfig: { + provider: { + type: "default" as const, + attributionSuffix: "actions", + }, + }, + }; + ``` + + + + + + Select a wallet provider: + + + + ```typescript title="actions.ts" + import { PrivyClient } from '@privy-io/node' + + const privyClient = new PrivyClient( + process.env.PRIVY_APP_ID, + process.env.PRIVY_APP_SECRET, + ) + + const walletConfig = { + hostedWalletConfig: { + provider: { + type: "privy" as const, + config: { + privyClient, + }, + }, + }, + smartWalletConfig: { + provider: { + type: "default" as const, + attributionSuffix: "actions", + }, + }, + }; + ``` + + + ```typescript title="actions.ts" + import { Turnkey } from '@turnkey/sdk-server' + + const turnkeyClient = new Turnkey({ + apiBaseUrl: 'https://api.turnkey.com', + apiPublicKey: process.env.TURNKEY_API_KEY, + apiPrivateKey: process.env.TURNKEY_API_SECRET, + defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID, + }) + + const walletConfig = { + hostedWalletConfig: { + provider: { + type: "turnkey" as const, + config: { + client: turnkeyClient.apiClient(), + }, + }, + }, + smartWalletConfig: { + provider: { + type: "default" as const, + attributionSuffix: "actions", + }, + }, + }; + ``` + + + + + + + + Configure which assets you want to support across all lend providers: + + ```typescript title="actions.ts" + // Additional config from previous steps... + + // Import popular assets + import { USDC } from '@eth-optimism/actions-sdk/assets' + import type { Asset, AssetsConfig } from "@eth-optimism/actions-sdk"; + + // Or define custom assets + export const CustomToken: Asset = { + address: { + [mainnet.id]: '0x123...', + [unichain.id]: '0x456...', + [baseSepolia.id]: '0x789...', + }, + metadata: { + decimals: 6, + name: 'Custom Token', + symbol: 'CUSTOM', + }, + type: 'erc20', + } + + // Configure allowed/blocked assets + const assetsConfig: AssetsConfig = { + allow: [USDC, CustomToken], + block: [], // Optional + } + ``` + + + + Define which markets you want to support or block within your app: + + ```typescript title="actions.ts" + // Additional config from previous steps... + + export const GauntletUSDC: LendMarketConfig = { + address: '0xabc...', + chainId: unichain.id, + name: 'Gauntlet USDC', + asset: USDC, + lendProvider: 'morpho', + } + ``` + + + + Configure which lend protocols you want to support. You can enable one or multiple providers: + + ```typescript title="actions.ts" + // Additional config from previous steps... + + import type { LendConfig } from "@eth-optimism/actions-sdk"; + + const lendConfig: LendConfig = { + morpho: { + marketAllowlist: [GauntletUSDC], + marketBlocklist: [], // Optional + }, + aave: { + marketAllowlist: [AaveWETH], + marketBlocklist: [], // Optional + }, + }; + ``` + + + + Configure supported chains: + + ```typescript title="actions.ts" + // Additional config from previous steps... + + import { optimism, base } from "viem/chains"; + + // Define any EVM chain + const OPTIMISM = { + chainId: optimism.id, + rpcUrls: env.OPTIMISM_RPC_URL, + bundler: { + // Bundle and sponsor txs with a gas paymaster + type: "simple" as const, + url: env.OPTIMISM_BUNDLER_URL, + }, + }; + + const BASE = { + chainId: base.id, + rpcUrls: env.BASE_RPC_URL, + bundler: { + // Bundle and sponsor txs with a gas paymaster + type: "simple" as const, + url: env.BASE_BUNDLER_URL, + }, + }; + + const chains = [OPTIMISM, BASE]; + ``` + + + + Finally bring it all together and initialize Actions: + + ```typescript title="actions.ts" + // Additional config from previous steps... + + export const actions = createActions({ + wallet: walletConfig, + assets: assetsConfig, + lend: lendConfig, + chains, + }); + ``` + + + + Once you've initialized your actions instance, import it anywhere you need to take action: + + ```typescript + import { actions } from './actions'; + + // Use actions anywhere in your app + const market = await actions.lend.getMarket({ ... }); + const wallet = await actions.wallet.createSmartWallet({ ... }); + const receipt = await wallet.lend.openPosition({ ... }); + ``` + + + + +## Next Steps + +For detailed API documentation and type definitions, see the [Actions SDK Reference](/app-developers/reference/actions). diff --git a/docs/public-docs/app-developers/guides/interoperability/estimate-costs.mdx b/docs/public-docs/app-developers/guides/interoperability/estimate-costs.mdx new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/docs/public-docs/app-developers/guides/interoperability/get-started.mdx b/docs/public-docs/app-developers/guides/interoperability/get-started.mdx new file mode 100644 index 0000000000000..60c7ed0b5ee84 --- /dev/null +++ b/docs/public-docs/app-developers/guides/interoperability/get-started.mdx @@ -0,0 +1,58 @@ +--- +title: "Build interoperable apps on OP Stack devnet" +description: "Learn about deploying contracts, cross-chain messaging, and tutorials to help you build applications on OP Stack chains." +--- + +Reimagine your app with OP Stack interop to deliver the unified UX your users expect. Hack on net-new, bold use cases on Interop devnet. + + + Explore the [Superchain Dev Console](https://console.optimism.io/?utm_source=op-docs&utm_medium=docs) to build, launch, and grow your app on OP Stack chains. + + +## Connect to OP Stack Interop + +Choose your development environment to build, test, and quickly iterate on your apps. + +| Environment | Purpose | Getting Started | +| --------------------- | ----------------------------------------- | ---------------------------------------------------------- | +| **Local development** | Rapid iteration and testing with Supersim | [Setup Supersim guide](/app-developers/tutorials/development/supersim) | +| **Interop devnet** | Large-scale testing on testnets | [Network specs](/app-developers/guides/building-apps) | + +## Tools & resources for building interoperable apps + +| Tool | Description | +| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | +| [Superchain Faucet](https://console.optimism.io/faucet?utm_source=op-docs&utm_medium=docs) | One-stop shop to grab testnet ETH for any OP Stack network. | +| [Supersim](/> app-developers/tools/development/supersim) | Local multi-chain testing environment for smart contracts. | +| [Super CLI](https://github.com/ethereum-optimism/super-cli) | Command-line tool for seamless multichain app deployment and testing. | +| [Superchain Relayer](https://github.com/ethereum-optimism/superchain-relayer) | UI for monitoring and managing cross-chain transactions. | +| [Interop Docs](/> op-stack/interop/explainer) | Comprehensive Interop information in the Optimism Docs. | +| [Developer Console](https://console.optimism.io/?utm_source=op-docs&utm_medium=docs) | Comprehensive tool to build, launch, and grow your app on OP Stack chains. | +| [Developer Support GitHub](https://github.com/ethereum-optimism/developers/discussions) | Quick and easy developer support. | + +## Handy step-by-step guides + + + + + + + + + + + + + + + + +## Discover and build net-new use cases with OP Stack Interop + +There is so much more than just bridge abstraction. Hack on the various cutting-edge applications that are uniquely enabled by OP Stack Interop. Here are some ideas to get you started: + +| Superloans | Superlend | SuperCDP | +| :--------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------- | +| Use collateral on ChainA and ChainB to execute an arbitrage opportunity on ChainA. | Deposit ETH into lending protocols across chains for optimal yield, with automatic rebalancing based on best rates. | Collateralized crosschain debt protocol that holds assets and issues cross-chain tokens on user preferred chains. | + + diff --git a/docs/public-docs/app-developers/guides/interoperability/message-expiration.mdx b/docs/public-docs/app-developers/guides/interoperability/message-expiration.mdx new file mode 100644 index 0000000000000..6a37a4c6433c5 --- /dev/null +++ b/docs/public-docs/app-developers/guides/interoperability/message-expiration.mdx @@ -0,0 +1,34 @@ +--- +title: Message expiration +description: What message expiration is, why it exists, and how to reemit a previously sent message if it has expired and was never relayed. +--- + +# Message expiration + +[Messages](/app-developers/guides/interoperability/message-passing) referenced between OP Stack chains have a limited validity period called the expiry window. Once this window elapses, the referenced message becomes invalid and can no longer be referenced. + +For messages using [`L2ToL2CrossDomainMessenger`](/app-developers/guides/interoperability/message-passing), if a message expires before being referenced, developers can reemit the message on the source chain. This triggers a fresh `SentMessage` event, enabling the message to be relayed. + +## The expiry window + +The expiry window is an offchain constant, defined by [OP-Supervisor](/chain-operators/reference/components/op-supervisor), that defines how long a cross-chain message or event emitted remains valid. For any chain in the [Superchain interop cluster](/interop/explainer#superchain-interop-cluster), messages must be referenced within 7 days (604,800 seconds) of the log being created. + +After this period, a message can no longer be referenced unless the event is remitted. + +## Reemitting an expired message + +The `resendMessage` function on the [`L2ToL2CrossDomainMessenger`](/app-developers/guides/interoperability/message-passing) contract allows developers to reemit a message that was sent but not yet relayed. + +This emits a new `SentMessage` log with the same content as the original message, enabling offchain relayers to pick it up again. + +The process to reemit an expired message: + +1. Call [`resendMessage`](https://github.com/ethereum-optimism/optimism/blob/a979a9444dbb482843f2a42f437ced54a8ac1053/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol#L110-L128) on the origin chain to reemit the message event. The contract verifies the message hash was originally sent. The call requires [every parameter](https://github.com/ethereum-optimism/optimism/blob/a979a9444dbb482843f2a42f437ced54a8ac1053/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol#L110-L128) to rebuild the original message. + +2. [Relay the new message](/interop/message-passing#executing-message) as normal. + +Note: Re-emitting an already relayed message will have no effect on the destination chain since the re-emitted event won't be logged by OP-Supervisor. + +## Next steps + +* Learn how to [pass messages between blockchains](/app-developers/tutorials/interoperability/message-passing) diff --git a/docs/public-docs/app-developers/guides/interoperability/message-passing.mdx b/docs/public-docs/app-developers/guides/interoperability/message-passing.mdx new file mode 100644 index 0000000000000..87fa8cdf2f5f8 --- /dev/null +++ b/docs/public-docs/app-developers/guides/interoperability/message-passing.mdx @@ -0,0 +1,101 @@ +--- +title: Interop message passing overview +description: Learn about cross-chain message passing on OP Stack chains. +--- + +OP Stack interop is in active development. Some features may be experimental. + + This is an explanation of how interop works. + You can find a step by step tutorial [here](/app-developers/tutorials/interoperability/message-passing). + + +The low-level [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol) contract handles basic message execution. It verifies whether an initiating message exists but does not check the message's destination, processing status, or other attributes. + +The [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract extends `CrossL2Inbox` by providing complete cross-domain messaging functionality. + +For high-level interoperability, both messages use the `L2ToL2CrossDomainMessenger` contract on their respective chains. + +## Initiating message + +```mermaid + +sequenceDiagram + participant app as Application + box rgba(0,0,0,0.1) Source Chain + participant srcContract as Source Contract + participant srcXdom as L2ToL2CrossDomainMessenger + participant log as Event Log + end + app->>srcContract: 1. Send a message + srcContract->>srcXdom: 2. Call contract A
in chainId B
with calldata C + note over srcXdom: 3. Sanity checks + srcXdom->>log: 4. Log event SentMessage + +``` + +1. The application sends a transaction to a contract on the source chain. + +2. The contract calls [`L2ToL2CrossDomainMessenger.SendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L125-L142). + The call requires these parameters: + + * `_destination`: The chain ID of the destination blockchain. + * `_target`: The address of the contract on that blockchain. + * `_message`: The actual message. + + This message is provided to `_target` as calldata, which means it includes a function selector and the parameters for that function call. + +3. `L2ToL2CrossDomainMessenger` on the source chain verifies the message is legitimate: + * The destination chain is one to which this chain can send messages. + * The destination chain is *not* the source chain. + * The target is neither `CrossL2Inbox` nor `L2ToL2CrossDomainMessenger`. + +4. `L2ToL2CrossDomainMessenger` emits a log entry. + In addition to the parameters, the log entry also includes: + + * `_nonce`: A [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) value to ensure the message is only executed once. + + * `_sender`: The contract that sent the cross domain message. + +## Executing message + +```mermaid + +sequenceDiagram + participant app as Autorelayer + box rgba(0,0,0,0.1) Source Chain + participant log as Event Log + end + participant super as OP-Supervisor
(destination chain
node) + box rgba(0,0,0,0.1) Destination Chain + participant dstXdom as L2ToL2CrossDomainMessenger + participant Xinbox as CrossL2Inbox + participant dstContract as Destination Contract + end + log->>super: 1. Initiating message log event + app->>dstXdom: 2. Send an executing message + dstXdom->>Xinbox: 3. Verify the initiating message is real + note over dstXdom: 4. Sanity checks + dstXdom->>dstContract: 5. Call with provided calldata +``` + +1. Before the executing message is processed, the log event of the initiating message has to get to `op-supervisor` on the destination chain before the [expiry window](/app-developers/guides/interoperability/message-expiration) of 7 days. + +2. The autorelayer, the application, or a contract calling on the application's behalf calls [`L2ToL2CrossDomainMessenger.relayMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L150-L203). + This call includes the message that was sent (`_sentMessage`), as well as the [fields required to find that message (`_id`)](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/L2/ICrossL2Inbox.sol#L4-L10). + +3. The `L2ToL2CrossDomainMessenger` uses `CrossL2Inbox` to verify the message was sent from the source. + +4. `L2ToL2CrossDomainMessenger` on the destination chain verifies the message is legitimate: + + * `_destination`: Chain ID of the destination chain. + * `_nonce`: Nonce of the message sent + * `_sender`: Address that sent the message + * `_target`: Target contract or wallet address. + * `message`: Message payload to call target with. + + +5. If everything checks out, `L2ToL2CrossDomainMessenger` calls the destination contract with the calldata provided in the message. + +## Next steps + +* Learn how to [pass messages between blockchains](/app-developers/tutorials/interoperability/message-passing). diff --git a/docs/public-docs/app-developers/guides/interoperability/reading-logs.mdx b/docs/public-docs/app-developers/guides/interoperability/reading-logs.mdx new file mode 100644 index 0000000000000..4f0833ef98178 --- /dev/null +++ b/docs/public-docs/app-developers/guides/interoperability/reading-logs.mdx @@ -0,0 +1,215 @@ +--- +title: Reading Logs with OP Stack Interop +description: Learn how to reference logs from one chain on another within the OP Stack interop cluster. +--- + +OP Stack interop is in active development. Some features may be experimental. +OP Stack interop enables developers to leverage current and historical logs from other blockchains within the [OP Stack interop cluster](/interop/explainer#superchain-interop-cluster) directly on their local chain. +This allows smart contracts to consume local and cross-chain logs with low latency in a trust-minimized way. + +## Overview + +Instead of relying solely on [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol), developers can use [`CrossL2Inbox#validateMessage`](https://github.com/ethereum-optimism/optimism/blob/af091753917c1d7101314cbfe8ac5cbc2efe0e5e/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L49) and treat `CrossL2Inbox` as an oracle for logs that occurred on different chains or even their local chain. + +This enables developers to: + +* Build cross-chain applications that react to events happening across OP Stack chains. +* Create novel applications that leverage data from multiple chains. + + +When reading logs, you must reference logs created within the [expiry window of 7 days](/app-developers/guides/interoperability/message-expiration). + + +## Why use `CrossL2Inbox`? + +* **Reference existing logs**: Allows contracts to verify and use logs that were already emitted, without requiring those logs to have been sent as cross-chain messages. +* **Trust-minimized security**: Leverages the existing OP Stack security model with no additional trust assumptions. +* **Flexibility**: Can be used to validate events from another chain or even the local chain. + +## How it works + +### Architecture + +The process works through the [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/af091753917c1d7101314cbfe8ac5cbc2efe0e5e/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L33) contract, which serves as an oracle for logs from other chains in the OP Stack interop cluster: + +1. A smart contract on `Chain A` emits a log (event) +2. Your contract on `Chain B` calls `CrossL2Inbox#validateMessage` with the log's identifier +3. The `CrossL2Inbox` contract verifies the log's authenticity +4. Your contract can then use the validated log data + +### Key components + +* **Identifier**: A struct containing information about the log, including `chainId`, `origin` (contract address), and other log metadata +* **[validateMessage](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L79)**: Function that verifies a log's authenticity before allowing its use + +## Example: cross-chain attestation verification + +Let's walk through a conceptual example of verifying an Ethereum Attestation Service (EAS) attestation across chains. +EAS is a [predeploy](/> app-developers/reference/contracts/interop/predeploy) in the OP Stack for making attestations on or off-chain about anything. + +### Source chain: creating an attestation + +On the source chain (e.g., OP Mainnet), a user creates an attestation using EAS: + +```mermaid +sequenceDiagram + participant User + participant App as Application + participant EAS as EAS Contract + participant Log as Event Log + + User->>App: Request attestation + App->>EAS: createAttestation() + EAS->>Log: Emit AttestationCreated event + Note over Log: Event contains attestation data +``` + +1. The user initiates a request for an attestation through an application. + +2. The application calls the `createAttestation()` function on the EAS (Ethereum Attestation Service) contract on the source chain. + +3. The EAS contract processes the attestation request and emits an `AttestationCreated` event. + +4. The event is recorded in the chain's log, containing all necessary attestation data. + +### Destination chain: verifying the attestation + +On the destination chain (e.g., Unichain), a DeFi application wants to verify this attestation: + +```mermaid +sequenceDiagram + participant User + participant DeFi as DeFi Application + participant Verifier as AttestationVerifier + participant CrossL2 as CrossL2Inbox + participant OP as OP-Supervisor Service + + User->>DeFi: Request access using attestation + DeFi->>Verifier: verifyAttestation(id, attestationEvent) + Verifier->>CrossL2: validateMessage(id, keccak256(attestationEvent)) + CrossL2->>OP: Check if log exists + OP-->>CrossL2: Confirm log validity + CrossL2-->>Verifier: Return validation result + Verifier-->>DeFi: Return verification status + DeFi-->>User: Grant access based on attestation +``` + +1. The user requests access to a DeFi application on the destination chain, referencing an attestation created on the source chain. + +2. The DeFi application calls a verification function on an attestation verifier contract, passing the attestation's identifier and event data. + +3. The attestation verifier calls `validateMessage()` on the `CrossL2Inbox` contract, passing the attestation identifier and a hash of the event data. + +4. The [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol) contract interacts with the [`OP-Supervisor`](/chain-operators/reference/components/op-supervisor) service to check if the specified log exists on the source chain. + +5. The `OP-Supervisor` confirms the validity of the log to the `CrossL2Inbox` contract. + +6. The `CrossL2Inbox` returns the validation result to the attestation verifier. + +7. The attestation verifier returns the verification status to the DeFi application. + +8. If validation is successful, the DeFi application grants the user access based on the verified attestation. + +The primary benefit of this approach is that it allows your contract to verify attestations that already exist on another chain without requiring those attestations to have been explicitly sent as cross-chain messages. + +## Overview of the process + +To implement cross-chain log reading: + +```mermaid +flowchart TD + A[1. Identify log to consume] --> B[2. Create Identifier struct] + B --> C[3. Call validateMessage] + C --> D[4. Process validated log data] + + subgraph "Conceptual Approach" + E["Define an Identifier struct with: + - chainId: The source chain ID + - origin: The source contract address + - Other required identifier parameters"] + + F["Call validateMessage on CrossL2Inbox + Pass the identifier and hash of log data"] + end + + B --> E + C --> F +``` + +1. First, identify which log from another chain you want to consume in your application. + +2. Create an Identifier struct that contains all necessary information about the log, including the chain ID and the contract address that emitted the log. + +3. Call the `validateMessage()` function on the `CrossL2Inbox` contract, passing the identifier and a hash of the log data. + +4. After validation, process the log data according to your application's requirements. + +## Important considerations + +* This feature works between chains within the [OP Stack interop cluster](/interop/explainer#superchain-interop-cluster). +* The same functionality can be used on a single chain (for example, to maintain a consistent architecture). + +### Handling validation failures + +* The `validateMessage` call will revert the entire transaction if validation fails. +* Consider implementing a try-catch pattern in your application's frontend to handle these failures. +* Design your contract to allow for retry mechanisms where appropriate. + +## Comparison with `L2ToL2CrossDomainMessenger` + +| Feature | L2ToL2CrossDomainMessenger | CrossL2Inbox#validateMessage | +| ---------- | ---------------------------------------------- | ------------------------------------------------- | +| Purpose | Send messages between chains | Verify logs from other chains or local chain | +| Initiation | Source explicitly sends message to destination | Destination queries for existing logs from source | +| Use Case | Transfer tokens, trigger actions | Verify attestations, reference events | +| Flow | Push model | Pull model | + +## End-to-End flow comparison + +```mermaid +flowchart LR + subgraph "L2ToL2CrossDomainMessenger (Push Model)" + A[Source Contract] -->|sendMessage| B[Source L2ToL2CrossDomainMessenger] + B -->|emit event| C[Event Log] + C -.->|relayed by| D[Autorelayer] + D -->|relayMessage| E[Destination L2ToL2CrossDomainMessenger] + E -->|execute| F[Destination Contract] + end + + subgraph "CrossL2Inbox (Pull Model)" + G[Source Contract] -->|emit event| H[Event Log] + H -.->|monitored by| I[OP-Supervisor] + J[Destination Contract] -->|validateMessage| K[CrossL2Inbox] + K <--->|verify log| I + end +``` + +This diagram compares the two approaches for cross-chain communication: + +### L2ToL2CrossDomainMessenger (Push Model): + +1. A source contract calls `sendMessage()` on the `L2ToL2CrossDomainMessenger`. + +2. The messenger emits an event to the event log. + +3. An autorelayer detects the event and relays it to the destination chain. + +4. The destination `L2ToL2CrossDomainMessenger` receives the relayed message. + +5. The destination messenger executes the message on the target contract. + +### CrossL2Inbox (Pull Model): + +1. A source contract emits an event to the event log. + +2. The `OP-Supervisor` service monitors events across chains. + +3. A destination contract calls `validateMessage()` on the `CrossL2Inbox`. + +4. The `CrossL2Inbox` verifies the log's existence by communicating with the `OP-Supervisor`. + +5. The destination contract receives verification and proceeds with its logic. + +## Next steps + +* Learn how to [pass messages between blockchains](/app-developers/tutorials/interoperability/message-passing) diff --git a/docs/public-docs/app-developers/guides/testing-apps.mdx b/docs/public-docs/app-developers/guides/testing-apps.mdx new file mode 100644 index 0000000000000..28e060e139a17 --- /dev/null +++ b/docs/public-docs/app-developers/guides/testing-apps.mdx @@ -0,0 +1,42 @@ +--- +title: Testing apps for OP Stack chains +description: Learn best practices for testing apps on OP Stack chains. +--- + +For the most part, running applications on OP Stack chains is identical to running them on Ethereum, so the testing is identical too. +In this guide, you learn the best practices for OP Stack testing where there are differences. + +## Unit tests and single layer integration tests + +The vast majority of tests do not involve any OP Stack-specific features. +In those cases, while you *could* test everything on an OP Stack chain or a test network, that would normally be inefficient. +Most Ethereum development stacks include features that make testing easier, which normal Ethereum clients, such as geth (and our modified version, `op-geth`) don't support. +Therefore, it is a good idea to run the majority of tests, which do not rely on OP Stack-specific features, in the development stack. +It is a lot faster. + +It is a best practice to design and run thorough tests across an OP test network, either in your [local multichain development environment](/app-developers/tools/development/supersim), our [devnets](/op-stack/introduction/op-stack), or on [the test network](/op-mainnet/network-information/connecting-to-op#op-sepolia), depending on your use case. Alternatively, with [Tenderly Virtual TestNets](https://docs.tenderly.co/virtual-testnets?mtm_campaign=ext-docs&mtm_kwd=optimism)you can run tests with complete integration with existing protocols, access to unlimited faucets, continuous state sync, and access to development tools such as Debugger and Simulator UI. +Running proper testing is key to identifying fringe cases where the equivalence between OP Stack chains and Ethereum breaks down (or where Ethereum mainnet itself and the development stack may be non-equivalent in a production environment). + +## Multilayer integration tests + +Some apps need OP Stack-specific features that aren't available as part of the development stack. +For example, if your decentralized application relies on [inter-domain communication](/app-developers/guides/bridging/messaging), the effort of developing a stub to let you debug it in a development stack is probably greater than the hassle of having the automated test go to [a local multichain development environment](/app-developers/tools/development/supersim) each time. + +## Testing and Staging with Tenderly + +Tenderly [Virtual TestNets](https://docs.tenderly.co/virtual-testnets?mtm_campaign=ext-docs&mtm_kwd=optimism) provide a powerful environment for testing OP Stack applications with mainnet-like conditions. They offer several advantages for testing OP Stack applications: + +* **Mainnet State Replication**: Virtual TestNets can sync with the latest OP Stack mainnet state, allowing you to test against real network conditions and interact with up-to-date protocols without spending real assets. +* **Unlimited Faucet**: Access [unlimited test tokens](https://docs.tenderly.co/virtual-testnets/unlimited-faucet?mtm_campaign=ext-docs&mtm_kwd=optimism) for both native currency and ERC-20 tokens, enabling comprehensive testing of complex DeFi interactions. +* **Collaborative Testing**: Your entire team can access the same testing environment, making it easier to debug issues and validate fixes. +* **CI/CD Integration**: Incorporate automated testing in your deployment pipeline using [Virtual TestNets' API](https://docs.tenderly.co/reference/api#/operations/createAlert?mtm_campaign=ext-docs&mtm_kwd=optimism) and [GitHub Actions integration](https://docs.tenderly.co/virtual-testnets/ci-cd/github-actions-foundry?mtm_campaign=ext-docs&mtm_kwd=optimism). +* **Development tools**: Rely on the built-in [developer explorer](https://docs.tenderly.co/developer-explorer?mtm_campaign=ext-docs&mtm_kwd=optimism) and debugging tools to analyze test transactions and contract interactions. + +## Integration with other products + +In many cases a decentralized application requires the services of other contracts. +For example, [Perpetual v. 2](https://docs.perp.com/docs/guides/integration-guide) cannot function without [Uniswap v. 3](https://uniswap.org/blog/uniswap-v3). + +* If that is the case, you can use [mainnet forking](/app-developers/reference/tools/supersim/fork). It works with OP Stack chains. +* Create a Virtual TestNet to get access to third party contracts (e.g. Uniswap) and it's latest or historical state. +* Alternatively, you can connect to our [test network](/op-mainnet/network-information/connecting-to-op#op-sepolia) if those contracts are also deployed there (in many cases they are). diff --git a/docs/public-docs/app-developers/guides/transactions/estimates.mdx b/docs/public-docs/app-developers/guides/transactions/estimates.mdx new file mode 100644 index 0000000000000..e615495b64fd2 --- /dev/null +++ b/docs/public-docs/app-developers/guides/transactions/estimates.mdx @@ -0,0 +1,101 @@ +--- +title: Estimating transaction fees on OP Mainnet +description: Learn how to properly estimate the total cost of a transaction on OP Mainnet. +--- + + +Check out the guide on understanding [Transaction Fees on OP Mainnet](/op-stack/transactions/fees) for an in-depth explanation of how OP Mainnet transaction fees work. + + +It's important to properly estimate the cost of a transaction on OP Mainnet before submitting it to the network. +Here you'll learn how to estimate both of the components that make up the total cost of an OP Mainnet transaction, the [execution gas fee](/op-stack/transactions/fees#execution-gas-fee) and the [L1 data fee](/op-stack/transactions/fees#l1-data-fee). +Make sure to read the guide on [Transaction Fees on OP Mainnet](/op-stack/transactions/fees) for a detailed look at how these fees work under the hood. + +## Execution gas fee + + +Estimating the execution gas fee on OP Mainnet is just like estimating the execution gas fee on Ethereum. +Steps are provided here for reference and convenience, but you can use the same tooling that you'd use to estimate the execution gas fee for a transaction on Ethereum. + + +A transaction's execution gas fee is exactly the same fee that you would pay for the same transaction on Ethereum. +This fee is equal to the amount of gas used by the transaction multiplied by the gas price attached to the transaction. +Refer to the guide on [Transaction Fees on OP Mainnet](/op-stack/transactions/fees#execution-gas-fee) for more information about the execution gas fee. + +When estimating the execution gas fee for a transaction, you'll need to know the gas limit and the [max fee per gas](https://ethereum.org/en/developers/docs/gas/#maxfee) for the transaction. +Your transaction fee will then be the product of these two values. +Refer to the guide on [Setting Transaction Gas Parameters on OP Mainnet](./parameters) to learn how to select an appropriate gas limit and max fee per gas for your transaction. + + + +Using the same tooling that you'd use to estimate the gas limit for a transaction on Ethereum, estimate the gas limit for your transaction on OP Mainnet. + OP Mainnet is designed to be [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) so transactions will use the same amount of gas on OP Mainnet as they would on Ethereum. + This means you can feed your transaction to the [`eth_estimateGas`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_estimategas) JSON-RPC method just like you would on Ethereum. Alternatively, use Tenderly's [`tenderly_estimateGas`](https://docs.tenderly.co/node/rpc-reference/optimism-mainnet/tenderly_estimateGas) for 100% accurate gas estimations. + + + + +Like Ethereum, OP Mainnet uses an `EIP-1559` style fee market to determine the current base fee per gas. + You can then additionally specify a priority fee (also known as a tip) to incentivize the Sequencer to include your transaction more quickly. + Make sure to check out the guide on [Setting Transaction Gas Parameters on OP Mainnet](./parameters) to learn more about how to select an appropriate max fee per gas for your transaction. + + + + +## L1 data fee + + +The Viem library provides a convenient method for estimating the L1 data fee for a transaction. +Check out the tutorial on [Estimating Transaction Costs on OP Mainnet](/app-developers/tutorials/transactions/sdk-estimate-costs) to learn how to use the Viem library to estimate the L1 data fee for your transaction. +Keep reading if you'd like to learn how to estimate the L1 data fee without the Viem library. + + +The L1 data fee is a fee paid to the Sequencer for the cost of publishing your transaction to Ethereum. +This fee is paid in ETH and is calculated based on the size of your transaction in bytes and the current gas price on Ethereum. +Refer to the guide on [Transaction Fees on OP Mainnet](/op-stack/transactions/fees#l1-data-fee) for more information about the L1 data fee. + +Unlike the execution gas fee, the L1 data fee is an **intrinsic** fee for every transaction. +This fee is automatically charged based on the size of your transaction and the current Ethereum gas price. +You currently cannot specify a custom L1 data fee for your transaction. + + +The L1 data fee is paid based on the current Ethereum gas price as tracked within the [`GasPriceOracle`](https://github.com/ethereum-optimism/optimism/blob/233ede59d16cb01bdd8e7ff662a153a4c3178bdd/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol) smart contract. +This gas price is updated automatically by the OP Mainnet protocol. +Your transaction will be charged the Ethereum gas price seen by the protocol at the time that your transaction is included in an OP Mainnet block. +This means that the L1 data fee for your transaction may differ from your estimated L1 data fee. + + + + +The L1 data fee is calculated based on the size of your serialized transaction in bytes. + Most Ethereum tooling will provide a method for serializing a transaction. + For instance, Ethers.js provides the [`ethers.utils.serializeTransaction`](https://docs.ethers.org/v5/api/utils/transactions/#utils-serializeTransaction) method. + + + + +Once you have serialized your transaction, you can estimate the L1 data fee by calling the [`getL1Fee`](https://github.com/ethereum-optimism/optimism/blob/233ede59d16cb01bdd8e7ff662a153a4c3178bdd/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol#L109-L124) method on the [`GasPriceOracle`](https://github.com/ethereum-optimism/optimism/blob/233ede59d16cb01bdd8e7ff662a153a4c3178bdd/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol) smart contract available on OP Mainnet and all OP Stack chains. + This method takes the serialized transaction as input and returns the L1 data fee in wei using the formula described in the [Transaction Fees on OP Mainnet](/op-stack/transactions/fees#l1-data-fee) guide. + + + Fee estimation is typically performed before the transaction is signed. + As a result, the `getL1Fee` method assumes that your input is an **unsigned** Ethereum transaction. + + + + + +### Tooling + +Several tools are available to help you estimate the L1 Data Fee for your transaction. +Selecting the right tool for your use case will depend on your specific needs. + +* [Viem](https://viem.sh/op-stack#getting-started-with-op-stack) provides first-class support for OP Stack chains, including OP Mainnet. You can use Viem to estimate gas costs and send cross-chain transactions (like transactions through the Standard Bridge system). It's strongly recommended to use Viem if you are able to do so as it will provide the best native support at the moment. + +### Future proofing + +The L1 Data Fee formula is subject to change in the future, especially as the data availability landscape evolves. +As a result, it's important to future-proof your transaction fee estimation code to ensure that it will continue to function properly as the L1 Data Fee formula changes. + +* Use existing [tooling](#tooling) to estimate the L1 Data Fee for your transaction if possible. This tooling will be updated to reflect any changes to the L1 Data Fee formula. This way you won't need to modify your code to account for any changes to the formula. +* Use the `getL1Fee` method on the `GasPriceOracle` if you are unable to use existing tooling. The `getL1Fee` method will be updated to reflect any changes to the L1 Data Fee formula. It's strongly recommended that you do **not** implement the L1 Data Fee formula yourself. diff --git a/docs/public-docs/app-developers/guides/transactions/flashblocks-and-gas-usage.mdx b/docs/public-docs/app-developers/guides/transactions/flashblocks-and-gas-usage.mdx new file mode 100644 index 0000000000000..d34e0a82e62f4 --- /dev/null +++ b/docs/public-docs/app-developers/guides/transactions/flashblocks-and-gas-usage.mdx @@ -0,0 +1,155 @@ +--- +title: "Flashblocks and Gas Usage on OP Stack" +description: "Explains how Flashblocks affect block gas usage, transaction inclusion, and how to avoid unexpected rejections for large transactions." +--- + +This document explains how **Flashblocks** affect block gas usage, transaction inclusion, large-transaction handling, and how to avoid unexpected transaction rejections. + +If you are unfamiliar with Flashblocks you can read more [here](https://docs.optimism.io/op-stack/features/flashblocks#flashblocks). + +--- + +## Background: Block Gas Limit vs. Transaction Gas Limit + +On OP Mainnet, the **theoretical block gas limit** is currently **40M gas**. In a traditional EVM mental model, a single transaction can use up to the full block gas limit, as long as it does not exceed it. + +With **Flashblocks**, block construction is incremental and *effective transaction inclusion depends on the gas already consumed within the block*, not just the theoretical maximum. + +--- + +## What Are Flashblocks? + +Flashblocks are an incremental block-building mechanism used by the sequencer and block builder. + +Instead of constructing the full block at once, the block is built in **N flashblocks** (currently 8): + +- Each flashblock increases the *cumulative gas budget* available for inclusion +- Earlier flashblocks can only include smaller transactions +- Larger transactions can only be included later, *if enough gas remains* + +### Example + +Assume: + +- Block gas limit **B = 40M** +- Number of flashblocks **N = 8** + +Then the cumulative gas limit increases as follows: + +| Flashblock | Max cumulative gas available | +| ---------- | ---------------------------- | +| 1 | 5M | +| 2 | 10M | +| 3 | 15M | +| 4 | 20M | +| 5 | 25M | +| 6 | 30M | +| 7 | 35M | +| 8 | 40M | + +A transaction with a **20M gas limit** can only be included starting from flashblock 4, *and only if prior flashblocks have not already consumed that gas*. + +--- + +## Why a Transaction Can Get Stuck + +A transaction may be submitted with a gas limit lower than the total block gas limit (e.g. **38M < 40M**) and still remain pending or, depending on the EL implementation, be rejected with the error: `exceeds block gas limit`. + +This happens because transaction acceptance and transaction inclusion are decided by different components. + +Genrally: +- Incoming transactions first enter the Execution Layer txpool. +- The EL checks whether `tx.gasLimit` exceeds the **full block gas limit**. + - If it does, it rejects with `exceeds block gas limit`. + - Otherwise, it keeps the transaction in the **pending/queued mempool** and **P2P-gossips** it eventually reaching **rbuilder**. +- **rbuilder** applies the incremental **remaining block gas** check to decide whether to include the tx in a flashblock or leave it pending. + +### Real-World Example + +- Block gas limit: **40M** +- Gas already used in earlier flashblocks: **13M** +- Remaining gas: **27M** +- Incoming transaction gas limit: **38M** + +The **EL** checks if the transaction exceeds **40M**. Since **38M < 40M**, the transaction is accepted into the pending/queued mempool and gossiped to **rbuilder**. +**rbuilder** applies the incremental remaining-gas constraint. With only **27M** gas remaining and the tx requiring **38M**, the transaction cannot be included yet and stays pending. + +**Result:** the transaction is not rejected, but remains pending in the mempool until there is enough remaining gas to include it in a flashblock. + +--- + +## Client Behavior and Error Messages + +There are two distinct outcomes depending on the transaction size: + +- **Rejected immediately (txpool admission failure):** + The EL (e.g. **op-geth**, **op-reth**) rejects transactions only when the transaction gas limit exceeds the **full block gas limit**: + - Condition: `tx.gasLimit > blockGasLimit` + - Error returned to the user: `exceeds block gas limit` + +- **Accepted but pending inclusion:** + If the transaction gas limit is **within** the block gas limit, but still too large to fit into the **remaining block gas** at that point in rbuilder, it will not be included yet. + - The transaction remains in the **pending/queued mempool** or rbuilder, waiting for enough remaining gas to become available in a later flashblock. + - From the user’s perspective, the transaction may appear **pending** until it gets enough gas available for inclusion. + +In practice, transactions are sent to Execution Layer RPC nodes (e.g. via proxyd), checked, and only if accepted into the EL txpool are eventually propagated to the sequencer/rbuilder for potential inclusion. + +Implementing mechanisms such as txpool rebroadcasting can mitigate this divergence automatically, so users do not notice retries. However, when an error is surfaced, it may originate from the ingress client’s gas checks. + +--- + +## Practical Guidance for Application Developers and End Users + +### Leave Headroom for Large Transactions + +If you submit large transactions, avoid targeting the full block gas limit. + +**Recommended approach:** + +- Leave **20–30% headroom** relative to the block gas limit +- For a 40M block, aim for ≤ **28–32M gas** + +Or: + +- Proactively cap single-transaction gas usage to ~16.7M gas to align with the upcoming L2 Fusaka transaction limit + +This increases the chance that the transaction fits within Execution Layer gas checks under partial block utilization. + +More generally, designing applications to avoid extremely large single transactions is good long-term practice since, with upcoming protocol changes (Fusaka on L2), transactions will be capped at 16.7M gas anyway. + +--- + +### Expect Variable Gas Availability During Congestion + +Under high demand: + +- Early flashblocks are often full +- Remaining gas later in the block may be limited +- Very large transactions may be rejected or require retries + +This is expected behavior with Flashblocks and not necessarily an issue with the transaction itself. + +--- + +### Transaction Resubmission + +Because block state evolves quickly: + +- Retrying submission in a later block may succeed +- Large transactions are more likely to be included when earlier flashblocks are less congested + +--- + +## Key Takeaways + +- Flashblocks build blocks incrementally, unlocking gas capacity over time rather than all at once + *(e.g. in a 40M gas block with 8 flashblocks, only 5M is available at the first flashblock, then 10M, and so on)* + +- A transaction must be **within the full block gas limit** to be accepted into the EL txpool, but it must fit within the **remaining block gas** to be included by rbuilder in the next flashblock + *(e.g. if 13M gas out of 40M has already been used, a 38M gas transaction can be accepted by EL but cannot be included until enough remaining gas is available once it reaches rbuilder)* + +- As a result, large transactions may be **accepted but remain pending** even when their gas limit is below the nominal block limit + *(e.g. a 38M gas transaction can be accepted in a 40M gas block but delayed during periods of high activity)* + +- Leaving meaningful headroom is the most reliable way to improve inclusion success for large transactions + *(e.g. targeting 28–32M gas instead of the full 40M or proactively implementing the Fusaka limit of 16.7M gas)* diff --git a/docs/public-docs/app-developers/guides/transactions/parameters.mdx b/docs/public-docs/app-developers/guides/transactions/parameters.mdx new file mode 100644 index 0000000000000..25461c8d7e4b5 --- /dev/null +++ b/docs/public-docs/app-developers/guides/transactions/parameters.mdx @@ -0,0 +1,65 @@ +--- +title: Setting transaction gas parameters on OP Mainnet +description: Learn how to set gas parameters for transactions on OP Mainnet. +--- + +OP Mainnet is designed to be [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) which means that it is as compatible with Ethereum as possible, down to the client software used to run OP Mainnet nodes. +Like Ethereum, OP Mainnet has an EIP-1559 style fee mechanism that dynamically adjusts a [base fee](https://ethereum.org/en/developers/docs/gas/#base-fee) that acts as the minimum fee that a transaction must pay to be included in a block. +OP Mainnet also allows transactions to pay a [priority fee](https://ethereum.org/en/developers/docs/gas/#priority-fee) (also known as a tip) to incentivize the Sequencer to include transactions more quickly. + +Setting the base fee and the priority fee appropriately is important to ensure that your transactions are included in a timely manner. +This guide will walk you through some best practices for determining the base fee and priority fee for your transactions. + +## Selecting the base fee + +The base fee is the minimum fee that a transaction must pay to be included in a block. +Transactions that specify a maximum fee per gas that is less than the current base fee cannot be included in the blockchain. + +The simplest way to select a base fee is to look at the latest available OP Mainnet block. +Each OP Mainnet block includes the current base fee and the amount of gas used within that block. +You can use this information to predict a reasonable maximum fee for your transaction. + +Note that, like Ethereum, the base fee is not explicitly defined within a transaction. +Instead, the maximum base fee is determined as the difference between the `maxFeePerGas` and the `maxPriorityFeePerGas` fields of any given transaction. + + + +Using the JSON-RPC API or your favorite Ethereum library, retrieve the latest block on OP Mainnet. + + + + +From the block, retrieve the `baseFeePerGas` and `gasUsed` fields. + + + + + +OP Mainnet adjusts the base fee based on the amount of gas used in the previous block. + If the previous block used more than 5m gas (of the 30m gas limit), then the base fee will increase by up to 10%. + If the previous block used less than 5m gas, then the base fee will decrease by up to 10%. + Refer to the [OP Mainnet EIP-1559 Parameters](/op-stack/protocol/differences#eip-1559-parameters) section for more details. + + + + + +Using the current base fee per gas and the amount of gas used in the previous block, you can predict the next base fee per gas. + If you are highly sensitive to the base fee, you may want to select a base fee per gas that is either 10% higher or 10% lower than the previous base fee. + However, you may run the risk that your transaction will not be included in a block quickly. + If you are less sensitive to the base fee, you may wish to simply use a large multiple of the previous base fee (e.g. 2x). + + + + +## Selecting the priority fee + +The priority fee is an optional tip that can be paid to the Sequencer to incentivize them to include your transaction more quickly. +The priority fee is paid in addition to the base fee. + +The simplest way to select a priority fee is to use the [`eth_maxPriorityFeePerGas`](https://docs.alchemy.com/reference/eth-maxpriorityfeepergas) JSON-RPC method to retrieve an estimate for an acceptable priority fee. +Many Ethereum libraries will provide a function to call this JSON-RPC method. +You can also use the [`eth_feeHistory`](https://docs.alchemy.com/reference/eth-feehistory) JSON-RPC method to retrieve historical priority fee data. +You can then use this data to predict a reasonable priority fee for your transaction. + +Alternatively, you can rely on Tenderly's [`tenderly_gasPrice`](https://docs.tenderly.co/node/rpc-reference/optimism-mainnet/tenderly_gasPrice?mtm_campaign=ext-docs\&mtm_kwd=optimism) to get real-time gas predictions with 3 levels of likelihood for transaction inclusion. diff --git a/docs/public-docs/app-developers/guides/transactions/statuses.mdx b/docs/public-docs/app-developers/guides/transactions/statuses.mdx new file mode 100644 index 0000000000000..1f2a9b00d1609 --- /dev/null +++ b/docs/public-docs/app-developers/guides/transactions/statuses.mdx @@ -0,0 +1,51 @@ +--- +title: Transaction statuses on OP Mainnet +description: Learn about the statuses transactions can have on OP Mainnet. +--- + +Transactions on OP Mainnet can have a number of different statuses depending on where a transaction is in the process of being included in the blockchain. +Understanding these statuses can help you troubleshoot issues, build safer applications, and display more accurate information to your users. + +## Pending + +**Instant after sending to the Sequencer** + +A transaction is considered "pending" when it has been sent to the Sequencer but has not yet been included in a block. +This is the first status a transaction will have after being sent to the Sequencer. +At this point the transaction is not part of the blockchain and there is no guarantee that the transaction will be included in the blockchain. + +The list of all pending transactions can be retrieved by calling the standard JSON-RPC method [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) with the parameter `pending` as the block number. + +## Sequencer confirmed or unsafe + +**Typically within 2-4 seconds** + +A transaction is considered "sequencer confirmed" or "unsafe" when it has been included in a block by the Sequencer but that block has **not** yet been published to Ethereum. +Although the transaction is included in a block, it is still possible for the transaction to be excluded from the final blockchain if the Sequencer fails to publish the block to Ethereum within the [Sequencing Window](/connect/resources/glossary#sequencing-window) (approximately 12 hours). +Applications should make sure to consider this possibility when displaying information about transactions that are in this state. + +The latest "sequencer confirmed" block can be retrieved by calling the standard JSON-RPC method [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) with the parameter `safe` as the block number and comparing this to the result returned for the `latest` block. +If the `safe` block is behind the `latest` block, then the earliest "sequencer confirmed" block is the `safe` block plus one. + +## Published to Ethereum or safe + +**Typically within 5-10 minutes, up to 12 hours** + +A transaction is considered "safe" when it has been included in a block by the Sequencer and that block has been published to Ethereum but that block is not yet finalized. +Once a block has been published to Ethereum there is a high likelihood that the block will be included in the final blockchain. +However, it is still possible for the block to be excluded from the final blockchain if the Ethereum blockchain experiences a reorganization. + +The latest "safe" block can be retrieved by calling the standard JSON-RPC method [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) with the parameter `safe` as the block number. + +Transactions typically become "safe" within a few minutes of becoming "sequencer confirmed". + + +## Finalized + +**Typically within 15-20 minutes, up to 12 hours** + +A transaction is considered "finalized" when it has been included in a block by the Sequencer, that block has been published to Ethereum, and that block has been finalized. +Once a block has been finalized it is guaranteed to be included in the OP Mainnet blockchain. +Applications that require the highest level of certainty that a transaction will be included in the blockchain should wait until the transaction is "finalized" before considering the transaction to be successful. + +The latest "finalized" block can be retrieved by calling the standard JSON-RPC method [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) with the parameter `finalized` as the block number. diff --git a/docs/public-docs/app-developers/guides/transactions/troubleshooting.mdx b/docs/public-docs/app-developers/guides/transactions/troubleshooting.mdx new file mode 100644 index 0000000000000..f5556ccc3c54b --- /dev/null +++ b/docs/public-docs/app-developers/guides/transactions/troubleshooting.mdx @@ -0,0 +1,49 @@ +--- +title: Troubleshooting transactions +description: Learn how to troubleshoot common problems with transactions. +--- + +## Transactions stuck in the transaction pool + +OP Chain uses EIP-1559, but with different parameters than L1 Ethereum. +As a result, while the base fee on L1 can grow by up to 12.5% in a twelve-second period (in the case of a single 30M gas block), the L2 base fee can grow by up to 77% (in the case of six 30M gas blocks). +However, it still shrinks by only up to 12.5% in the same twelve-second period (if all the blocks are empty). + +If the maximum fee per gas specified by the transaction is less than the block base fee, it does not get included until the base fee drops to below the value in the transaction. +When this happens, some users may see their transaction become stuck. +No ETH is lost, but the transaction does not clear on its own. + +We have a workaround that users and wallet operators can implement immediately, and we expect a protocol-level fix to be live by the end of Q4. + +### Recommendation + +Set the maximum fee per gas for transactions to a relatively high value, such as 0.1 gwei. +This will *not* increase the transaction cost because the same base fee, determined by a formula, is charged to all the transactions in the block. +To save on the cost of L2 gas you want to minimize the max priority fee. + +Also, if the [current base fee](https://optimistic.grafana.net/public-dashboards/c84a5a9924fe4e14b270a42a8651ceb8?orgId=1&refresh=5m) is comparable to 0.1 gwei or higher, you might want to suggest to users a higher multiple of the base fee than you would on L1 Ethereum because it can grow faster in the time interval between transaction creation and transaction signing and submission. + +#### Recommendations for wallet developers + +Wallets are usually in charge of determining the default priority fee and max fee that a transaction would include, so the above recommendations can be applied directly. + + +#### Recommendations for app developers + +As an app developer, you can usually override the default recommendation of the wallet +(see, for example, [ethers](https://github.com/ethers-io/ethers.js/blob/v5.7/packages/contracts/lib/index.d.ts#L10-L11)). +As long as not all wallets are upgraded according to our recommendations, it makes sense for apps to get the current base fee and recommend a value based on that. + + +#### Recommendations for users + +As a user, you are the final authority on transaction fields. Sometimes when submitting a transaction, the gas fee is set too low, and it gets stuck in the transaction pool (a.k.a. mempool). If you want to push that transaction through, [you can cancel it by submitting another transaction with the same nonce](https://info.etherscan.com/how-to-cancel-ethereum-pending-transactions/). This method increases the fee and the sequencer will process it from the mempool quicker. + +## Deposit transactions don't have a chainId on L2 + + [Deposit transactions](https://specs.optimism.io/protocol/deposits.html?utm_source=op-docs&utm_medium=docs#the-deposited-transaction-type) are transactions added to the L2 blockchain as part of the block derivation process. + These transactions come from a dummy address and don't have a signature. + Because in Ethereum the chainID is encoded as part of the signature, this means there is no recoverable chainID for these transactions. + + This is not a problem because the only source of deposit transactions is the block derivation process. + There shouldn't be a need to recover the chainID. diff --git a/docs/public-docs/app-developers/quickstarts/actions.mdx b/docs/public-docs/app-developers/quickstarts/actions.mdx new file mode 100644 index 0000000000000..5df2bcf624496 --- /dev/null +++ b/docs/public-docs/app-developers/quickstarts/actions.mdx @@ -0,0 +1,438 @@ +--- +title: Integrating DeFi with Actions SDK +description: Perform DeFi actions with lightweight, composable, and type-safe modules. +--- + + + Actions SDK is still under construction and not ready for production use! This + guide is meant for early testing purposes only. + + +The [Actions SDK](https://actions.money/) is an open source Typescript development toolkit that simplifies the act of integrating DeFi into your application. + +## How it works + +Here's a breakdown of what's under the hood: + +- **Modular Providers**: Actions is built with a set of core adapters called "Providers". Providers let you to pick an choose the right services and protocols for your use-case. + +- **Embedded Wallets**: Actions supports popular embedded [wallet providers](https://actions.money/#wallet), allowing your users to access DeFi with email authentication flows alone. + +- **Configure Actions**: Extend your embedded wallet with DeFi actions like Lend, Borrow, Swap, and Pay. Set multiple providers for each Action to choose the best markets across DeFi. + +- **Customize assets & chains**: Allow and block assets, markets, chains, and protocols from your application from a single config. + +## Installation + +Install the Actions SDK in your project: + + +```bash npm +npm install @eth-optimism/actions-sdk +``` + +```bash pnpm +pnpm add @eth-optimism/actions-sdk +``` + +```bash yarn +yarn add @eth-optimism/actions-sdk +``` + +```bash bun +bun add @eth-optimism/actions-sdk +``` + +```bash deno +deno add @eth-optimism/actions-sdk +``` + + + +## Choose a Wallet Provider + +Actions works with both frontend and backend wallets depending on your needs: + + + + Select a wallet provider: + + + + **Set Up Privy** + + Sign up for Privy and follow the full Privy installation [guide](https://docs.privy.io/basics/react/installation). + + **Configure Wallet Provider** + + Once you have set up Privy embedded wallets, pass them to Actions SDK: + + ```typescript + import { actions } from './config' + import { useWallets } from '@privy-io/react-auth' + + // PRIVY: Fetch wallet + const { wallets } = useWallets() + const embeddedWallet = wallets.find( + (wallet) => wallet.walletClientType === 'privy', + ) + + // ACTIONS: Let wallet make onchain Actions + const wallet = await actions.wallet.toActionsWallet({ + connectedWallet: embeddedWallet, + }) + ``` + + **Configure Smart Wallets** + + Optionally, create signers for smart wallets you control: + + ```typescript + import { actions } from './config' + import { useWallets } from '@privy-io/react-auth' + + // PRIVY: Fetch wallet + const { wallets } = useWallets() + const embeddedWallet = wallets.find( + (wallet) => wallet.walletClientType === 'privy', + ) + + // ACTIONS: Create signer from hosted wallet + const signer = await actions.wallet.createSigner({ + connectedWallet: embeddedWallet, + }) + + // ACTIONS: Create smart wallet + const { wallet } = await actions.wallet.createSmartWallet({ + signer: signer + }) + ``` + + + + **Set Up Turnkey** + + Sign up for Turnkey and follow the full Turnkey installation [guide](https://docs.turnkey.com/sdks/react/getting-started). + + **Configure Wallet Provider** + + Once you have set up Turnkey embedded wallets, pass them to Actions SDK: + + ```typescript + import { useTurnkey, WalletSource } from "@turnkey/react-wallet-kit" + import { actions, USDC, ExampleMarket } from './config' + + // Fetch Turnkey wallet + const { wallets, httpClient, session } = useTurnkey() + const embeddedWallet = wallets.find( + (wallet) => + wallet.accounts.some( + (account) => account.addressFormat === 'ADDRESS_FORMAT_ETHEREUM', + ) && wallet.source === WalletSource.Embedded, + ) + + const walletAddress = embeddedWallet.accounts[0].address + + // Convert to Actions wallet + const wallet = await actions.wallet.toActionsWallet({ + client: httpClient, + organizationId: session.organizationId, + signWith: walletAddress, + }) + + // Wallet can now take action + const receipt = await wallet.lend.openPosition({ + amount: 100, + asset: USDC, + ...ExampleMarket + }) + ``` + + **Configure Smart Wallets** + + Optionally, create signers for smart wallets you control: + + ```typescript + import { useTurnkey, WalletSource } from "@turnkey/react-wallet-kit" + import { actions } from './config' + + // Fetch Turnkey wallet + const { wallets, httpClient, session } = useTurnkey() + const embeddedWallet = wallets.find( + (wallet) => + wallet.accounts.some( + (account) => account.addressFormat === 'ADDRESS_FORMAT_ETHEREUM', + ) && wallet.source === WalletSource.Embedded, + ) + const walletAddress = embeddedWallet.accounts[0].address + + // Create signer + const signer = await actions.wallet.createSigner({ + client: httpClient, + organizationId: session.organizationId, + signWith: walletAddress, + }) + + // Create smart wallet + const { wallet } = await actions.wallet.createSmartWallet({ + signer: signer + }) + ``` + + + + **Set Up Dynamic** + + Sign up for Dynamic and follow the full Dynamic installation [guide](https://www.dynamic.xyz/docs/wallets/embedded-wallets/mpc/setup). + + **Configure Wallet Provider** + + Once you have set up Dynamic embedded wallets, pass them to Actions SDK: + + ```typescript + import { useDynamicContext } from "@dynamic-labs/sdk-react-core" + import { actions, USDC, ExampleMarket } from './config' + + // Fetch Dynamic wallet + const { primaryWallet } = useDynamicContext() + + // Convert to Actions wallet + const wallet = await actions.wallet.toActionsWallet({ + wallet: primaryWallet, + }) + + // Wallet can now take action + const receipt = await wallet.lend.openPosition({ + amount: 100, + asset: USDC, + ...ExampleMarket + }) + ``` + + **Configure Smart Wallets** + + Optionally, create signers for smart wallets you control: + + ```typescript + import { useDynamicContext } from "@dynamic-labs/sdk-react-core" + import { actions } from './config' + + // Fetch Dynamic wallet + const { primaryWallet } = useDynamicContext() + + // Create signer + const signer = await actions.wallet.createSigner({ + wallet: primaryWallet + }) + + // Create smart wallet + const { wallet } = await actions.wallet.createSmartWallet({ + signer: signer + }) + ``` + + + + + + + Select a wallet provider: + + + + **Set Up Privy** + + Sign up for Privy and follow the full Privy installation [guide](https://docs.privy.io/basics/nodeJS/installation). + + **Configure Wallet Provider** + + Once you have set up Privy embedded wallets, pass them to Actions SDK: + + ```typescript + import { actions } from './config' + import { PrivyClient } from '@privy-io/node' + + // PRIVY: Create wallet + const privyClient = new PrivyClient(env.PRIVY_APP_ID, env.PRIVY_APP_SECRET) + + const privyWallet = await privyClient.wallets().create({ + chain_type: 'ethereum', + owner: { user_id: 'privy:did:xxxxx' }, + }) + + // ACTIONS: Let wallet make onchain Actions + const wallet = await actions.wallet.toActionsWallet({ + walletId: privyWallet.id, + address: privyWallet.address, + }) + ``` + + **Configure Smart Wallets** + + Optionally, create signers for smart wallets you control: + + ```typescript + import { actions } from './config' + import { PrivyClient } from '@privy-io/node' + import { getAddress } from 'viem' + + const privyClient = new PrivyClient(env.PRIVY_APP_ID, env.PRIVY_APP_SECRET) + + // PRIVY: Create wallet + const privyWallet = await privyClient.wallets().create({ + chain_type: 'ethereum', + owner: { user_id: 'privy:did:xxxxx' }, + }) + + // ACTIONS: Create signer + const signer = await actions.wallet.createSigner({ + walletId: privyWallet.id, + address: getAddress(privyWallet.address), + }) + + // ACTIONS: Create smart wallet + const { wallet } = await actions.wallet.createSmartWallet({ + signer: signer + }) + ``` + + + + **Set Up Turnkey** + + Sign up for Turnkey and follow the full Turnkey installation [guide](https://docs.turnkey.com/sdks/javascript-server). + + **Configure Wallet Provider** + + Once you have set up Turnkey embedded wallets, pass them to Actions SDK: + + ```typescript + import { Turnkey } from '@turnkey/sdk-server' + import { actions, USDC, ExampleMarket } from './config' + + const turnkeyClient = new Turnkey({ + apiBaseUrl: 'https://api.turnkey.com', + apiPublicKey: process.env.TURNKEY_API_KEY, + apiPrivateKey: process.env.TURNKEY_API_SECRET, + defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID, + }) + + // Create Turnkey wallet + const turnkeyWallet = await turnkeyClient.apiClient().createWallet({ + walletName: 'ETH Wallet', + accounts: [{ + curve: 'CURVE_SECP256K1', + pathFormat: 'PATH_FORMAT_BIP32', + path: "m/44'/60'/0'/0/0", + addressFormat: 'ADDRESS_FORMAT_ETHEREUM', + }], + }) + + // Convert to Actions wallet + const wallet = await actions.wallet.toActionsWallet({ + organizationId: turnkeyWallet.activity.organizationId, + signWith: turnkeyWallet.addresses[0], + }) + + // Wallet can now take action + const receipt = await wallet.lend.openPosition({ + amount: 100, + asset: USDC, + ...ExampleMarket + }) + ``` + + **Configure Smart Wallets** + + Optionally, create signers for smart wallets you control: + + ```typescript + import { Turnkey } from '@turnkey/sdk-server' + import { actions } from './config' + + const turnkeyClient = new Turnkey({ + apiBaseUrl: 'https://api.turnkey.com', + apiPublicKey: process.env.TURNKEY_API_KEY, + apiPrivateKey: process.env.TURNKEY_API_SECRET, + defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID, + }) + + // Create Turnkey wallet + const turnkeyWallet = await turnkeyClient.apiClient().createWallet({ + walletName: 'ETH Wallet', + accounts: [{ + curve: 'CURVE_SECP256K1', + pathFormat: 'PATH_FORMAT_BIP32', + path: "m/44'/60'/0'/0/0", + addressFormat: 'ADDRESS_FORMAT_ETHEREUM', + }], + }) + + // Create signer + const signer = await actions.wallet.createSigner({ + organizationId: turnkeyWallet.activity.organizationId, + signWith: turnkeyWallet.addresses[0], + }) + + // Create smart wallet + const { wallet } = await actions.wallet.createSmartWallet({ + signer: signer + }) + ``` + + + + + + +## Create your ActionsConfig + +Follow the [Configuring Actions](/app-developers/guides/configuring-actions) guide to define which protocols, chains, and assets to support. + +## Take Action + +Once configured, you can use Actions to perform DeFi operations: + +```typescript +import { USDC, ETH, USDT } from "@eth-optimism/actions-sdk/assets"; +import { ExampleMarket } from "@/actions/markets"; + +// Enable asset lending in DeFi +const lendReceipt = await wallet.lend.openPosition({ + amount: 1, + asset: USDC, + ...ExampleMarket, +}); + +// Manage user market positions +const lendPosition = await wallet.lend.getPosition(market); + +// Fetch wallet balance +const balance = await wallet.getBalance(); + +// ⚠️ COMING SOON +const borrowReceipt = await wallet.borrow.openPosition({ + amount: 1, + asset: USDT, + ...market, +}); + +// ⚠️ COMING SOON +const swapReceipt = await wallet.swap.execute({ + amountIn: 1, + assetIn: USDC, + assetOut: ETH, +}); + +// ⚠️ COMING SOON +const sendReceipt = await wallet.send({ + amount: 1, + asset: USDC, + to: "vitalik.eth", +}); +``` + +## Next Steps + +- [Configure Actions](/app-developers/guides/configuring-actions) to customize protocols, chains, and assets +- Check out the [Actions demo](https://actions.money/earn) for a complete example application diff --git a/docs/public-docs/app-developers/reference/actions/integrating-wallets.mdx b/docs/public-docs/app-developers/reference/actions/integrating-wallets.mdx new file mode 100644 index 0000000000000..33b935267f0cf --- /dev/null +++ b/docs/public-docs/app-developers/reference/actions/integrating-wallets.mdx @@ -0,0 +1,86 @@ +--- +title: Integrating wallets +description: Reference guide for integrating wallet providers with Actions SDK. +--- + +## Which wallets are right for my use case? + +In order to let your users access DeFi, they will need an EVM-compatible wallet. There are plenty of considerations when selecting a wallet schema that works for you: + +- Who will maintain custody of funds? +- What permissions must exist for my use case? +- Where in my stack should transaction signatures originate? +- How can my users on and off ramp funds? + +Actions SDK supports popular [embedded wallet providers](#embedded-wallet-providers) to address all of these questions while remaining flexible to your use case. + +## Embedded Wallet Providers + +Embedded wallet providers give your users the ability to sign onchain transactions through your app's existing email authentication and authorization flows. + +Actions works with: + + + + + + + +## Gas Sponsorship + +Signing and sending onchain transactions requires gas, or fee payment, which adds [additional overhead](/app-developers/guides/transactions/estimates) for you and friction for users. + +Actions supports gas sponsorship via a combination of smart contract wallets and [paymasters](https://eips.ethereum.org/EIPS/eip-7677). First, configure a paymaster in the chain config by specifying a bundler url. Now, any transactions submitted via an actions SmartWallet on that chain will automatically use your paymaster, therefore eliminating the need for the wallet to pay gas. + +## Connect your wallet to Actions + +Regardless of where and how transactions are signed, Actions has you covered. + + + + Follow embedded wallet provider documentation and installation steps. + Actions works with Typescript clients, both frontend React and backend Node. + + + Import [Actions SDK](https://actions.money/) alongside your chosen wallet + provider SDK. + + + Follow embedded wallet provider documentation for wallet creation and + access. + + + Call `actions.wallet.toActionsWallet(...)`, and [pass + in](/app-developers/quickstarts/actions#choose-a-wallet-provider) the + provider wallet. + + + The returned [Wallet + ](/app-developers/reference/actions/integrating-wallets#wallet-instance) is + now capable of calling Actions + [functions](/app-developers/quickstarts/actions#take-action) like Lend, + Borrow, Swap and Pay! + + + +## Smart wallets & signers + +In addition to using embedded provider wallets directly, Actions [supports the creation](/app-developers/quickstarts/actions#choose-a-wallet-provider) of custom smart contract wallets. This additional wallet type is separate from, but still controlled by the owner of the embedded wallet. + +If you [configure it](/app-developers/guides/configuring-actions), Actions will deploy a [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) compliant [Coinbase Smart Wallets](https://github.com/coinbase/smart-wallet) on the chains you've chosen to support. + +Once created, an embedded wallet can be added as a signer on the smart wallet, capable of signing transactions on behalf of the Smart Wallet. + +See [Wallet Documentation](/app-developers/reference/actions/wallet-definitions) for additional details. diff --git a/docs/public-docs/app-developers/reference/actions/lend-documentation.mdx b/docs/public-docs/app-developers/reference/actions/lend-documentation.mdx new file mode 100644 index 0000000000000..01bb89f22c827 --- /dev/null +++ b/docs/public-docs/app-developers/reference/actions/lend-documentation.mdx @@ -0,0 +1,11 @@ +--- + +title: Lend Documentation +description: API reference for Actions SDK lending operations, functions, and parameters. + +--- + +import WalletLendNamespace from "/snippets/actions/wallet-lend-namespace.mdx"; + +{/* This component is generated by script: scripts/generate-actions-components.ts */} + diff --git a/docs/public-docs/app-developers/reference/actions/wallet-definitions.mdx b/docs/public-docs/app-developers/reference/actions/wallet-definitions.mdx new file mode 100644 index 0000000000000..da1fb80c26a9d --- /dev/null +++ b/docs/public-docs/app-developers/reference/actions/wallet-definitions.mdx @@ -0,0 +1,15 @@ +--- + +title: Wallet Documentation +description: API reference for Actions SDK wallet classes, functions, and parameters. + +--- + +import WalletNamespace from "/snippets/actions/wallet-namespace.mdx"; +import Wallet from "/snippets/actions/wallet.mdx"; + +{/* This component is generated by script: scripts/generate-actions-components.ts */} + + +{/* This component is generated by script: scripts/generate-actions-components.ts */} + diff --git a/docs/public-docs/app-developers/reference/contracts/interop/predeploy.mdx b/docs/public-docs/app-developers/reference/contracts/interop/predeploy.mdx new file mode 100644 index 0000000000000..75b635f4ca272 --- /dev/null +++ b/docs/public-docs/app-developers/reference/contracts/interop/predeploy.mdx @@ -0,0 +1,61 @@ +--- +title: Interoperability predeploys +description: Learn how interoperability predeploys work. +--- + +OP Stack interop is in active development. Some features may be experimental. +The following predeploys have been added to enable interoperability. +*Predeployed smart contracts* exist at predetermined addresses, coming from the genesis state. +They're similar to [precompiles](https://www.evm.codes/precompiled) but run directly in the EVM instead of running as native code. + +## CrossL2Inbox + +The `CrossL2Inbox` is the system predeploy for cross chain messaging. +Anyone can trigger the execution or validation of cross chain messages, on behalf of any user. + +* **Address:** `0x4200000000000000000000000000000000000022` +* **Specs:** [`CrossL2Inbox`](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs&utm_medium=docs#crossl2inbox) +* **Source code:** [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol) + +## L2ToL2CrossDomainMessenger + +The `L2ToL2CrossDomainMessenger` is a higher level abstraction on top of the `CrossL2Inbox` that provides general message passing. +It's utilized for secure ERC20 token transfers between L2 chains. +Messages sent through the `L2ToL2CrossDomainMessenger` on the source chain receive both replay protection and domain binding (the executing transaction can only be valid on a single chain). + +* **Address:** `0x4200000000000000000000000000000000000023` +* **Specs:** [`L2ToL2CrossDomainMessenger`](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs&utm_medium=docs#l2tol2crossdomainmessenger) +* **Source code:** [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) + +## SuperchainETHBridge + +The `SuperchainETHBridge` is a predeploy contract that facilitates cross-chain ETH bridging within the OP Stack interop cluster. It serves as an abstraction layer on top of the `L2ToL2CrossDomainMessenger` specifically designed for native ETH transfers between chains. The contract integrates with the `ETHLiquidity` contract to manage native ETH liquidity across chains, ensuring seamless cross-chain transfers of native ETH. + +* **Address:** `0x4200000000000000000000000000000000000024` +* **Specs:** [`SuperchainETHBridge`](https://specs.optimism.io/interop/superchain-eth-bridge.html) +* **Source code:** [`SuperchainETHBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainETHBridge.sol) + + +## ETHLiquidity + +The `ETHLiquidity` contract is a predeploy that manages native ETH liquidity for cross-chain transfers within the OP Stack interop set. It works in conjunction with the `SuperchainETHBridge` to facilitate the movement of ETH between chains without requiring modifications to the EVM to generate new ETH. + +The contract is initialized with a very large balance (type(uint248).max wei) to ensure it can handle all legitimate minting operations. This design allows the `SuperchainETHBridge` to have a guaranteed source of ETH liquidity on each chain, which is essential for the cross-chain ETH transfer mechanism. + +* **Address:** `0x4200000000000000000000000000000000000025` +* **Specs:** [`ETHLiquidity`](https://specs.optimism.io/interop/eth-liquidity.html) +* **Source code:** [`ETHLiquidity`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/ETHLiquidity.sol) + + +## SuperchainTokenBridge + +The `SuperchainTokenBridge` is an abstraction on top of the `L2ToL2CrossDomainMessenger` that facilitates token bridging using interop, as described in the [token bridging spec](https://specs.optimism.io/interop/token-bridging.html). + +* **Address:** `0x4200000000000000000000000000000000000028` +* **Specs:** [`SuperchainTokenBridge`](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs&utm_medium=docs#superchainerc20bridge) +* **Source code:** [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol) + + +## Next steps + +* Learn [how messages get from one chain to another chain](/app-developers/guides/interoperability/message-passing) diff --git a/docs/public-docs/app-developers/reference/networks.mdx b/docs/public-docs/app-developers/reference/networks.mdx new file mode 100644 index 0000000000000..71e1e3b84fe78 --- /dev/null +++ b/docs/public-docs/app-developers/reference/networks.mdx @@ -0,0 +1,6 @@ +--- +title: Networks +description: Learn about networks in the Optimism ecosystem. This guide provides detailed + information and resources about networks. +--- + diff --git a/docs/public-docs/app-developers/reference/rpc-providers.mdx b/docs/public-docs/app-developers/reference/rpc-providers.mdx new file mode 100644 index 0000000000000..f3b4e3e0ac688 --- /dev/null +++ b/docs/public-docs/app-developers/reference/rpc-providers.mdx @@ -0,0 +1,238 @@ +--- +title: OP Stack RPC directory +description: Find public RPC endpoints and production RPC providers across all OP Stack networks. +--- +This directory provides developers with a comprehensive collection of RPC endpoints across all OP Stack networks, making it easier to build, deploy, and scale applications on the OP Stack ecosystem. + +## Public RPC endpoints + +The following table lists public RPC endpoints for all OP Stack networks. These endpoints are **rate-limited and not suitable for production use**, but are perfect for development, testing, and proof-of-concept work. +For production use, please see the [Production RPC Providers](#production-rpc-providers) section below. + + + + | Chain | Public RPC URL | Documentation | + | ---------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | + | OP Mainnet | [https://mainnet.optimism.io](https://mainnet.optimism.io) | [Docs](https://docs.optimism.io/superchain/networks) | + | Base | [https://mainnet.base.org](https://mainnet.base.org) | [Docs](https://docs.base.org/chain/network-information) | + | Ink | [https://rpc-gel.inkonchain.com](https://rpc-gel.inkonchain.com) | [Docs](https://docs.inkonchain.com/general/network-information) | + | Unichain | [https://mainnet.unichain.org](https://mainnet.unichain.org) | [Docs](https://docs.unichain.org/docs/technical-information/network-information) | + | Soneium | [https://rpc.soneium.org/](https://rpc.soneium.org/) | [Docs](https://docs.soneium.org/docs/builders/overview) | + | Mode | [https://mainnet.mode.network/](https://mainnet.mode.network/) | [Docs](https://docs.mode.network/user-guides/network-details) | + | Zora | [https://rpc.zora.energy](https://rpc.zora.energy) | [Docs](https://docs.zora.co/zora-network/network) | + | Swell | [https://swell-mainnet.alt.technology](https://swell-mainnet.alt.technology) | [Docs](https://build.swellnetwork.io/docs/guides/getting-started) | + | Arena-Z | [https://rpc.arena-z.gg](https://rpc.arena-z.gg) | [Docs](https://raas.gelato.network/rollups/details/public/arena-z) | + | Metal | [https://rpc.metall2.com](https://rpc.metall2.com) | [Docs](https://docs.metall2.com/chain/networks) | + | World | [https://worldchain-mainnet.g.alchemy.com/public](https://worldchain-mainnet.g.alchemy.com/public) | [Docs](https://docs.world.org/world-chain/quick-start/info) | + | Lisk | [https://rpc.api.lisk.com](https://rpc.api.lisk.com) | [Docs](https://docs.lisk.com/network-info/) | + | Polynomial | [https://rpc.polynomial.fi](https://rpc.polynomial.fi) | [Docs](https://docs.polynomial.fi/links) | + | Mint | [https://rpc.mintchain.io](https://rpc.mintchain.io) | [Docs](https://docs.mintchain.io/build/network) | + | Superseed | [https://mainnet.superseed.xyz](https://mainnet.superseed.xyz) | [Docs](https://docs.superseed.xyz/build-on-superseed/network-information) | + | Shape | [https://mainnet.shape.network](https://mainnet.shape.network) | [Docs](https://docs.shape.network/technical-details/network-information) | + | Epic | [https://mainnet.ethernitychain.io](https://mainnet.ethernitychain.io) | [Docs](https://docs.ethernity.io/introduction/network-information) | + | Race | [https://racemainnet.io](https://racemainnet.io) | [Docs](https://raceecosystem.gitbook.io/docs/building-on-race-tm/network-information) | + | BoB | [https://rpc.gobob.xyz/](https://rpc.gobob.xyz/) | [Docs](https://docs.gobob.xyz/learn/user-guides/getting-started/networks) | + + + + | Chain | Public RPC URL | Documentation | + | ------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | + | OP Sepolia | [https://sepolia.optimism.io](https://sepolia.optimism.io) | [Docs](https://docs.optimism.io/superchain/networks) | + | Base Sepolia | [https://sepolia.base.org](https://sepolia.base.org) | [Docs](https://docs.base.org/chain/network-information) | + | Ink Sepolia | [https://rpc-gel-sepolia.inkonchain.com](https://rpc-gel-sepolia.inkonchain.com) | [Docs](https://docs.inkonchain.com/general/network-information) | + | Unichain Sepolia | [https://sepolia.unichain.org](https://sepolia.unichain.org) | [Docs](https://docs.unichain.org/docs/technical-information/network-information) | + | Soneium Sepolia | [https://rpc.minato.soneium.org/](https://rpc.minato.soneium.org/) | [Docs](https://docs.soneium.org/docs/builders/overview) | + | Mode Sepolia | [https://sepolia.mode.network](https://sepolia.mode.network) | [Docs](https://docs.mode.network/user-guides/network-details) | + | Zora Sepolia | [https://sepolia.rpc.zora.energy](https://sepolia.rpc.zora.energy) | [Docs](https://docs.zora.co/zora-network/network) | + | Swell Sepolia | [https://swell-testnet.alt.technology](https://swell-testnet.alt.technology) | [Docs](https://build.swellnetwork.io/docs/guides/getting-started) | + | Arena-Z Testnet | [https://rpc.arena-z.t.raas.gelato.cloud](https://rpc.arena-z.t.raas.gelato.cloud) | [Docs](https://raas.gelato.network/rollups/details/public/arena-z) | + | Metal Testnet | [https://testnet.rpc.metall2.com/](https://testnet.rpc.metall2.com/) | [Docs](https://docs.metall2.com/chain/networks) | + | World Sepolia | [https://worldchain-sepolia.g.alchemy.com/public](https://worldchain-sepolia.g.alchemy.com/public) | [Docs](https://docs.world.org/world-chain/quick-start/info) | + | Lisk Sepolia | [https://rpc.sepolia-api.lisk.com](https://rpc.sepolia-api.lisk.com) | [Docs](https://docs.lisk.com/network-info/) | + | Polynomial Sepolia | [https://rpc.sepolia.polynomial.fi](https://rpc.sepolia.polynomial.fi) | [Docs](https://docs.polynomial.fi/links) | + | Mint Sepolia | [https://sepolia-testnet-rpc.mintchain.io](https://sepolia-testnet-rpc.mintchain.io) | [Docs](https://docs.mintchain.io/build/network) | + | Superseed Sepolia | [https://sepolia.superseed.xyz](https://sepolia.superseed.xyz) | [Docs](https://docs.superseed.xyz/build-on-superseed/network-information) | + | Shape Sepolia | [https://sepolia.shape.network](https://sepolia.shape.network) | [Docs](https://docs.shape.network/technical-details/network-information) | + | Epic Testnet | [https://testnet.ethernitychain.io](https://testnet.ethernitychain.io) | [Docs](https://docs.ethernity.io/introduction/network-information) | + | Race Testnet | [https://racetestnet.io](https://racetestnet.io) | [Docs](https://raceecosystem.gitbook.io/docs/building-on-race-tm/network-information) | + | BoB Sepolia | [https://sepolia.rpc.gobob.xyz/](https://sepolia.rpc.gobob.xyz/) | [Docs](https://docs.gobob.xyz/learn/user-guides/getting-started/networks) | + + + +## Production RPC providers + +The following providers offer production-grade RPC access to OP Stack networks. Most providers offer both free tiers with higher rate limits than public RPCs and paid plans for production applications. + + +### 1RPC + +**Description**: [1RPC](https://www.1rpc.io/) offers [free and paid plans](https://www.1rpc.io/#pricing) for the following [networks](https://docs.1rpc.io/using-the-web3-api/networks): + +**Supported Testnets**: Mode Sepolia, World Sepolia + +**Supported Mainnets**: OP Mainnet, Mode, Base + +### Ankr + +**Description**: [Ankr](https://www.ankr.com/) offers [free and paid plans](https://www.ankr.com/rpc/pricing/) for the following [networks](https://www.ankr.com/rpc/): + +**Supported Testnets**: OP Sepolia, Swell Sepolia, Base Sepolia + +**Supported Mainnets**: OP Mainnet, Swell, Base + +### Alchemy + +**Description**: [Alchemy](https://www.alchemy.com/) offers [free and paid plans](https://www.alchemy.com/pricing) for the following [networks](https://docs.alchemy.com/reference/api-overview): + +**Supported Testnets**: OP Sepolia, Ink Sepolia, Unichain Sepolia, Soneium Sepolia, Base Sepolia, World Sepolia, Shape Sepolia + +**Supported Mainnets**: OP Mainnet, Ink, Unichain, Soneium, Base, World, Shape + +### All That Node + +**Description**: [All That Node](https://www.allthatnode.com/) offers [free and paid plans](https://www.allthatnode.com/pricing.dsrv) for the following [networks](https://docs.allthatnode.com/docs/supported-protocols-1): + +**Supported Testnets**: OP Sepolia + +**Supported Mainnets**: OP Mainnet, Base + +### Blast + +**Description**: [Blast](https://blastapi.io/) offers [free and paid plans](https://blastapi.io/pricing) for the following [networks](https://blastapi.io/chains): + +**Supported Testnets**: OP Sepolia, Mode Sepolia, Base Sepolia, BoB Sepolia + +**Supported Mainnets**: OP Mainnet, Mode, Base, BoB + +### Blockdaemon + +**Description**: [Blockdaemon](https://www.blockdaemon.com/) offers [free and paid plans](https://www.blockdaemon.com/api/pricing) for the following [networks](https://www.blockdaemon.com/protocols): + +**Supported Testnets**: OP Sepolia, Unichain Sepolia, Base Sepolia + +**Supported Mainnets**: OP Mainnet, Unichain, Base + +### BlockPI + +**Description**: [BlockPI](https://blockpi.io/) offers [free and paid plans](https://blockpi.io/pricing) for the following [networks](https://blockpi.io/chains): + +**Supported Testnets**: OP Sepolia, Unichain Sepolia, Base Sepolia + +**Supported Mainnets**: OP Mainnet, Unichain, Base + +### Chainstack + +**Description**: [Chainstack](https://chainstack.com/) offers [free and paid plans](https://chainstack.com/pricing/) for the following [networks](https://chainstack.com/protocols/): + +**Supported Testnets**: OP Sepolia, Base Sepolia + +**Supported Mainnets**: OP Mainnet, Base + +### dRPC NodeCloud + +**Description**: [dRPC](https://drpc.org/nodecloud-multichain-rpc-management) offers [free and paid plans](https://drpc.org/pricing) for the following [networks](https://drpc.org/chainlist): + +**Supported Testnets**: OP Sepolia, Ink Sepolia, Unichain Sepolia, Soneium Sepolia, Mode Sepolia, Zora Sepolia, Swell Sepolia, Metal Sepolia, Base Sepolia, World Sepolia, Lisk Sepolia, Superseed Sepolia, BoB Sepolia + +**Supported Mainnets**: OP Mainnet, Ink, Unichain, Soneium, Mode, Zora, Swell, Metal, Base, World, Lisk, Superseed, BoB + +### GetBlock + +**Description**: [GetBlock](https://getblock.io/) offers [free and paid plans](https://getblock.io/pricing/) for the following [networks](https://docs.getblock.io/api-reference/overview#supported-networks): + +**Supported Testnets**: OP Sepolia + +**Supported Mainnets**: OP Mainnet + +### Grove + +**Description**: [Grove](https://grove.city/) offers [free and paid plans](https://www.grove.city/pricing) for the following [networks](https://www.grove.city/services): + +**Supported Testnets**: OP Sepolia, Base Sepolia + +**Supported Mainnets**: OP Mainnet, Base Mainnet, Ink Mainnet + +### Infura + +**Description**: [Infura](https://infura.io) offers [free and paid plans](https://www.infura.io/pricing) for the following [networks](https://www.infura.io/networks): + +**Supported Testnets**: OP Sepolia, Unichain Sepolia, Swell Sepolia, Base Sepolia + +**Supported Mainnets**: OP Mainnet, Unichain, Swell, Base + +### Moralis + +**Description**: [Moralis](https://moralis.io) offers [free and paid plans](https://developers.moralis.com/pricing/) for the following [networks](https://developers.moralis.com/chains/): + +**Supported Testnets**: OP Sepolia, Base Sepolia, Lisk Sepolia + +**Supported Mainnets**: OP Mainnet, Base, Lisk + +### Nodies DLB + +**Description**: [Nodies DLB](https://www.nodies.app/) offers [free and paid plans](https://www.nodies.app/pricing) for the following [networks](https://docs.nodies.app/overview/supported-blockchains): + +**Supported Testnets**: OP Sepolia, Base Sepolia + +**Supported Mainnets**: OP Mainnet, Ink, Base + +### NOWNodes + +**Description**: [NOWNodes](https://nownodes.io/) offers [free and paid plans](https://nownodes.io/pricing) for the following [networks](https://nownodes.io/nodes): + +**Supported Testnets**: n/a + +**Supported Mainnets**: OP Mainnet, Base, Lisk + +### OnFinality + +**Description**: [OnFinality](https://onfinality.io/) offers [free and paid plans](https://onfinality.io/pricing) for the following [networks](https://onfinality.io/networks): + +**Supported Testnets**: OP Sepolia, Unichain Sepolia, Base Sepolia + +**Supported Mainnets**: OP Mainnet, Unichain, Base + +### QuickNode + +**Description**: [QuickNode](https://www.quicknode.com/) offers [free and paid plans](https://www.quicknode.com/pricing) for the following [networks](https://www.quicknode.com/chains): + +**Supported Testnets**: OP Sepolia, Ink Sepolia, Unichain Sepolia, Base Sepolia, World Sepolia, Race Sepolia + +**Supported Mainnets**: OP Mainnet, Ink, Unichain, Mode, Zora, Base, World, Lisk, Race + +### RockX + +**Description**: [RockX](https://www.rockx.com/) offers [free and paid plans](https://access.rockx.com/product/optimism-blockchain-api-for-web3-builders) for the following [networks](https://access.rockx.com/): + +**Supported Testnets**: n/a + +**Supported Mainnets**: OP Mainnet, Base + +### Tenderly + +**Description**: [Tenderly](https://tenderly.co/) offers [free and paid plans](https://tenderly.co/pricing) for the following [networks](https://docs.tenderly.co/node/rpc-reference): + +**Supported Testnets**: OP Sepolia, Ink Sepolia, Unichain Sepolia, Soneium Sepolia, Mode Sepolia, Swell Sepolia, Base Sepolia, World Sepolia, Lisk Sepolia, Polynomial Sepolia, BoB Sepolia + +**Supported Mainnets**: OP Mainnet, Ink, Unichain, Soneium, Mode, Swell, Base, World, Lisk, Polynomial, BoB + +### Validation Cloud + +**Description**: [Validation Cloud](https://www.validationcloud.io/) offers [free and paid plans](https://www.validationcloud.io/node) for the following [networks](https://docs.validationcloud.io/v1/optimism/overview): + +**Supported Testnets**: n/a + +**Supported Mainnets**: OP Mainnet, Base + +## Directory governance + +The OP Stack RPC Directory is maintained by OP Labs with the following policies: + +* Providers must submit a docs PR to the [docs](https://github.com/ethereum-optimism/docs/) to be added +* To be listed, providers must support at least one network in the [OP Stack ecosystem](/op-stack/protocol/superchain-registry) +* Anyone can submit a PR to remove a provider that does not support a listed network + +## Next steps + +* Want to run your own node? See the [Node operators guide](/node-operators/reference/architecture/rollup-node). +* Looking for other developer tools? See [developer tools overview](/app-developers/tools) to explore more options! diff --git a/docs/public-docs/app-developers/reference/tokens/tokenlist.mdx b/docs/public-docs/app-developers/reference/tokens/tokenlist.mdx new file mode 100644 index 0000000000000..db9291b752bba --- /dev/null +++ b/docs/public-docs/app-developers/reference/tokens/tokenlist.mdx @@ -0,0 +1,39 @@ +--- +title: Bridged token addresses +description: This reference guide lists the correct bridged token addresses for each token. +--- + +Various ERC-20 tokens originally deployed to Ethereum also have corresponding "bridged" representations on OP Mainnet. +The [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) exists to help users discover the correct bridged token addresses for each token. +This page is automatically generated from the Superchain Token List. + + +**Tokens listed on this page are provided for convenience only** and are automatically derived from the [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io). +**The presence of a token on this page does not imply any endorsement of the token or its minter.** + + +### USDC on OP Mainnet + + + The legacy bridged version of USDC (USDC.e) at address `0x7f5c764cbc14f9669b88837ca1490cca17c31607` is being deprecated on OP Mainnet. + Users and developers should migrate to using the native USDC token issued directly by [Circle](https://www.circle.com/en/), the issuer of [USDC](https://www.circle.com/en/usdc?gad_source=1). + + +Information about the bridged `USDC.e` token and native USDC token can be found below. + +| Symbol | Description | Address | +| -------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `USDC.e` | Bridged USDC from Ethereum | [`0x7f5c764cbc14f9669b88837ca1490cca17c31607`](https://explorer.optimism.io/token/0x7f5c764cbc14f9669b88837ca1490cca17c31607) | +| `USDC` | Native USDC issued by Circle | [`0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85`](https://explorer.optimism.io/token/0x0b2c639c533813f4aa9d7837caf62653d097ff85) | + +## OP Mainnet + + +For a complete and up-to-date list of bridged token addresses for OP Mainnet (Chain ID: 10), please refer to the [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) repository. + + +## OP Sepolia + + +For a complete and up-to-date list of bridged token addresses for OP Sepolia (Chain ID: 11155420), please refer to the [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) repository. + diff --git a/docs/public-docs/app-developers/reference/tools/supersim/chain-a.mdx b/docs/public-docs/app-developers/reference/tools/supersim/chain-a.mdx new file mode 100644 index 0000000000000..9539eeb6fde6d --- /dev/null +++ b/docs/public-docs/app-developers/reference/tools/supersim/chain-a.mdx @@ -0,0 +1,91 @@ +--- +title: OPChainA (chainID 901) +description: Learn network details and contract addresses for OPChainA (chainID 901). +--- + +This guide provides network details and contract addresses for OPChainA (chainID 901) when running `supersim` vanilla mode. + +## Network details + +| **Parameter** | **Value** | +| ------------- | ---------------------------------------------- | +| **Name** | OPChainA | +| **Chain ID** | 901 | +| **RPC URL** | [http://127.0.0.1:9545](http://127.0.0.1:9545) | + +## Contract addresses + +### L1 contracts + +```json +{ + "AddressManager": "0x78d21C9820A9135215202A9a8D6521483D4b75cD", + "AnchorStateRegistry": "0x21799f09394c50220CCD95E7dAc1cdD774FC871a", + "AnchorStateRegistryProxy": "0xa6F40d5770b3509aB40B2effa5cb544D29743ec7", + "DelayedWETH": "0x49BBFf1629824A1e7993Ab5c17AFa45D24AB28c9", + "DelayedWETHProxy": "0x309DA6B9a8fE16afD7D067528d358E55314bEa6b", + "DisputeGameFactory": "0x20B168142354Cee65a32f6D8cf3033E592299765", + "DisputeGameFactoryProxy": "0x444689B81D485bc58AB81aC02A95a937fAa152D7", + "L1CrossDomainMessenger": "0x094e6508ba9d9bf1ce421fff3dE06aE56e67901b", + "L1CrossDomainMessengerProxy": "0xcd712b03bc6424BF45cE6C29Fc90FFDece228F6E", + "L1ERC721Bridge": "0x5C4F5e749A61a9503c4AAE8a9393e89609a0e804", + "L1ERC721BridgeProxy": "0x018dC24a6617c47cAa00C3fA25097214B2D4F447", + "L1StandardBridge": "0xb7900B27Be8f0E0fF65d1C3A4671e1220437dd2b", + "L1StandardBridgeProxy": "0x8d515eb0e5F293B16B6bBCA8275c060bAe0056B0", + "L2OutputOracle": "0x19652082F846171168Daf378C4fD3ee85a0D4A60", + "L2OutputOracleProxy": "0x6cE0530E823e23be85D8e151FB023605eB4F6d43", + "Mips": "0xB3A0348310a0ff78E5FbDB7f14BB7d3e02d40773", + "OptimismMintableERC20Factory": "0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567", + "OptimismMintableERC20FactoryProxy": "0x15c855966C196Be3a8ca747E8A8Bf40928d4741f", + "OptimismPortal": "0x37a418800d0c812A9dE83Bc80e993A6b76511B57", + "OptimismPortal2": "0xfcbb237388CaF5b08175C9927a37aB6450acd535", + "OptimismPortalProxy": "0xF5fe61a258CeBb54CCe428F76cdeD04Cbc12F53d", + "PreimageOracle": "0x3bd7E801E51d48c5d94Ea68e8B801DFFC275De75", + "ProtocolVersions": "0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F", + "ProtocolVersionsProxy": "0x6dA4f6489039d9f4F3144954DDF5bb2F4986e90b", + "ProxyAdmin": "0xe32a4D31ffD5596542DAc8239a1DE3Fff9d63475", + "SafeProxyFactory": "0x4a05c09875DE2DD5B81Bc01dd46eD4699b181bfA", + "SafeSingleton": "0x99A395CE6d6b37CaaCBad64fB42d556b6CA73a48", + "SuperchainConfig": "0x068E44eB31e111028c41598E4535be7468674D0A", + "SuperchainConfigProxy": "0x7E6c6ebCF109fa23277b86bdA39738035C21BB86", + "SystemConfig": "0x6167B477F8d9138aa509f54b2800443857e28c0f", + "SystemConfigProxy": "0xf32919Ed2490b56EaD65E72749894aE4C9523320", + "SystemOwnerSafe": "0xc052b7316C87390E555aF97D42bCd5FB6d5eEFDa" +} +``` + +### L2 contracts + +```json +{ + // OP Stack predeploys + "L2ToL1MessagePasser": "0x4200000000000000000000000000000000000016", + "L2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "L2StandardBridge": "0x4200000000000000000000000000000000000010", + "L2ERC721Bridge": "0x4200000000000000000000000000000000000014", + "SequencerFeeVault": "0x4200000000000000000000000000000000000011", + "OptimismMintableERC20Factory": "0x4200000000000000000000000000000000000012", + "OptimismMintableERC721Factory": "0x4200000000000000000000000000000000000017", + "L1BlockInterop": "0x4200000000000000000000000000000000000015", + "GasPriceOracle": "0x420000000000000000000000000000000000000F", + "ProxyAdmin": "0x4200000000000000000000000000000000000018", + "BaseFeeVault": "0x4200000000000000000000000000000000000019", + "L1FeeVault": "0x420000000000000000000000000000000000001A", + "OperatorFeeVault": "0x420000000000000000000000000000000000001B", + "GovernanceToken": "0x4200000000000000000000000000000000000042", + "SchemaRegistry": "0x4200000000000000000000000000000000000020", + "EAS": "0x4200000000000000000000000000000000000021", + "CrossL2Inbox": "0x4200000000000000000000000000000000000022", + "L2ToL2CrossDomainMessenger": "0x4200000000000000000000000000000000000023", + "SuperchainETHBridge": "0x4200000000000000000000000000000000000024", + "SuperchainTokenBridge": "0x4200000000000000000000000000000000000028", + + // Periphery + "L2NativeSuperchainERC20": "0x420beeF000000000000000000000000000000001" +} +``` + +## Next steps + +* Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline. +* For more info about how OP Stack interoperability works under the hood, [check out the specs](https://specs.optimism.io/interop/overview.html?utm_source=op-docs&utm_medium=docs). diff --git a/docs/public-docs/app-developers/reference/tools/supersim/chain-b.mdx b/docs/public-docs/app-developers/reference/tools/supersim/chain-b.mdx new file mode 100644 index 0000000000000..5811994c5523d --- /dev/null +++ b/docs/public-docs/app-developers/reference/tools/supersim/chain-b.mdx @@ -0,0 +1,91 @@ +--- +title: OPChainB (chainID 902) +description: Learn network details and contract addresses for OPChainB (chainID 902). +--- + +This guide provides network details and contract addresses for OPChainB (chainID 902) when running `supersim` vanilla mode. + +## Network details + +| **Parameter** | **Value** | +| ------------- | ---------------------------------------------- | +| **Name** | OPChainB | +| **Chain ID** | 902 | +| **RPC URL** | [http://127.0.0.1:9546](http://127.0.0.1:9546) | + +## Contract addresses + +### L1 contracts + +```json +{ + "AddressManager": "0xafB51A0f73C8409AeA1207DF7f39885c927BeA46", + "AnchorStateRegistry": "0x05493149c84A71063f7948127bb931f8377F779C", + "AnchorStateRegistryProxy": "0xfd0269a716A59fF125Bd7eb65Cd3427C8555bab7", + "DelayedWETH": "0x49BBFf1629824A1e7993Ab5c17AFa45D24AB28c9", + "DelayedWETHProxy": "0xA63353128502269b4A4A4c2677fE316cd9ad4397", + "DisputeGameFactory": "0x20B168142354Cee65a32f6D8cf3033E592299765", + "DisputeGameFactoryProxy": "0x5F416fEb15c8B382d338FDBDb7D44967ca2b59BC", + "L1CrossDomainMessenger": "0x094e6508ba9d9bf1ce421fff3dE06aE56e67901b", + "L1CrossDomainMessengerProxy": "0xeCA0f912b4bd255f3851951caE5775CC9400aA3B", + "L1ERC721Bridge": "0x5C4F5e749A61a9503c4AAE8a9393e89609a0e804", + "L1ERC721BridgeProxy": "0xDCE41E6C0901586EE27Eac329EBD4b5fe5A7170d", + "L1StandardBridge": "0xb7900B27Be8f0E0fF65d1C3A4671e1220437dd2b", + "L1StandardBridgeProxy": "0x67B2aB287a32bB9ACe84F6a5A30A62597b10AdE9", + "L2OutputOracle": "0x19652082F846171168Daf378C4fD3ee85a0D4A60", + "L2OutputOracleProxy": "0x006Af3fB62c4BE4fB0393995d364BbFe6b0F3CB2", + "Mips": "0xB3A0348310a0ff78E5FbDB7f14BB7d3e02d40773", + "OptimismMintableERC20Factory": "0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567", + "OptimismMintableERC20FactoryProxy": "0x1A2A942d891e525D1Ab192578a378980729fD585", + "OptimismPortal": "0x35e67BC631C327b60C6A39Cff6b03a8adBB19c2D", + "OptimismPortal2": "0xfcbb237388CaF5b08175C9927a37aB6450acd535", + "OptimismPortalProxy": "0xdfC9DEAbEEbDaa7620C71e2E76AEda32919DE5f2", + "PreimageOracle": "0x3bd7E801E51d48c5d94Ea68e8B801DFFC275De75", + "ProtocolVersions": "0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F", + "ProtocolVersionsProxy": "0xE139cB0CDa5EF722870068ea331d0989776A7aDf", + "ProxyAdmin": "0xff5E6C2Af859f70B875BA59B958BEde60E36bf69", + "SafeProxyFactory": "0xb68f3B057fE3c6CdDF9DB35837Ea769FCc81978a", + "SafeSingleton": "0xeeB44D84d505AbD958d032e90704c56443eB3ED0", + "SuperchainConfig": "0x068E44eB31e111028c41598E4535be7468674D0A", + "SuperchainConfigProxy": "0x2ED4AA34573c36bF3856e597501aEf9d9Dc1687C", + "SystemConfig": "0x6167B477F8d9138aa509f54b2800443857e28c0f", + "SystemConfigProxy": "0x2Db03FE998D7c20E4B65afD1f50f04Ec4BfAb694", + "SystemOwnerSafe": "0xBF3830711B7c559042453B7546dB4736eFB4245e" +} +``` + +### L2 contracts + +```json +{ + // OP Stack predeploys + "L2ToL1MessagePasser": "0x4200000000000000000000000000000000000016", + "L2CrossDomainMessenger": "0x4200000000000000000000000000000000000007", + "L2StandardBridge": "0x4200000000000000000000000000000000000010", + "L2ERC721Bridge": "0x4200000000000000000000000000000000000014", + "SequencerFeeVault": "0x4200000000000000000000000000000000000011", + "OptimismMintableERC20Factory": "0x4200000000000000000000000000000000000012", + "OptimismMintableERC721Factory": "0x4200000000000000000000000000000000000017", + "L1BlockInterop": "0x4200000000000000000000000000000000000015", + "GasPriceOracle": "0x420000000000000000000000000000000000000F", + "ProxyAdmin": "0x4200000000000000000000000000000000000018", + "BaseFeeVault": "0x4200000000000000000000000000000000000019", + "L1FeeVault": "0x420000000000000000000000000000000000001A", + "OperatorFeeVault": "0x420000000000000000000000000000000000001B", + "GovernanceToken": "0x4200000000000000000000000000000000000042", + "SchemaRegistry": "0x4200000000000000000000000000000000000020", + "EAS": "0x4200000000000000000000000000000000000021", + "CrossL2Inbox": "0x4200000000000000000000000000000000000022", + "L2ToL2CrossDomainMessenger": "0x4200000000000000000000000000000000000023", + "SuperchainETHBridge": "0x4200000000000000000000000000000000000024", + "SuperchainTokenBridge": "0x4200000000000000000000000000000000000028", + + // Periphery + "L2NativeSuperchainERC20": "0x420beeF000000000000000000000000000000001" +} +``` + +## Next steps + +* Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline. +* For more info about how OP Stack interoperability works under the hood, [check out the specs](https://specs.optimism.io/interop/overview.html?utm_source=op-docs&utm_medium=docs). diff --git a/docs/public-docs/app-developers/reference/tools/supersim/fork.mdx b/docs/public-docs/app-developers/reference/tools/supersim/fork.mdx new file mode 100644 index 0000000000000..46e5b50b537b0 --- /dev/null +++ b/docs/public-docs/app-developers/reference/tools/supersim/fork.mdx @@ -0,0 +1,144 @@ +--- +title: Fork mode +description: Learn how to fork Supersim. +--- + +Supersim fork mode to simulate and interact with the state of the chain without needing to re-deploy or modify the contracts. This is possible if you're relying on contracts already deployed on testnet / mainnet chains. + +```sh +supersim fork +``` + +## How it works + +The `supersim` fork command simplifies the process of forking multiple chains in the OP Stack ecosystem simultaneously. It determines the appropriate block heights for each chain and launches both the L1 and L2 chains based on these values. + +This allows you to locally fork any chain in a superchain network of the [superchain registry](https://github.com/ethereum-optimism/superchain-registry), default `mainnet` versions. + +### Example startup logs + +``` +Available Accounts +----------------------- +(0): 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +--- truncated for brevity --- + +Private Keys +----------------------- +(0): 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +--- truncated for brevity --- + +Orchestrator Config: +L1: + Name: mainnet Chain ID: 1 RPC: http://127.0.0.1:8545 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-1-1521250718 +L2: + Name: op Chain ID: 10 RPC: http://127.0.0.1:9545 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-10 + Name: base Chain ID: 8453 RPC: http://127.0.0.1:9546 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-8453 + Name: zora Chain ID: 7777777 RPC: http://127.0.0.1:9547 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-7777777 +``` + +## Configuration + +``` +NAME: + supersim fork - Locally fork a network in the superchain registry + +USAGE: + supersim fork [command options] + +OPTIONS: + + --l1.fork.height value (default: 0) ($SUPERSIM_L1_FORK_HEIGHT) + L1 height to fork the superchain (bounds L2 time). `0` for latest + + --chains value ($SUPERSIM_CHAINS) + chains to fork in the superchain, mainnet options: [arena-z, automata, base, + bob, cyber, ethernity, funki, hashkeychain, ink, lisk, lyra, metal, mint, mode, + op, orderly, polynomial, race, redstone, shape, soneium, sseed, swan, swell, tbn, + unichain, worldchain, xterio-eth, zora]. In order to replace the public rpc endpoint + for a chain, specify the ($SUPERSIM_RPC_URL_) env variable. i.e + SUPERSIM_RPC_URL_OP=http://optimism-mainnet.infura.io/v3/ + + --network value (default: "mainnet") ($SUPERSIM_NETWORK) + superchain network. options: mainnet, sepolia, sepolia-dev-0. In order to + replace the public rpc endpoint for the network, specify the + ($SUPERSIM_RPC_URL_) env variable. i.e + SUPERSIM_RPC_URL_MAINNET=http://mainnet.infura.io/v3/ + + --interop.enabled (default: true) ($SUPERSIM_INTEROP_ENABLED) + enable interop predeploy and functionality + + --admin.port value (default: 8420) ($SUPERSIM_ADMIN_PORT) + Listening port for the admin server + + --interop.l2tol2cdm.override value ($SUPERSIM_INTEROP_L2TO2CDM_OVERRIDE) + Path to the L2ToL2CrossDomainMessenger build artifact that overrides + the default implementation + + --l1.port value (default: 8545) ($SUPERSIM_L1_PORT) + Listening port for the L1 instance. `0` binds to any available port + + --l2.count value (default: 2) ($SUPERSIM_L2_COUNT) + Number of L2s. Max of 5 + + --l2.starting.port value (default: 9545) ($SUPERSIM_L2_STARTING_PORT) + Starting port to increment from for L2 chains. `0` binds each chain to any + available port + + --interop.autorelay (default: false) ($SUPERSIM_INTEROP_AUTORELAY) + Automatically relay messages sent to the L2ToL2CrossDomainMessenger using + account 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 + + --interop.delay value (default: 0) ($SUPERSIM_INTEROP_DELAY) + Delay before relaying messages sent to the L2ToL2CrossDomainMessenger + + --logs.directory value ($SUPERSIM_LOGS_DIRECTORY) + Directory to store logs + + --l1.host value (default: "127.0.0.1") ($SUPERSIM_L1_HOST) + Host address for the L1 instance + + --l2.host value (default: "127.0.0.1") ($SUPERSIM_L2_HOST) + Host address for L2 instances + + --odyssey.enabled (default: false) ($SUPERSIM_ODYSSEY_ENABLED) + Enable odyssey experimental features + + --dependency.set value ($SUPERSIM_DEPENDENCY_SET) + Override local chain IDs in the dependency set.(format: '[901,902]' or '[]') + + --log.level value (default: INFO) ($SUPERSIM_LOG_LEVEL) + The lowest log level that will be output + + --log.format value (default: text) ($SUPERSIM_LOG_FORMAT) + Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', + 'json-pretty', + + --log.color (default: false) ($SUPERSIM_LOG_COLOR) + Color the log output if in terminal mode + + --log.pid (default: false) ($SUPERSIM_LOG_PID) + Show pid in the log + + --help, -h (default: false) + show help +``` + +## Notes + +### Fork height + +The fork height is determined by L1 block height (default `latest`). This is then used to derive the corresponding L2 block to start from. + +### Interoperability contracts + +By default, interop contracts are not deployed on forked networks. To include them, run `supersim` with the `--interop.enabled` flag. + +```sh +supersim fork --chains=op,base,zora --interop.enabled +``` + +## Next steps + +* Explore the Supersim [included contracts](/app-developers/reference/tools/supersim/included-contracts) being used to help replicate the OP Stack environment. +* Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline. diff --git a/docs/public-docs/app-developers/reference/tools/supersim/included-contracts.mdx b/docs/public-docs/app-developers/reference/tools/supersim/included-contracts.mdx new file mode 100644 index 0000000000000..db6b18b55fd82 --- /dev/null +++ b/docs/public-docs/app-developers/reference/tools/supersim/included-contracts.mdx @@ -0,0 +1,43 @@ +--- +title: Included contracts +description: Learn about the Supersim included contracts. +--- + +The `supersim` chain environment includes contracts already deployed to help replicate the OP Stack interop environment. See [OP Chain A](./chain-a) for contract address examples for a L2 system. + +## OP Stack system contracts (L1) + +These are the L1 contracts that are required for a rollup as part of the OP Stack protocol. Examples are the [OptimismPortal](https://github.com/ethereum-optimism/optimism-legacy/blob/8205f678b7b4ac4625c2afe351b9c82ffaa2e795/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol), [L1StandardBridge](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/L1StandardBridge.sol), and [L1CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol). + +For more details, see [example contracts](/op-mainnet/network-information/op-addresses#ethereum-mainnet) or the [source code](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1). + +## OP Stack L2 contracts (L2) + +The OP Stack system contracts on the L2 are included at the standard addresses by default. + +* [Standard OP Stack predeploys (L2)](https://specs.optimism.io/protocol/predeploys.html) +* [Interoperability predeploys (*experimental*) (L2)](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs&utm_medium=docs) +* [OP Stack preinstalls (L2)](https://specs.optimism.io/protocol/preinstalls.html?utm_source=op-docs&utm_medium=docs) + +## Periphery contracts (L2) + +L2 chains running on `supersim` also includes some useful contracts for testing purposes that are not part of the OP Stack by default. + +### L2NativeSuperchainERC20 + +A simple ERC20 included in Supersim for testing cross-chain token transfers. It includes permissionless minting for easy testing. + +Source: [L2NativeSuperchainERC20.sol](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/L2NativeSuperchainERC20.sol) + +Deployed address: `0x420beeF000000000000000000000000000000001` + +#### Minting new tokens + +```bash +cast send 0x420beeF000000000000000000000000000000001 "mint(address _to, uint256 _amount)" $RECIPIENT_ADDRESS 1ether --rpc-url $L2_RPC_URL +``` + +## Next steps + +* Get network details about the two OP Stack systems spun up in vanilla mode: [OPChainA (chainID 901)](/app-developers/reference/tools/supersim/chain-a) and [OPChainB (chainID 902)](/app-developers/reference/tools/supersim/chain-b). +* Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline. diff --git a/docs/public-docs/app-developers/reference/tools/supersim/vanilla.mdx b/docs/public-docs/app-developers/reference/tools/supersim/vanilla.mdx new file mode 100644 index 0000000000000..dcbf863ddfa46 --- /dev/null +++ b/docs/public-docs/app-developers/reference/tools/supersim/vanilla.mdx @@ -0,0 +1,150 @@ +--- +title: Vanilla mode +description: Learn how to use Supersim in vanilla mode (non-forked). +--- + +This guide explains how to start `supersim` in vanilla (non-forked) mode. By default, two OP Stack systems will be spun up in vanilla mode: + +* OPChainA (chainID 901) +* OPChainB (chainID 902) + Both "roll up" into a single L1 chain (chainID 900). + +## How it works + +```sh +supersim +``` + +Vanilla mode will start 3 chains, with the OP Stack contracts & periphery contracts already deployed. + +* (1) L1 Chain + * Chain 900 +* (2) L2 Chains + * Chain 901 + * Chain 902 + +### Example startup logs + +``` +Available Accounts +----------------------- +(0): 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +(1): 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +(2): 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC +(3): 0x90F79bf6EB2c4f870365E785982E1f101E93b906 +(4): 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 +(5): 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc +(6): 0x976EA74026E726554dB657fA54763abd0C3a0aa9 +(7): 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 +(8): 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f +(9): 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 + +Private Keys +----------------------- +(0): 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +(1): 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d +(2): 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a +(3): 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 +(4): 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a +(5): 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba +(6): 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e +(7): 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356 +(8): 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 +(9): 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 + +Orchestrator Config: +L1: + Name: L1 Chain ID: 900 RPC: http://127.0.0.1:8545 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-900 +L2: + Name: OPChainA Chain ID: 901 RPC: http://127.0.0.1:9545 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-901 + Name: OPChainB Chain ID: 902 RPC: http://127.0.0.1:9546 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-902 +``` + +## Configuration + +``` +NAME: + supersim - Superchain Multi-L2 Simulator + +USAGE: + supersim [global options] command [command options] + +VERSION: + untagged + +DESCRIPTION: + Local multichain optimism development environment + +COMMANDS: + fork Locally fork a network in the superchain registry + docs Display available docs links + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + + --admin.port value (default: 8420) ($SUPERSIM_ADMIN_PORT) + Listening port for the admin server + + --interop.l2tol2cdm.override value ($SUPERSIM_INTEROP_L2TO2CDM_OVERRIDE) + Path to the L2ToL2CrossDomainMessenger build artifact that overrides + the default implementation + + --l1.port value (default: 8545) ($SUPERSIM_L1_PORT) + Listening port for the L1 instance. `0` binds to any available port + + --l2.count value (default: 2) ($SUPERSIM_L2_COUNT) + Number of L2s. Max of 5 + + --l2.starting.port value (default: 9545) ($SUPERSIM_L2_STARTING_PORT) + Starting port to increment from for L2 chains. `0` binds each chain to any + available port + + --interop.autorelay (default: false) ($SUPERSIM_INTEROP_AUTORELAY) + Automatically relay messages sent to the L2ToL2CrossDomainMessenger using + account 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 + + --interop.delay value (default: 0) ($SUPERSIM_INTEROP_DELAY) + Delay before relaying messages sent to the L2ToL2CrossDomainMessenger + + --logs.directory value ($SUPERSIM_LOGS_DIRECTORY) + Directory to store logs + + --l1.host value (default: "127.0.0.1") ($SUPERSIM_L1_HOST) + Host address for the L1 instance + + --l2.host value (default: "127.0.0.1") ($SUPERSIM_L2_HOST) + Host address for L2 instances + + --odyssey.enabled (default: false) ($SUPERSIM_ODYSSEY_ENABLED) + Enable odyssey experimental features + + --dependency.set value ($SUPERSIM_DEPENDENCY_SET) + Override local chain IDs in the dependency set.(format: '[901,902]' or '[]') + + --log.color (default: false) ($SUPERSIM_LOG_COLOR) + Color the log output if in terminal mode + + --log.format value (default: text) ($SUPERSIM_LOG_FORMAT) + Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', + 'json-pretty', + + --log.level value (default: INFO) ($SUPERSIM_LOG_LEVEL) + The lowest log level that will be output + + --log.pid (default: false) ($SUPERSIM_LOG_PID) + Show pid in the log + + MISC + + --help, -h (default: false) + show help + + --version, -v (default: false) + print the version +``` + +## Next steps + +* Explore the Supersim [included contracts](/app-developers/reference/tools/supersim/included-contracts) being used to help replicate the OP Stack interop environment. +* Get network details about the two OP Stack systems spun up in vanilla mode: [OPChainA (chainID 901)](/app-developers/reference/tools/supersim/chain-a) and [OPChainB (chainID 902)](/app-developers/reference/tools/supersim/chain-b). +* Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline. diff --git a/docs/public-docs/app-developers/tools/account-abstraction.mdx b/docs/public-docs/app-developers/tools/account-abstraction.mdx new file mode 100644 index 0000000000000..778ad1d21be64 --- /dev/null +++ b/docs/public-docs/app-developers/tools/account-abstraction.mdx @@ -0,0 +1,55 @@ +--- +title: Account abstraction +description: This guide explains how to use account abstraction to remove friction from + your app experience +--- + + + This page includes providers that meet specific [inclusion criteria](#inclusion-criteria), as outlined below. Please visit the [community account abstractions page](https://github.com/ethereum-optimism/developers/blob/main/community/tools/account-abstraction.md) for an additional listing of third-party account abstraction tools. + + +[ERC-4337](https://www.erc4337.io/docs/paymasters/introduction), also known as Account Abstraction, enables more opportunities for apps and wallet developers to innovate on user experiences, including the ability to: + +* Batch transactions together (e.g. approve and execute a swap in one go) +* Offer wallets with easy recovery and no seed phrase +* Sponsor the gas fees for transactions +* Enable users to pay gas in the token(s) of their choice + +## Bundlers + +The OP Stack includes support for the `eth_sendRawTransactionConditional` RPC method to assist bundlers on shared 4337 mempools. See the [specification](/op-stack/features/send-raw-transaction-conditional) for how this method is implemented in op-geth. + +If used by the chain operator, also see the supplemental [op-txproxy](/chain-operators/tools/op-txproxy) service which may apply additional restrictions prior to reaching the block builder. + + + As of today, this endpoint is not enabled by default in the stack. The operator must explicitly configure this. + + +## Account abstraction tools + +Ready to enable account abstraction experiences in your app? Here's some helpful information on account abstraction infrastructure like ERC-4337 bundler and gas manager APIs that are available on OP Mainnet: + +* [Alchemy](https://www.alchemy.com/account-abstraction): + Account Kit is a complete solution for account abstraction. Using Account Kit, you can create a smart contract wallet for every user that leverages account abstraction to simplify every step of your app's onboarding experience. It also offers Gas Manager and Bundler APIs for sponsoring gas and batching transactions. + +* [Biconomy](https://docs.biconomy.io/): is an Account Abstraction toolkit that enables you to provide the simplest UX for your app or wallet. It offers modular smart accounts, as well as paymasters and bundlers as a service for sponsoring gas and executing transactions at scale. + +* [GroupOS](https://docs.groupos.xyz/introduction/group-os): provides Smart Wallets that are ERC-4337 compliant smart wallets, offering full flexibility, programmability and extensibility as well as out-of-the-box toolkit groups need to gaslessly onboard and activate wallets to games, applications, and/or protocols. + +* [Openfort](https://openfort.io/docs/?utm_source=optimism&utm_medium=docs&utm_campaign=backlinks): an open-source wallet infrastructure solution. The core offerings include embedded wallets, global wallets and AA infrastructure (Paymaster and Bundler). It enables rapid integration of wallet functionality, intuitive onboarding, and stablecoin flows. + +* [Pimlico](https://docs.pimlico.io/): provides an infrastructure platform that makes building smart accounts simpler. If you are developing an ERC-4337 smart account, they provide bundlers, verifying paymasters, ERC-20 paymasters, and much more. + +* [Reown](https://reown.com/?utm_source=optimism&utm_medium=docs&utm_campaign=backlinks) gives developers the tools to build user experiences that make digital ownership effortless, intuitive, and secure. Using Reown's AppKit SDK, you can enable your users to create a smart wallet using their social logins, configure a paymaster to sponsor gas fees, enable chain abstraction and a lot more. + +* [Safe](https://docs.safe.global/home/what-is-safe): provides modular smart account infrastructure and account abstraction stack via their Safe\{Core\} Account Abstraction SDK, API, and Protocol. + +* [Stackup](https://docs.stackup.sh/docs): provides smart account tooling for building account abstraction within your apps. They offer Paymaster and Bundler APIs for sponsoring gas and sending account abstraction transactions. + +* [thirdweb](https://portal.thirdweb.com/react/v5/account-abstraction/get-started?utm_source=opdocs&utm_medium=docs): + offers the complete tool-kit to leverage account abstraction technology to enable seamless user experiences for your users. This includes Account Factory contracts that lets your users spin up Smart Accounts, Bundler for UserOps support, and Paymaster to enable gas sponsorships. + +## Helpful tips + +* [EIP-1271 Signature Validation](https://eip1271.io/) +* [Making smart accounts work with WalletConnect v2](https://safe-global.notion.site/WalletConnect-v2-update-Issues-and-solutions-for-smart-wallets-3fc32fad6af4485fa5823eaebd486819) diff --git a/docs/public-docs/app-developers/tools/block-explorers.mdx b/docs/public-docs/app-developers/tools/block-explorers.mdx new file mode 100644 index 0000000000000..65f45313609d8 --- /dev/null +++ b/docs/public-docs/app-developers/tools/block-explorers.mdx @@ -0,0 +1,70 @@ +--- +title: Block explorers +description: Learn about different block explorers you can use to interact with contracts and view transaction history for OP Mainnet and OP Sepolia. +--- + +## Blockscout + +We have a Blockscout explorer for [OP Mainnet](https://optimism.blockscout.com) and [OP Sepolia](https://optimism-sepolia.blockscout.com/). It includes: + +* [Verified testnet contract source code, along with the ability to interact with it](https://optimism.blockscout.com/verified-contracts) +* [Detailed testnet transaction information](https://optimism.blockscout.com/tx/0xa1b04233084d4067ec0bb3e09301012900f0e209f14a3d406f3d6dc696eea138) + +Blockscout also has some OP-Mainnet-specific features: + +* [An interactive list of deposits (L1-L2)](https://optimism.blockscout.com/l2-deposits) +* [An interactive list of withdrawals (L2-L1)](https://optimism.blockscout.com/l2-withdrawals) +* [Transaction batches](https://optimism.blockscout.com/l2-txn-batches) +* [App marketplace](https://optimism.blockscout.com/apps) +* And much more! + +## Etherscan + +We have Etherscan explorers for the [OP Mainnet](https://explorer.optimism.io) and the [OP Sepolia](https://testnet-explorer.optimism.io/). +Etherscan has lots of tools to help you debug transactions. + +Optimistic Etherscan has all the tools you expect from Etherscan, such as: + +* [Verified contract source code, along with the ability to interact with it](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F#code) +* [Detailed transaction information](https://explorer.optimism.io/tx/0x292423266d6da24126dc4e0e81890c22a67295cc8b1a987e71ad84748511452f) +* And everything else you might find on Etherscan! + +It's also got some OP-Mainnet-specific features: + +* [A list of L1-to-L2 transactions](https://explorer.optimism.io/txsEnqueued) +* [A list of L2-to-L1 transactions](https://explorer.optimism.io/txsExit) +* [A tool for finalizing L2-to-L1 transactions](https://explorer.optimism.io/messagerelayer) +* And more! Just check it out and click around to find all of the available features. + +## Superscan by Routescan + +[Superscan](https://superscan.network) is the dev-focused OP Stack explorer unified at the ecosystem level, powered by [Routescan](https://routescan.io). On the Superscan, developers can quickly glance at transactions, blocks, addresses, deployed contracts and more across OP Stack chains in unified pages. + +The Superscan currently includes: + +* Mainnet - OP Mainnet, Base, Zora, Mode, Cyber, Orderly, Fraxtal, Public Goods Network +* Testnet - Zora, Mode, Orderly, Fraxtal + + +## Tenderly + +Tenderly's [Developer Explorer](https://docs.tenderly.co/developer-explorer?mtm_campaign=ext-docs&mtm_kwd=optimism) for OP Mainnet and OP Sepolia allows you to monitor and inspect transactions, providing a high level of detail and additional tools: + +Tenderly Developer Explorer lets you: + +* Keep track of specific [contracts](https://docs.tenderly.co/developer-explorer/contracts?mtm_campaign=ext-docs&mtm_kwd=optimism) and their transactions +* Inspect [transaction execution](https://docs.tenderly.co/developer-explorer/inspect-transaction?mtm_campaign=ext-docs&mtm_kwd=optimism) with fully decoded transaction trace +* [Debug](https://docs.tenderly.co/debugger?mtm_campaign=ext-docs&mtm_kwd=optimism) failing and [simulate](https://docs.tenderly.co/simulator-ui/using-simulation-ui?mtm_campaign=ext-docs&mtm_kwd=optimism) correct transactions before sending them on-chain +* Evaluate [function-level gas usage](https://docs.tenderly.co/debugger/gas-profiler?mtm_campaign=ext-docs&mtm_kwd=optimism) for any transaction +* Set up [Alerts](https://docs.tenderly.co/alerts/tutorials-and-quickstarts/alerting-quickstart-guide?mtm_campaign=ext-docs&mtm_kwd=optimism) to monitor interactions, access control, asset transfers, and contracts' state changes +* Create a [Virtual TestNet](https://docs.tenderly.co/virtual-testnets?mtm_campaign=ext-docs&mtm_kwd=optimism) from a specific OP Mainnet or OP Chain transaction for systematic research + + +## Access to pre-regenesis history + +Because of our final regenesis on 11 November 2021, older transactions are not part of the current blockchain and do not appear on [Etherscan](https://explorer.optimism.io/?utm_source=op-docs&utm_medium=docs). +However, you **can** access transaction history between 23 June 2021 and the final regenesis using a number of different tools. For detailed instructions, see [Regenesis History](/node-operators/guides/management/regenesis-history). + +## Next Steps + +* Looking for other developer tools? See [developer tools overview](/app-developers/tools) to explore more options! diff --git a/docs/public-docs/app-developers/tools/data/analytics-tools.mdx b/docs/public-docs/app-developers/tools/data/analytics-tools.mdx new file mode 100644 index 0000000000000..ea1779a36b5c6 --- /dev/null +++ b/docs/public-docs/app-developers/tools/data/analytics-tools.mdx @@ -0,0 +1,47 @@ +--- +title: Analytics tools +description: Learn about platforms you can use to gather analytics and set up customizations + about OP Mainnet. +--- +The following guide lists platforms you can use to gather analytics and set up customizations about OP Mainnet. + +## Blocknative + +[Blocknative](https://www.blocknative.com/) lets you [decode](http://docs.blocknative.com/ethernow/batch-decoder-api) and [analyze](https://docs.blocknative.com/blocknative-data-archive/blob-archive) OP Stack Batches submitted to the Ethereum L1. You can inspect, analyze, decode, and download the data of any batch – confirmed on-chain or not – via public APIs or visually through the [Ethernow Explorer](http://ethernow.xyz). Below you can find links to the different resources: + +* [Batch Decoding API](http://docs.blocknative.com/ethernow/batch-decoder-api): decode OP Stack Batch transactions into their human-readable JSON format. +* [Blob Archive API](https://docs.blocknative.com/blocknative-data-archive/blob-archive): take any versioned hash of an OP Stack Blob and receive the blob data (even beyond the 4096 epoch window of storage). +* [Mempool Archive](https://docs.blocknative.com/blocknative-data-archive/mempool-archive): Analyze any OP Stack transaction that was in the Ethereum mempool to see detection time, time pending, gas, etc. +* [Ethernow Explorer](http://ethernow.xyz): Visually see transactions and batches enter the mempool and get organized into blocks. Use this filter to see OP Stack Batches enter the mempool and land on-chain. + +## Tenderly + +[Tenderly](https://tenderly.co/?mtm_campaign=ext-docs&mtm_kwd=optimism) provides comprehensive monitoring and security solutions for OP-powered Chains, allowing you to stay informed and respond proactively to potential issues in real time. + +* Configure [Tenderly Alerts](https://docs.tenderly.co/alerts/intro-to-alerts?mtm_campaign=ext-docs&mtm_kwd=optimism) for monitoring wallets and setting up real-time notifications on transactions and contract events. Notifications trigger external webhooks, PagerDuty, or chat apps like Telegram and Slack. +* Rely on [Developer Explorer](https://docs.tenderly.co/developer-explorer?mtm_campaign=ext-docs&mtm_kwd=optimism) to monitor and analyze transaction execution with a high level of detail. +* Use [Web3 Actions](https://docs.tenderly.co/web3-actions/intro-to-web3-actions?mtm_campaign=ext-docs&mtm_kwd=optimism) to automate predefined responses, improving security and user experience. +* Integrate [Simulation RPC](https://docs.tenderly.co/simulations/single-simulations#simulate-via-rpc?mtm_campaign=ext-docs&mtm_kwd=optimism) to predict transaction outcomes such as the expected asset changes, precise gas usage, and emitted events. + +## Dune Analytics + +[Dune Analytics](https://dune.com) allows anyone to create dashboards that present information about OP Chains (OP Mainnet, Base, and Zora are available). See [Dune Docs](https://dune.com/docs/) for more info. +You can find a list of community created dashboards for OP Chains [here](https://dune.com/browse/dashboards?q=tags%3Aop%2Coptimism%2Csuperchain\&order=trending\&time_range=24h), or [create your own](https://docs.dune.com/#queries) dashboard. + +For developers building apps on OP chains, Dune's developer platform, [Sim](https://sim.dune.com/), provides real-time onchain data access via [Sim APIs](https://docs.sim.dune.com/) and custom indexing with [Sim IDX](https://docs.sim.dune.com/idx). + +Here are some of our favorite dashboards so far: + +* [OP Chains / Superchain - L2 Activity, Chain Economics](https://dune.com/oplabspbc/op-stack-chains-l1-activity) +* [OP Chains / Superchain - Popular Apps & Project Usage Trends](https://dune.com/oplabspbc/superchain-op-chains-apps-and-project-usage-trends) +* [By OP Chain - L2/L1 Chain Economics](https://dune.com/oplabspbc/optimism-l2-l1-economics) +* [OP Token House Delegates](https://dune.com/optimismfnd/optimism-op-token-house) +* [Superchain NFTs](https://dune.com/oplabspbc/superchain-nfts) + +## Additional tools and resources + +Here are some additional tools and resources for OP Mainnet analytics and development: + +* L2 Usage and Comparison: [growthepie](https://www.growthepie.xyz/) +* OP Analytics (Incentive Tracking, Helper Functions, Public Analysis): [OP Analytics on GitHub](https://github.com/ethereum-optimism/op-analytics) +* Contribute to NumbaNERDs: [Issues on GitHub](https://github.com/ethereum-optimism/op-analytics/issues) diff --git a/docs/public-docs/app-developers/tools/data/data-and-dashboards.mdx b/docs/public-docs/app-developers/tools/data/data-and-dashboards.mdx new file mode 100644 index 0000000000000..cab93ee21d4c2 --- /dev/null +++ b/docs/public-docs/app-developers/tools/data/data-and-dashboards.mdx @@ -0,0 +1,12 @@ +--- +title: Data and Dashboards +description: Learn about various dashboards and terms to explore various OP Stack metrics. +--- + +The following resources are available to help you explore various OP Stack metrics: + +* [Data Glossary](/app-developers/tools/data/data-glossary): Definitions, methodology, and calculation notes for key metrics +* [Superchain Health Dashboard](https://docs.google.com/spreadsheets/d/1f-uIW_PzlGQ_XFAmsf9FYiUf0N9l_nePwDVrw0D5MXY/edit?gid=192497306#gid=192497306): High-level metrics across OP Stack chains +* [Superchain Strategic Focus Dashboard](https://app.hex.tech/61bffa12-d60b-484c-80b9-14265e268538/app/d28726b2-ff11-4f94-8a9f-6bb0a86f4b46/latest): In-depth metrics with chain-level splits and industry benchmarks +* [Optimism Superchain Raw Onchain Data](https://console.cloud.google.com/bigquery/analytics-hub/exchanges/projects/523274563936/locations/us/dataExchanges/optimism_192d403716e/listings/optimism_superchain_raw_onchain_data_192fdc72e35): Raw Blocks, Logs, Transactions, Traces data for OP Stack chains, updated daily. +* [Optimism Superchain 4337 Account Abstraction Data](https://console.cloud.google.com/bigquery/analytics-hub/exchanges/projects/523274563936/locations/us/dataExchanges/optimism_192d403716e/listings/optimism_superchain_4337_account_abstraction_data_1954d8919e1): Decoded account abstraction UserOps across OP Stack chains. diff --git a/docs/public-docs/app-developers/tools/data/data-glossary.mdx b/docs/public-docs/app-developers/tools/data/data-glossary.mdx new file mode 100644 index 0000000000000..91ce3be808c55 --- /dev/null +++ b/docs/public-docs/app-developers/tools/data/data-glossary.mdx @@ -0,0 +1,109 @@ +--- +title: Data Glossary +description: This glossary explains various data terms. +--- + +This glossary is a living document and will be updated over time as new metrics emerge or definitions evolve. + +| Metric | What It Measures | Why It Matters | +| ------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------- | +| Real Economic Value (REV) | Fees paid by users to transact (txn fees + out-of-protocol tips) | Captures users' willingness to pay for onchain activity | +| Collective Revenue | ETH earned by the Optimism Collective | Revenue can be directed by governance to support ecosystem growth | +| Total Value Locked (TVL) | Tokens locked in DeFi protocols and other apps | Supply side of the DeFi ecosystem | +| Gas Used per Second | Average compute consumed onchain | Measures throughput and execution load | +| Median Transaction Fee | Typical cost for a user to transact | Lower fees reduce friction and may unlock broader usage | +| Market Share | OP Stack ecosystem's share of activity vs. the broader crypto industry | Tracks relative performance against L2s or the broader market | + +## Measure Demand + +### Transaction Fees Paid + +**Metric:** Real Economic Value (REV) + +**Definition:** The total fees paid to execute a transaction onchain. This includes both the traditional gas fees required for inclusion onchain and additional fees paid to transaction execution services (e.g., Jito, Flashbots, Timeboost). + +**Calculation:** Gas Fees + Out-of-Protocol Tips (e.g., Jito, Flashbots, Timeboost) + +* Out-of-Protocol Tips can be sourced from Defillama's MEV category + +**Why it matters:** + +* REV is a topline metric that "measures the monetary demand to transact onchain" (Blockworks). +* It's used as a proxy for users' willingness to pay, capturing all transaction fees to better reflect real demand (excludes app-level fees like DEX swap costs). + +### Revenue + +**Metric:** Estimated Optimism Collective Revenue (Collective Revenue) + +**Definition:** The amount of ETH expected to be earned by the Optimism Collective from revenue sharing. + +**Calculation:** For each chain, take the greater of (a) 2.5% of Chain Revenue or (b) 15% of Net Onchain Profit. OP Mainnet contributes 100% of Net Onchain Profit. + +**Key Components:** + +* **Net Onchain Profit:** Chain Revenue - L1 Gas Fees +* **Chain Revenue:** Sum of the L1 Data Fee + L2 Base Fee + L2 Priority Fee + L2 Operator Fee (Also includes any additional fee types added in the future.) +* *L1 Gas Fees:* Total gas fees spent by the chain on L1 in transaction batches (including blob costs) and state output submissions or dispute games. +* **Transaction Batches:** All transactions where the transaction from address is the `batcherHash` address and the transaction to address is the `batchInbox` as defined in the chains' `SystemConfigProxy` contract. +* **State Output Submissions or Dispute Games:** All transactions where the transaction from address is the `Proposer` and the transaction to address is either the `outputOracleProxy` or the `disputeGameFactoryProxy` as defined in the chains' `SystemConfigProxy` contract. +* Each chain's `SystemConfigProxy` contract can be found in the superchain-registry. +* **Resolving Dispute Games:** All transactions sent to dispute game contracts created by the `disputeGameFactoryProxy`, where the transaction's method id (function call) is either `Resolve`, `ResolveClaim`, or `ClaimCredit`. + +**Why it matters:** + +* This is what the Optimism Collective earns by operating OP Stack chains, which can be directed by governance to support ecosystem growth. +* See How (and why) the OP Stack drives fees to the Optimism Collective (Optimism blog, Aug 2024) + +## Onchain Signals + +### Value Onchain + +**Metric:** Total Value Locked (TVL) + +**Definition:** "Value of all coins held in smart contracts of the protocol" (Defillama). + +**Calculation:** The sum of all USD value of assets locked in applications, as reported by DefiLlama. + +* TVL can be priced in USD or a crypto asset like ETH, but both are subject to price volatility. USD is often used because it's easier to interpret and consistent across the broader crypto ecosystem. + +**Why it Matters:** TVL represents the supply side of onchain economic activity for use in protocols such as Decentralized Exchanges (DEXs) and lending markets. Strong TVL in the right places may enable greater onchain demand. + +#### How to Measure Growth: Net TVL Flows + +Because TVL is influenced by market fluctuations, it can be misleading when trying to measure true growth or user behavior. Net TVL Flows can adjust for this by tracking the net change in token balances, valued at current prices. + +**Calculation:** ( `# of Tokens on Day N` - `# of Tokens on Day 0` ) \* `Price of Tokens on Day N` + +**Example:** If an app has 100,000 ETH locked on Day 0 when ETH/USD is $2,000, and 90,000 ETH locked at $3,000 on Day N: + +* Net TVL Flows = −10,000 ETH × $3,000 = $30 million in net outflows +* Naive TVL change would suggest growth: $200 million → $270 million + +## Network Usage & Infrastructure + +**Metric:** Gas Used per Second (gas/s) + +**Definition:** "Gas refers to the unit that measures the amount of computational effort required to execute specific operations on Ethereum" (ethereum.org). Gas Used is tracked as an average rate per second for simplicity. + +**Why it Matters:** Gas, sometimes referred to as blockspace, is the limited resource that blockchains provide. Gas used shows how much of that resource is actually being consumed. + +**Caution:** Gas is only comparable across chains that use Ethereum-equivalent gas units. + +### User Experience (UX) + +**Metric:** Median Transaction Fee (USD) + +**Definition:** The median gas fee paid to submit a transaction, expressed in USD for simplicity and easier comparison across ecosystems. + +**Calculation:** Median of all transaction fees over a period of time, marked at the USD price at the time of the transaction. + +**Why it Matters:** This metric serves as a proxy for the cost to transact. Lower median fees enable broader usage by reducing friction, lowering breakeven costs, and unlocking use cases that would otherwise be cost-prohibitive. + +## Market Share + +**Definition:** The OP Stack ecosystem's share of a broader market segment for any measure (e.g., L2s, total crypto). + +**Calculation:** OP Stack Metric Value / Total Market Metric Value + +**Why it Matters:** Market share helps isolate whether growth is driven by the OP Stack ecosystem itself, or is simply part of a broader market trend. A rising share signals outperformance, while a declining share suggests that other ecosystems are growing faster. + diff --git a/docs/public-docs/app-developers/tools/data/oracles.mdx b/docs/public-docs/app-developers/tools/data/oracles.mdx new file mode 100644 index 0000000000000..c4b6d0b238974 --- /dev/null +++ b/docs/public-docs/app-developers/tools/data/oracles.mdx @@ -0,0 +1,157 @@ +--- +title: Oracles +description: Learn about different oracles and how you can use them to access offchain data + onchain as well as random number generation. +--- + + This page includes providers that meet specific [inclusion criteria](#inclusion-criteria), as outlined below. Please visit the [community oracles page](https://github.com/ethereum-optimism/developers/blob/main/community/tools/oracles.md) for an additional listing of third-party Oracles. + + +This reference guide lists different Oracles you can use when building on Optimism. [Oracles](https://ethereum.org/en/developers/docs/oracles/) provide offchain data onchain. This allows code running on a blockchain to access a wide variety of information. +For example, a [stablecoin](https://ethereum.org/en/stablecoins/) that accepts ETH as collateral needs to know the ETH/USD exchange rate: + +* How many stablecoins can we give a user for a given amount of ETH? +* Do we need to liquidate any deposits because they are under collateralized? + +## Security and decentralization + +Different oracles have different security assumptions and different levels of decentralization. +Usually they are either run by the organization that produces the information, or have a mechanism to reward entities that provide accurate information and penalize those that provide incorrect information. + +## Types of oracles + +There are two types of oracles: + +1. **Push oracles** are updated continuously and always have up to date information available onchain. + +2. **Pull oracles** are only updated when information is requested by a contract. + Pull oracles are themselves divided into two types: + 1. Double-transaction oracles, which require two transactions. + The first transaction is the request for information, which usually causes the oracle to emit an event that triggers some offchain mechanism to provide the answer (through its own transaction). + The second transaction actually reads onchain the result from the oracle and uses it. + 2. Single-transaction oracles, which only require one transaction. The way this works is that the transaction that requests the information includes a callback (address and the call data to provide it). + When the oracle is updated (which also happens through a transaction, but one that is not sent by the user), the oracle uses the callback to inform a contract of the result. + +## Random number generation (RNG) + +Random number generation in blockchain applications ensures that smart contracts can access unbiased random values. This is essential for certain use cases like generative NFTs, gaming, commit & reveal schemes and more. Various approaches include using a trusted third party, blockhash-based methods, Verifiable Random Functions (VRF), quantum random numbers to name a few. Each method has trade-offs between simplicity, security, and trust assumptions, allowing developers to select the most suitable option for their use case. + +## List of oracles + +### Gas oracle + +OP Mainnet provides a [Gas Price Oracle](https://github.com/ethereum-optimism/optimism/blob/233ede59d16cb01bdd8e7ff662a153a4c3178bdd/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol) that provides information about [gas prices and related parameters](/op-stack/transactions/fees). +It can also calculate the total cost of a transaction for you before you send it. + +This contract is a predeploy at address `0x420000000000000000000000000000000000000F`: + +* [On OP Mainnet](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F#readContract) +* [On OP Sepolia](https://testnet-explorer.optimism.io/address/0x420000000000000000000000000000000000000F) + +This is a push Oracle. +OP Mainnet (and the testnets) updates the gas price parameters onchain whenever those parameters change. +The L1 gas price, which can be volatile, is only pushed once every 5 minutes, and each time can change only by up to 20%. + +* [Blocknative](https://docs.blocknative.com/gas-prediction) provides real-time gas estimation powered by predictive modeling to forecast gas price distribution for select OP Stack chains, including OP Mainnet and Base. The [API](https://docs.blocknative.com/gas-prediction) is also available on Ethereum Mainnet to estimate base fee costs. + +### API3 + +The [API3 Market](https://market.api3.org/optimism) provides access to 200+ price feeds on [Optimism Mainnet](https://market.api3.org/optimism) and [Testnet](https://market.api3.org/optimism-sepolia-testnet). The price feeds operate as a native push oracle and can be activated instantly via the Market UI. + +The price feeds are delivered by an aggregate of [first-party oracles](https://docs.api3.org/oev-searchers/glossary.html#first-party-oracles) using signed data and support [OEV recapture](). +[https://docs.api3.org/dapps/integration/security-considerations.html#oracle-extractable-value-oev](https://docs.api3.org/dapps/integration/security-considerations.html#oracle-extractable-value-oev) +Unlike traditional data feeds, reading [API3 price feeds](https://docs.api3.org/oev-searchers/in-depth/#dapps-catalog) enables dApps to auction off the right to update the price feeds to searcher bots which facilitates more efficient liquidation processes for users and LPs of DeFi money markets. The OEV recaptured is returned to the dApp. + +API3's QRNG provides dApps with truly random numbers based on quantum mechanics at no charge. More details [here](https://api3.org/) + +### Chainlink + +[Chainlink](https://chain.link/) is the industry-standard decentralized computing platform powering the verifiable web. +Chainlink powers verifiable applications and high-integrity markets for banking, DeFi, global trade, gaming, and other major sectors. + +Chainlink provides a number of [price feeds](https://docs.chain.link/docs/optimism-price-feeds/). +Those feeds are available on the production network @ [Op Mainnet](https://docs.chain.link/data-feeds/price-feeds/addresses?network=optimism\&page=1#optimism-mainnet). + +* Data Feeds: Chainlink Data Feeds provide a secure, reliable, and decentralized source of off-chain data to power unique smart contract use cases for DeFi and beyond. +* Automation: Chainlink Automation is an ultra-reliable and performant smart contract automation solution enabling developers to quickly scale their operations in a verifiable, decentralized, and cost-efficient manner, to build next-generation apps. +* CCIP: Chainlink CCIP provides a secure interoperability protocol for powering token transfers and sending arbitrary messages cross-chain. + +This is a push Oracle. See the [Using Data Feeds guide](https://docs.chain.link/docs/get-the-latest-price/) to learn how to use the Chainlink feeds. + +* Chainlink VRF provides cryptographically secure randomness for blockchain-based applications. More details [here](https://chain.link/vrf) + +### Chronicle + +The first Oracle on Ethereum, Chronicle's decentralized Oracle network was originally built within MakerDAO for the development of DAI and is now available to builders on OP Mainnet and OP Stack chains. + +* **Data Feeds**: Builders can choose from 65+ data feeds, including crypto assets, yield rates, and RWAs. Chronicle's data is sourced via custom-built data models, only utilizing Tier 1 Primary Sources, such as the markets where tokens are actively traded, including Coinbase, Binance, Uniswap, and Curve. +* **Transparency & Integrity**: Chronicle's Oracle network is fully transparent and verifiable. Via [The Chronicle](https://chroniclelabs.org/dashboard/oracle/DAI/USD?blockchain=OPT\&txn=0x53e60e6e79eb938e5ca3ca6c56b0795e003dd6b3a17cfd810ca5042b3d33b680\&contract=0x104916d38828DA8B83a88A1775Aa058e1F0B1647), the data supply chain for any Oracle can be viewed in real-time and historically, including data sources and the identity of all Validators/Signers. Users can cryptographically challenge the integrity of every Oracle update using the 'verify' feature. Data is independently sourced by a [community of Validators](https://chroniclelabs.org/validators), including Gitcoin, Etherscan, Infura, DeFi Saver, and MakerDAO. +* **Gas Efficiency:** Pioneering the Schnorr-based Oracle architecture, Chronicle's Oracles use 60-80% less gas per update than other Oracle providers. This lowest cost per update allows Push Oracle updates to be made more regularly, ensuring more accurate and granular data reporting. + +Every Oracle implementation is customized to fit your needs. Implement one of our existing data models or contact Chronicle to develop custom Oracle data feeds via [Discord](https://discord.gg/CjgvJ9EspJ) or [Email](mailto:gm@chroniclelabs.org). Developers can dive deeper into Chronicle Protocol's architecture and unique design choices [via the docs](https://docs.chroniclelabs.org/). + +### Gelato + +[Gelato VRF](https://www.gelato.network/) enables smart contracts on Optimism to access verifiable randomness. Gelato VRF offers real randomness for blockchain applications by leveraging Drand, a trusted decentralized source for random numbers. + +Gelato VRF (Verifiable Random Function) provides trustable randomness on EVM-compatible blockchains. Here's a brief overview: + +* Contract Deployment: Use GelatoVRFConsumerBase.sol as an interface for requesting random numbers. +* Requesting Randomness: Emit the RequestedRandomness event to signal the need for a random number. +* Processing: Gelato VRF fetches the random number from Drand. +* Delivery: The fulfillRandomness function delivers the random number to the requesting contract. + +Ready to integrate? Head over to the [Gelato VRF Quick Start Guide](https://docs.gelato.network/web3-services/vrf/quick-start). + +### Pyth Network + +The Pyth Network is a financial oracle network which delivers over 400 low-latency, high-fidelity price feeds across cryptocurrencies, FX pairs, equities, ETFs, and commodities. + +* Pyth's price data is sourced from over [95 first-party sources](https://pyth.network/publishers) including exchanges, market makers, and financial services providers. +* Pyth [Price Feeds](https://www.pyth.network/developers/price-feed-ids) offer both the real-time spot price of the asset as well as an accompanying confidence interval band around that price +* The Pyth [TradingView](https://docs.pyth.network/guides/how-to-create-tradingview-charts) integration allows users to view and display Pyth prices on their own website and UI. + +You can explore the full catalog of Pyth Price Feed IDs for [OP Mainnet and Sepolia (EVM Stable)](https://www.pyth.network/developers/price-feed-ids). + +* Pyth Entropy allows developers to quickly and easily generate secure random numbers on the blockchain. More details [here](https://pyth.network/blog/secure-random-numbers-for-blockchains) + +### RedStone + +[RedStone](https://redstone.finance/) offers flexible Data Feeds for Lending Markets, Perpetuals, Options, Stablecoins, Yield Aggregators and other types of novel DeFi protocols. The infrastructure is well battle-tested and secures hundreds of millions of USD across mainnet. + +Builders can choose how they want to consume the data among 3 dedicated models: + +* [RedStone Core](https://docs.redstone.finance/docs/dapps/redstone-pull/) (pull oracle) - less than 10s update time, broad spectrum of feeds, best for most use cases. All [Core Price Feeds](https://app.redstone.finance/#/app/tokens) are available on OP Mainnet & OP Sepolia. +* [RedStone Classic](https://docs.redstone.finance/docs/dapps/redstone-push/) (push oracle) - for protocols designed for the traditional oracle interface, customizable heartbeat and deviation threshold. +* [Hybrid (push + push) ERC7412](https://docs.redstone.finance/docs/dapps/redstone-erc7412/) - specifically for Perps and Options, highest update frequency and front-running protection. + +Interested in integration? [Get in contact](https://discord.com/invite/PVxBZKFr46) with the RedStone team! + +### Stork + +[Stork](https://stork.network) delivers price feeds with ultra-low latency, high uptime, and broad asset coverage for DeFi applications on OP Mainnet and OP Stack chains. + +* **Ultra-Low Latency**: Stork provides price data at ultra-low latency, enabling protocols to maintain functionality during high-volatility periods and support time-sensitive trading operations. +* **High Reliability**: With best-in-class uptime and reliability, Stork supports perpetuals markets, lending protocols, and DeFi ecosystems that require consistent data availability. +* **Day 1 Asset Support**: Price feeds for new digital assets are available from Day 1, allowing protocols to launch new markets immediately after token generation events. +* **Broad Coverage**: Stork provides comprehensive asset coverage across crypto assets, equities, ETFs and commodities, supporting a wide range of DeFi use cases from derivatives trading to collateralized lending. + +Visit the [Stork documentation](https://docs.stork.network) to get started. + +### DIA + +[DIA](https://www.diadata.org/) is a cross-chain, trustless oracle network delivering verifiable price feeds for Optimism. DIA sources raw trade data directly from primary markets and computes it onchain, ensuring complete transparency and data integrity. + +- Complete verifiability from source to destination smart contract. +- Direct data sourcing from 100+ primary markets eliminating intermediary risk. +- Support for 20,000+ assets across all major asset classes. +- Custom oracle configuration with tailored sources and methodologies. + +To get started: + +- [Explore the Optimism Oracle Integration Guide](https://www.diadata.org/docs/guides/chain-specific-guide/optimism) +- [Request a Custom Oracle](https://www.diadata.org/docs/guides/how-to-guides/request-a-custom-oracle) + +## Next steps + +* Looking for other developer tools? See [developer tools overview](/app-developers/tools) to explore more options! diff --git a/docs/public-docs/app-developers/tools/faucets.mdx b/docs/public-docs/app-developers/tools/faucets.mdx new file mode 100644 index 0000000000000..0d0e4a4f0e738 --- /dev/null +++ b/docs/public-docs/app-developers/tools/faucets.mdx @@ -0,0 +1,46 @@ +--- +title: Testnet faucets +description: Learn how to get testnet ETH on test networks like Sepolia and OP Sepolia for development and testing purposes. +--- + +Faucets are developer tools that allow you to get free ETH (and other tokens) on test networks like Sepolia and OP Sepolia so that you can send transactions and create smart contracts. +Here you'll find a list of active faucets that you can try out. +Different faucets use different authentication methods, so you may have to try a few before you find one that works for you. +Faucets can occasionally also run out of ETH, so if you're having trouble getting ETH from a faucet, try another one. + + + Tokens on test networks like Sepolia or OP Sepolia have no value and are only meant for testing. + Optimists only take what they need so that others can use faucets too! + + +## Optimism's faucet + +The [Optimism's faucet](https://console.optimism.io/faucet?utm_source=op-docs&utm_medium=docs) is a developer tool hosted by Optimism that allows developers to get free testnet ETH to test apps on testnet OP Chains like Base Sepolia, OP Sepolia, PGN Sepolia, Zora Sepolia, and other OP Stack chains. +Optimism's faucet is a great place to start if you're looking for testnet ETH. + +## Additional faucets + +| Faucet Name | Supported Networks | +| ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| [Alchemy Faucet](https://sepoliafaucet.com) | Sepolia | +| [Chain Platform Faucet](https://faucet.chainplatform.co/faucets/ethereum-sepolia/) | Sepolia | +| [Ethereum Ecosystem Faucets](https://www.ethereum-ecosystem.com/faucets) | Sepolia, OP Sepolia, Base Sepolia | +| [ETHGlobal Testnet Faucet](https://ethglobal.com/faucet) | Sepolia, OP Sepolia, Base Sepolia, Zora Sepolia, Holesky | +| [Farcaster Frame Faucet by LearnWeb3](https://warpcast.com/haardikkk/0x28f4237d) | Sepolia, OP Sepolia | +| [Infura Faucet](https://www.infura.io/faucet/sepolia) | Sepolia | +| [Native USDC Faucet](https://faucet.circle.com/) | Sepolia, OP Sepolia | +| [QuickNode Faucet](https://faucet.quicknode.com/optimism/) | Sepolia, OP Sepolia | +| [Tenderly Unlimited Faucet](https://docs.tenderly.co/virtual-testnets/unlimited-faucet?mtm_campaign=ext-docs\&mtm_kwd=optimism) | OP Sepolia, OP Mainnet, and [85+ other networks](https://docs.tenderly.co/supported-networks?mtm_campaign=ext-docs\&mtm_kwd=optimism) | +| [thirdweb OP Sepolia Faucet](https://thirdweb.com/op-sepolia-testnet?utm_source=opdocs\&utm_medium=docs) | OP Sepolia | +| [thirdweb Sepolia Faucet](https://thirdweb.com/sepolia?utm_source=opdocs\&utm_medium=docs) | Sepolia | +| [ethfaucet.com](https://ethfaucet.com?utm_source=opdocs&utm_medium=docs) | Sepolia, OP Sepolia, Base Sepolia, Zora Sepolia, Unichain Sepolia, Ink Sepolia, Mode Sepolia, Shape Sepolia, Worldchain Sepolia, BOB Sepolia | + +## Bridge from Sepolia + +If you have testnet ETH on Sepolia, you can bridge it to OP Sepolia (and vice versa) using the [Superchain Bridges UI](https://app.optimism.io/bridge/?utm_source=op-docs&utm_medium=docs) or this collection of [Superchain Testnet Tools](https://www.superchain.tools/). + +## Next steps + +* If you're new to onchain development, check out [Optimism Unleashed](https://cryptozombies.io/en/optimism) by CryptoZombies and [Superchain Builder NFT](https://web.archive.org/web/20231218203510/https://blog.thirdweb.com/guides/optimism-superchain-faucet-nft/) by ThirdWeb. +* If you're familiar with onchain development, check out the [Optimism Ecosystem's Contributions Dashboard](https://github.com/ethereum-optimism/ecosystem-contributions) for project ideas that the Optimism Collective is looking for. +* Looking for other developer tools? See [developer tools overview](/app-developers/tools) to explore more options! diff --git a/docs/public-docs/app-developers/tools/supersim.mdx b/docs/public-docs/app-developers/tools/supersim.mdx new file mode 100644 index 0000000000000..a5319952f255b --- /dev/null +++ b/docs/public-docs/app-developers/tools/supersim.mdx @@ -0,0 +1,54 @@ +--- +title: Supersim Multichain Development Environment +description: Learn how to use the Supersim local dev environment tool designed to simulate the OP Stack multi-chain environment. +--- + + + Interop is currently in active development and not yet ready for production use. The information provided here may change. Check back regularly for the most up-to-date information. + + +[Supersim](https://github.com/ethereum-optimism/Supersim) is a local development environment tool designed to simulate the OP Stack for developers building multi-chain applications. It provides a simplified way to test and develop applications that interact with multiple chains within the OP Stack ecosystem. + +## Supersim workflow + +```mermaid +graph LR + A[Write Smart Contracts] --> B[Deploy on Supersim] + B --> C[Test Cross-Chain Interactions] + C --> D[Debug and Refine] + D --> B + C --> E[Ready for Production] +``` + +This diagram illustrates the typical workflow for developers using Supersim, from writing smart contracts to testing and refining cross-chain interactions. + +## Features and benefits + +* Simulates multiple OP Stack chains locally (e.g., chain 901, 902) +* Supports testing of cross-chain messaging and interactions +* Includes pre-deployed interoperability contracts +* Offers a CLI interface for starting and managing Supersim instances +* Provides local JSON-RPC endpoints for each simulated chain +* Allows for custom configuration of chain parameters +* Facilitates testing of Superchain-specific features like cross-chain token transfers +* Easy to use with common Ethereum development tools +* Supports chain forking + +## Supersim CLI interaction + +```mermaid +graph TD + A[Developer] --> B[Supersim CLI] + B --> C[Chain 901] + B --> D[Chain 902] + B --> E[...] + C --> F[JSON-RPC Endpoint] + D --> G[JSON-RPC Endpoint] + E --> H[JSON-RPC Endpoint] +``` + +This diagram illustrates how developers interact with Supersim through the CLI, which simulates OP Stack-specific features (specifically interop) on locally run chains, each with its own JSON-RPC endpoint and pre-deployed interoperability contracts. + +## Next steps + +* View more [Supersim tutorials](/app-developers/tutorials/development/supersim) diff --git a/docs/public-docs/app-developers/tutorials/bridging/bridge-crosschain-eth.mdx b/docs/public-docs/app-developers/tutorials/bridging/bridge-crosschain-eth.mdx new file mode 100644 index 0000000000000..37b6e38a7e400 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/bridging/bridge-crosschain-eth.mdx @@ -0,0 +1,385 @@ +--- +title: Transferring ETH +description: Learn how to transfer ETH across the OP Stack interop cluster +--- + + + +OP Stack interop is in active development. Some features may be experimental. + + This tutorial provides step-by-step instructions for how to send ETH from one chain in the OP Stack interop cluster to another. + For a conceptual overview, + see the [interoperable ETH explainer](/op-stack/interop/superchain-eth-bridge). + + +## Overview + +Crosschain ETH transfers across OP Stack chains are facilitated through the [SuperchainETHBridge](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainETHBridge.sol) contract. +This tutorial walks through how to send ETH from one chain to another. +You can do this on [Supersim](/> app-developers/tools/development/supersim) or production once it is released. + +### What you'll build + +* A TypeScript application to transfer ETH between chains + +### What you'll learn + +* How to send ETH on the blockchain and between blockchains +* How to relay messages between chains + +## Prerequisites + +Before starting this tutorial, ensure your development environment meets the following requirements: + +### Technical knowledge + +* Intermediate TypeScript knowledge +* Understanding of smart contract development +* Familiarity with blockchain concepts + +### Development environment + +* Unix-like operating system (Linux, macOS, or WSL for Windows) +* Node.js version 16 or higher +* Git for version control + +### Required tools + +The tutorial uses these primary tools: + +* Foundry: For smart contract development +* Supersim: For local blockchain simulation +* TypeScript: For implementation +* Viem: For blockchain interaction + + + + 1. Install [Foundry](https://book.getfoundry.sh/getting-started/installation). + 1. Install [Node](https://nodejs.org/en). + 1. Install [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). + The exact mechanism to do this depends on your operating system; most come with it preinstalled. + + + + You can run this tutorial either with [Supersim](/> app-developers/tools/development/supersim) running locally, or using the [Interop devnet](/app-developers/guides/building-apps). + Select the correct tab and follow the directions. + + + + + 1. Follow the [Installation Guide](/app-developers/tutorials/development/supersim/installation) to install Supersim for running blockchains with Interop. + + 1. Start Supersim. + + ```sh + ./supersim --interop.autorelay + ``` + + 1. Supersim uses Foundry's `anvil` blockchains, which start with ten prefunded accounts. + Set these environment variables to access one of those accounts on the L2 blockchains. + + ```sh + export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ``` + + 1. Specify the URLs to the chains. + + ```sh + SRC_URL=http://localhost:9545 + DST_URL=http://localhost:9546 + ``` + + + + Get the ETH balances for your address on both the source and destination chains. + + ```sh + cast balance --ether `cast wallet address $PRIVATE_KEY` --rpc-url $SRC_URL + cast balance --ether `cast wallet address $PRIVATE_KEY` --rpc-url $DST_URL + ``` + + + + + + + + 1. Set `PRIVATE_KEY` to the private key of an address that has [Sepolia ETH](https://cloud.google.com/application/web3/faucet/ethereum/sepolia). + + ```sh + export PRIVATE_KEY=0x + ``` + + 1. Send ETH to the two L2 blockchains via their OptimismPortal contracts on Sepolia. + + ```sh + cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIVATE_KEY --value 0.02ether 0x7385d89d38ab79984e7c84fab9ce5e6f4815468a + cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIVATE_KEY --value 0.02ether 0x55f5c4653dbcde7d1254f9c690a5d761b315500c + ``` + + 1. Wait a few minutes until you can see the ETH [on the block explorer](https://sid.testnet.routescan.io/) for your address. + + 1. Specify the URLs to the chains. + + ```sh + SRC_URL=https://interop-alpha-0.optimism.io + DST_URL=https://interop-alpha-1.optimism.io + ``` + + + + Get the ETH balances for your address on both the source and destination chains. + + ```sh + cast balance --ether `cast wallet address $PRIVATE_KEY` --rpc-url $SRC_URL + cast balance --ether `cast wallet address $PRIVATE_KEY` --rpc-url $DST_URL + ``` + + + + + + + Run these commands: + + ```sh + DST_CHAINID=`cast chain-id --rpc-url $DST_URL` + MY_ADDRESS=`cast wallet address $PRIVATE_KEY` + SUPERCHAIN_ETH_BRIDGE=0x4200000000000000000000000000000000000024 + BEFORE=`cast balance $MY_ADDRESS --rpc-url $DST_URL | cast from-wei` + cast send --rpc-url $SRC_URL --private-key $PRIVATE_KEY $SUPERCHAIN_ETH_BRIDGE "sendETH(address,uint256)" $MY_ADDRESS $DST_CHAINID --value 0.001ether + sleep 10 + AFTER=`cast balance $MY_ADDRESS --rpc-url $DST_URL | cast from-wei` + echo -e Balance before transfer\\t$BEFORE + echo -e Balance after transfer\\t$AFTER + ``` + + + + Messages are relayed automatically in the interop devnet. + + + + ```sh + mkdir transfer-eth + cd transfer-eth + npm init -y + npm install --save-dev -y viem tsx @types/node @eth-optimism/viem typescript + mkdir src + ``` + + + + + ```sh + curl https://raw.githubusercontent.com/ethereum-optimism/optimism/refs/heads/develop/packages/contracts-bedrock/snapshots/abi/SuperchainETHBridge.json > src/SuperchainETHBridge.abi.json + ``` + + + + + ```typescript + import { + createWalletClient, + http, + publicActions, + getContract, + Address, + formatEther, + parseEther, + } from 'viem' + + import { privateKeyToAccount } from 'viem/accounts' + + import { + supersimL2A, + supersimL2B, + interopAlpha0, + interopAlpha1 + } from '@eth-optimism/viem/chains' + + import { + walletActionsL2, + publicActionsL2, + contracts as optimismContracts + } from '@eth-optimism/viem' + + import superchainEthBridgeAbi from './SuperchainETHBridge.abi.json' + + const supersimAddress = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) + const sourceChain = account.address === supersimAddress ? supersimL2A : interopAlpha0 + const destinationChain = account.address === supersimAddress ? supersimL2B : interopAlpha1 + + const sourceWallet = createWalletClient({ + chain: sourceChain, + transport: http(), + account, + }).extend(publicActions) + .extend(publicActionsL2()) + .extend(walletActionsL2()) + + const destinationWallet = createWalletClient({ + chain: destinationChain, + transport: http(), + account, + }).extend(publicActions) + .extend(publicActionsL2()) + .extend(walletActionsL2()) + + const ethBridgeOnSource = await getContract({ + address: optimismContracts.superchainETHBridge.address, + abi: superchainEthBridgeAbi, + client: sourceWallet, + }) + + const reportBalance = async (address: string): Promise => { + const sourceBalance = await sourceWallet.getBalance({ address }) + const destinationBalance = await destinationWallet.getBalance({ address }) + + console.log(` + Address: ${address} + Balance on source chain: ${formatEther(sourceBalance)} + Balance on destination chain: ${formatEther(destinationBalance)} + `) + } + + console.log('Before transfer') + await reportBalance(account.address) + + const sourceHash = await ethBridgeOnSource.write.sendETH({ + value: parseEther('0.001'), + args: [account.address, destinationChain.id], + }) + const sourceReceipt = await sourceWallet.waitForTransactionReceipt({ + hash: sourceHash, + }) + + console.log('After transfer on source chain') + await reportBalance(account.address) + + const sentMessages = await sourceWallet.interop.getCrossDomainMessages({ + logs: sourceReceipt.logs, + }) + const sentMessage = sentMessages[0] + const relayMessageParams = await sourceWallet.interop.buildExecutingMessage({ + log: sentMessage.log, + }) + + const relayMsgTxnHash = await destinationWallet.interop.relayCrossDomainMessage(relayMessageParams) + await destinationWallet.waitForTransactionReceipt({ hash: relayMsgTxnHash }) + + console.log('After relaying message to destination chain') + await reportBalance(account.address) + ``` + + + + + + ```typescript + import { + supersimL2A, + supersimL2B, + interopAlpha0, + interopAlpha1 + } from '@eth-optimism/viem/chains' + ``` + + Import all chain definitions from `@eth-optimism/viem`. + + ```typescript + const supersimAddress="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) + const sourceChain = account.address == supersimAddress ? supersimL2A : interopAlpha0 + const destinationChain = account.address == supersimAddress ? supersimL2B : interopAlpha1 + ``` + + If the address we use is `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`, one of the prefunded addresses on `anvil`, assume we're using Supersim. + Otherwise, use Interop devnet. + + ```typescript + const sourceHash = await ethBridgeOnSource.write.sendETH({ + value: parseEther('0.001'), + args: [account.address, destinationChain.id] + }) + const sourceReceipt = await sourceWallet.waitForTransactionReceipt({ + hash: sourceHash + }) + ``` + + To relay a message we need the information in the receipt. + Also, we need to wait until the transaction with the relayed message is actually part of a block. + + ```typescript + const sentMessages = await sourceWallet.interop.getCrossDomainMessages({ + logs: sourceReceipt.logs, + }) + const sentMessage = sentMessages[0] + ``` + + A single transaction can send multiple messages. + But here we know we sent just one, so we look for the first one in the list. + + ```typescript + const relayMessageParams = await sourceWallet.interop.buildExecutingMessage({ + log: sentMessage.log, + }) + + const relayMsgTxnHash = await destinationWallet.interop.relayCrossDomainMessage(relayMessageParams) + ``` + + This is how you use `@eth-optimism/viem` to create an executing message. + + + + + 1. Run the example. + + ```sh + npx tsx src/transfer-eth.mts + ``` + + 2. Read the results. + + ``` + Before transfer + + Address: 0x7ED53BfaA58B79Dd655B2f229258C093b6C09A8C + Balance on source chain: 0.020999799151902245 + Balance on destination chain: 0.026999459226731331 + ``` + + The initial state. Note that the address depends on your private key; it should be different from mine. + + ``` + After transfer on source chain + + Address: 0x7ED53BfaA58B79Dd655B2f229258C093b6C09A8C + Balance on source chain: 0.019999732176717961 + Balance on destination chain: 0.026999459226731331 + ``` + + After the initiating message the balance on the source chain is immediately reduced. + Notice that even though we are sending 0.001 ETH, the balance on the source chain is reduced by a bit more (here, approximately 67 gwei). + This is the cost of the initiating transaction on the source chain. + Of course, as there has been no transaction on the destination chain, that balance is unchanged. + + ``` + After relaying message to destination chain + + Address: 0x7ED53BfaA58B79Dd655B2f229258C093b6C09A8C + Balance on source chain: 0.019999732176717961 + Balance on destination chain: 0.027999278943880868 + ``` + + Now the balance on the destination chain increases, by slightly less than 0.001 ETH. + The executing message also has a transaction cost (in this case, about 180gwei). + + + +## Next steps + +* Check out the [SuperchainETHBridge guide](/op-stack/interop/superchain-eth-bridge) for more information. +* Review the [OP Stack interop explainer](/> op-stack/interop/explainer) for answers to common questions about interoperability. + diff --git a/docs/public-docs/app-developers/tutorials/bridging/cross-dom-bridge-erc20.mdx b/docs/public-docs/app-developers/tutorials/bridging/cross-dom-bridge-erc20.mdx new file mode 100644 index 0000000000000..30c8c67b7cd9e --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/bridging/cross-dom-bridge-erc20.mdx @@ -0,0 +1,540 @@ +--- +title: Bridging ERC-20 tokens to OP Mainnet +description: Learn how to use @eth-optimism/viem and viem packages to transfer ERC-20 tokens between Layer 1 (Ethereum or Sepolia) and Layer 2 (OP Mainnet or OP Sepolia). +--- + +This tutorial explains how you can use [@eth-optimism/viem](https://www.npmjs.com/package/@eth-optimism/viem) and [viem](https://viem.sh/op-stack) to bridge ERC-20 tokens between L1 (Ethereum or Sepolia) and L2 (OP Mainnet or OP Sepolia). +The `@eth-optimism/viem` and `viem` packages are an easy way to add bridging functionality to your javascript-based application. +They also provide some safety rails to prevent common mistakes that could cause tokens to be made inaccessible. + +Behind the scenes, `@eth-optimism/viem` package uses the [Standard Bridge](/app-developers/guides/bridging/standard-bridge) contracts to transfer tokens. +Make sure to check out the [Standard Bridge guide](/app-developers/guides/bridging/standard-bridge) if you want to learn more about how the bridge works under the hood. + + + The Standard Bridge **does not** support [**fee on transfer + tokens**](https://github.com/d-xo/weird-erc20#fee-on-transfer) or [**rebasing + tokens**](https://github.com/d-xo/weird-erc20#balance-modifications-outside-of-transfers-rebasingairdrops) + because they can cause bridge accounting errors. + + +## Supported networks + +Viem supports any of the [OP Stack networks](https://viem.sh/op-stack/chains). +If you want to use a network that isn't included by default, you can add it to Viem's chain [configurations](https://viem.sh/op-stack/chains#configuration). + +## Dependencies + +* [node](https://nodejs.org/en/) +* [pnpm](https://pnpm.io/installation) + +## Create a demo project + +You're going to use the `@eth-optimism/viem` package for this tutorial. +Since the `@eth-optimism/viem` package is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it. + + + + + ```bash + mkdir bridging-erc20-tokens + cd bridging-erc20-tokens + ``` + + + + + ```bash + pnpm init + ``` + + + + + ```bash + pnpm add @eth-optimism/viem + ``` + + + + + ```bash + pnpm add viem + ``` + + + + + Want to create a new wallet for this tutorial? If you have + [`cast`](https://book.getfoundry.sh/getting-started/installation) installed + you can run `cast wallet new` in your terminal to create a new wallet and get + the private key. + + +## Get ETH on Sepolia and OP Sepolia + +This tutorial explains how to bridge tokens from Sepolia to OP Sepolia. +You will need to get some ETH on both of these testnets. + + + You can use [this faucet](https://sepoliafaucet.com) to get ETH on Sepolia. + You can use the [Superchain + Faucet](https://console.optimism.io/faucet?utm_source=op-docs&utm_medium=docs) to get ETH on OP + Sepolia. + + +## Add a private key to your environment + +You need a private key to sign transactions. +Set your private key as an environment variable with the `export` command. +Make sure this private key corresponds to an address that has ETH on both Sepolia and OP Sepolia. + +```bash +export TUTORIAL_PRIVATE_KEY=0x... +``` + +## Start the Node REPL + +You're going to use the Node REPL to interact with the `@eth-optimism/viem`. +To start the Node REPL, run the following command in your terminal: + +```bash +node +``` + +This will bring up a Node REPL prompt that allows you to run javascript code. + +## Import dependencies + +You need to import some dependencies into your Node REPL session. +The `@eth-optimism/viem` package uses ESM modules, and to use in the Node.js REPL, you need to use dynamic imports with await. +Here's how to do it: + + + + + ```js + + const viem = await import('viem'); + const { createPublicClient, createWalletClient, http, formatEther, parseEther} = viem; + const accounts = await import('viem/accounts'); + const { privateKeyToAccount } = accounts; + const viemChains = await import('viem/chains'); + const { optimismSepolia, sepolia } = viemChains; + const opActions = await import('@eth-optimism/viem/actions'); + const { depositERC20, withdrawOptimismERC20 } = opActions; +``` + + + +## Set session variables + +You'll need a few variables throughout this tutorial. +Let's set those up now. + + + + + This step retrieves your private key from the environment variable you set earlier and converts it into an account object that Viem can use for transaction signing. + + The private key is essential for authorizing transactions on both L1 and L2 networks. + For security reasons, we access it from an environment variable rather than hardcoding it. + + ```js + const PRIVATE_KEY = process.env.TUTORIAL_PRIVATE_KEY; + const account = privateKeyToAccount(PRIVATE_KEY); +``` + + + + + Here we establish the connections to both networks by creating four different clients: + + 1. L1 Public Client: For reading data from the Sepolia network + 2. L1 Wallet Client: For signing and sending transactions on Sepolia + 3. L2 Public Client: For reading data from OP Sepolia + 4. L2 Wallet Client: For signing and sending transactions on OP Sepolia + + Each client is configured with the appropriate chain information and RPC endpoint. + This dual-network setup allows us to seamlessly interact with both layers using the same account. + Replace `` with your API key from a RPC provider. + + ```js + const L1_RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com'; + const L2_RPC_URL = 'https://sepolia.optimism.io'; + + const publicClientL1 = createPublicClient({ + chain: sepolia, + transport: http(L1_RPC_URL), + }); + + const walletClientL1 = createWalletClient({ + account, + chain: sepolia, + transport: http(L1_RPC_URL), + }); + + const publicClientL2 = createPublicClient({ + chain: optimismSepolia, + transport: http(L2_RPC_URL), + }); + + const walletClientL2 = createWalletClient({ + account, + chain: optimismSepolia, + transport: http(L2_RPC_URL), + }); +``` + + + + + We define the addresses of the ERC-20 tokens on both networks. + These are specially deployed test tokens with corresponding implementations on both L1 (Sepolia) and L2 (OP Sepolia). + + The L2 token is configured to recognize deposits from its L1 counterpart. + We also define a constant `oneToken` representing the full unit (10^18 wei) to simplify our deposit and withdrawal operations. + + ```js + const l1Token = "0x5589BB8228C07c4e15558875fAf2B859f678d129"; + const l2Token = "0xD08a2917653d4E460893203471f0000826fb4034"; +``` + + + If you're coming from the [Bridging Your Standard ERC-20 Token to OP Mainnet + Using the Standard Bridge](./standard-bridge-standard-token) or [Bridging Your + Custom ERC-20 Token to OP Mainnet Using the Standard + Bridge](./standard-bridge-custom-token) tutorials, you can use the addresses + of your own ERC-20 tokens here instead. + + + + +## Get L1 tokens + +You're going to need some tokens on L1 that you can bridge to L2. +The L1 testing token located at [`0x5589BB8228C07c4e15558875fAf2B859f678d129`](https://sepolia.etherscan.io/address/0x5589BB8228C07c4e15558875fAf2B859f678d129) has a `faucet` function that makes it easy to get tokens. + + + + + The Application Binary Interface (ABI) defines how to interact with the smart contract functions. This ERC-20 ABI includes several critical functions: + + * `balanceOf`: Allows us to check token balances for any address + * `faucet`: A special function in this test token that mints new tokens to the caller + * `approve`: Required to grant the bridge permission to transfer tokens on our behalf + * `allowance`: To check how many tokens we've approved for the bridge + * `decimals` and `symbol`: Provide token metadata + + This comprehensive ABI gives us everything we need to manage our tokens across both L1 and L2. + + ```js + const erc20ABI = [ + { + inputs: [ + { + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "balanceOf", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "faucet", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "spender", + type: "address" + }, + { + internalType: "uint256", + name: "value", + type: "uint256" + } + ], + name: "approve", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool" + } + ], + stateMutability: "nonpayable", + type: "function" + }, + ]; +``` + + + + + Now we'll call the `faucet` function on the L1 test token contract to receive free tokens for testing. + This transaction will mint new tokens directly to our wallet address. + + The function doesn't require any parameters - it simply credits a predetermined amount to whoever calls it. + We store the transaction hash for later reference and wait for the transaction to be confirmed. + + ```js + console.log('Getting tokens from faucet...'); + const tx = await walletClientL1.writeContract({ + address: l1Token, + abi: erc20ABI, + functionName: 'faucet', + account, + }); + console.log('Faucet transaction:', tx); +``` + + + + + After using the faucet, we verify our token balance by calling the `balanceOf` function on the L1 token contract. + + This step confirms that we've successfully received tokens before proceeding with the bridging process. + The balance is returned in the smallest unit (wei), but we format it into a more readable form using the `formatEther` utility function from `viem`, since this token uses 18 decimal places. + + ```js + const l1Balance = await publicClientL1.readContract({ + address: l1Token, + abi: erc20ABI, + functionName: 'balanceOf', + args: [account.address] + }); + console.log(`L1 Balance after receiving faucet: ${formatEther(l1Balance)}`); +``` + + + +## Deposit tokens + +Now that you have some tokens on L1, you can deposit those tokens into the `L1StandardBridge` contract. +You'll then receive the same number of tokens on L2 in return. + + + + + We define a variable `oneToken` that represents 1 full token in its base units (wei). + ERC-20 tokens typically use 18 decimal places, so 1 token equals 10^18 wei. + + This constant helps us work with precise token amounts in our transactions, avoiding rounding errors and ensuring exact value transfers. + We'll use this value for both deposits and withdrawals + + ```js + const oneToken = parseEther('1') +``` + + + + + ERC-20 tokens require a two-step process for transferring tokens on behalf of a user. + First, we must grant permission to the bridge contract to spend our tokens by calling the `approve` function on the token contract. + + We specify the bridge address from the chain configuration and the exact amount we want to bridge. + This approval transaction must be confirmed before the bridge can move our tokens. + + ```js + const bridgeAddress = optimismSepolia.contracts.l1StandardBridge[sepolia.id].address; + const approveTx = await walletClientL1.writeContract({ + address: l1Token, + abi: erc20ABI, + functionName: 'approve', + args: [bridgeAddress, oneToken], + }); + console.log('Approval transaction:', approveTx); +``` + + + + + After submitting the approval transaction, we need to wait for it to be confirmed on L1. + We use the `waitForTransactionReceipt` function to monitor the transaction until it's included in a block. + + The receipt provides confirmation details, including which block includes our transaction. + This step ensures our approval is finalized before attempting to bridge tokens. + + ```js + await publicClientL1.waitForTransactionReceipt({ hash: approveTx }); +``` + + + + + Now we can execute the actual bridging operation using the `depositERC20` function from the `@eth-optimism/viem` package. + + This function handles all the complex interactions with the `L1StandardBridge` contract for us. + We provide: + + * The addresses of both the L1 and L2 tokens + * The amount to bridge + * The target chain (OP Sepolia) + * Our wallet address as the recipient on L2 + * A minimum gas limit for the L2 transaction + + This streamlined process ensures our tokens are safely transferred to L2. + + ```js + console.log('Depositing tokens to L2...'); + const depositTx = await depositERC20(walletClientL1, { + tokenAddress: l1Token, + remoteTokenAddress: l2Token, + amount: oneToken, + targetChain: optimismSepolia, + to: account.address, + minGasLimit: 200000, + }); + console.log(`Deposit transaction hash: ${depositTx}`); +``` + + + Using a smart contract wallet? As a safety measure, `depositERC20` will fail + if you try to deposit ETH from a smart contract wallet without specifying a + `recipient`. Add the `recipient` option to the `depositERC20` call to fix + this. Check out the [@eth-optimism/viem + docs](https://github.com/ethereum-optimism/ecosystem/tree/main/packages/viem) for + more info on the options you can pass to `depositERC20`. + + + + + + After initiating the deposit, we need to wait for the L1 transaction to be confirmed. + This function tracks the transaction until it's included in an L1 block. + + Note that while this confirms the deposit was accepted on L1, there will still be a short delay (typically a few minutes) before the tokens appear on L2, as the transaction needs to be processed by the Optimism sequencer. + + ```js + const depositReceipt = await publicClientL1.waitForTransactionReceipt({ hash: depositTx }); + console.log(`Deposit confirmed in block ${depositReceipt.blockNumber}`); +``` + + + + + After the deposit transaction is confirmed, we check our token balance on L1 again to verify that the tokens have been deducted. + + This balance should be lower by the amount we bridged, as those tokens are now escrowed in the `L1StandardBridge` contract. + This step helps confirm that the first part of the bridging process completed successfully: + + ```js + const l1BalanceAfterDeposit = await publicClientL1.readContract({ + address: l1Token, + abi: erc20ABI, + functionName: 'balanceOf', + args: [account.address] + }); + console.log(`L1 Balance after deposit: ${formatEther(l1BalanceAfterDeposit)}`); +``` + + + + + After allowing some time for the L2 transaction to be processed, we check our token balance on L2 to verify that we've received the bridged tokens. + + The newly minted L2 tokens should appear in our wallet at the same address we used on L1. + This step confirms the complete success of the bridge operation from L1 to L2. + + ```js + const l2Balance = await publicClientL2.readContract({ + address: l2Token, + abi: erc20ABI, + functionName: 'balanceOf', + args: [account.address] + }); + console.log(`L2 Balance after withdrawal: ${formatEther(l2Balance)}`); +``` + + + +## Withdraw tokens + +You just bridged some tokens from L1 to L2. +Nice! +Now you're going to repeat the process in reverse to bridge some tokens from L2 to L1. + + + + + To move tokens back to L1, we use the `withdrawOptimismERC20` function from the `@eth-optimism/viem` package. + This function interacts with the `L2StandardBridge` contract to initialize the withdrawal process. + We specify: + + * The L2 token address + * The amount to withdraw (we're using half of a token in this tutorial) + * Our address as the recipient on L1 + * A minimum gas limit for the transaction + + Unlike deposits, withdrawals from L2 to L1 are not immediate and require a multi-step process including a 7-day challenge period for security reasons. + + ```js + console.log('Withdrawing tokens back to L1...'); + const withdrawTx = await withdrawOptimismERC20(walletClientL2, { + tokenAddress: l2Token, + amount: oneToken / 2n, + to: account.address, + minGasLimit: 200000, + }); + console.log(`Withdrawal transaction hash: ${withdrawTx}`); +``` + + + + + Similar to deposits, we wait for the withdrawal transaction to be confirmed on L2. + This receipt provides confirmation that the withdrawal has been initiated. + + The transaction logs contain critical information that will be used later in the withdrawal verification process. + This is only the first step in the withdrawal - the tokens are now locked on L2, but not yet available on L1. + + ```js + const withdrawReceipt = await publicClientL2.waitForTransactionReceipt({ hash: withdrawTx }); + console.log(`Withdrawal initiated in L2 block ${withdrawReceipt.blockNumber}`); +``` + + + This step can take a few minutes. Feel free to take a quick break while you + wait. + + + + + + After the withdrawal transaction is confirmed, we check our token balance on L2 again to verify that the tokens have been deducted. + Our L2 balance should now be lower by the amount we initiated for withdrawal. + + At this point, the withdrawal process has begun, but the tokens are not yet available on L1 - please refer to [Withdraw ETH](./cross-dom-bridge-eth#withdraw-eth) to continue with the “prove” and “finalize” withdrawal steps. + + ```js + const l2Balance = await publicClientL2.readContract({ + address: l2Token, + abi: erc20ABI, + functionName: 'balanceOf', + args: [account.address] + }); + console.log(`L2 Balance after withdrawal initiation: ${formatEther(l2Balance)}`); +``` + + + +## Next steps + +Congrats! +You've just deposited and withdrawn tokens using `@eth-optimism/viem` package. +You should now be able to write applications that use the `@eth-optimism/viem` package to transfer ERC-20 tokens between L1 and L2. +Although this tutorial used Sepolia and OP Sepolia, the same process works for Ethereum and OP Mainnet. diff --git a/docs/public-docs/app-developers/tutorials/bridging/cross-dom-bridge-eth.mdx b/docs/public-docs/app-developers/tutorials/bridging/cross-dom-bridge-eth.mdx new file mode 100644 index 0000000000000..9709df5847b96 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/bridging/cross-dom-bridge-eth.mdx @@ -0,0 +1,536 @@ +--- +title: Submitting Transactions from L1 +description: Learn how to process deposit transactions and withdrawals with Viem. +--- + + +This tutorial explains how to use [Viem](https://viem.sh/op-stack) to process cross-domain transactions: +- [Deposited transactions](https://specs.optimism.io/protocol/deposits.html): Also known as deposits, these are transactions initiated on L1 and executed on L2. They can be used to submit arbitrary L2 transactions from L1. + +- [Withdrawals](https://specs.optimism.io/protocol/withdrawals.html): These are cross-domain transactions initiated on L2 and finalized by a transaction executed on L1. They can be used to send arbitrary messages on L1 from L2 via the `OptimismPortal`. + +Both deposit transactions and withdrawals can transfer ETH and data. + + + +## Supported networks + +Viem supports any of the [OP Stack networks](https://viem.sh/op-stack/chains). +The OP Stack networks are included in Viem by default. +If you want to use a network that isn't included by default, you can add it to Viem's chain [configurations](https://viem.sh/op-stack/chains#configuration). + +## Dependencies + +* [node](https://nodejs.org/en/) +* [pnpm](https://pnpm.io/installation) + +## Create a demo project + +You're going to use Viem for this tutorial. +Since Viem is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it. + + + + + ```bash + mkdir bridge-eth + cd bridge-eth + ``` + + + + + ```bash + pnpm init + ``` + + + + + ```bash + pnpm add viem + ``` + + + + + Want to create a new wallet for this tutorial? + If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key. + + +## Get ETH on Sepolia + +This tutorial explains how to bridge ETH from Sepolia to OP Sepolia. +You will need to get some ETH on Sepolia to follow along. + + + You can use [this faucet](https://sepoliafaucet.com) to get ETH on Sepolia. + + +## Add a private key to your environment + +You need a private key in order to sign transactions. +Set your private key as an environment variable with the export command. +Make sure this private key corresponds to an address that has ETH on Sepolia. + +```bash +export TUTORIAL_PRIVATE_KEY=0x... +``` + +## Start the Node REPL + +You're going to use the Node REPL to interact with Viem. +To start the Node REPL run the following command in your terminal: + +```bash +node +``` + +This will bring up a Node REPL prompt that allows you to run JavaScript code. + +## Import dependencies + +You need to import some dependencies into your Node REPL session. + + + + +```js +const { createPublicClient, http, createWalletClient, parseEther, formatEther } = require('viem'); +const { sepolia, optimismSepolia } = require('viem/chains'); +const { privateKeyToAccount } = require('viem/accounts'); +const { getL2TransactionHashes, publicActionsL1, publicActionsL2, walletActionsL1, walletActionsL2 } = require('viem/op-stack'); +``` + + + + +```js +const PRIVATE_KEY = process.env.TUTORIAL_PRIVATE_KEY; +const account = privateKeyToAccount(PRIVATE_KEY); +``` + + + + +```js +const publicClientL1 = createPublicClient({ + chain: sepolia, + transport: http("https://ethereum-sepolia-rpc.publicnode.com"), +}).extend(publicActionsL1()) +``` + + + + +```js +const walletClientL1 = createWalletClient({ + account, + chain: sepolia, + transport: http("https://ethereum-sepolia-rpc.publicnode.com"), +}).extend(walletActionsL1()); +``` + + + + +```js +const publicClientL2 = createPublicClient({ + chain: optimismSepolia, + transport: http("https://sepolia.optimism.io"), +}).extend(publicActionsL2()); +``` + + + + +```js +const walletClientL2 = createWalletClient({ + account, + chain: optimismSepolia, + transport: http("https://sepolia.optimism.io"), +}).extend(walletActionsL2()); +``` + + + + +## Get ETH on Sepolia + +You're going to need some ETH on L1 that you can bridge to L2. +You can get some Sepolia ETH from [this faucet](https://sepoliafaucet.com). + +## Deposit ETH + +Now that you have some ETH on L1, in addition to using the method described in [Bridging ETH](/app-developers/guides/bridging/standard-bridge#bridging-eth), you can also deposit ETH using the approach shown in the example below. + +If you are using a contract account, you should pay attention to [Address Aliasing](https://specs.optimism.io/protocol/deposits.html#address-aliasing). + + + + + + + + See how much ETH you have on L1 so you can confirm that the deposit worked later on. + + ```js +const l1Balance = await publicClientL1.getBalance({ address: account.address }); +console.log(`L1 Balance: ${formatEther(l1Balance)} ETH`); +``` + + + We used `formatEther` method from `viem` to format the balance to ether. + + + + + + Use [buildDepositTransaction](https://viem.sh/op-stack/actions/buildDepositTransaction) to build the deposit transaction parameters on L2. + +Be sure to understand the meanings of the optional parameters `mint` and `value`. You can also use someone else’s address as the `to` value if desired. + + + ```js +const depositArgs = await publicClientL2.buildDepositTransaction({ + mint: parseEther("0.01"), + to: account.address, +}); +``` + + + + + Send the deposit transaction on L1 and log the L1 transaction hash. + + ```js +const depositHash = await walletClientL1.depositTransaction(depositArgs); +console.log(`Deposit transaction hash on L1: ${depositHash}`); +``` + + + + + Wait for the L1 transaction to be processed and log the receipt. + + ```js +const depositReceipt = await publicClientL1.waitForTransactionReceipt({ hash: depositHash }); +console.log('L1 transaction confirmed:', depositReceipt); +``` + + + + + Extracts the corresponding L2 transaction hash from the L1 receipt, and logs it. + This hash represents the deposit transaction on L2. + + ```js +const [l2Hash] = getL2TransactionHashes(depositReceipt); +console.log(`Corresponding L2 transaction hash: ${l2Hash}`); +``` + + + + + Wait for the L2 transaction to be processed and confirmed and logs the L2 receipt to verify completion. + + ```js +const l2Receipt = await publicClientL2.waitForTransactionReceipt({ + hash: l2Hash, +}); +console.log('L2 transaction confirmed:', l2Receipt); +console.log('Deposit completed successfully!'); +``` + + + + + + ```js +const { createPublicClient, http, createWalletClient, parseEther, formatEther } = require('viem'); +const { sepolia, optimismSepolia } = require('viem/chains'); +const { privateKeyToAccount } = require('viem/accounts'); +const { getL2TransactionHashes, publicActionsL1, publicActionsL2, walletActionsL1, walletActionsL2 } = require('viem/op-stack'); + +const PRIVATE_KEY = process.env.TUTORIAL_PRIVATE_KEY; +const account = privateKeyToAccount(PRIVATE_KEY); + +const publicClientL1 = createPublicClient({ + chain: sepolia, + transport: http("https://ethereum-sepolia-rpc.publicnode.com"), +}).extend(publicActionsL1()) + +const walletClientL1 = createWalletClient({ + account, + chain: sepolia, + transport: http("https://ethereum-sepolia-rpc.publicnode.com"), +}).extend(walletActionsL1()); + +const publicClientL2 = createPublicClient({ + chain: optimismSepolia, + transport: http("https://sepolia.optimism.io"), +}).extend(publicActionsL2()); + +const walletClientL2 = createWalletClient({ + account, + chain: optimismSepolia, + transport: http("https://sepolia.optimism.io"), +}).extend(walletActionsL2()); + +const l1Balance = await publicClientL1.getBalance({ address: account.address }); +console.log(`L1 Balance: ${formatEther(l1Balance)} ETH`); + +async function depositETH() { + +const depositArgs = await publicClientL2.buildDepositTransaction({ + mint: parseEther("0.01"), + to: account.address, +}); + +const depositHash = await walletClientL1.depositTransaction(depositArgs); +console.log(`Deposit transaction hash on L1: ${depositHash}`); + +const depositReceipt = await publicClientL1.waitForTransactionReceipt({ hash: depositHash }); +console.log('L1 transaction confirmed:', depositReceipt); + +const [l2Hash] = getL2TransactionHashes(depositReceipt); +console.log(`Corresponding L2 transaction hash: ${l2Hash}`); + +const l2Receipt = await publicClientL2.waitForTransactionReceipt({ + hash: l2Hash, +}); +console.log('L2 transaction confirmed:', l2Receipt); +console.log('Deposit completed successfully!'); +} +``` + + + +## Withdraw ETH + +You just bridged some ETH from L1 to L2. +Nice! +Now you’re going to repeat the process in reverse to bridge some ETH from L2 to L1. + +In addition to the method described in [Bridging ETH](/app-developers/guides/bridging/standard-bridge#bridging-eth), you can also withdraw ETH using the example approach shown below. + + + + + + + + Uses `buildInitiateWithdrawal` to create the withdrawal parameters. + Converts the withdrawal amount to `wei` and specifies the recipient on L1. + + ```js +//Add the same imports used in DepositETH function +const withdrawalArgs = await publicClientL1.buildInitiateWithdrawal({ +value: parseEther('0.005'), +to: account.address, +}); +``` + + + + + This sends the withdrawal transaction on L2, which initiates the withdrawal process on L2 and logs a transaction hash for tracking the withdrawal. + + ```js +const withdrawalHash = await walletClientL2.initiateWithdrawal(withdrawalArgs); +console.log(`Withdrawal transaction hash on L2: ${withdrawalHash}`); +``` + + + + + Wait one hour (max) for the L2 Output containing the transaction to be proposed, and log the receipt, which contains important details like the block number etc. + + ```js +const withdrawalReceipt = await publicClientL2.waitForTransactionReceipt({ hash: withdrawalHash }); +console.log('L2 transaction confirmed:', withdrawalReceipt); +``` + + + + + Next, is to prove to the bridge on L1 that the withdrawal happened on L2. To achieve that, you first need to wait until the withdrawal is ready to prove. + + ```js +const { output, withdrawal } = await publicClientL1.waitToProve({ +receipt: withdrawalReceipt, +targetChain: walletClientL2.chain +}); +``` + + Build parameters to prove the withdrawal on the L2. + + ```js +const proveArgs = await publicClientL2.buildProveWithdrawal({ +output, +withdrawal, +}); +``` + + + + + Once the withdrawal is ready to be proven, you'll send an L1 transaction to prove that the withdrawal happened on L2. + + ```js +const proveHash = await walletClientL1.proveWithdrawal(proveArgs); + +const proveReceipt = await publicClientL1.waitForTransactionReceipt({ hash: proveHash }); +``` + + + + + Before a withdrawal transaction can be finalized, you will need to wait for the finalization period. + This can only happen after the fault proof period has elapsed. On OP Mainnet, this takes 7 days. + + ```js +const awaitWithdrawal = await publicClientL1.waitToFinalize({ +targetChain: walletClientL2.chain, +withdrawalHash: withdrawal.withdrawalHash, +}); +``` + + + We're currently testing fault proofs on OP Sepolia, so withdrawal times + reflect Mainnet times. + + + + + + ```js +const finalizeHash = await walletClientL1.finalizeWithdrawal({ +targetChain: walletClientL2.chain, +withdrawal, +}); +``` + + + + + ```js +const finalizeReceipt = await publicClientL1.waitForTransactionReceipt({ +hash: finalizeHash +}); +``` + + + + + + + + ```js +//Add the same imports used in DepositETH function +const withdrawalArgs = await publicClientL1.buildInitiateWithdrawal({ +value: parseEther('0.005'), +to: account.address, +}); + +const withdrawalHash = await walletClientL2.initiateWithdrawal(withdrawalArgs); +console.log(`Withdrawal transaction hash on L2: ${withdrawalHash}`); + +const withdrawalReceipt = await publicClientL2.waitForTransactionReceipt({ hash: withdrawalHash }); +console.log('L2 transaction confirmed:', withdrawalReceipt); + +const { output, withdrawal } = await publicClientL1.waitToProve({ +receipt: withdrawalReceipt, +targetChain: walletClientL2.chain +}); + +const proveArgs = await publicClientL2.buildProveWithdrawal({ +output, +withdrawal, +}); + +const proveHash = await walletClientL1.proveWithdrawal(proveArgs); + +const proveReceipt = await publicClientL1.waitForTransactionReceipt({ hash: proveHash }); + +const awaitWithdrawal = await publicClientL1.waitToFinalize({ +targetChain: walletClientL2.chain, +withdrawalHash: withdrawal.withdrawalHash, +}); + +const finalizeHash = await walletClientL1.finalizeWithdrawal({ +targetChain: walletClientL2.chain, +withdrawal, +}); + +const finalizeReceipt = await publicClientL1.waitForTransactionReceipt({ +hash: finalizeHash +}); + +``` + + + + + +Recommend checking with [getWithdrawalStatus](https://viem.sh/op-stack/actions/getWithdrawalStatus) before the `waitToProve` and `waitToFinalize` actions. + + ```js + const status = await publicClientL1.getWithdrawalStatus({ + receipt: withdrawalReceipt, + targetChain: walletClientL2.chain + }) + console.log(`Withdrawal status: ${status}`) + ``` + + + +## Submitting Arbitrary L2 Transactions from L1 + +EOAs can submit any transaction on L1 that needs to be executed on L2. This also makes it possible for users to interact with contracts on L2 even when the [Sequencer is down](/op-stack/transactions/forced-transaction). + +If the caller is a contract on L1, you need to pay attention to [Address Aliasing](https://specs.optimism.io/protocol/deposits.html#address-aliasing). + +If you have just completed the [Bridging ERC-20 tokens to OP Mainnet](/app-developers/tutorials/bridging/cross-dom-bridge-erc20) tutorial, you can try initiating an ERC-20 transfer transaction on L1 that will be executed on L2. + +`encodeFunctionData` and `erc20Abi` can be imported from Viem. + +```js +const oneToken = parseEther('1') +// L2 faucet token contract +const to = "0xD08a2917653d4E460893203471f0000826fb4034" + +const data = encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [ + "0x000000000000000000000000000000000000dEaD", // recipient + oneToken / 2n, + ], +}); + +const args = await publicClientL2.buildDepositTransaction({ + account, + data, + to, +}); +``` + +## Use `OptimismPortal` to Send Arbitrary Messages on L1 from L2 + +The `L2ToL1MessagePasser` contract’s `initiateWithdrawal` function accepts a `_target` address and `_data` bytes. These are passed to a CALL opcode on L1 when `finalizeWithdrawalTransaction` is executed after the challenge period. + +This means that, by design, the `OptimismPortal` contract can be used to send arbitrary transactions on L1, with the `OptimismPortal` acting as the `msg.sender`. + + +## Important Considerations + + +* The purpose of this tutorial is to introduce deposited transactions and withdrawals. You should first consider whether the [standard bridge](/app-developers/guides/bridging/standard-bridge) and the [messenger](/app-developers/guides/bridging/messaging) meet your use case requirements. +* When working with deposited transactions, consider the implications of [Address Aliasing](https://specs.optimism.io/protocol/deposits.html#address-aliasing). +* When working with withdrawals, consider that [OptimismPortal can send arbitrary messages on L1](https://specs.optimism.io/protocol/withdrawals.html#optimismportal-can-send-arbitrary-messages-on-l1). +* Challenge period: The 7-day withdrawal challenge period is crucial for security. +* Gas costs: Withdrawals involve transactions on both L2 and L1, each incurring gas fees. +* Private key handling: Use secure key management practices in real applications. +* RPC endpoint security: Keep your API key (or any RPC endpoint) secure. + diff --git a/docs/public-docs/app-developers/tutorials/bridging/cross-dom-solidity.mdx b/docs/public-docs/app-developers/tutorials/bridging/cross-dom-solidity.mdx new file mode 100644 index 0000000000000..066dd3d4f9f80 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/bridging/cross-dom-solidity.mdx @@ -0,0 +1,318 @@ +--- +title: Communicating between OP Stack and Ethereum in Solidity +description: Learn how to write Solidity contracts on OP Stack and Ethereum that can talk to each other. +--- + + +This tutorial explains how to write Solidity contracts on OP Stack and Ethereum that can talk to each other. +Here you'll use a contract on OP Stack that can set a "greeting" variable on a contract on Ethereum, and vice-versa. +This is a simple example, but the same technique can be used to send any kind of message between the two chains. + +You won't actually be deploying any smart contracts as part of this tutorial. +Instead, you'll reuse existing contracts that have already been deployed to OP Stack and Ethereum. +Later in the tutorial you'll learn exactly how these contracts work so you can follow the same pattern to deploy your own contracts. + + + Just looking to bridge tokens between OP Stack and Ethereum? + Check out the tutorial on [Bridging ERC-20 Tokens to OP Stack With viem](./cross-dom-bridge-erc20). + + +## Message passing basics + +OP Stack uses a smart contract called the `CrossDomainMessenger` to pass messages between OP Stack and Ethereum. +Both chains have a version of this contract (the `L1CrossDomainMessenger` and the `L2CrossDomainMessenger`). +Messages sent from Ethereum to OP Stack are automatically relayed behind the scenes. +Messages sent from OP Stack to Ethereum must be explicitly relayed with a second transaction on Ethereum. +Read more about message passing in the guide to [Sending Data Between L1 and L2](/app-developers/guides/bridging/messaging). + +## Dependencies + +- [node](https://nodejs.org/en/) +- [pnpm](https://pnpm.io/installation) + +## Get ETH on Sepolia and OP Sepolia + +This tutorial explains how to send messages from Sepolia to OP Sepolia. +You will need to get some ETH on both of these testnets. + + + You can use [this faucet](https://sepoliafaucet.com/) to get ETH on Sepolia. + You can use the [Superchain Faucet](https://console.optimism.io/faucet?utm_source=op-docs&utm_medium=docs) to get ETH on OP Sepolia. + + +## Review the contracts + +You're about to use two contracts that have already been deployed to Sepolia and OP Sepolia, the `Greeter` contracts. +You can review the source code for the L1 `Greeter` contract [here on Etherscan](https://sepolia.etherscan.io/address/0x31A6Dd971306bb72f2ffF771bF30b1B98dB8B2c5#code). +You can review the source code for the L2 `Greeter` contract [here on Etherscan](https://testnet-explorer.optimism.io/address/0x5DE8a2957eddb140567fF90ba5d57bc9769f3055#code). +Both contracts have exactly the same source code. + +Feel free to review the source code for these two contracts now if you'd like. +This tutorial will explain how these contracts work in detail later on in the [How It Works](#how-it-works) section below. + +## Interact with the L1 Greeter + +You're first going to use the L1 `Greeter` contract to set the greeting on the L2 `Greeter` contract. +You'll send a transaction directly to the L1 `Greeter` contract which will ask the `L1CrossDomainMessenger` to send a message to the L2 `Greeter` contract. +After just a few minutes, you'll see the corresponding greeting set on the L2 `Greeter` contract. + + + + Sending a message to the L2 `Greeter` contract via the L1 `Greeter` contract requires that you call the `sendGreeting` function. + For simplicity, you'll interact with the contract directly on Etherscan. + Open up the [L1 `Greeter` contract on Sepolia Etherscan](https://sepolia.etherscan.io/address/0x31A6Dd971306bb72f2ffF771bF30b1B98dB8B2c5#writeContract) and click the "Connect to Web3" button. + + + + Put a greeting into the field next to the "sendGreeting" function and click the "Write" button. + You can use any greeting you'd like. + + + + It will take a few minutes for your message to reach L2. + Feel free to take a quick break while you wait. + + + You can use viem to programmatically check the status of any message between L1 and L2. + Later on in this tutorial you'll learn how to use viem and the `waitToProve` function to wait for various message statuses. + This same function can be used to wait for a message to be relayed from L1 to L2. + + + + + After a few minutes, you should see the greeting on the L2 `Greeter` contract change to the greeting you set. + Open up the [L2 `Greeter` contract on OP Sepolia Etherscan](https://testnet-explorer.optimism.io/address/0x5DE8a2957eddb140567fF90ba5d57bc9769f3055#readContract) and click the "Read Contract" button. + Paste your address into the field next to the "greeting" function and click the "Query" button. + You should see the message you sent from L1. + + + Haven't seen your message yet? + You might need to wait a little longer. + L2 transactions triggered on L1 are typically processed within one minute but can occasionally be slightly delayed. + + + +## Interact with the L2 Greeter + +Now you're going to use the L2 `Greeter` contract to set the greeting on the L1 `Greeter` contract. +You'll send a transaction directly to the L2 `Greeter` contract which will ask the `L2CrossDomainMessenger` to send a message to the L1 `Greeter` contract. +Unlike the previous step, you'll need to relay the message from L2 to L1 yourself. +You'll do this by sending two transactions on Sepolia, one proving transaction and one relaying transaction. + + + + Just like before, sending a message to the L1 `Greeter` contract via the L2 `Greeter` contract requires that you call the `sendGreeting` function. + Open up the [L2 `Greeter` contract on OP Sepolia Etherscan](https://testnet-explorer.optimism.io/address/0x5DE8a2957eddb140567fF90ba5d57bc9769f3055#writeContract) and click the "Connect to Web3" button. + + + + Put a greeting into the field next to the "sendGreeting" function and click the "Write" button. + You can use any greeting you'd like. + + + Copy the transaction hash from the transaction you just sent. + You'll need this for the next few steps. + Feel free to keep this tab open so you can easily copy the transaction hash later. + + + + + You're going to use viem to prove and relay your message to L1. + + ```bash + mkdir cross-dom + cd cross-dom + pnpm init + pnpm add viem + ``` + + + + Set your private key and transaction hash as environment variables. + + ```bash + export TUTORIAL_PRIVATE_KEY=0x... + export TUTORIAL_TRANSACTION_HASH=0x... + ``` + + + + Start a Node.js REPL with `node` and paste the following script to monitor, prove, and finalize the cross-domain message: + + ```js + const { createPublicClient, http, createWalletClient } = require('viem'); + const { optimismSepolia, sepolia } = require('viem/chains'); + const { publicActionsL1, publicActionsL2, walletActionsL1, walletActionsL2, getWithdrawals } = require('viem/op-stack'); + const { privateKeyToAccount } = require('viem/accounts'); + + const l1Provider = createPublicClient({ + chain: sepolia, + transport: http('https://eth-sepolia.g.alchemy.com/v2/your-key') + }).extend(publicActionsL1()); + + const l2Provider = createPublicClient({ + chain: optimismSepolia, + transport: http('https://opt-sepolia.g.alchemy.com/v2/your-key') + }).extend(publicActionsL2()); + + const account = privateKeyToAccount(process.env.TUTORIAL_PRIVATE_KEY); + + const l1Wallet = createWalletClient({ + account, + chain: sepolia, + transport: http('https://eth-sepolia.g.alchemy.com/v2/your-key') + }).extend(walletActionsL1()); + + const l2Wallet = createWalletClient({ + account, + chain: optimismSepolia, + transport: http('https://opt-sepolia.g.alchemy.com/v2/your-key') + }).extend(walletActionsL2()); + + const receipt = await l2Provider.getTransactionReceipt({ + hash: process.env.TUTORIAL_TRANSACTION_HASH + }); + + console.log('Waiting for message to be provable...'); + await l1Provider.getWithdrawalStatus({ + receipt, + targetChain: l2Provider.chain + }); + + console.log('Proving message...'); + const { output, withdrawal } = await l1Provider.waitToProve({ + receipt, + targetChain: l2Provider.chain + }); + + const proveArgs = await l2Provider.buildProveWithdrawal({ + account, + output, + withdrawal + }); + + await l1Wallet.proveWithdrawal(proveArgs); + + console.log('Waiting for message to be relayable...'); + await l1Provider.waitToFinalize({ + targetChain: l2Provider.chain, + withdrawalHash: withdrawal.withdrawalHash + }); + + console.log('Relaying message...'); + await l1Wallet.finalizeWithdrawal({ + targetChain: l2Wallet.chain, + withdrawal + }); + + console.log('Message relayed!'); + ``` + + + + After finalization, open the [L1 `Greeter` contract on Sepolia Etherscan](https://sepolia.etherscan.io/address/0x31A6Dd971306bb72f2ffF771bF30b1B98dB8B2c5#readContract). + Confirm that the greeting has been updated to the message you sent from L2. + + + +## How it works + +Congratulations! You've successfully sent a message from L1 to L2 and from L2 to L1. +This section explains how the `Greeter` contracts work so you can follow the same pattern to deploy your own contracts. +Luckily, both `Greeter` contracts are exactly the same so it's easy to see how everything comes together. + +### The Messenger variable + +The `Greeter` contract has a `MESSENGER` variable that keeps track of the `CrossDomainMessenger` contract on the current chain. +Check out the [Contract Addresses page](/op-mainnet/network-information/op-addresses) to see the addresses of the `CrossDomainMessenger` contracts on whatever network you'll be using. + +```solidity +address public immutable MESSENGER; +``` + +### The other Greeter variable + +The `Greeter` contract also has an `OTHER_GREETER` variable that keeps track of the `Greeter` contract on the other chain. +On L1, this variable is set to the address of the L2 `Greeter` contract, and vice-versa. + +```solidity +address public immutable OTHER_GREETER; +``` + +### The Greetings mapping + +The `Greeter` contract keeps track of the different greetings that users have sent inside a `greetings` mapping. +By using a mapping, this contract can keep track of greetings from different users at the same time. + +```solidity +mapping(address => string) public greetings; +``` + +### The Constructor + +The `Greeter` has a simple constructor that sets the `MESSENGER` and `OTHER_GREETER` variables. + +```solidity +constructor(address messenger, address otherGreeter) { + MESSENGER = messenger; + OTHER_GREETER = otherGreeter; +} +``` + +### The sendGreeting function + +The `sendGreeting` function is the most important function in the `Greeter` contract. +This is what you called earlier to send messages in both directions. +All this function does is use the `sendMessage` function found within the `CrossDomainMessenger` contract to send a message to the `Greeter` contract on the other chain. + +```solidity +function sendGreeting(string calldata newGreeting) external payable { + ICrossDomainMessenger(MESSENGER).sendMessage( + OTHER_GREETER, + abi.encodeWithSelector(Greeter.setGreeting.selector, msg.sender, newGreeting), + 100_000 + ); +} +``` + +### The setGreeting function + +The `setGreeting` function is the function that actually sets the greeting. +This function is called by the `CrossDomainMessenger` contract on the other chain. +It checks explicitly that the function can only be called by the `CrossDomainMessenger` contract. +It also checks that the `CrossChainMessenger` says that the message came from the `Greeter` contract on the other chain. +Finally, it sets the greeting in the `greetings` mapping. + +```solidity +function setGreeting(address sender, string calldata newGreeting) external { + require(msg.sender == MESSENGER, "Only messenger"); + require( + ICrossDomainMessenger(MESSENGER).xDomainMessageSender() == OTHER_GREETER, + "Only remote greeter" + ); + greetings[sender] = newGreeting; +} +``` + + + The two `require` statements in this function are important. + Without them, anyone could call this function and set the greeting to whatever they want. + You can follow a similar pattern in your own smart contracts. + + +## Conclusion + +You just learned how you can write Solidity contracts on Sepolia and OP Sepolia that can talk to each other. +You can follow the same pattern to write contracts that can talk to each other on Ethereum and OP Stack. + +```solidity +interface ICrossDomainMessenger { + function sendMessage(address target, bytes calldata message, uint32 gasLimit) external; + function xDomainMessageSender() external view returns (address); +} +``` + +This sort of cross-chain communication is useful for a variety of reasons. +For example, the [Standard Bridge](/app-developers/guides/bridging/standard-bridge) contracts use this same system to bridge ETH and ERC-20 tokens between Ethereum and OP Stack. +One cool way to take advantage of cross-chain communication is to do most of your heavy lifting on OP Stack and then send a message to Ethereum only when you have important results to share. +This way you can take advantage of the low gas costs on OP Stack while still being able to use Ethereum when you need it. diff --git a/docs/public-docs/app-developers/tutorials/bridging/deposit-transactions.mdx b/docs/public-docs/app-developers/tutorials/bridging/deposit-transactions.mdx new file mode 100644 index 0000000000000..19b04570b7ae6 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/bridging/deposit-transactions.mdx @@ -0,0 +1,94 @@ +--- +title: Deposit transactions +description: Learn about using deposit transactions with `supersim`. +--- + +Supersim supports [deposit transactions](/op-stack/bridging/deposit-flow). It uses a very lightweight solution without the `op-node` derivation pipeline by listening directly to the `TransactionDeposited` events on the `OptimismPortal` contract and simply forwarding the transaction to the applicable L2. + +The execution engine used with Supersim must support the Optimism [deposit transaction type](https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type). + +## `OptimismPortal` + +When starting Supersim, the L1 contracts for each L2 chain are emitted as output to the console. The `L1CrossDomainMessenger`, `L1StandardBridge`, and `OptimismPortal` can be used to initiate deposits in the same manner as one would on a production network like OP Mainnet or Base. + +```bash +Chain Configuration +----------------------- +L1: Name: Local ChainID: 900 RPC: http://127.0.0.1:8545 LogPath: ... + +L2: Predeploy Contracts Spec ( https://specs.optimism.io/protocol/predeploys.html ) + + * Name: OPChainA ChainID: 901 RPC: http://127.0.0.1:9545 LogPath: ... + L1 Contracts: + - OptimismPortal: 0x37a418800d0c812A9dE83Bc80e993A6b76511B57 + - L1CrossDomainMessenger: 0xcd712b03bc6424BF45cE6C29Fc90FFDece228F6E + - L1StandardBridge: 0x8d515eb0e5F293B16B6bBCA8275c060bAe0056B0 + + ... +``` + +If running Supersim in fork mode, the production contracts will be used for each of the forked networks. + +```bash +Chain Configuration +----------------------- +L1: Name: mainnet ChainID: 1 RPC: http://127.0.0.1:8545 LogPath: ... + +L2: Predeploy Contracts Spec ( https://specs.optimism.io/protocol/predeploys.html ) + + * Name: op ChainID: 10 RPC: http://127.0.0.1:9545 LogPath: ... + L1 Contracts: + - OptimismPortal: 0xbEb5Fc579115071764c7423A4f12eDde41f106Ed + - L1CrossDomainMessenger: 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1 + - L1StandardBridge: 0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1 + + * Name: mode ChainID: 34443 RPC: http://127.0.0.1:9546 LogPath: ... + L1 Contracts: + - OptimismPortal: 0x8B34b14c7c7123459Cf3076b8Cb929BE097d0C07 + - L1CrossDomainMessenger: 0x95bDCA6c8EdEB69C98Bd5bd17660BaCef1298A6f + - L1StandardBridge: 0x735aDBbE72226BD52e818E7181953f42E3b0FF21 + + ... +``` + +## Sample Deposit Flow + +We'll run through a sample deposit directly with the `OptimismPortal` using cast. + + + + ```bash + supersim + ``` + + + + ```bash + ... + * Name: OPChainA ChainID: 901 ... + L1 Contracts: + - OptimismPortal: 0x37a418800d0c812A9dE83Bc80e993A6b76511B57 + ... + ``` + + + + We'll be using the first pre-funded account to send this deposit of 1 ether + + ```bash + cast send 0x37a418800d0c812A9dE83Bc80e993A6b76511B57 --value 1ether --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ``` + + + + ```bash + INFO [11-28|13:56:06.756] OptimismPortal#depositTransaction chain.id=901 l2TxHash=0x592d6e13016751332115df1fce59904176bfe447854196ed1b97ee00f14be469 + ``` + + + +## Next steps + +* See the [transaction guides](/app-developers/guides/transactions) for more detailed information. +* Questions about Interop? Check out collection of [interop guides](/> op-stack/interop/explainer) or check out this [OP Stack interop design video walk-thru](https://www.youtube.com/watch?v=FKc5RgjtGes). +* For more info about how OP Stack interoperability works under the hood, [check out the specs](https://specs.optimism.io/interop/overview.html?utm_source=op-docs&utm_medium=docs). diff --git a/docs/public-docs/app-developers/tutorials/bridging/standard-bridge-custom-token.mdx b/docs/public-docs/app-developers/tutorials/bridging/standard-bridge-custom-token.mdx new file mode 100644 index 0000000000000..59a5cddac2293 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/bridging/standard-bridge-custom-token.mdx @@ -0,0 +1,339 @@ +--- +title: Bridging your custom ERC-20 token using the Standard Bridge +description: Learn how to bridge your custom ERC-20 token using the standard bridge. +--- + + +In this tutorial, you'll learn how to bridge a custom ERC-20 token from Ethereum to an OP Stack chain using the Standard Bridge system. +This tutorial is meant for developers who already have an existing ERC-20 token on Ethereum and want to create a bridged representation of that token on OP Mainnet. + +This tutorial explains how you can create a custom token that conforms to the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol) interface so that it can be used with the Standard Bridge system. +A custom token allows you to do things like trigger extra logic whenever a token is deposited. +If you don't need extra functionality like this, consider following the tutorial on [Bridging Your Standard ERC-20 Token Using the Standard Bridge](./standard-bridge-standard-token) instead. + + + The Standard Bridge **does not** support [**fee on transfer tokens**](https://github.com/d-xo/weird-erc20#fee-on-transfer) or [**rebasing tokens**](https://github.com/d-xo/weird-erc20#balance-modifications-outside-of-transfers-rebasingairdrops) because they can cause bridge accounting errors. + + +## About OptimismMintableERC20s + +The Standard Bridge system requires that L2 representations of L1 tokens implement the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol) interface. +This interface is a superset of the standard ERC-20 interface and includes functions that allow the bridge to properly verify deposits/withdrawals and mint/burn tokens as needed. +Your L2 token contract must implement this interface in order to be bridged using the Standard Bridge system. +This tutorial will show you how to create a custom token that implements this interface. + +## Dependencies + +* [node](https://nodejs.org/en/) +* [pnpm](https://pnpm.io/installation) + +## Get ETH on Sepolia and OP Sepolia + +This tutorial explains how to create a bridged ERC-20 token on OP Sepolia. +You will need to get some ETH on both of these testnets. + + + You can use [this faucet](https://sepoliafaucet.com/) to get ETH on Sepolia. + You can use the [Superchain Faucet](https://console.optimism.io/faucet?utm_source=op-docs\&utm_medium=docs) to get ETH on OP Sepolia. + + +## Add OP Sepolia to your wallet + +This tutorial uses [Remix](https://remix.ethereum.org) to deploy contracts. +You will need to add the OP Sepolia network to your wallet in order to follow this tutorial. +You can use [this website](https://chainid.link?network=op-sepolia) to connect your wallet to OP Sepolia. + +## Get an L1 ERC-20 token address + +You will need an L1 ERC-20 token for this tutorial. +If you already have an L1 ERC-20 token deployed on Sepolia, you can skip this step. +Otherwise, you can use the testing token located at [`0x5589BB8228C07c4e15558875fAf2B859f678d129`](https://sepolia.etherscan.io/address/0x5589BB8228C07c4e15558875fAf2B859f678d129) that includes a `faucet()` function that can be used to mint tokens. + +## Create an L2 ERC-20 token + +Once you have an L1 ERC-20 token, you can create a corresponding L2 ERC-20 token on OP Sepolia. +This tutorial will use [Remix](https://remix.ethereum.org) so you can easily deploy a token without a framework like [Hardhat](https://hardhat.org) or [Foundry](https://getfoundry.sh). +You can follow the same general process within your favorite framework if you prefer. + +In this section, you'll be creating an ERC-20 token that can be deposited but cannot be withdrawn. +This is just one example of the endless ways in which you could customize your L2 token. + + + + + Navigate to [Remix](https://remix.ethereum.org) in your browser. + + + + + Click the 📄 ("Create new file") button to create a new empty Solidity file. + You can name this file whatever you'd like, for example `MyCustomL2Token.sol`. + + + + + Copy the following example contract into your new file: + + ```solidity +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +// Import the standard ERC20 implementation from OpenZeppelin +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + +/** + * @title ILegacyMintableERC20 + * @notice Legacy interface for the StandardL2ERC20 contract. + */ +interface ILegacyMintableERC20 { + function mint(address _to, uint256 _amount) external; + function burn(address _from, uint256 _amount) external; + + function l1Token() external view returns (address); + function l2Bridge() external view returns (address); +} + +/** + * @title IOptimismMintableERC20 + * @notice Interface for the OptimismMintableERC20 contract. + */ +interface IOptimismMintableERC20 { + function remoteToken() external view returns (address); + function bridge() external view returns (address); + function mint(address _to, uint256 _amount) external; + function burn(address _from, uint256 _amount) external; +} + +/** + * @title Simplified Semver for tutorial + * @notice Simple contract to track semantic versioning + */ +contract Semver { + string public version; + + // Simple function to convert uint to string for version numbers + function toString(uint256 value) internal pure returns (string memory) { + // This function handles numbers from 0 to 999 which is sufficient for versioning + if (value == 0) { + return "0"; + } + + uint256 temp = value; + uint256 digits; + + while (temp != 0) { + digits++; + temp /= 10; + } + + bytes memory buffer = new bytes(digits); + + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + + return string(buffer); + } + + constructor(uint256 major, uint256 minor, uint256 patch) { + version = string(abi.encodePacked( + toString(major), + ".", + toString(minor), + ".", + toString(patch) + )); + } +} + +/** + * @title MyCustomL2Token + * @notice A custom L2 token based on OptimismMintableERC20 that can be deposited + * from L1 to L2, but cannot be withdrawn from L2 to L1. + */ +contract MyCustomL2Token is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, Semver { + /// @notice Address of the corresponding token on the remote chain. + address public immutable REMOTE_TOKEN; + + /// @notice Address of the StandardBridge on this network. + address public immutable BRIDGE; + + /// @notice Emitted whenever tokens are minted for an account. + /// @param account Address of the account tokens are being minted for. + /// @param amount Amount of tokens minted. + event Mint(address indexed account, uint256 amount); + + /// @notice Emitted whenever tokens are burned from an account. + /// @param account Address of the account tokens are being burned from. + /// @param amount Amount of tokens burned. + event Burn(address indexed account, uint256 amount); + + /// @notice A modifier that only allows the bridge to call + modifier onlyBridge() { + require(msg.sender == BRIDGE, "MyCustomL2Token: only bridge can mint and burn"); + _; + } + + /// @param _bridge Address of the L2 standard bridge. + /// @param _remoteToken Address of the corresponding L1 token. + /// @param _name ERC20 name. + /// @param _symbol ERC20 symbol. + constructor( + address _bridge, + address _remoteToken, + string memory _name, + string memory _symbol + ) + ERC20(_name, _symbol) + Semver(1, 0, 0) + { + REMOTE_TOKEN = _remoteToken; + BRIDGE = _bridge; + } + + /// @notice Allows the StandardBridge on this network to mint tokens. + /// @param _to Address to mint tokens to. + /// @param _amount Amount of tokens to mint. + function mint( + address _to, + uint256 _amount + ) + external + virtual + override(IOptimismMintableERC20, ILegacyMintableERC20) + onlyBridge + { + _mint(_to, _amount); + emit Mint(_to, _amount); + } + + /// @notice Burns tokens from an account. + /// @dev This function always reverts to prevent withdrawals to L1. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function burn( + address _from, + uint256 _amount + ) + external + virtual + override(IOptimismMintableERC20, ILegacyMintableERC20) + onlyBridge + { + // Instead of calling _burn(_from, _amount), we revert + // This makes it impossible to withdraw tokens back to L1 + revert("MyCustomL2Token: withdrawals are not allowed"); + + // Note: The following line would normally execute but is unreachable + // _burn(_from, _amount); + // emit Burn(_from, _amount); + } + + /// @notice ERC165 interface check function. + /// @param _interfaceId Interface ID to check. + /// @return Whether or not the interface is supported by this contract. + function supportsInterface(bytes4 _interfaceId) external pure virtual returns (bool) { + bytes4 iface1 = type(IERC165).interfaceId; + // Interface corresponding to the legacy L2StandardERC20 + bytes4 iface2 = type(ILegacyMintableERC20).interfaceId; + // Interface corresponding to the updated OptimismMintableERC20 + bytes4 iface3 = type(IOptimismMintableERC20).interfaceId; + return _interfaceId == iface1 || _interfaceId == iface2 || _interfaceId == iface3; + } + + /// @notice Legacy getter for the remote token. Use REMOTE_TOKEN going forward. + function l1Token() public view override returns (address) { + return REMOTE_TOKEN; + } + + /// @notice Legacy getter for the bridge. Use BRIDGE going forward. + function l2Bridge() public view override returns (address) { + return BRIDGE; + } + + /// @notice Getter for REMOTE_TOKEN. + function remoteToken() public view override returns (address) { + return REMOTE_TOKEN; + } + + /// @notice Getter for BRIDGE. + function bridge() public view override returns (address) { + return BRIDGE; + } +} +``` + + + + + Take a moment to review the example contract. It's closely based on the official [`OptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/v1.12.2/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol) contract with one key modification: + + The `burn` function has been modified to always revert, making it impossible to withdraw tokens back to L1. + + Since the bridge needs to burn tokens when users want to withdraw them to L1, this means that users will not be able to withdraw tokens from this contract. Here's the key part of the contract that prevents withdrawals: + + ```solidity + /// @notice Burns tokens from an account. + /// @dev This function always reverts to prevent withdrawals to L1. + /// @param _from Address to burn tokens from. + /// @param _amount Amount of tokens to burn. + function burn( + address _from, + uint256 _amount + ) + external + virtual + override(IOptimismMintableERC20, ILegacyMintableERC20) + onlyBridge + { + // Instead of calling _burn(_from, _amount), we revert + // This makes it impossible to withdraw tokens back to L1 + revert("MyCustomL2Token: withdrawals are not allowed"); + + // Note: The following line would normally execute but is unreachable + // _burn(_from, _amount); + // emit Burn(_from, _amount); + } +``` + + + + + Save the file to automatically compile the contract. + If you've disabled auto-compile, you'll need to manually compile the contract by clicking the "Solidity Compiler" tab (this looks like the letter "S") and press the blue "Compile" button. + + Make sure you're using Solidity compiler version 0.8.15 (the same version used in the official Optimism contracts). + + + + + Open the deployment tab (this looks like an Ethereum logo with an arrow pointing left). + Make sure that your environment is set to "Injected Provider", your wallet is connected to OP Sepolia, and Remix has access to your wallet. + Then, select the `MyCustomL2Token` contract from the deployment dropdown and deploy it with the following parameters: + + ```text + _bridge: "0x4200000000000000000000000000000000000010" // L2 Standard Bridge address + _remoteToken: "" // Your L1 token address + _name: "My Custom L2 Token" // Your token name + _symbol: "MCL2T" // Your token symbol + ``` + + Note: The L2 Standard Bridge address is a predefined address on all OP Stack chains, so it will be the same on OP Sepolia and OP Mainnet. + + + +## Bridge some tokens + +Now that you have an L2 ERC-20 token, you can bridge some tokens from L1 to L2. +Check out the tutorial on [Bridging ERC-20 tokens with viem](./cross-dom-bridge-erc20) to learn how to bridge your L1 ERC-20 to L2s using viem. +Remember that the withdrawal step will *not* work for the token you just created! +This is exactly what this tutorial was meant to demonstrate. + +## Add to the Superchain Token List + +The [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io#readme) is a common list of tokens deployed on chains within the Optimism Superchain. +This list is used by services like the [Superchain Bridges UI](https://app.optimism.io/bridge?utm_source=op-docs\&utm_medium=docs). +If you want your OP Mainnet token to be included in this list, take a look at the [review process and merge criteria](https://github.com/ethereum-optimism/ethereum-optimism.github.io#review-process-and-merge-criteria). diff --git a/docs/public-docs/app-developers/tutorials/bridging/standard-bridge-standard-token.mdx b/docs/public-docs/app-developers/tutorials/bridging/standard-bridge-standard-token.mdx new file mode 100644 index 0000000000000..0b303ae3f9486 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/bridging/standard-bridge-standard-token.mdx @@ -0,0 +1,105 @@ +--- +title: Bridging Your Standard ERC-20 Token Using the Standard Bridge +description: Learn how to bridge your standard ERC-20 token using the standard bridge. +--- + + +In this tutorial you'll learn how to bridge a standard ERC-20 token from Ethereum to an OP Stack chain using the Standard Bridge system. +This tutorial is meant for developers who already have an existing ERC-20 token on Ethereum and want to create a bridged representation of that token on layer 2. + +This tutorial explains how to use the [`OptimismMintableERC20Factory`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol) to deploy a standardized ERC-20 token on layer 2. +Tokens created by this factory contract are compatible with the Standard Bridge system and include basic logic for deposits, transfers, and withdrawals. +If you want to include specialized logic within your L2 token, see the tutorial on [Bridging Your Custom ERC-20 Token Using the Standard Bridge](./standard-bridge-custom-token) instead. + + + The Standard Bridge **does not** support [**fee on transfer tokens**](https://github.com/d-xo/weird-erc20#fee-on-transfer) or [**rebasing tokens**](https://github.com/d-xo/weird-erc20#balance-modifications-outside-of-transfers-rebasingairdrops) because they can cause bridge accounting errors. + + +## About OptimismMintableERC20s + +The Standard Bridge system requires that L2 representations of L1 tokens implement the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/v1.12.0/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol) interface. +This interface is a superset of the standard ERC-20 interface and includes functions that allow the bridge to properly verify deposits/withdrawals and mint/burn tokens as needed. +Your L2 token contract must implement this interface in order to be bridged using the Standard Bridge system. +This tutorial will show you how to use the [`OptimismMintableERC20Factory`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol) to create a basic standardized ERC-20 token on layer 2. + +## Dependencies + +* [cast](https://book.getfoundry.sh/getting-started/installation) + +## Get ETH on Sepolia and OP Sepolia + +This tutorial explains how to create a bridged ERC-20 token on OP Sepolia. +You will need to get some ETH on both of these testnets. + + + You can use [this faucet](https://sepoliafaucet.com) to get ETH on Sepolia. + You can use the [Superchain Faucet](https://console.optimism.io/faucet?utm_source=op-docs\&utm_medium=docs) to get ETH on OP Sepolia. + + +## Get an L1 ERC-20 token address + +You will need an L1 ERC-20 token for this tutorial. +If you already have an L1 ERC-20 token deployed on Sepolia, you can skip this step. +Otherwise, you can use the testing token located at [`0x5589BB8228C07c4e15558875fAf2B859f678d129`](https://sepolia.etherscan.io/address/0x5589BB8228C07c4e15558875fAf2B859f678d129) that includes a `faucet()` function that can be used to mint tokens. + +## Create an L2 ERC-20 Token + +Once you have an L1 ERC-20 token, you can use the [`OptimismMintableERC20Factory`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol) to create a corresponding L2 ERC-20 token on OP Sepolia. +All tokens created by the factory implement the `IOptimismMintableERC20` interface and are compatible with the Standard Bridge system. + + + + + You'll need a private key in order to sign transactions. + Set your private key as an environment variable with the `export` command. + Make sure this private key corresponds to an address that has ETH on OP Sepolia. + + ```bash + export TUTORIAL_PRIVATE_KEY=0x... + ``` + + + + + You'll need an RPC URL in order to connect to OP Sepolia. + Set your RPC URL as an environment variable with the `export` command. + + ```bash +export TUTORIAL_RPC_URL=https://sepolia.optimism.io +``` + + + + + You'll need to know the address of your L1 ERC-20 token in order to create a bridged representation of it on OP Sepolia. + Set your L1 ERC-20 token address as an environment variable with the `export` command. + + ```bash +# Replace this with your L1 ERC-20 token if not using the testing token! +export TUTORIAL_L1_ERC20_ADDRESS=0x5589BB8228C07c4e15558875fAf2B859f678d129 +``` + + + + + You can now deploy your L2 ERC-20 token using the [`OptimismMintableERC20Factory`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol). + Use the `cast` command to trigger the deployment function on the factory contract. + This example command creates a token with the name "My Standard Demo Token" and the symbol "L2TKN". + The resulting L2 ERC-20 token address is printed to the console. + + ```bash +cast send 0x4200000000000000000000000000000000000012 "createOptimismMintableERC20(address,string,string)" $TUTORIAL_L1_ERC20_ADDRESS "My Standard Demo Token" "L2TKN" --private-key $TUTORIAL_PRIVATE_KEY --rpc-url $TUTORIAL_RPC_URL --json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address +``` + + + +## Bridge some tokens + +Now that you have an L2 ERC-20 token, you can bridge some tokens from L1 to L2. +Check out the tutorial on [Bridging ERC-20 tokens with viem](./cross-dom-bridge-erc20) to learn how to bridge your L1 ERC-20 to L2s using viem. + +## Add to the Superchain Token List + +The [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io#readme) is a common list of tokens deployed on chains within the Optimism Superchain. +This list is used by services like the [Superchain Bridges UI](https://app.optimism.io/bridge?utm_source=op-docs\&utm_medium=docs). +If you want your OP Mainnet token to be included in this list, take a look at the [review process and merge criteria](https://github.com/ethereum-optimism/ethereum-optimism.github.io#review-process-and-merge-criteria). diff --git a/docs/public-docs/app-developers/tutorials/development/supersim/first-steps.mdx b/docs/public-docs/app-developers/tutorials/development/supersim/first-steps.mdx new file mode 100644 index 0000000000000..099829001a8d7 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/development/supersim/first-steps.mdx @@ -0,0 +1,116 @@ +--- +title: First steps +description: Take your first steps with Supersim. +--- + +`supersim` allows testing multichain features **locally**. Previously, testing multichain features required complex docker setups or using a testnet. To see it in practice, this tutorial walks you through sending some ETH from the L1 to the L2. + +## Deposit ETH from the L1 into the L2 (L1 to L2 message passing) + + + + Grab the balance of the sender account on L2: + + ```sh + cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:9545 + ``` + + + + You can use two different methods to complete this action: `OptimismPortal` or `L1StandardBridge`. + + #### First method: `OptimismPortal` + + * Send the Ether to `OptimismPortal` contract of the respective L2 (on chain 900) + + + For chain 901, the contract is `0x37a418800d0c812A9dE83Bc80e993A6b76511B57`. + + + * Initiate a bridge transaction on the L1: + + ```sh + cast send 0x37a418800d0c812A9dE83Bc80e993A6b76511B57 --value 0.1ether --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ``` + + #### Second method: `L1StandardBridge` + + * Call `bridgeETH` function on the `L1StandardBridgeProxy` / `L1StandardBridge` contract of the respective L2 on L1 (chain 900) + + + For chain 901, the contract is `0x8d515eb0e5F293B16B6bBCA8275c060bAe0056B0`. + + + * Initiate a bridge transaction on the L1: + + ```sh + cast send 0x8d515eb0e5F293B16B6bBCA8275c060bAe0056B0 "bridgeETH(uint32 _minGasLimit, bytes calldata _extraData)" 50000 0x --value 0.1ether --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ``` + + + + Verify that the ETH balance of the sender has increased on the L2: + + ```sh + cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:9545 + ``` + + + +## Send an interoperable ERC20 token from chain 901 to 902 (L2 to L2 message passing) + +In a typical L2 to L2 cross-chain transfer, two transactions are required: + +1. Send transaction on the source chain – This initiates the token transfer on Chain 901. +2. Relay message transaction on the destination chain – This relays the transfer details to Chain 902. + +To simplify this process, you can use the `--interop.autorelay` flag. This flag automatically triggers the relay message transaction once the initial send transaction is completed on the source chain, improving the developer experience by removing the need to manually send the relay message. + + + + ```sh + supersim --interop.autorelay + ``` + + + + Run the following command to mint 1000 test ERC20 tokens to the recipient address: + + ```sh + cast send 0x420beeF000000000000000000000000000000001 "mint(address _to, uint256 _amount)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1000 --rpc-url http://127.0.0.1:9545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + + ``` + + + + Send the tokens from Chain 901 to Chain 902 using the following command: + + ```sh + cast send 0x4200000000000000000000000000000000000028 "sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId)" 0x420beeF000000000000000000000000000000001 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1000 902 --rpc-url http://127.0.0.1:9545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ``` + + + + In a few seconds, you should see the RelayedMessage on chain 902: + + ```sh + # example + INFO [08-30|14:30:14.698] SuperchainTokenBridge#RelayERC20 token=0x420beeF000000000000000000000000000000001 from=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 to=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 amount=1000 source=901 + ``` + + + + Verify that the balance of the test ERC20 on chain 902 has increased: + + ```sh + cast balance --erc20 0x420beeF000000000000000000000000000000001 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:9546 + ``` + + + +With the steps above, you've now successfully completed both an L1 to L2 ETH bridge and an L2 to L2 interoperable ERC20 token transfer, all done locally using `supersim`. This approach simplifies multichain testing, allowing you to focus on development without the need for complex setups or relying on external testnets. + +## Next steps + +* Learn how to start Supersim in [vanilla (non-forked) mode](/app-developers/reference/tools/supersim/vanilla) or [forked mode](/app-developers/reference/tools/supersim/fork). +* Explore the Supersim [included contracts](/app-developers/reference/tools/supersim/included-contracts) being used to help replicate the OP Stack environment. diff --git a/docs/public-docs/app-developers/tutorials/development/supersim/installation.mdx b/docs/public-docs/app-developers/tutorials/development/supersim/installation.mdx new file mode 100644 index 0000000000000..557c8040b475f --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/development/supersim/installation.mdx @@ -0,0 +1,47 @@ +--- +title: Installation +description: Learn how to install Supersim. +--- + +This page provides installation instructions for `supersim`. + + + + `supersim` requires `anvil`, which is installed alongside the Foundry toolchain. + + Follow the [Foundry toolchain](https://book.getfoundry.sh/getting-started/installation) guide for detailed instructions. + + + + Either download precompiled binaries or install using Homebrew: + + * Precompiled binaries: Download the executable for your platform from the [GitHub releases page](https://github.com/ethereum-optimism/supersim/releases). + + * Homebrew: Install [Homebrew](https://brew.sh/) (OS X, Linux), and then run: + + ```sh + brew tap ethereum-optimism/tap + brew install supersim + ``` + + + + Start `supersim` in vanilla mode by running: + + ```sh + supersim + ``` + Vanilla mode will start 3 chains, with the OP Stack contracts already deployed. + + * (1) L1 Chain + * Chain 900 + * (2) L2 Chains + * Chain 901 + * Chain 902 + + + +## Next steps + +* Continue to the [First Steps](/app-developers/tutorials/development/supersim/first-steps) tutorial to try L1 to L2 message passing. +* Explore [Supersim](/app-developers/tutorials/development/supersim) features, particularly in [vanilla mode](/app-developers/reference/tools/supersim/vanilla), which starts 3 chains (L1 and L2). diff --git a/docs/public-docs/app-developers/tutorials/interoperability/contract-calls.mdx b/docs/public-docs/app-developers/tutorials/interoperability/contract-calls.mdx new file mode 100644 index 0000000000000..c2cd3a36a1ed6 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/interoperability/contract-calls.mdx @@ -0,0 +1,186 @@ +--- +title: Making crosschain contract calls (ping pong) +description: Learn how to make crosschain contract calls using ping pong. +--- + +OP Stack interop is in active development. Some features may be experimental. +This guide walks through the `CrossChainPingPong.sol` contract, focusing on high level design and steps to integrating the `L2ToL2CrossChainMessenger` contract. For more info, view the [source code](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/pingpong/CrossChainPingPong.sol). + +## High level overview + +`CrossChainPingPong.sol` implements a cross-chain ping-pong game using the `L2ToL2CrossDomainMessenger`. + +* Players hit a **virtual ball** back and forth between allowed L2 chains. The game starts with a serve +* from a designated start chain, and each hit increases the rally count. The contract tracks the last hitter's address, chain ID, and the current rally count. + +### Diagram + +```mermaid +sequenceDiagram + participant Chain1 as Chain 1 + participant Chain2 as Chain 2 + + Note over Chain1: 🚀 Game Starts (starting chain) + Note over Chain1: 🏓 Hit Ball + Chain1->>Chain2: 📤 Send PingPongBall {rallyCount: 1, lastHitter: Chain1} + Chain1-->>Chain1: Emit BallSent event + activate Chain2 + Note over Chain2: 📥 Receive Ball + Chain2-->>Chain2: Emit BallReceived event + + Note over Chain2: 🏓 Hit Ball + Chain2->>Chain1: 📤 Send PingPongBall {rallyCount: 2, lastHitter: Chain2} + Chain2-->>Chain2: Emit BallSent event + deactivate Chain2 + activate Chain1 + Note over Chain1: 📥 Receive Ball + Chain1-->>Chain1: Emit BallReceived event + + Note over Chain1,Chain2: Game continues... +``` + +### Flow + + + + * Deployed on all participating chains + * Utilizes CREATE2 with the same parameter, `_serverChainId`, resulting in the same address and initial state. + + + + * Call `hitBallTo` on the chain with the ball, specifying a destination chain. + * Contract uses `L2ToL2CrossDomainMessenger` to send the ball data to the specified chain. + * The reference to the ball is deleted from the serving chain. + + + + * `L2ToL2CrossDomainMessenger` on destination chain calls `receiveBall`. + * Contract verifies the message sender and origin. + * Ball data is stored, indicating its presence on this chain. + + + + * Any user on the chain currently holding the ball calls `hitBallTo` to send it to another chain. + * Contract updates the `PingPongBall` data (increment rally count, update last hitter). + * Process repeats from step 2. + + + +## Walkthrough + +Here's an explanation of the functions in the contract, with a focus on how it interacts with `L2ToL2CrossChainMessenger`. + +### Initialize contract state + +#### Constructor Setup + +```solidity +constructor(uint256 _serverChainId) { + if (block.chainid == _serverChainId) { + ball = PingPongBall(1, block.chainid, msg.sender); + } +} +``` + +If the starting chain, initialize the ball allowing it to be hittable. + +#### Reliance on CREATE2 for cross chain consistency + +While not explicitly mentioned in the code, this contract's design implicitly assumes the use of CREATE2 for deployment. Here's why CREATE2 is crucial for this setup: + +* **Predictable Addresses**: + CREATE2 enables deployment at the same address on all chains, crucial for cross-chain message verification: + ```solidity + if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender(); + ``` + +* **Self-referential Messaging**: + The contract sends messages to itself on other chains: + ```solidity + messenger.sendMessage(_toChainId, address(this), _message); + ``` + This requires `address(this)` to be consistent across chains. + +* **Initialization State Considerations**: + + The starting chain id is part of the initcode, meaning a deployment with a differing value would result in a different address via CREATE2. This is a nice feature as there's an implicit agreement on the starting chain from the address. + + Without CREATE2, you would need to: + + * Manually track contract addresses for each chain. + * Implement a more complex initialization process to register contract addresses across chains. + * Potentially redesign the security model that relies on address matching. + +### Hit the ball + +`hitBallTo`: This function is used to hit the ball, when present, to another chain + +#### Hitting constraints + +```solidity +function hitBallTo(uint256 _toChainId) public { + if (ball.lastHitterAddress == address(0)) revert BallNotPresent(); + if (_toChainId == block.chainid) revert InvalidDestination(); + ... +} +``` + +* The `ball` contract variable is populated on the chain, indicating its presence +* The destination must be a different chain + +### Define receiving handler + +```solidity +modifier onlyCrossDomainCallback() { + if (msg.sender != address(messenger)) revert CallerNotL2ToL2CrossDomainMessenger(); + if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender(); + + _; +} + +function receiveBall(PingPongBall memory _ball) onlyCrossDomainCallback() external { + // Hold reference to the ball + ball = _ball; + + emit BallReceived(messenger.crossDomainMessageSource(), block.chainid, _ball); +} +``` + +* The handler simply stores reference to the received ball +* The handler can only be invocable by the cross chain messenger +* Since the contract is self-referential, the cross chain sender must be the same contract address + +### Hit the ball cross-chain + +```solidity +function hitBallTo(uint256 _toChainId) public { + ... + + // Construct a new ball + PingPongBall memory newBall = PingPongBall(ball.rallyCount + 1, block.chainid, msg.sender); + + // Delete current reference + delete ball; + + // Send to the destination + messenger.sendMessage(_toChainId, address(this), abi.encodeCall(this.receiveBall, (newBall))); + + emit BallSent(block.chainid, _toChainId, newBall); +} +``` + +* Populate a new ball with updated properties +* Delete reference to the current ball so it's no longer hittable +* Invoke the contract on the destination chain matching the `receiveBall` handler defined in (2). + +## Takeaways + +This is just one of many patterns to use the `L2ToL2CrossDomainMessenger` in your contract to power cross chain calls. Key points to remember: + +* **Simple Message Passing**: This design sends simple messages between identical contracts on different chains. Each message contains only the essential game state (rally count, last hitter). More complex systems might involve multiple contracts, intermediary relayers. + +* **Cross Chain Sender Verification**: Always verify the sender of cross-chain messages. This includes checking both the immediate caller (the messenger) and the original sender on the source chain. + +* **Cross Chain Contract Coordination**: This design uses CREATE2 for consistent contract addresses across chains, simplifying cross-chain verification. Alternative approaches include: + * Beacon proxy patterns for upgradeable contracts + * Post-deployment setup where contract addresses are specified after deployment diff --git a/docs/public-docs/app-developers/tutorials/interoperability/event-contests.mdx b/docs/public-docs/app-developers/tutorials/interoperability/event-contests.mdx new file mode 100644 index 0000000000000..085b01708d588 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/interoperability/event-contests.mdx @@ -0,0 +1,192 @@ +--- +title: Deploying crosschain event composability (contests) +description: Learn how to deploy crosschain event composability using contests. +--- + +OP Stack interop is in active development. Some features may be experimental. +We showcase cross chain composability through the implementation of contests. Leveraging the same underlying mechanism powering [TicTacToe](/app-developers/tutorials/interoperability/event-reads), these contests can permissionlessly integrate with the events emitted by any contract on OP Stack chains. + + + See the [frontend documentation](https://github.com/ethereum-optimism/supersim/tree/main/examples/contests) for how the contests UI is presented to the user. + + +## How it works + +Unlike [TicTacToe](/app-developers/tutorials/interoperability/event-reads) which is deployed on every participating chain, the contests are deployed on a single L2, behaving like an application-specific OP Stack chain rather than a horizontally scaled app. + + + + [Contests.sol](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/contests/Contests.sol) contains the implementation of the contests. We won't go into the details of the implementation here, but instead focus on how the contests can leverage cross chain event reading to compose with other contracts on OP Stack chains. + + + + The system predeploy that enables pulling in validated cross-chain events is the [CrossL2Inbox](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs&utm_medium=docs#crossl2inbox). + + ```solidity + contract ICrossL2Inbox { + function validateMessage(Identifier calldata _id, bytes32 _msgHash) external view; + } + ``` + + + + The two contest options are detailed below: [BlockHash contest](#blockhash-contest) and [TicTacToe contest](#tictactoe-contest). + + #### BlockHash contest + + With the existence of an event that emits the blockhash and height of a block, we can create a contest on the parity of the blockhash being even or odd. + + ```solidity + contract BlockHashEmitter { + event BlockHash(uint256 blockHeight, bytes32 blockHash); + + function emitBlockHash(uint256 _blockHeight) external { + bytes32 hash = blockhash(_blockHeight); + require(hash != bytes32(0)); + + emit BlockHash(_blockHeight, hash); + } + } + ``` + + Integrating this emitter into a contest is extremely simple. The `BlockHashContestFactory` is a simple factory that creates a new contest for a given chain and block height. + + #### TicTacToe contest + + A contest for TicTacToe is created on an accepted game between two players, captured by the emitted `AcceptedGame` event. When decoding the event, the game is uniquely identified by the chain it was created on, `chainId`, and the associated `gameId`. These identifying properties of the game are used to create the resolver for the game. + + ```solidity + contract TicTacToeContestFactory { + Contests public contests; + TicTacToe public tictactoe; + + function newContest(Identifier calldata _id, bytes calldata _data) public payable { + // Validate Log + require(_id.origin == address(tictactoe), "not an event from the TicTacToe contract"); + CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_data)); + + bytes32 selector = abi.decode(_data[:32], (bytes32)); + require(selector == TicTacToe.AcceptedGame.selector, "incorrect event"); + + // Decode the event data + (uint256 chainId, uint256 gameId, address creator,) = abi.decode(_data[32:], (uint256, uint256, address, address)); + + IContestResolver resolver = new TicTacToeGameResolver(contests, tictactoe, chainId, gameId, creator); + contests.newContest{ value: msg.value }(resolver, msg.sender); + } + } + ``` + + + + A contest is identified by and has its outcome determined by the `IContestResolver` instance. The resolver starts in the `UNDECIDED` state, updated into `YES` or `NO` when resolving itself + with the contest. + + ```solidity + enum ContestOutcome { + UNDECIDED, + YES, + NO + } + + interface IContestResolver { + function outcome() external returns (ContestOutcome); + } + ``` + + #### Resolve BlockHash contest + + When live, **anyone** can resolve the BlockHash contest by simply providing the right `BlockHash` event to the deployed resolver. + + ```solidity + contract BlockHashContestFactory { + Contests public contests; + BlockHashEmitter public emitter; // Same emitter deployed on every chain + + function newContest(uint256 _chainId, uint256 _blockNumber) public payable { + IContestResolver resolver = new BlockHashResolver(contests, emitter, _chainId, _blockNumber); + contests.newContest{ value: msg.value }(resolver, msg.sender); + } + } + + contract BlockHashResolver is IContestResolver { + Contests public contests; + ContestOutcome public outcome; + BlockHashEmitter public emitter; + + // The target chain & block height + uint256 public chainId; + uint256 public blockNumber; + + function resolve(Identifier calldata _id, bytes calldata _data) external { + require(outcome == ContestOutcome.UNDECIDED); + + // Validate Log + require(_id.origin == address(emitter), "not an event from the emitter"); + require(_id.chainId == chainId, "must match target chain"); + CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_data)); + + bytes32 selector = abi.decode(_data[:32], (bytes32)); + require(selector == BlockHashEmitter.BlockHash.selector, "incorrect event"); + + // Event should correspond to the right contest + uint256 dataBlockNumber = abi.decode(_data[32:64], (uint256)); + require(dataBlockNumber == blockNumber, "must match target block height"); + + // Resolve the contest (yes if odd, no if even) + bytes32 blockHash = abi.decode(_data[64:], (bytes32)); + outcome = uint256(blockHash) % 2 != 0 ? ContestOutcome.YES : ContestOutcome.NO; + contests.resolveContest(this); + } + + } + ``` + + #### Resolve TicTacToe contest + + When live, **anyone** can resolve the TicTacToe contest by providing the `GameWon` or `GameDraw` event of the associated game from the TicTacToe contract. + + ```solidity + contract TicTacToeGameResolver is IContestResolver { + Contests public contests; + ContestOutcome public outcome; + TicTacToe public tictactoe; + + // @notice Game for this resolver + Game public game; + + constructor(Contests _contest, TicTacToe _tictactoe, uint256 _chainId, uint256 _gameId, address _creator) { + contests = _contest; + tictactoe = _tictactoe; + + game = Game({chainId: _chainId, gameId: _gameId, creator: _creator}); + outcome = ContestOutcome.UNDECIDED; + } + + // @notice resolve this game by providing the game ending event + function resolve(Identifier calldata _id, bytes calldata _data) external { + // Validate Log + require(_id.origin == address(tictactoe)); + CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_data)); + + // Ensure this is a finalizing event + bytes32 selector = abi.decode(_data[:32], (bytes32)); + require(selector == TicTacToe.GameWon.selector || selector == TicTacToe.GameDraw.selector, "event not a game outcome"); + + // Event should correspond to the right game + (uint256 _chainId, uint256 gameId, address winner,,) = abi.decode(_data[32:], (uint256, uint256, address, uint8, uint8)); + require(_chainId == game.chainId && gameId == game.gameId); + + // Resolve based on if the creator has won (non-draw) + outcome = winner == game.creator && selector != TicTacToe.GameDraw.selector ? ContestOutcome.YES : ContestOutcome.NO; + contests.resolveContest(this); + } + } + ``` + + + +## Takeaways + +* Leveraging superchain interop, contracts in the superchain can compose with each other in a similar fashion to how they would on a single chain. No restrictions are placed on the kinds of events a contract can consume via the `CrossL2Inbox`. +* In this example, the `BlockHashContestFactory` and `TicTacToeContestFactory` can be seen as just starting points for the `Contests` app chain. As more contracts and apps are created in the superchain, developers can compose with them in a similar fashion without needing to change the `Contests` contract at all. diff --git a/docs/public-docs/app-developers/tutorials/interoperability/event-reads.mdx b/docs/public-docs/app-developers/tutorials/interoperability/event-reads.mdx new file mode 100644 index 0000000000000..d1b36ca44235e --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/interoperability/event-reads.mdx @@ -0,0 +1,192 @@ +--- +title: Making cross-chain event reads (tic-tac-toe) +description: Learn how to make cross-chain event reads using tic-tac-toe. +--- + +OP Stack interop is in active development. Some features may be experimental. +This guide reviews a horizontally scalable implementation of TicTacToe. This [implementation](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/tictactoe/TicTacToe.sol) allows players to play each other from any chain without cross-chain calls, instead relying on cross-chain event reading. Since OP Stack interop can allow for event reading with a 1-block latency, the experience is the **same as a single-chain implementation**. + + + Check out the [frontend documentation](https://github.com/ethereum-optimism/supersim/tree/main/examples/tictactoe) to see how the game UI is presented to the player. + + +## How it works + +We use events to define the ordering of a game with players only maintaining a local view. By default, a chain is also a part of its own interoperable dependency set, which means players on the same chain can also play each other **with no code changes**! + +The system predeploy that enables pulling in validated cross-chain events is the [CrossL2Inbox](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs&utm_medium=docs#crossl2inbox). + +```solidity +contract ICrossL2Inbox { + function validateMessage(Identifier calldata _id, bytes32 _msgHash) external view; +} +``` + +This contract relies on a **CREATE2** deployment to ensure a consistent address across all chains, used to assert the origin of the pulled in game event. + + + + A game is uniquely identified by the chain it was started from with a unique nonce. This identifier is included in all event fields such that each player can uniquely reference it locally. + + To start a game, a player invokes `newGame` which broadcasts a `NewGame` event that any opponent **on any chain** can react to. + + ```solidity + event NewGame(uint256 chainId, uint256 gameId, address player); + + function newGame() external { + emit NewGame(block.chainid, nextGameId, msg.sender); + nextGameId++; + } + ``` + + + + When a `NewGame` event is observed, any player can declare their intent to play via `acceptGame`, referencing the `NewGame` event. An `AcceptedGame` event is emitted to signal to the creator that a game is ready to begin. + + ```solidity + event AcceptedGame(uint256 chainId, uint256 gameId, address opponent, address player); + + function acceptGame(ICrossL2Inbox.Identifier calldata _newGameId, bytes calldata _newGameData) external { + if (_newGameId.origin != address(this)) revert IdOriginNotTicTacToe(); + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_newGameId, keccak256(_newGameData)); + + bytes32 selector = abi.decode(_newGameData[:32], (bytes32)); + if (selector != NewGame.selector) revert DataNotNewGame(); + + ... + + emit AcceptedGame(chainId, gameId, game.opponent, game.player); + } + ``` + + To prepare for the game, the event data is decoded and a local view of this game is stored. + + ```solidity + (uint256 chainId, uint256 gameId, address opponent) = abi.decode(_newGameData[32:], (uint256, uint256, address)); + if (opponent == msg.sender) revert SenderIsOpponent(); + + // Record Game Metadata (no moves) + Game storage game = games[chainId][gameId][msg.sender]; + game.player = msg.sender; + game.opponent = opponent; + game.gameId = gameId; + game.lastOpponentId = _newGameId; + game.movesLeft = 9; + + emit AcceptedGame(chainId, gameId, game.opponent, game.player); + ``` + + + + As `AcceptedGame` events are emitted, the player must pick one opponent to play. The opponent's `AcceptedGame` event is used to instantiate the game and play the starting move via the `MovePlayed` event. + + ```solidity + event MovePlayed(uint256 chainId, uint256 gameId, address player, uint8 _x, uint8 _y); + + function startGame(ICrossL2Inbox.Identifier calldata _acceptedGameId, bytes calldata _acceptedGameData, uint8 _x, uint8 _y) external { + if (_acceptedGameId.origin != address(this)) revert IdOriginNotTicTacToe(); + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_acceptedGameId, keccak256(_acceptedGameData)); + + bytes32 selector = abi.decode(_acceptedGameData[:32], (bytes32)); + if (selector != AcceptedGame.selector) revert DataNotAcceptedGame(); + + ... + + emit MovePlayed(chainId, gameId, game.player, _x, _y); + ``` + + The event fields contain the information required to perform the necessary validation. + + * The game identifier for lookup + * The caller is the appropriate player + * The player is accepting from the same starting chain + + ```solidity + (uint256 chainId, uint256 gameId, address player, address opponent) = // player, opponent swapped in local view + abi.decode(_acceptedGameData[32:], (uint256, uint256, address, address)); + + // The accepted game was started from this chain, from the sender + if (chainId != block.chainid) revert GameChainMismatch(); + if (msg.sender != player) revert SenderNotPlayer(); + + // Game has not already been started with an opponent. + Game storage game = games[chainId][gameId][msg.sender]; + if (game.opponent != address(0)) revert GameStarted(); + + // Store local view of this game + ... + + // Locally record the move by the player with 1 + game.moves[_x][_y] = 1; + game.lastOpponentId = _acceptedGameId; + + emit MovePlayed(chainId, gameId, game.player, _x, _y); + ``` + + + + Once a game is started, players can continually make moves by invoking `makeMove`, reacting to a `MovePlayed` event of their opponent. + + ```solidity + function makeMove(ICrossL2Inbox.Identifier calldata _movePlayedId, bytes calldata _movePlayedData, uint8 _x, uint8 _y) external { + if (_movePlayedId.origin != address(this)) revert IdOriginNotTicTacToe(); + ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_movePlayedId, keccak256(_movePlayedData)); + + bytes32 selector = abi.decode(_movePlayedData[:32], (bytes32)); + if (selector != MovePlayed.selector) revert DataNotMovePlayed(); + } + ``` + + Similar to `acceptGame`, validation is performed and the move of their opponent is first locally recorded. + + * The game identifier for lookup + * The caller is the player for this game + * The opponent event corresponds to the same game + * Ordering is enforced by ensuring that the supplied event is always forward progressing. + + ```solidity + (uint256 chainId, uint256 gameId,, uint8 oppX, uint8 oppY) = abi.decode(_movePlayedData[32:], (uint256, uint256, address, uint8, uint8)); + + // Game was instantiated for this player & the move is for the same game + Game storage game = games[chainId][gameId][msg.sender]; + if (game.player != msg.sender) revert GameNotExists(); + if (game.gameId != gameId) revert GameNotExists(); + + // The move played event is forward progressing from the last observed event + if (_movePlayedId.chainId != game.lastOpponentId.chainId) revert IdChainMismatch(); + if (_movePlayedId.blockNumber <= game.lastOpponentId.blockNumber) revert MoveNotForwardProgressing(); + game.lastOpponentId = _movePlayedId; + + // Mark the opponents move + game.moves[oppX][oppY] = 2; + game.movesLeft--; + ``` + + When a move is played we check if the game has been drawn or won, determining the subsequent event to emit. + + The `makeMove` function is only callable when an opponent has a new `MovePlayed` event. Therefore, if the game is won or drawn, it cannot be progressed any further by the opponent. + + ```solidity + // Make the players move + game.moves[_x][_y] = 1; + game.movesLeft--; + + // Determine the status of the game + if (_isGameWon(game)) { + emit GameWon(chainId, gameId, game.player, _x, _y); + } else if (game.movesLeft == 0) { + emit GameDraw(chainId, gameId, game.player, _x, _y); + } else { + emit MovePlayed(chainId, gameId, game.player, _x, _y); + } + ``` + + + +## Takeaways + +Leveraging superchain interop, we can build new types of horizontally scalable contracts that do not rely on hub/spoke messaging with relayers. + +* As new chains are added to the superchain, this contract can be installed by anyone and immediately playable with no necessary code changes. The frontend simply needs to react the addition of a new chain. +* The concept of a "chain" can be completely abstracted away from the user. When connecting their wallet, the frontend can simply pick the chain which the user has funds on with the lowest gas fees. +* Event reading enables a new level of composability for cross-chain interactions. Imagine [contests](/app-developers/tutorials/interoperability/event-contests) contract that resolves based on the outcome of a TicTacToe game via the `GameWon` or `GameLost` event without the need for a trusted oracle, nor permission or native integration with the TicTacToe contract. diff --git a/docs/public-docs/app-developers/tutorials/interoperability/manual-relay.mdx b/docs/public-docs/app-developers/tutorials/interoperability/manual-relay.mdx new file mode 100644 index 0000000000000..f6456b601d3de --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/interoperability/manual-relay.mdx @@ -0,0 +1,371 @@ +--- +title: Relay transactions manually +description: Learn to relay transactions directly by sending the correct transaction. +--- +OP Stack interop is in active development. Some features may be experimental. + +Messages are relayed automatically in the interop devnet. + +## Overview + +Learn to relay transactions directly by sending the correct transaction. + + + + **Prerequisite technical knowledge** + + * Familiarity with blockchain concepts + * Familiarity with [Foundry](https://book.getfoundry.sh/getting-started/installation), especially `cast` + + **What you'll learn** + + * How to use `cast` to relay transactions when autorelay does not work + * How to relay transactions using JavaScript + + **Development environment requirements** + + * Unix-like operating system (Linux, macOS, or WSL for Windows) + * Node.js version 16 or higher + * Git for version control + * Supersim environment configured and running + * Foundry tools installed (forge, cast, anvil) + + +### What you'll build + +* A program to relay messages using [the JavaScript library](https://www.npmjs.com/package/@eth-optimism/viem) +* A shell script to relay messages using [`cast`](https://book.getfoundry.sh/cast/) + +## Setup + +These steps are necessary to run the tutorial, regardless of whether you are using `cast` or the JavaScript API. + + + + This exercise needs to be done on Supersim. + You cannot use the devnets because you cannot disable autorelay on them. + + 1. Follow the [installation guide](/app-developers/tutorials/development/supersim/installation). + 2. Run Supersim *without* `--interop.relay`. + + ```sh + ./supersim + ``` + + + + The results of this step are similar to what the [message passing tutorial](/app-developers/tutorials/interoperability/message-passing) would produce if you did not have autorelay on. + + Execute this script: + + ```sh + #! /bin/sh + # full shell script preserved here... + # (Greeter.sol, GreetingSender.sol, sendAndRelay.sh setup) + # ... + ``` + + This script installs `Greeter.sol` on chain B and `GreetingSender.sol` on chain A. + These smart contracts let us send a message from chain A that needs to be relayed to chain B. + + Then, the script creates `./manual-relay/sendAndRelay.sh` to manually relay a message from chain A to chain B. + That script is [explained below](#manual-relay-using-cast). + + Finally, this script writes out some parameter setting lines that should be executed on the main shell before you continue. + With a fresh Supersim running, these should be: + + ```sh + GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3 + GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3 + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ``` + + + +## Manual relay using the API + + + + Use a [Node](https://nodejs.org/en) project. + + 1. Initialize a new Node project. + + ```sh + mkdir -p manual-relay/offchain + cd manual-relay/offchain + npm init -y + npm install --save-dev viem @eth-optimism/viem + mkdir src + ``` + + 2. Export environment variables: + + ```sh + export GREETER_A_ADDRESS GREETER_B_ADDRESS PRIVATE_KEY + ``` + + + + Create a file `manual-relay.mjs` with: + + ```javascript + import { + createWalletClient, + http, + publicActions, + getContract, + } from 'viem' + import { privateKeyToAccount } from 'viem/accounts' + import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' + import { walletActionsL2, publicActionsL2 } from '@eth-optimism/viem' + + import { readFileSync } from 'fs'; + + const greeterData = JSON.parse(readFileSync('../onchain/out/Greeter.sol/Greeter.json')) + const greetingSenderData = JSON.parse(readFileSync('../onchain/out/Greeter.sol/Greeter.json')) + + const account = privateKeyToAccount(process.env.PRIVATE_KEY) + + const walletA = createWalletClient({ + chain: supersimL2A, + transport: http(), + account + }).extend(publicActions) + .extend(publicActionsL2()) + // .extend(walletActionsL2()) + + const walletB = createWalletClient({ + chain: supersimL2B, + transport: http(), + account + }).extend(publicActions) + // .extend(publicActionsL2()) + .extend(walletActionsL2()) + + const greeter = getContract({ + address: process.env.GREETER_B_ADDRESS, + abi: greeterData.abi, + client: walletB + }) + + const greetingSender = getContract({ + address: process.env.GREETER_A_ADDRESS, + abi: greetingSenderData.abi, + client: walletA + }) + + const txnBHash = await greeter.write.setGreeting( + ["Greeting directly to chain B"]) + await walletB.waitForTransactionReceipt({hash: txnBHash}) + + const greeting1 = await greeter.read.greet() + console.log(`Chain B Greeting: ${greeting1}`) + + const txnAHash = await greetingSender.write.setGreeting( + ["Greeting through chain A"]) + const receiptA = await walletA.waitForTransactionReceipt({hash: txnAHash}) + + const greeting2 = await greeter.read.greet() + console.log(`Greeting before the relay transaction: ${greeting2}`) + + const sentMessages = await walletA.interop.getCrossDomainMessages({ + logs: receiptA.logs, + }) + const sentMessage = sentMessages[0] // We only sent 1 message + const relayMessageParams = await walletA.interop.buildExecutingMessage({ + log: sentMessage.log, + }) + const relayMsgTxnHash = await walletB.interop.relayCrossDomainMessage(relayMessageParams) + + const receiptRelay = await walletB.waitForTransactionReceipt({ + hash: relayMsgTxnHash, + }) + + const greeting3 = await greeter.read.greet() + console.log(`Greeting after the relay transaction: ${greeting3}`) + ``` + + + + ```javascript + import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains' + import { walletActionsL2, publicActionsL2 } from '@eth-optimism/viem' + ``` + + + Run JavaScript program: + + ```sh + node manual-relay.mjs + ``` + + + + To see what messages were relayed by a specific transaction: + + ```javascript + import { decodeRelayedL2ToL2Messages } from '@eth-optimism/viem' + + const decodedRelays = decodeRelayedL2ToL2Messages({ receipt: receiptRelay }) + + console.log(decodedRelays) + console.log(decodedRelays.successfulMessages[0].log) + ``` + + + +## Manual relay using `cast` + +You can see an example of how to manually relay using `cast` in `manual-relay/sendAndRelay.sh`. +It is somewhat complicated, so the setup creates one that is tailored to your environment. + +Run the script: + +```sh +./manual-relay/sendAndRelay.sh +``` + +Here is the detailed explanation: + +1. Configuration parameters + + ```sh + #! /bin/sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + URL_CHAIN_A=http://localhost:9545 + URL_CHAIN_B=http://localhost:9546 + GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3 + GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3 + CHAIN_ID_B=902 + ``` + + This is the configuration. + The greeter addresses are identical because the nonce for the user address has an identical nonce on both chains. + +2. Send a message that needs to be relayed + + ```sh + cast send -q --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A $$" + ``` + + Send a message from chain A to chain B. The `$$` is the process ID, so if you rerun the script you'll see that the information changes. + +3. Find the log entry to relay + + ```sh + cast logs "SentMessage(uint256,address,uint256,address,bytes)" --rpc-url $URL_CHAIN_A | tail -14 > log-entry + ``` + + Whenever `L2ToL2CrossDomainMessenger` sends a message to a different blockchain, it emits a [`SendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L83-L91) event. + Extract only the latest `SendMessage` event from the logs. + + + + ```yaml + - address: 0x4200000000000000000000000000000000000023 + blockHash: 0xcd0be97ffb41694faf3a172ac612a23f224afc1bfecd7cb737a7a464cf5d133e + blockNumber: 426 + data: 0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001948656c6c6f2066726f6d20636861696e2041203131333030370000000000000000000000000000000000000000000000000000000000000000000000 + logIndex: 0 + removed: false + topics: [ + 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320 + 0x0000000000000000000000000000000000000000000000000000000000000386 + 0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa3 + 0x0000000000000000000000000000000000000000000000000000000000000000 + ] + transactionHash: 0x1d6f2e5e2c8f3eb055e95741380ca36492f784b9782848b66b66c65c5937ff3a + transactionIndex: 0 + ``` + + +4. Manipulate the log entry to obtain information + + ```sh + TOPICS=`cat log-entry | grep -A4 topics | awk '{print $1}' | tail -4 | sed 's/0x//'` + TOPICS=`echo $TOPICS | sed 's/ //g'` + ``` + + Consolidate the log topics into a single hex string. + + ```sh + ORIGIN=0x4200000000000000000000000000000000000023 + BLOCK_NUMBER=`cat log-entry | awk '/blockNumber/ {print $2}'` + LOG_INDEX=`cat log-entry | awk '/logIndex/ {print $2}'` + TIMESTAMP=`cast block $BLOCK_NUMBER --rpc-url $URL_CHAIN_A | awk '/timestamp/ {print $2}'` + CHAIN_ID_A=`cast chain-id --rpc-url $URL_CHAIN_A` + SENT_MESSAGE=`cat log-entry | awk '/data/ {print $2}'` + ``` + + Read additional fields from the log entry. + + ```sh + LOG_ENTRY=0x`echo $TOPICS$SENT_MESSAGE | sed 's/0x//'` + ``` + + Consolidate the entire log entry. + +5. Create the access list for the executing message + + +```sh +RPC_PARAMS=$(cat < op-stack/interop/explainer) for answers to common questions about interoperability. +* Read the [message passing explainer](/app-developers/guides/interoperability/message-passing) to understand what happens "under the hood". +* Write a revolutionary app that uses multiple blockchains within the OP Stack ecosystem. diff --git a/docs/public-docs/app-developers/tutorials/interoperability/message-passing.mdx b/docs/public-docs/app-developers/tutorials/interoperability/message-passing.mdx new file mode 100644 index 0000000000000..0597d84e7de7e --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/interoperability/message-passing.mdx @@ -0,0 +1,440 @@ +--- +title: Interop message passing tutorial +description: Learn to implement cross-chain communication on OP Stack chains by building a message passing system using the L2ToL2CrossDomainMessenger contract. +--- + + + OP Stack interop is in active development. Some features may be experimental. + +## Overview + +This tutorial demonstrates how to implement cross-chain communication within the OP Stack ecosystem. You'll build a complete +message passing system that enables different chains to interact with each other using the `L2ToL2CrossDomainMessenger` contract. + + + + **Prerequisite technical knowledge** + + * Intermediate Solidity programming + * Basic TypeScript knowledge + * Understanding of smart contract development + * Familiarity with blockchain concepts + + **What you'll learn** + + * How to deploy contracts across different chains + * How to implement cross-chain message passing + * How to handle sender verification across chains + * How to relay messages manually between chains + + **Development environment** + + * Unix-like operating system (Linux, macOS, or WSL for Windows) + * Node.js version 16 or higher + * Git for version control + + **Required tools** + + The tutorial uses these primary tools: + + * Foundry: For smart contract development + * Supersim: For local blockchain simulation (optional) + * TypeScript: For offchain code (for relaying messages manually) + * Viem: For interactions with the chain from the offchain app + + +### What You'll Build + +* A `Greeter` contract that stores and updates a greeting +* A `GreetingSender` contract that sends cross-chain messages to update the greeting +* A TypeScript application to relay messages between chains + + + This tutorial provides step-by-step instructions for implementing cross-chain messaging. + For a conceptual overview, + see the [Message Passing Explainer](/app-developers/guides/interoperability/message-passing). + + +In this tutorial, you will learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass messages between interoperable blockchains. + +## Setting up your development environment + + + + * Foundry for smart contract development (required in all cases) + * Supersim for local blockchain simulation (optional) + + + + ```sh + forge --version + ./supersim --version + ``` + + + +## Implementing onchain message passing (in Solidity) + +The implementation consists of three main components: + +1. **Greeter Contract**: Deployed on `Chain B`, receives and stores messages. +2. **GreetingSender Contract**: Deployed on `Chain A`, initiates cross-chain messages. + + + + 1. If you are using [Supersim](/> app-developers/tools/development/supersim), go to the directory where Supersim is installed and start it with autorelay. + + ```sh + ./supersim --interop.autorelay + ``` + + If you are using [the devnets](/app-developers/guides/building-apps), just skip this step. + + + + Supersim creates three `anvil` blockchains: + + | Role | ChainID | RPC URL | + | -------- | ------: | ---------------------------------------------- | + | L1 | 900 | [http://127.0.0.1:8545](http://127.0.0.1:8545) | + | OPChainA | 901 | [http://127.0.0.1:9545](http://127.0.0.1:9545) | + | OPChainB | 902 | [http://127.0.0.1:9546](http://127.0.0.1:9546) | + + + + These are the three networks involved in the devnet: + + | Role | ChainID | RPC URL | + | ------------ | --------: | -------------------------------------------------------------------------------- | + | L1 (Sepolia) | 11155111 | [https://eth-sepolia.public.blastapi.io](https://eth-sepolia.public.blastapi.io) | + | ChainA | 420120000 | [https://interop-alpha-0.optimism.io](https://interop-alpha-0.optimism.io) | + | ChainB | 420120001 | [https://interop-alpha-1.optimism.io](https://interop-alpha-1.optimism.io) | + + + + 2. In a separate shell, store the configuration in environment variables. + + + + Set these parameters for Supersim. + + ```sh + PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + URL_CHAIN_A=http://127.0.0.1:9545 + URL_CHAIN_B=http://127.0.0.1:9546 + INTEROP_BRIDGE=0x4200000000000000000000000000000000000028 + ``` + + + + For Devnet, specify in `PRIVATE_KEY` the private key you used for the setup script and then these parameters. + + ```sh + USER_ADDRESS=`cast wallet address --private-key $PRIVATE_KEY` + URL_CHAIN_A=https://interop-alpha-0.optimism.io + URL_CHAIN_B=https://interop-alpha-1.optimism.io + INTEROP_BRIDGE=0x4200000000000000000000000000000000000028 + ``` + + + + + + To verify that the chains are running, check the balance of `$USER_ADDRESS`. + + ```sh + cast balance --ether $USER_ADDRESS --rpc-url $URL_CHAIN_A + cast balance --ether $USER_ADDRESS --rpc-url $URL_CHAIN_B + ``` + + + + + 1. Create a new Foundry project. + + ```sh + mkdir onchain-code + cd onchain-code + forge init + ``` + + 2. In `src/Greeter.sol` put this file. + This is a variation on [Hardhat's Greeter contract](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol). + + ```solidity + //SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + contract Greeter { + string greeting; + + event SetGreeting( + address indexed sender, // msg.sender + string greeting + ); + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + emit SetGreeting(msg.sender, _greeting); + } + } + ``` + + 3. Deploy the `Greeter` contract to Chain B and store the resulting contract address in the `GREETER_B_ADDRESS` environment variable. + + ```sh + GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` + ``` + + + + The command that deploys the contract is: + + ```sh + forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast + ``` + + The command output gives us the deployer address, the address of the new contract, and the transaction hash: + + ``` + Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + Deployed to: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 + Transaction hash: 0xf155d360ec70ee10fe0e02d99c16fa5d6dc2a0e79b005fec6cbf7925ff547dbf + ``` + + The [`awk`](https://www.tutorialspoint.com/awk/index.htm) command looks for the line that has `Deployed to:` and writes the third word in that line, which is the address. + + ```sh + awk '/Deployed to:/ {print $3}' + ``` + + Finally, in UNIX (including Linux and macOS) when the command line includes backticks, the shell executes the code between the backticks and puts the output, in this case the contract address, in the command. + So we get. + + ```sh + GREETER_B_ADDRESS= + ``` + + + + + Run these commands to verify the contract works. + The first and third commands retrieve the current greeting, while the second command updates it. + + ```sh + cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii + cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "setGreeting(string)" Hello$$ + cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii + ``` + + + 4. Install the Optimism Solidity libraries into the project. + + ```sh + cd lib + npm install @eth-optimism/contracts-bedrock + cd .. + echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt + ``` + + 5. Create `src/GreetingSender.sol`. + + ```solidity + //SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; + import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/src/L2/IL2ToL2CrossDomainMessenger.sol"; + + import { Greeter } from "src/Greeter.sol"; + + contract GreetingSender { + IL2ToL2CrossDomainMessenger public immutable messenger = + IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + address immutable greeterAddress; + uint256 immutable greeterChainId; + + constructor(address _greeterAddress, uint256 _greeterChainId) { + greeterAddress = _greeterAddress; + greeterChainId = _greeterChainId; + } + + function setGreeting(string calldata greeting) public { + bytes memory message = abi.encodeCall( + Greeter.setGreeting, + (greeting) + ); + messenger.sendMessage(greeterChainId, greeterAddress, message); + } + } + ``` + + + + ```solidity + function setGreeting(string calldata greeting) public { + bytes memory message = abi.encodeCall( + Greeter.setGreeting, + (greeting) + ); + messenger.sendMessage(greeterChainId, greeterAddress, message); + } + ``` + + This function encodes a call to `setGreeting` and sends it to a contract on another chain. + `abi.encodeCall(Greeter.setGreeting, (greeting))` constructs the [calldata](https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html) by encoding the function selector and parameters. + The encoded message is then passed to `messenger.sendMessage`, which forwards it to the destination contract (`greeterAddress`) on the specified chain (`greeterChainId`). + + This ensures that `setGreeting` is executed remotely with the provided `greeting` value (as long as there is an executing message to relay it). + + + 7. Deploy `GreetingSender` to chain A. + + ```sh + CHAIN_ID_B=`cast chain-id --rpc-url $URL_CHAIN_B` + GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'` + ``` + + + + Send a greeting from chain A to chain B. + + ```sh + cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii + cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A" + sleep 4 + cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii + ``` + + The `sleep` call is because it can take up to two seconds until the transaction is included in chain A, and then up to two seconds until the relay transaction is included in chain B. + + + +## Sender information + +Run this command to view the events to see who called `setGreeting`. + +```sh +cast logs --rpc-url $URL_CHAIN_B 'SetGreeting(address,string)' +``` + +The sender information is stored in the second event topic. +However, for cross-chain messages, this value corresponds to the local `L2ToL2CrossDomainMessenger` contract address (`4200000000000000000000000000000000000023`), making it ineffective for identifying the original sender. + +In this section we change `Greeter.sol` to emit a separate event in it receives a cross domain message, with the sender's identity (address and chain ID). + + + + 1. Modify `src/Greeter.sol` to this code. + + ```solidity + //SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + + import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol"; + + interface IL2ToL2CrossDomainMessenger { + function crossDomainMessageContext() external view returns (address sender_, uint256 source_); + } + + contract Greeter { + + IL2ToL2CrossDomainMessenger public immutable messenger = + IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + string greeting; + + event SetGreeting( + address indexed sender, // msg.sender + string greeting + ); + + event CrossDomainSetGreeting( + address indexed sender, // Sender on the other side + uint256 indexed chainId, // ChainID of the other side + string greeting + ); + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + emit SetGreeting(msg.sender, _greeting); + + if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { + (address sender, uint256 chainId) = + messenger.crossDomainMessageContext(); + emit CrossDomainSetGreeting(sender, chainId, _greeting); + } + } + } + ``` + + + + ```solidity + interface IL2ToL2CrossDomainMessenger { + function crossDomainMessageContext() external view returns (address sender_, uint256 source_); + } + ``` + + This definition isn't part of the [npmjs package](https://www.npmjs.com/package/@eth-optimism/contracts-bedrock) at writing, so we just add it here. + + ```solidity + if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) { + (address sender, uint256 chainId) = + messenger.crossDomainMessageContext(); + emit CrossDomainSetGreeting(sender, chainId, _greeting); + } + ``` + + If we see that we got a message from `L2ToL2CrossDomainMessenger`, we call [`L2ToL2CrossDomainMessenger.crossDomainMessageContext`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L118-L126). + + + 2. Redeploy the contracts. + Because the address of `Greeter` is immutable in `GreetingSender`, we need to redeploy both contracts. + + ```sh + GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'` + GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'` + ``` + + + + 1. Set the greeting through `GreetingSender`. + + ```sh + cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii + cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A, with a CrossDomainSetGreeting event" + sleep 4 + cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii + ``` + + 2. Read the log entries. + + ```sh + cast logs --rpc-url $URL_CHAIN_B 'CrossDomainSetGreeting(address,uint256,string)' + echo $GREETER_A_ADDRESS + echo 0x385=`echo 0x385 | cast --to-dec` + echo 0x190a85c0=`echo 0x190a85c0 | cast --to-dec` + ``` + + See that the second topic (the first indexed log parameter) is the same as `$GREETER_A_ADDRESS`. + The third topic can be either `0x385=901`, which is the chain ID for supersim chain A, or `0x190a85c0=420120000`, which is the chain ID for devnet alpha 0. + + + + +## Next steps + +* Review the [OP Stack Interop Explainer](/> op-stack/interop/explainer) for answers to common questions about interoperability. +* Read the [Message Passing Explainer](/app-developers/guides/interoperability/message-passing) to understand what happens "under the hood". +* Write a revolutionary app that uses multiple blockchains within the OP Stack ecosystem. diff --git a/docs/public-docs/app-developers/tutorials/transactions/sdk-estimate-costs.mdx b/docs/public-docs/app-developers/tutorials/transactions/sdk-estimate-costs.mdx new file mode 100644 index 0000000000000..7f5b5cf225a79 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/transactions/sdk-estimate-costs.mdx @@ -0,0 +1,245 @@ +--- +title: Estimating transaction costs on OP Stack +description: Learn how to use viem to estimate the cost of a transaction on OP Stack. +--- + +In this tutorial, you'll learn how to use [viem](https://viem.sh/op-stack/) to estimate the cost of a transaction on OP Mainnet. +You'll learn how to estimate the [execution gas fee](/app-developers/transactions/fees#execution-gas-fee) and the [L1 data fee](/app-developers/transactions/fees#l1-data-fee) independently. +You'll also learn how to estimate the total cost of the transaction all at once. + + + Check out the full explainer on [OP Stack transaction fees](/app-developers/guides/transactions/fees) for more information on how OP Mainnet charges fees under the hood. + + +## Supported networks + +Viem supports any of the [OP Stack networks](/op-mainnet/network-information/connecting-to-op). +The OP Stack networks are included in Viem by default. +If you want to use a network that isn't included by default, you can add it to Viem's chain configurations. + +## Dependencies + +* [node](https://nodejs.org/en/) +* [pnpm](https://pnpm.io/installation) + +## Create a demo project + +You're going to use Viem for this tutorial. +Since Viem is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it. + + + + + ```bash + mkdir op-est-cost-tutorial + cd op-est-cost-tutorial + ``` + + + + + ```bash + pnpm init + ``` + + + + + ```bash + pnpm add viem + ``` + + + + + Want to create a new wallet for this tutorial? + If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key. + + +## Get ETH on Sepolia + +This tutorial explains how to bridge ETH from Sepolia to OP Sepolia. +You will need to get some ETH on Sepolia to follow along. + + + You can use [this faucet](https://sepoliafaucet.com) to get ETH on Sepolia. + + +## Get ETH on OP Sepolia + +This tutorial explains how to estimate transaction costs on OP Sepolia. +You will need to get some ETH on OP Sepolia in order to run the code in this tutorial. + + + You can use the [Superchain faucet](https://console.optimism.io/faucet?utm_source=op-docs&utm_medium=docs) to get ETH on OP Sepolia. + + +## Add a private key to your environment + +You need a private key in order to sign transactions. +Set your private key as an environment variable with the `export` command. +Make sure this private key corresponds to an address that has ETH on Sepolia. + +```bash +export TUTORIAL_PRIVATE_KEY=0x... +``` + +## Start the Node REPL + +You're going to use the Node REPL to interact with Viem. +To start the Node REPL run the following command in your terminal: + +```bash +node +``` + +This will bring up a Node REPL prompt that allows you to run JavaScript code. + +## Set session variables + +You'll need a few variables throughout this tutorial. +Let's set those up now. + + + + + +```js +const { createPublicClient, createWalletClient, http, parseEther, parseGwei, formatEther } = require('viem'); +const { privateKeyToAccount } = require('viem/accounts'); +const { optimismSepolia } = require('viem/chains'); +const { publicActionsL2, walletActionsL2 } = require('viem/op-stack'); +``` + + + + +```js +const privateKey = process.env.TUTORIAL_PRIVATE_KEY +const account = privateKeyToAccount(privateKey) +``` + + + + +```js +const publicClient = createPublicClient({ + chain: optimismSepolia, + transport: http("https://sepolia.optimism.io"), +}).extend(publicActionsL2()) +``` + + + + +```js +const walletClientL2 = createWalletClient({ + chain: optimismSepolia, + transport: http("https://sepolia.optimism.io"), +}).extend(walletActionsL2()) +``` + + + +## Estimate transaction costs + +You're now going to use the Viem to estimate the cost of a transaction on OP Mainnet. +Here you'll estimate the cost of a simple transaction that sends a small amount of ETH from your address to the address `0x1000000000000000000000000000000000000000`. + + + + + Viem makes it easy to create unsigned transactions so you can estimate the cost of a transaction before asking a user to sign it. + Here you'll create an unsigned transaction that sends a small amount of ETH from your address to the address `0x1000000000000000000000000000000000000000`. + +```js + const transaction = { + account, + to: '0x1000000000000000000000000000000000000000', + value: parseEther('0.00069420'), + gasPrice: await publicClient.getGasPrice() + } +``` + + + + + With Viem you can estimate the total cost of a transaction using the [estimateTotalFee](https://viem.sh/op-stack/actions/estimateTotalFee) method. + +```js + const totalEstimate = await publicClient.estimateTotalFee(transaction) + console.log(`Estimated Total Cost: ${formatEther(totalEstimate)} ETH`) +``` + + + + + Now that you've estimated the total cost of the transaction, go ahead and send it to the network. + This will make it possible to see the actual cost of the transaction to compare to your estimate. + +```js + const txHash = await walletClientL2.sendTransaction(transaction) + console.log(`Transaction Hash: ${txHash}`) + + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }) + console.log('receipt', receipt); +``` + + + + + Once you get back the transaction receipt, check the actual execution gas fee. + You can do so by accessing the `gasUsed` and `effectiveGasPrice` from the transaction receipt. + You can then multiply these values to get the actual L2 cost of the transaction + +```js + const l2CostActual = receipt.gasUsed * receipt.effectiveGasPrice + console.log(`Actual Execution Gas Fee: ${formatEther(l2CostActual)} ETH`) +``` + + + + + You can also check the actual L1 data fee. + +```js + const l1CostActual = receipt.l1Fee + console.log(`Actual L1 Data Fee: ${formatEther(l1CostActual)} ETH`) +``` + + + + + Sum these two together to get the actual total cost of the transaction. + +```js + const totalActual = l2CostActual + l1CostActual + console.log(`Actual Total Cost: ${formatEther(totalActual)} ETH`) +``` + + + + + Finally, check the difference between the estimated total cost and the actual total cost. + This will give you a sense of how accurate your estimate was. + Estimates will never be entirely accurate, but they should be close! + +```js + const difference = totalEstimate >= totalActual ? totalEstimate - totalActual : totalActual - totalEstimate + console.log(`Estimation Difference: ${formatEther(difference)} ETH`) +``` + + + + +Estimates will never be entirely accurate due to network conditions and gas price fluctuation, but they should be close to the actual costs. + + +## Next steps + +* Always estimate before sending: Estimating costs before sending a transaction helps prevent unexpected fees and failed transactions. +* Account for gas price volatility: Gas prices can change rapidly. Consider adding a buffer to your estimates or implementing a gas price oracle for more accurate pricing. +* Optimize transaction data: Minimize the amount of data in your transactions to reduce L1 data fees. +* Monitor network conditions: Keep an eye on network congestion and adjust your estimates accordingly. +* Use appropriate gas limits: Setting too low a gas limit can cause transactions to fail, while setting it too high can result in unnecessary costs. +* Implement retry mechanisms: If a transaction fails due to underestimated gas, implement a retry mechanism with adjusted gas parameters. diff --git a/docs/public-docs/app-developers/tutorials/transactions/sdk-trace-txns.mdx b/docs/public-docs/app-developers/tutorials/transactions/sdk-trace-txns.mdx new file mode 100644 index 0000000000000..308c64470d3c1 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/transactions/sdk-trace-txns.mdx @@ -0,0 +1,202 @@ +--- +title: Tracing deposits and withdrawals +description: Learn how to use the viem library to trace deposits and withdrawals between L1 + and L2. +--- +In this tutorial, you'll learn how to use the [viem](https://viem.sh) library to trace a [Standard Bridge](/app-developers/guides/bridging/standard-bridge) deposit or withdrawal between L1 and L2. +You'll specifically learn how to determine the status of a deposit or withdrawal and how to retrieve the transaction receipt for the executed transaction on L1 (for withdrawals) or L2 (for deposits). + +## Dependencies + +* [node](https://nodejs.org/en/) +* [pnpm](https://pnpm.io/installation) + +## Create a demo project + +You're going to use the viem library for this tutorial. +Since viem is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it. + + + + + ```bash + mkdir trace-trx + cd trace-trx + ``` + + + + + ```bash + pnpm init + ``` + + + + + ```bash + pnpm add viem + ``` + + + +## Add RPC URLs to your environment + +You'll be using the `getTransactionReceipt` function from the viem library during this tutorial. This function uses event queries to retrieve the receipt for a deposit or withdrawal. +Since this function uses large event queries, you'll need to use an RPC provider like [Alchemy](https://alchemy.com) that supports indexed event queries. +Grab an L1 and L2 RPC URL for Sepolia and OP Sepolia, respectively. + +```bash +export L1_RPC_URL="https://YOUR_L1_ SEPOLIA_RPC_URL_HERE" +export L2_RPC_URL="https://YOUR_L2_OP_SEPOLIA_RPC_URL_HERE" +``` + +## Start the Node REPL + +You're going to use the Node REPL to interact with viem. To start the Node REPL, run the following command in your terminal: + +```bash +node +``` + +This will bring up a Node REPL prompt that allows you to run JavaScript code. + +## Import dependencies + +You need to import some dependencies into your Node REPL session. + +### Import viem + +```js +const { createPublicClient, http } = require('viem'); +const { optimismSepolia, sepolia } = require('viem/chains'); +``` + +## Set session variables + +You'll need a few variables throughout this tutorial. Let's set those up now. + + + + + ```js + const l1RpcUrl = process.env.L1_RPC_URL; + const l2RpcUrl = process.env.L2_RPC_URL; + ``` + + + + + You'll be tracing a specific deposit in this tutorial. Deposit tracing is generally based on the transaction hash of the transaction that triggered the deposit. You can replace this transaction hash with your own if you'd like. + + ```js + const depositHash = '0x5896d6e4a47b465e0d925723bab838c62ef53468139a5e9ba501efd70f90cccb' + ``` + + + + + You'll also be tracing a specific withdrawal in this tutorial. Like with deposits, withdrawal tracing is generally based on the transaction hash of the transaction that triggered the withdrawal. You can replace this transaction hash with your own if you'd like. + + ```js + const withdrawalHash = '0x18b8b4022b8d9e380fd89417a2e897adadf31e4f41ca17442870bf89ad024f42' + ``` + + + + + ```js + const l1Client = createPublicClient({ + chain: sepolia, + transport: http(l1RpcUrl), + }); + + const l2Client = createPublicClient({ + chain: optimismSepolia, + transport: http(l2RpcUrl), + }); + ``` + + + +## Trace a Deposit + +You can use viem to trace a deposit. + + + + + You can query for the deposit status using the transaction hash of the deposit. + + ```js + console.log('Grabbing deposit status...') + const depositStatus = await l2Client.getTransactionReceipt({ hash: depositHash }); + console.log(depositStatus); + ``` + + + + + Retrieve the transaction receipt for the deposit using the viem client. + + ```js +console.log('Grabbing deposit receipt...') +const depositReceipt = await l2Client.getTransaction({ hash: depositHash }); +console.log(depositReceipt); +``` + + + + + You can directly query for the L2 transaction that executed the deposit. + + ```js +console.log('Grabbing deposit txn...') +const depositTransaction = await l2Client.getTransaction({ hash: depositHash }); +console.log(depositTransaction); +``` + + + +## Trace a withdrawal + +You can use viem's functions to trace a withdrawal. + + + + + Like deposits, withdrawals can have multiple statuses depending on where they are in the process. + + ```js +console.log('Grabbing withdrawal status...') +const withdrawalStatus = await l1Client.getTransactionReceipt({ hash: withdrawalHash }); +console.log(withdrawalStatus); +``` + + + + + Retrieve the L1 transaction receipt for the withdrawal. + + ```js +console.log('Grabbing withdrawal receipt...') +const withdrawalReceipt = await l1Client.getTransaction({ hash: withdrawalHash }); +console.log(withdrawalReceipt); +``` + + + + + Directly query for the L1 transaction that executed the withdrawal. + + ```js +console.log('Grabbing withdrawal txn...') +const withdrawalTransaction = await l1Client.getTransaction({ hash: withdrawalHash }); +console.log(withdrawalTransaction); +``` + + + +## Next steps + +* Check out the tutorial on [bridging ERC-20 tokens with the @eth-optimism/viem package](/app-developers/tutorials/bridging/cross-dom-bridge-erc20) to learn how to create deposits and withdrawals. diff --git a/docs/public-docs/app-developers/tutorials/transactions/send-tx-from-eth.mdx b/docs/public-docs/app-developers/tutorials/transactions/send-tx-from-eth.mdx new file mode 100644 index 0000000000000..b0471f29d30d4 --- /dev/null +++ b/docs/public-docs/app-developers/tutorials/transactions/send-tx-from-eth.mdx @@ -0,0 +1,235 @@ +--- +title: Triggering OP Stack transactions from Ethereum +description: Learn how to force transaction inclusion without the OP Stack Sequencer using + Viem. +--- +OP Stack currently uses a single-Sequencer block production model. +This means that there is only one Sequencer active on the network at any given time. Single-Sequencer models are simpler than their highly decentralized counterparts but they are also more vulnerable to potential downtime. + +Sequencer downtime must not be able to prevent users from transacting on the network. As a result, OP Stack includes a mechanism for "forcing" transactions to be included in the blockchain. This mechanism involves triggering a transaction on OP Stack by sending a transaction on Ethereum. +In this tutorial you'll learn how to trigger a transaction on OP Stack from Ethereum using Viem. You'll use the OP Sepolia testnet, but the same logic will apply to OP Stack. + +## Dependencies + +* [node](https://nodejs.org/en/) +* [pnpm](https://pnpm.io/installation) + +## Create a demo project + +You're going to use the `viem` package for this tutorial. Since Viem is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it. + + + + + ```bash + mkdir trigger-transaction + cd trigger-transaction + ``` + + + + + ```bash + pnpm init + ``` + + + + + ```bash + pnpm add viem + ``` + + + + + Want to create a new wallet for this tutorial? + If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key. + + +## Get ETH on Sepolia and OP Sepolia + +This tutorial explains how to bridge tokens from Sepolia to OP Sepolia. You will need to get some ETH on both of these testnets. + + + You can use [this faucet](https://sepoliafaucet.com) to get ETH on Sepolia. + You can use the [Superchain Faucet](https://console.optimism.io/faucet?utm_source=op-docs&utm_medium=docs) to get ETH on OP Sepolia. + + +## Add a private key to your environment + +You need a private key in order to sign transactions. +Set your private key as an environment variable with the `export` command. +Make sure this private key corresponds to an address that has ETH on both Sepolia and OP Sepolia. + +```bash +export TUTORIAL_PRIVATE_KEY=0x... +``` + +## Start the Node REPL + +You're going to use the Node REPL to interact with Viem. +To start the Node REPL run the following command in your terminal: + +```bash +node +``` + +This will bring up a Node REPL prompt that allows you to run JavaScript code. + +## Import dependencies + +You need to import some dependencies into your Node REPL session. + + + + + ```js + const { createPublicClient, createWalletClient, http, parseEther, formatEther } = require('viem'); + const { optimismSepolia, sepolia } = require('viem/chains'); + const { privateKeyToAccount } = require('viem/accounts'); + const { publicActionsL2, publicActionsL1, walletActionsL2, walletActionsL1, getL2TransactionHashes } = require ('viem/op-stack') +``` + + + +## Set session variables + +You'll need a few variables throughout this tutorial. Let's set those up now. + + + + + ```js + const privateKey = process.env.TUTORIAL_PRIVATE_KEY; + const account = privateKeyToAccount(privateKey); +``` + + + + + ```js + const l1PublicClient = createPublicClient({ chain: sepolia, transport: http("https://rpc.ankr.com/eth_sepolia") }).extend(publicActionsL1()) + const l2PublicClient = createPublicClient({ chain: optimismSepolia, transport: http("https://sepolia.optimism.io") }).extend(publicActionsL2()); + const l1WalletClient = createWalletClient({ chain: sepolia, transport: http("https://rpc.ankr.com/eth_sepolia") }).extend(walletActionsL1()); +``` + + + +## Check your initial balance + +You'll be sending a small amount of ETH as part of this tutorial. Quickly check your balance on OP Sepolia so that you know how much you had at the start of the tutorial. + +```js + const initialBalance = await l2PublicClient.getBalance({ address }); + console.log(`Initial balance: ${formatEther(initialBalance)} ETH`); +``` + +## Trigger the transaction + +Now you'll use the `OptimismPortal` contract to trigger a transaction on OP Sepolia by sending a transaction on Sepolia. + + + + + ```js + const optimismPortalAbi = [ + { + inputs: [ + { internalType: 'uint256', name: '_gasLimit', type: 'uint256' }, + { internalType: 'bytes', name: '_data', type: 'bytes' }, + ], + name: 'depositTransaction', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + ]; +``` + + + + + When sending transactions via the `OptimismPortal` contract it's important to always include a gas buffer. This is because the `OptimismPortal` charges a variable amount of gas depending on the current demand for L2 transactions triggered via L1. If you do not include a gas buffer, your transactions may fail. + + ```js + const optimismPortalAddress = '0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383'; + const gasLimit = 100000n; + const data = '0x'; + const value = parseEther('0.000069420'); + + const gasEstimate = await l1PublicClient.estimateContractGas({ + address: optimismPortalAddress, + abi: optimismPortalAbi, + functionName: 'depositTransaction', + args: [gasLimit, data], + value, + account: account.address, + }); +``` + + + + + Now you'll send the transaction. Note that you are including a buffer of 20% on top of the gas estimate. + + ```js + const { request } = await l1PublicClient.simulateContract({ + account, + address: optimismPortalAddress, + abi: optimismPortalAbi, + functionName: 'depositTransaction', + args: [gasLimit, data], + value, + gas: gasEstimate * 120n / 100n, // 20% buffer + }) + + const l1TxHash = await l1WalletClient.writeContract(request) + console.log(`L1 transaction hash: ${l1TxHash}`) +``` + + + + + First you'll need to wait for the L1 transaction to be mined. + + ```js + const l1TxHash = await l1WalletClient.writeContract(request) +``` + + + + + Now you'll need to wait for the corresponding L2 transaction to be included in a block. This transaction is automatically created as a result of your L1 transaction. Here you'll determine the hash of the L2 transaction and then wait for that transaction to be included in the L2 blockchain. + + ```js + const [l2Hash] = getL2TransactionHashes(l1TxHash) + console.log(`Corresponding L2 transaction hash: ${l2Hash}`); + + const l2Receipt = await l2PublicClient.waitForTransactionReceipt({ + hash: l2Hash, + }); + console.log('L2 transaction confirmed:', l2Receipt); +``` + + + +## Check your updated balance + +You should have a little less ETH on OP Sepolia now. Check your balance to confirm. + +```js + const finalBalance = await l2Wallet.getBalance() + console.log(`Final balance: ${formatEther(finalBalance)} ETH`); +``` + +Make sure that the difference is equal to the amount you were expecting to send. + +```js + const difference = initialBalance - finalBalance + console.log(`Difference in balance: ${formatEther(difference)} ETH`); +``` + +## Next steps + +You've successfully triggered a transaction on OP Sepolia by sending a transaction on Sepolia using Viem. Although this tutorial demonstrated the simple example of sending a basic ETH transfer from your L2 address via the OptimismPortal contract, you can use this same technique to trigger any transaction you want. You can trigger smart contracts, send ERC-20 tokens, and more. diff --git a/docs/public-docs/chain-operators/guides/configuration/batcher.mdx b/docs/public-docs/chain-operators/guides/configuration/batcher.mdx new file mode 100644 index 0000000000000..e96d0f09b4069 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/configuration/batcher.mdx @@ -0,0 +1,910 @@ +--- +title: Batcher configuration +description: Learn the OP Stack batcher configurations. +--- + +This page lists all configuration options for the op-batcher. The op-batcher posts +L2 sequencer data to the L1, to make it available for verifiers. The following +options are from the `--help` in [v1.10.0](https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.10.0). + +## Batcher policy + +The batcher policy defines high-level constraints and responsibilities regarding how L2 data is posted to L1. Below are the [standard guidelines](/chain-operators/reference/standard-configuration) for configuring the batcher within the OP Stack. + +| Parameter | Description | Administrator | Requirement | Notes | +| -------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Data Availability Type | Specifies whether the batcher uses **blobs**, **calldata**, or **auto** to post transaction data to L1. | Batch submitter address | Ethereum (Blobs or Calldata) | - Alternative data availability (Alt-DA) is not yet supported in the standard configuration.
- The sequencer can switch at will between blob transactions and calldata, with no restrictions, because both are fully secured by L1. | +| Batch Submission Frequency | Determines how frequently the batcher submits aggregated transaction data to L1 (via the batcher transaction). | Batch submitter address | Must target **1,800 L1 blocks** (6 hours on Ethereum, assuming 12s L1 block time) or lower | - Batches must be posted before the sequencing window closes (commonly 12 hours by default).
- Leave a buffer for L1 network congestion and data size to ensure that each batch is fully committed in a timely manner. | + +* **Data Availability Types**: + * **Calldata** is generally simpler but can be more expensive on mainnet Ethereum, depending on gas prices. + * **Blobs** are typically lower cost when your chain has enough transaction volume to fill large chunks of data. + * The `op-batcher` can toggle between these approaches by setting the `--data-availability-type=` flag or with the `OP_BATCHER_DATA_AVAILABILITY_TYPE` env variable. Setting this flag to `auto` will allow the batcher to automatically switch between `calldata` and `blobs` based on the current L1 gas price. + +* **Batch Submission Frequency** (`OP_BATCHER_MAX_CHANNEL_DURATION` and related flags): + * Standard OP Chains frequently target a maximum channel duration between 1–6 hours. + * Your chain should never exceed your L2's sequencing window (commonly 12 hours). + * If targeting a longer submission window (e.g., 5 or 6 hours), be aware that the [safe head](https://github.com/ethereum-optimism/specs/blob/main/specs/glossary.md#safe-l2-head) can stall up to that duration. + +Include these high-level "policy" requirements when you set up or modify your `op-batcher` configuration. See the [batcher configuration](#all-configuration-variables) reference, which explains each CLI flag and environment variable in depth. + +## Recommendations + +### Set your `OP_BATCHER_MAX_CHANNEL_DURATION` + + + The default value inside `op-batcher`, if not specified, is still `0`, which means channel duration tracking is disabled. + For very low throughput chains, this would mean to fill channels until close to the sequencing window and post the channel to `L1 SUB_SAFETY_MARGIN` L1 blocks before the sequencing window expires. + + +To minimize costs, we recommend setting your `OP_BATCHER_MAX_CHANNEL_DURATION` to target 5 hours, with a value of `1500` L1 blocks. When non-zero, this parameter is the max time (in L1 blocks, which are 12 seconds each) between which batches will be submitted to the L1. If you have this set to 5 for example, then your batcher will send a batch to the L1 every 5\*12=60 seconds. When using blobs, because 130kb blobs need to be purchased in full, if your chain doesn't generate at least \~130kb of data in those 60 seconds, then you'll be posting only partially full blobs and wasting storage. + +* We do not recommend setting any values higher than targeting 5 hours, as batches have to be submitted within the sequencing window which defaults to 12 hours for OP chains, otherwise your chain may experience a 12 hour long chain reorg. 5 hours is the longest length of time we recommend that still sits snugly within that 12 hour window to avoid affecting stability. +* If your chain fills up full blobs of data before the `OP_BATCHER_MAX_CHANNEL_DURATION` elapses, a batch will be submitted anyways - (e.g. even if the OP Mainnet batcher sets an `OP_BATCHER_MAX_CHANNEL_DURATION` of 5 hours, it will still be submitting batches every few minutes) + + + While setting an`OP_BATCHER_MAX_CHANNEL_DURATION` of `1500` results in the cheapest fees, it also means that your [safe head](https://github.com/ethereum-optimism/specs/blob/main/specs/glossary.md#safe-l2-head) can stall for up to 5 hours. + + * This will negatively impact apps on your chain that rely on the safe head for operation. While many apps can likely operate simply by following the unsafe head, often Centralized Exchanges or third party bridges wait until transactions are marked safe before processing deposits and withdrawal. + * Thus a larger gap between posting batches can result in significant delays in the operation of certain types of high-security applications. + + +### Configure your batcher to use multiple blobs + + + When there's blob congestion, running with high blob counts can backfire, because you will have a harder time getting blobs included and then fees will bump, which always means a doubling of the priority fees. + + +The `op-batcher` has the capabilities to send multiple blobs per single blob transaction. This is accomplished by the use of multi-frame channels, see the [specs](https://specs.optimism.io/protocol/derivation.html#frame-format?utm_source=op-docs&utm_medium=docs) for more technical details on channels and frames. + +A minimal batcher configuration (with env vars) to enable 6-blob batcher transactions is: + +``` + - OP_BATCHER_BATCH_TYPE=1 # span batches, optional + - OP_BATCHER_DATA_AVAILABILITY_TYPE=blobs + - OP_BATCHER_TARGET_NUM_FRAMES=6 # 6 blobs per tx + - OP_BATCHER_TXMGR_MIN_BASEFEE=2.0 # 2 gwei, might need to tweak, depending on gas market + - OP_BATCHER_TXMGR_MIN_TIP_CAP=2.0 # 2 gwei, might need to tweak, depending on gas market + - OP_BATCHER_RESUBMISSION_TIMEOUT=240s # wait 4 min before bumping fees +``` + +This enables blob transactions and sets the target number of frames to 6, which translates to 6 blobs per transaction. +The minimum tip cap and base fee are also lifted to 2 gwei because it is uncertain how easy it will be to get 6-blob transactions included and slightly higher priority fees should help. +The resubmission timeout is increased to a few minutes to give more time for inclusion before bumping the fees because current transaction pool implementations require a doubling of fees for blob transaction replacements. + +Multi-blob transactions are particularly useful for medium to high-throughput chains, where enough transaction volume exists to fill up 6 blobs in a reasonable amount of time. +You can use [this calculator](https://docs.google.com/spreadsheets/d/1V3CWpeUzXv5Iopw8lBSS8tWoSzyR4PDDwV9cu2kKOrs/edit?gid=186414307#gid=186414307) for your chain to determine what number of blobs are right for you, and what gas scalar configuration to use. Please also refer to guide on [Using Blobs](/chain-operators/guides/features/blobs) for chain operators. + +### Set your `--batch-type=1` to use span batches + +Span batches reduce the overhead of OP Stack chains, introduced in the Delta network upgrade. This is beneficial for sparse and low-throughput OP Stack chains. + +The overhead is reduced by representing a span of consecutive L2 blocks in a more efficient manner, while preserving the same consistency checks as regular batch data. + +## Batcher sequencer throttling + +This feature is a batcher-driven sequencer-throttling control loop. This is to avoid sudden spikes in L1 DA-usage consuming too much available gas and causing a backlog in batcher transactions. The batcher can throttle the sequencer's data throughput instantly when it sees too much batcher data built up. + +There are two throttling knobs: + +1. Transaction throttling, which skips individual transactions whose estimated compressed L1 DA usage goes over a certain threshold, and +2. Block throttling, which caps a block's estimated total L1 DA usage and leads to not including transactions during block building that would move the block's L1 DA usage past a certain threshold. + +**Feature requirements** + +* This new feature is enabled by default and requires running op-geth version `v1.101411.1` or later. It can be disabled by setting `--throttle-threshold` to 0. The sequencer's op-geth node has to be updated first, before updating the batcher, so that the new required RPC is available at the time of the batcher restart. +* It is required to upgrade to `op-conductor/v0.2.0` if you are using conductor's leader-aware rpc proxy feature. This conductor release includes support for the `miner_setMaxDASize` op-geth rpc proxy. + +**Configuration** + + + Note that this feature requires the batcher to correctly follow the sequencer at all times, or it would set throttling parameters on a non-sequencer EL client. That means, active sequencer follow mode has to be enabled correctly by listing all the possible sequencers in the L2 rollup and EL endpoint flags. + + +The batcher is configured for throttling by default with the parameters described below (and corresponding flags which allow the defaults to be overridden): + +* Backlog of pending block bytes beyond which the batcher will enable throttling on the sequencer via `--throttle.unsafe-da-bytes-lower/upper-threshold` (env var `OP_BATCHER_THROTTLE_UNSAFE_DA_BYTES_LOWER/UPPER_THRESHOLD`): 3\_200\_000 and 12\_800\_000 (batcher backlog of 3.2MB to 12.8MB of data to batch). Disable throttling by setting the lower threshold to `0`. The upper threshold sets the level where the maximum throttling intensity is reached. +* Individual tx size throttling via `--throttle.tx-size-lower/upper-limit` (env var `OP_BATCHER_THROTTLE_TX_SIZE_LOWER/UPPER-LIMIT`): 150 and 20\_000. This is the limit on the estimated compressed size of a transaction when throttling is at maximum/minimum intensity respectively. +* Block size throttling via `--throttle.block-size-lower/upper-limit` (env var `OP_BATCHER_THROTTLE_BLOCK_SIZE_LOWER/UPPER-LIMIT`): 2\_000 and 130\_000. This is the limit on the estimated compressed size of a block when throttling is at maximum/minimum intensity respectively. +* Throttler controller type via `--throttle.controller-type` (env var `OP_BATCHER_THROTTLE_CONTROLLER_TYPE`): `quadratic` by default. This determines how transaction and block size limits are interpolated when throttling is at intermediate intensity. + +For full details, see the [readme](https://github.com/ethereum-optimism/optimism/blob/develop/op-batcher/readme.md). + +If the batcher at startup has throttling enabled and the sequencer's `op-geth` instance to which it's talking doesn't have the `miner_setMaxDASize` RPC enabled, it will fail with an error message like: + +``` +lvl=warn msg="Served miner_setMaxDASize" reqid=1 duration=11.22µs err="the method miner_setMaxDASize does not exist/is not available" +In this case, make sure the miner API namespace is enabled for the correct transport protocol (HTTP or WS), see next paragraph. +``` + +The new RPC `miner_setMaxDASize` is available in `op-geth` since `v1.101411.1`. It has to be enabled by adding the miner namespace to the correct API flags, like + +``` +GETH_HTTP_API: web3,debug,eth,txpool,net,miner +GETH_WS_API: debug,eth,txpool,net,miner +``` + +It is recommended to add it to both, HTTP and WS. + +## Example configuration + +This is a basic example of a batcher configuration. Optimal batcher configuration is going to differ for each chain, +however you can see some of the most important variables configured below: + +``` + OP_BATCHER_WAIT_NODE_SYNC: true + OP_BATCHER_CHECK_RECENT_TXS_DEPTH: 5 + OP_BATCHER_POLL_INTERVAL: "5s" + OP_BATCHER_BATCH_TYPE: "1" # span + OP_BATCHER_COMPRESSION_ALGO: brotli-10 + OP_BATCHER_DATA_AVAILABILITY_TYPE: auto + OP_BATCHER_MAX_CHANNEL_DURATION: "150" # up to 30 min to fill blobs + OP_BATCHER_TARGET_NUM_FRAMES: "5" # 5 blobs, can go to 6 with Pectra activated on L1 + OP_BATCHER_SUB_SAFETY_MARGIN: "300" # 1h safety margin to prevent seq window elapse + OP_BATCHER_NUM_CONFIRMATIONS: "4" + OP_BATCHER_NETWORK_TIMEOUT: "10s" + OP_BATCHER_TXMGR_MIN_BASEFEE: "2.0" + OP_BATCHER_TXMGR_MIN_TIP_CAP: "2.0" + OP_BATCHER_TXMGR_FEE_LIMIT_MULTIPLIER: 16 # allow up to 4 doublings + OP_BATCHER_MAX_PENDING_TX: "10" + OP_BATCHER_RESUBMISSION_TIMEOUT: "180s" # wait 3 min before bumping fees + OP_BATCHER_ACTIVE_SEQUENCER_CHECK_DURATION: 5s +``` + +Lower throughput chains, which aren't filling up channels before the `MAX_CHANNEL_DURATION` is hit, +may save gas by increasing the `MAX_CHANNEL_DURATION`. See the [recommendations section](#set-your--op_batcher_max_channel_duration). + +## All configuration variables + +### Batch generation + +#### batch-type + +The batch type. 0 for `SingularBatch` and 1 for `SpanBatch`. The default value +is `0` for `SingularBatch`. +See the [span batch feature page](/op-stack/features/span-batches) to learn more. + + + `--batch-type=` + `--batch-type=singular` + `OP_BATCHER_BATCH_TYPE=` + + +#### max-blocks-per-span-batch + +Indicates how many blocks back the batcher should look during startup for a +recent batch tx on L1. This can speed up waiting for node sync. It should be +set to the verifier confirmation depth of the sequencer (e.g. 4). The default +value is `0`. + + + `--check-recent-txs-depth=` + `--check-recent-txs-depth=0` + `OP_BATCHER_CHECK_RECENT_TXS_DEPTH=0` + + +#### compression-algo + +The compression algorithm to use. Valid options: zlib, brotli, brotli-9, +brotli-10, brotli-11. The default value is `zlib`. + + + `--compression-algo=` + `--compression-algo=zlib` + `OP_BATCHER_COMPRESSION_ALGO=zlib` + + +#### compressor + +The type of compressor. Valid options: none, ratio, shadow. The default value +is `shadow`. + + + `--compressor=` + `--compressor=shadow` + `OP_BATCHER_COMPRESSOR=shadow` + + +#### approx-compr-ratio + +Only relevant for ratio compressor. The approximate compression ratio (`<=1.0`). The default value is `0.6`. + + + `--approx-compr-ratio=` + `--approx-compr-ratio=0.6` + `OP_BATCHER_APPROX_COMPR_RATIO=0.6` + + +#### poll-interval + +How frequently to poll L2 for new blocks. The default value is `6s`. + + + `--poll-interval=` + `--poll-interval=6s` + `OP_BATCHER_POLL_INTERVAL=6s` + + +### Batch submission + +#### data-availability-type + + + Setting this flag to `auto` will allow the batcher to automatically switch between `calldata` and `blobs` based on the current L1 gas price. + + +The data availability type to use for submitting batches to the L1. Valid +options: `calldata`, `blobs`, and `auto`. The default value is `calldata`. + + + `--data-availability-type=` + `--data-availability-type=calldata` + `OP_BATCHER_DATA_AVAILABILITY_TYPE=calldata` + + +#### target-num-frames + +The target number of frames to create per channel. Controls number of blobs per +blob tx, if using Blob DA. The default value is `1`. + + + `--target-num-frames=` + `--target-num-frames=1` + `OP_BATCHER_TARGET_NUM_FRAMES=1` + + +#### max-channel-duration + +The maximum duration of L1-blocks to keep a channel open. 0 to disable. The +default value is `0`. + + + `--max-channel-duration=` + `--max-channel-duration=0` + `OP_BATCHER_MAX_CHANNEL_DURATION=0` + + +#### sub-safety-margin + +The batcher tx submission safety margin (in #L1-blocks) to subtract from a +channel's timeout and sequencing window, to guarantee safe inclusion of a +channel on L1. The default value is `10`. + + + `--sub-safety-margin=` + `--sub-safety-margin=10` + `OP_BATCHER_SUB_SAFETY_MARGIN=10s` + + +#### max-l1-tx-size-bytes + +The maximum size of a batch tx submitted to L1. Ignored for blobs, where max +blob size will be used. The default value is `120000`. + + + `--max-l1-tx-size-bytes=` + `--max-l1-tx-size-bytes=120000` + `OP_BATCHER_MAX_L1_TX_SIZE_BYTES=120000` + + +### Batcher startup + +#### wait-node-sync + +Indicates if, during startup, the batcher should wait for a recent batcher tx +on L1 to finalize (via more block confirmations). This should help avoid +duplicate batcher txs. The default value is `false`. + + + `--wait-node-sync=` + `--wait-node-sync=false` + `OP_BATCHER_WAIT_NODE_SYNC=false` + + +#### check-recent-txs-depth + +Indicates how many blocks back the batcher should look during startup for a +recent batch tx on L1. This can speed up waiting for node sync. It should be +set to the verifier confirmation depth of the sequencer (e.g. 4). The default +value is `0`. + + + `--check-recent-txs-depth=` + `--check-recent-txs-depth=0` + `OP_BATCHER_CHECK_RECENT_TXS_DEPTH=0` + + +#### stopped + +Initialize the batcher in a stopped state. The batcher can be started using the +admin\_startBatcher RPC. The default value is `false`. + + + `--stopped=` + `--stopped=false` + `OP_BATCHER_STOPPED=false` + + +### Throttling + +The batcher supports throttling configuration to limit DA usage for transactions and blocks, and to control throttle intensity via several controller types. The following flags are available: + +#### throttle.additional-endpoints +Comma-separated list of endpoints to distribute throttling configuration to (in addition to the L2 endpoints specified with `--l2-eth-rpc`). + + + `--throttle.additional-endpoints=,,...` + `--throttle.additional-endpoints=http://builder1:8545,http://builder2:8545` + `THROTTLE_ADDITIONAL_ENDPOINTS=http://builder1:8545,http://builder2:8545` + + +#### throttle.tx-size-lower-limit +The DA size limit for individual transactions when the throttle intensity is at its maximum. `0` disables limits; use `1` as the smallest effective limit. + + + `--throttle.tx-size-lower-limit=` + `--throttle.tx-size-lower-limit=150` + `THROTTLE_TX_SIZE_LOWER_LIMIT=150` + + +#### throttle.tx-size-upper-limit +The DA size limit for individual transactions as throttle intensity approaches `0` (minimal throttling). Not applied when throttling is inactive. + + + `--throttle.tx-size-upper-limit=` + `--throttle.tx-size-upper-limit=20000` + `THROTTLE_TX_SIZE_UPPER_LIMIT=20000` + + +#### throttle.block-size-lower-limit +The DA size limit for blocks when throttle intensity is at maximum (applies for linear and quadratic controllers). `0` disables limits; use `1` as the smallest effective limit. + + + `--throttle.block-size-lower-limit=` + `--throttle.block-size-lower-limit=2000` + `THROTTLE_BLOCK_SIZE_LOWER_LIMIT=2000` + + +#### throttle.block-size-upper-limit +The DA size limit for blocks when throttling is inactive (throttle intensity = 0). + + + `--throttle.block-size-upper-limit=` + `--throttle.block-size-upper-limit=130000` + `THROTTLE_BLOCK_SIZE_UPPER_LIMIT=130000` + + +#### throttle.controller-type +Type of throttle controller to use. Valid values: `step`, `linear`, or `quadratic` (default). + + + `--throttle.controller-type=` + `--throttle.controller-type=quadratic` + `THROTTLE_CONTROLLER_TYPE=quadratic` + + +#### throttle.unsafe-da-bytes-lower-threshold +Threshold on `unsafe_da_bytes` beyond which the batcher will start to throttle the block builder. `0` disables throttling. + + + `--throttle.unsafe-da-bytes-lower-threshold=` + `--throttle.unsafe-da-bytes-lower-threshold=3200000` + `THROTTLE_UNSAFE_DA_BYTES_LOWER_THRESHOLD=3200000` + + +#### throttle.unsafe-da-bytes-upper-threshold +Threshold on `unsafe_da_bytes` at which throttling reaches maximum intensity (applies for linear and quadratic controllers). + + + `--throttle.unsafe-da-bytes-upper-threshold=` + `--throttle.unsafe-da-bytes-upper-threshold=12800000` + `THROTTLE_UNSAFE_DA_BYTES_UPPER_THRESHOLD=12800000` + + +### Transaction manager + +#### num-confirmations + +Number of confirmations which we will wait after sending a transaction. The +default value is `10`. + + + `--num-confirmations=` + `--num-confirmations=10` + `OP_BATCHER_NUM_CONFIRMATIONS=10` + + +#### max-pending-tx + +The maximum number of pending transactions. 0 for no limit. The default value +is `1`. + + + `--max-pending-tx=` + `--max-pending-tx=1` + `OP_BATCHER_MAX_PENDING_TX=1` + + +#### resubmission-timeout + +Duration we will wait before resubmitting a transaction to L1. The default +value is `48s`. + + + `--resubmission-timeout=` + `--resubmission-timeout=48s` + `OP_BATCHER_RESUBMISSION_TIMEOUT=48s` + + +#### safe-abort-nonce-too-low-count + +Number of ErrNonceTooLow observations required to give up on a tx at a +particular nonce without receiving confirmation. The default value is `3`. + + + `--safe-abort-nonce-too-low-count=` + `--safe-abort-nonce-too-low-count=3` + `OP_BATCHER_SAFE_ABORT_NONCE_TOO_LOW_COUNT=3` + + +#### txmgr.min-basefee + +Enforces a minimum base fee (in GWei) to assume when determining tx fees. 1 +GWei by default. The default value is `1`. + + + `--txmgr.min-basefee=` + `--txmgr.min-basefee=1` + `OP_BATCHER_TXMGR_MIN_BASEFEE=1` + + +#### txmgr.min-tip-cap + +Enforces a minimum tip cap (in GWei) to use when determining tx fees. 1 GWei by +default. The default value is `1`. + + + `--txmgr.min-tip-cap=` + `--txmgr.min-tip-cap=1` + `OP_BATCHER_TXMGR_MIN_TIP_CAP=1` + + +#### fee-limit-multiplier + +The multiplier applied to fee suggestions to put a hard limit on fee increases. +The default value is `5`. + + + `--fee-limit-multiplier=` + `--fee-limit-multiplier=5` + `OP_BATCHER_TXMGR_FEE_LIMIT_MULTIPLIER=5` + + +#### txmgr.fee-limit-threshold + +The minimum threshold (in GWei) at which fee bumping starts to be capped. +Allows arbitrary fee bumps below this threshold. The default value is `100`. + + + `--txmgr.fee-limit-threshold=` + `--txmgr.fee-limit-threshold=100` + `OP_BATCHER_TXMGR_FEE_LIMIT_THRESHOLD=100` + + +#### txmgr.receipt-query-interval + +Frequency to poll for receipts. The default value is `12s`. + + + `--txmgr.receipt-query-interval=` + `--txmgr.receipt-query-interval=12s` + `OP_BATCHER_TXMGR_RECEIPT_QUERY_INTERVAL=12s` + + +#### txmgr.not-in-mempool-timeout + +Timeout for aborting a tx send if the tx does not make it to the mempool. The +default value is `2m0s`. + + + `--txmgr.not-in-mempool-timeout=` + `--txmgr.not-in-mempool-timeout=2m0s` + `OP_BATCHER_TXMGR_TX_NOT_IN_MEMPOOL_TIMEOUT=2m0s` + + +#### txmgr.send-timeout + +Timeout for sending transactions. If 0 it is disabled. The default value is +`0s`. + + + `--txmgr.send-timeout=` + `--txmgr.send-timeout=0s` + `OP_BATCHER_TXMGR_TX_SEND_TIMEOUT=0s` + + +### Authentication and wallet + +#### private-key + +The private key to use with the service. Must not be used with mnemonic. + + + `--private-key=` + `--private-key=` + `OP_BATCHER_PRIVATE_KEY=` + + +#### mnemonic + +The mnemonic used to derive the wallets for either the service. + + + `--mnemonic=` + `--mnemonic=` + `OP_BATCHER_MNEMONIC=` + + +#### hd-path + +The HD path used to derive the sequencer wallet from the mnemonic. The mnemonic +flag must also be set. + + + `--hd-path=` + `--hd-path=` + `OP_BATCHER_HD_PATH=` + + + +#### signer.address + +Address the signer is signing transactions for. + + + `--signer.address=` + `--signer.address=` + `OP_BATCHER_SIGNER_ADDRESS=` + + +#### signer.endpoint + +Signer endpoint the client will connect to. + + + `--signer.endpoint=` + `--signer.endpoint=` + `OP_BATCHER_SIGNER_ENDPOINT=` + + +#### signer.header + +Headers to pass to the remote signer. Format `key=value`.\ +Value can contain any character allowed in an HTTP header.\ +When using env vars, split multiple headers with commas.\ +When using flags, provide one key-value pair per flag. + + + `--signer.header=` + `--signer.header="Authorization=Bearer 123abc"` + `OP_BATCHER_SIGNER_HEADER=Authorization=Bearer 123abc` + + +#### signer.tls.ca + +tls ca cert path. The default value is `tls/ca.crt`. + + + `--signer.tls.ca=` + `--signer.tls.ca=tls/ca.crt` + `OP_BATCHER_SIGNER_TLS_CA=tls/ca.crt` + + +#### signer.tls.cert + +tls cert path. The default value is `tls/tls.crt`. + + + `--signer.tls.cert=` + `--signer.tls.cert=tls/tls.crt` + `OP_BATCHER_SIGNER_TLS_CERT=` + + +#### signer.tls.key + +tls key. The default value is `tls/tls.key`. + + + `--signer.tls.key=` + `--signer.tls.key=tls/tls.key` + `OP_BATCHER_SIGNER_TLS_KEY=` + + +### Network connections + +#### l1-eth-rpc + +HTTP provider URL for L1. + + + `--l1-eth-rpc=` + `--l1-eth-rpc` + `OP_BATCHER_L1_ETH_RPC=` + + +#### l2-eth-rpc + +HTTP provider URL for L2 execution engine. A comma-separated list enables the +active L2 endpoint provider. Such a list needs to match the number of +rollup-rpcs provided. + + + `--l2-eth-rpc=` + `--l2-eth-rpc=` + `OP_BATCHER_L2_ETH_RPC=` + + +#### rollup-rpc + +HTTP provider URL for Rollup node. A comma-separated list enables the active L2 +endpoint provider. Such a list needs to match the number of l2-eth-rpcs +provided. + + + `--rollup-rpc=` + `--rollup-rpc=` + `OP_BATCHER_ROLLUP_RPC=` + + +#### rpc.addr + +rpc listening address. The default value is `0.0.0.0`. + + + `--rpc.addr=` + `--rpc.addr=0.0.0.0` + `OP_BATCHER_RPC_ADDR=0.0.0.0` + + +#### rpc.enable-admin + +Enable the admin API. The default value is `false`. + + + `--rpc.enable-admin=` + `--rpc.enable-admin=false` + `OP_BATCHER_RPC_ENABLE_ADMIN=false` + + +#### rpc.port + +rpc listening port. The default value is `8545`. + + + `--rpc.port=` + `--rpc.port=8545` + `OP_BATCHER_RPC_PORT=8545` + + +#### network-timeout + +Timeout for all network operations. The default value is `10s`. + + + `--network-timeout=` + `--network-timeout=10s` + `OP_BATCHER_NETWORK_TIMEOUT=10s` + + +### Alt-DA mode (Beta feature) + +#### altda.enabled + +Enable Alt-DA mode, Alt-DA mode is a Beta feature of the OP Stack. +While it has received initial review from core contributors, it is still +undergoing testing, and may have bugs or other issues. +The default value is `false`. + + + `--altda.enabled=` + `--altda.enabled=false` + `OP_BATCHER_ALTDA_ENABLED=false` + + +#### altda.da-server + +HTTP address of a DA Server. + + + `--altda.da-server=` + `--altda.da-server=http://da.example.com:1234` + `OP_BATCHER_ALTDA_DA_SERVER=http://da.example.com:1234` + + +#### altda.da-service + +Use DA service type where commitments are generated by Alt-DA server. The default +value is `false`. + + + `--altda.da-service=` + `--altda.da-service=true` + `OP_BATCHER_ALTDA_DA_SERVER=true` + + +#### altda.get-timeout + +Timeout for get requests. **0 means no timeout**. + + + `--altda.get-timeout=` + `--altda.get-timeout=5s` + `OP_BATCHER_ALTDA_GET_TIMEOUT=5s` + + +#### altda.put-timeout + +Timeout for put requests. **0 means no timeout**. + + + `--altda.put-timeout=` + `--altda.put-timeout=10s` + `OP_BATCHER_ALTDA_PUT_TIMEOUT=10s` + + +#### altda.max-concurrent-da-requests + +Maximum number of concurrent requests to the DA server. + + + `--altda.max-concurrent-da-requests=` + `--altda.max-concurrent-da-requests=4` + `OP_BATCHER_ALTDA_MAX_CONCURRENT_DA_REQUESTS=4` + + +#### altda.verify-on-read + +Verify input data matches the commitments from the DA storage service. + + + `--altda.verify-on-read=` + `--altda.verify-on-read=true` + `OP_BATCHER_ALTDA_VERIFY_ON_READ=true` + + +### Logging and monitoring + +#### log.level + +The lowest log level that will be output. The default value is `INFO`. + + + `--log.level=` + `--log.level=INFO` + `OP_BATCHER_LOG_LEVEL=INFO` + + +#### log.format + +Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', +'json-pretty'. The default value is `text`. + + + `--log.format=` + `--log.format=text` + `OP_BATCHER_LOG_FORMAT=text` + + +#### log.color + +Color the log output if in terminal mode. The default value is `false`. + + + `--log.color=` + `--log.color=false` + `OP_BATCHER_LOG_COLOR=false` + + +#### log.pid + +Show PID in the log. + + + `--log.pid=` + `--log.pid=true` + `OP_BATCHER_LOG_PID=true` + + +#### metrics.enabled + +Enable the metrics server. The default value is `false`. + + + `--version=` + `--version=false` + `OP_BATCHER_METRICS_ENABLED=false` + + +### Miscellaneous + +#### active-sequencer-check-duration + +The duration between checks to determine the active sequencer endpoint. The +default value is `2m0s`. + + + `--active-sequencer-check-duration=` + `--active-sequencer-check-duration=2m0s` + `OP_BATCHER_ACTIVE_SEQUENCER_CHECK_DURATION=2m0s` + + +#### pprof.addr + +pprof listening address. The default value is `0.0.0.0`. + + + `--pprof.addr=` + `--pprof.addr=0.0.0.0` + `OP_BATCHER_PPROF_ADDR=0.0.0.0` + + +#### pprof.enabled + +Enable the pprof server. The default value is `false`. + + + `--pprof.enabled=` + `--pprof.enabled=false` + `OP_BATCHER_PPROF_ENABLED=false` + + +#### pprof.path + +pprof file path. If it is a directory, the path is `{dir}/{profileType}.prof`. + + + `--pprof.path=` + `--pprof.path=` + `OP_BATCHER_PPROF_PATH=` + + +#### pprof.port + +pprof listening port. The default value is `6060`. + + + `--pprof.port=` + `--pprof.port=6060` + `OP_BATCHER_PPROF_PORT=6060` + + +#### pprof.type + +pprof profile type. One of cpu, heap, goroutine, threadcreate, block, mutex, +allocs. + + + `--pprof.type=` + `--pprof.type` + `OP_BATCHER_PPROF_TYPE=` + + +#### help + +Show help. The default value is false. + + + `--help=` + `--help=false` + `OP_BATCHER_HELP=false` + + +#### version + +Print the version. The default value is false. + + + `--version=` + `--version=false` + `OP_BATCHER_VERSION=false` + diff --git a/docs/public-docs/chain-operators/guides/configuration/getting-started.mdx b/docs/public-docs/chain-operators/guides/configuration/getting-started.mdx new file mode 100644 index 0000000000000..8a8205186a417 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/configuration/getting-started.mdx @@ -0,0 +1,54 @@ +--- +title: Chain Operator Configurations +description: Learn how to configure an OP Stack chain. +--- + +OP Stack chains can be configured for the Chain Operator's needs. +Each component of the stack has its own considerations. +See the following for documentation for details on configuring each piece. + + + + +Deploying your OP Stack contracts requires creating a deployment configuration + JSON file. This defines the behavior of your network at its genesis. + * **Important Notes:** + * The Rollup Configuration sets parameters for the L1 smart contracts upon deployment. These parameters govern the behavior of your chain and are critical to its operation. + * Be aware that many of these values cannot be changed after deployment or require a complex process to update. + Carefully consider and validate all settings during configuration to avoid issues later. + + * [Rollup Configuration Documentation](/chain-operators/guides/configuration/rollup) + + + + + +The batcher is the service that submits the L2 Sequencer data to L1, to make + it available for verifiers. These configurations determine the batcher's + behavior. + + * [Batcher Configuration Documentation](/chain-operators/guides/configuration/batcher) + + + + + +The proposer is the service that submits the output roots to the L1. These + configurations determine the proposer's behavior. + + * [Proposer Configuration Documentation](/chain-operators/guides/configuration/proposer) + + + + + +The rollup node has a wide array of configurations for both the consensus and + execution clients. + + * [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients) + * [Execution Client Configuration](/node-operators/guides/configuration/execution-clients) + + + + + diff --git a/docs/public-docs/chain-operators/guides/configuration/op-challenger-config-guide.mdx b/docs/public-docs/chain-operators/guides/configuration/op-challenger-config-guide.mdx new file mode 100644 index 0000000000000..e8e3182e4c881 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/configuration/op-challenger-config-guide.mdx @@ -0,0 +1,605 @@ +--- +title: How to configure challenger for your chain +description: Learn how to configure challenger for your OP Stack chain. +--- + + +This guide provides step-by-step instructions for setting up the configuration and monitoring options for `op-challenger`. The challenger is a critical fault proofs component that monitors dispute games and challenges invalid claims to protect your OP Stack chain. + +See the [OP-Challenger Explainer](/op-stack/fault-proofs/challenger) for a general overview of this fault proofs feature. + +The challenger is responsible for: + +* Monitoring dispute games created by the fault proof system +* Challenging invalid claims in dispute games +* Defending valid state transitions +* Resolving games when possible + +## Prerequisites + +### Essential requirements + +Before configuring your challenger, complete the following steps: + + + + * L1 contracts deployed with dispute game factory + * Fault proof system active on your chain + * Access to your chain's contract addresses + * [Generate an absolute prestate](/operators/chain-operators/tutorials/absolute-prestate#generating-the-absolute-prestate) for your network version - This is critical as the challenger will refuse to interact with games if it doesn't have the matching prestate + + + + * L1 RPC endpoint (Ethereum, Sepolia, etc.) + * L1 Beacon node endpoint (for blob access) + * L2 archive node with debug API enabled + * Rollup node (op-node) with historical data + + + + * `rollup.json` - Rollup configuration file + * `genesis-l2.json` - L2 genesis file + * `prestate.json` - The absolute prestate file generated in step 1 + + + +### Software requirements + +* Git (for cloning repositories) +* Go 1.21+ (if building from source) +* Docker and Docker Compose (optional but recommended) +* Access to a funded Ethereum account for challenger operations + +### Finding the current stable releases + +To ensure you're using the latest compatible versions of OP Stack components, always check the official releases page: + +[OP Stack releases page](https://github.com/ethereum-optimism/optimism/releases) + +Look for the latest `op-challenger/v*` release. The challenger version used in this guide (op-challenger/v1.5.0) is a verified stable version. + +Always check the release notes to ensure you're using compatible versions with your chain's deployment. + +## Software installation + +For challenger deployment, you can either build from source (recommended for better control and debugging) or use Docker for a containerized setup. + + + +### Build and Configure + +Building from source gives you full control over the binaries and is the preferred approach for production deployments. + +**Clone and build op-challenger** + +```bash +# Clone the optimism monorepo +git clone https://github.com/ethereum-optimism/optimism.git +cd optimism + +# Check out the latest release of op-challenger +git checkout op-challenger/v1.5.0 + +# Install dependencies and build +just op-challenger + +# Binary will be available at ./op-challenger/bin/op-challenger +``` + +### Verify installation + +Check that you have properly installed the challenger component: + +```bash +# Make sure you're in the optimism directory +./op-challenger/bin/op-challenger --help + +# You should see the challenger help output with available commands and flags +``` + +## Configuration setup + + + + After building the binaries, create your challenger working directory: + + ```bash + # Create challenger directory (this should be at the same level as optimism directory) + mkdir challenger-node + cd challenger-node + + # Create necessary subdirectories + mkdir scripts + mkdir challenger-data + + # Verify the optimism directory is accessible + # Directory structure should look like: + # /optimism/ (contains the built binaries) + # /challenger-node/ (your working directory) + ``` + + + + ```bash + # Copy configuration files to your challenger directory + # Adjust paths based on your deployment setup + cp /path/to/your/rollup.json . + cp /path/to/your/genesis-l2.json . + ``` + + + + You'll need to gather several pieces of information before creating your configuration. Here's where to get each value: + + **L1 network access:** + + * L1 RPC URL: Your L1 node endpoint (Infura, Alchemy, or self-hosted) + * L1 Beacon URL: Beacon chain API endpoint for blob access + + **L2 network access:** + + * L2 RPC URL: Your op-geth archive node endpoint + * Rollup RPC URL: Your op-node endpoint with historical data + + **Challenger wallet:** + + * Private key for challenger operations (must be funded) + + **Network configuration:** + + * Game factory address from your contract deployment + * Network identifier (e.g., op-sepolia, op-mainnet, or custom) + + Copy and paste in your terminal, to create your env file. + + ```bash + # Create .env file with your actual values + cat > .env << 'EOF' + # L1 Configuration - Replace with your actual RPC URLs + L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + + # L2 Configuration - Replace with your actual node endpoints + L2_RPC_URL=http://localhost:8545 + ROLLUP_RPC_URL=http://localhost:8547 + L1_BEACON=http://sepolia-cl-1:5051 + + # Wallet configuration - Choose either mnemonic + HD path OR private key + MNEMONIC="test test test test test test test test test test test junk" + HD_PATH="m/44'/60'/0'/0/0" + # PRIVATE_KEY=0xYOUR_ACTUAL_PRIVATE_KEY # Alternative to mnemonic + + # Network configuration + NETWORK=op-sepolia + GAME_FACTORY_ADDRESS=0xYOUR_GAME_FACTORY_ADDRESS + + # Trace configuration + TRACE_TYPE=permissioned,cannon + + # Data directory + DATADIR=./challenger-data + + # Cannon configuration + # Path to the cannon binary (built from optimism repo) + CANNON_BIN=/cannon/bin/cannon + + # Configuration files + CANNON_ROLLUP_CONFIG= + CANNON_L2_GENESIS= + CANNON_SERVER=/op-program/bin/op-program + CANNON_PRESTATE= + EOF + ``` + + **Important:** Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values. + + + + + + * This is the HTTP provider URL for a standard L1 node, can be a full node. `op-challenger` will be sending many requests, so chain operators need a node that is trusted and can easily handle many transactions. + * Note: Challenger has a lot of money, and it will spend it if it needs to interact with games. That might risk not defending games or challenging games correctly, so chain operators should really trust the nodes being pointed at Challenger. + + + + + * This is needed just to get blobs from. + * In some instances, chain operators might need a blob archiver or L1 consensus node configured not to prune blobs: + * If the chain is proposing regularly, a blob archiver isn't needed. There's only a small window in the blob retention period that games can be played. + * If the chain doesn't post a valid output root in 18 days, then a blob archiver running a challenge game is needed. If the actor gets pushed to the bottom of the game, it could lose if it's the only one protecting the chain. + + + + + * This needs to be `op-geth` archive node, with `debug` enabled. + * Technically doesn't need to go to bedrock, but needs to have access to the start of any game that is still in progress. + + + + + * This needs to be an `op-node` archive node because challenger needs access to output roots from back when the games start. See below for important configuration details: + + 1. Safe Head Database (SafeDB) Configuration for op-node: + + * The `op-node` behind the `op-conductor` must have the SafeDB enabled to ensure it is not stateless. + + * To enable SafeDB, set the `--safedb.path` value in your configuration. This specifies the file path used to persist safe head update data. + + * Example Configuration: + + ``` + --safedb.path # Replace with your actual path + ``` + + + If this path is not set, the SafeDB feature will be disabled. + + + 2. Ensuring Historical Data Availability: + + * Both `op-node` and `op-geth` must have data from the start of the games to maintain network consistency and allow nodes to reference historical state and transactions. + * For `op-node`: Configure it to maintain a sufficient history of blockchain data locally or use an archive node. + * For `op-geth`: Similarly, configure to store or access historical data. + * Example Configuration: + + ``` + op-node \ + --rollup-rpc \ + --safedb.path + ``` + + + Replace `` with the URL of your archive node and `` with the desired path for storing SafeDB data. + + + + + + * Chain operators must specify a private key or use something else (like `op-signer`). + * This uses the same transaction manager arguments as `op-node` , batcher, and proposer, so chain operators can choose one of the following options: + * a mnemonic + * a private key + * `op-signer` endpoints + + + + + * This identifies the L2 network `op-challenger` is running for, e.g., `op-sepolia` or `op-mainnet`. + * When using the `--network` flag, the `--game-factory-address` will be automatically pulled from the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json). + * When cannon is executed, challenger needs the roll-up config and the L2 Genesis, which is op-geth's Genesis file. Both files are automatically loaded when Cannon Network is used, but custom networks will need to specify both Cannon L2 Genesis and Cannon rollup config. + * For custom networks not in the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json), the `--game-factory-address` and rollup must be specified, as follows: + + ``` + --cannon-rollup-config rollup.json \ + --cannon-l2-genesis genesis-l2.json \ + # use this if running challenger outside of the docker image + --cannon-server ./op-program/bin/op-program \ + # json or url, version of op-program deployed on chain + # if you use the wrong one, you will lose the game + # if you deploy your own contracts, you specify the hash, the root of the json file + # op mainnet are tagged versions of op-program + # make reproducible prestate + # challenger verifies that onchain + --cannon-prestate ./op-program/bin/prestate.json \ + # load the game factory address from system config or superchain registry + # point the game factory address at the dispute game factory proxy + --game-factory-address + ``` + + + These options vary based on which `--network` is specified. Chain operators always need to specify a way to load prestates and must also specify the cannon-server whenever the docker image isn't being used. + + + + + + * This is a directory that `op-challenger` can write to and store whatever data it needs. It will manage this directory to add or remove data as needed under that directory. + * If running in docker, it should point to a docker volume or mount point, so the data isn't lost on every restart. The data can be recreated if needed but particularly if challenger has executed cannon as part of responding to a game it may mean a lot of extra processing. + + + + + The pre-state is effectively the version of `op-program` that is deployed on chain. And chain operators must use the right version. `op-challenger` will refuse to interact with games that have a different absolute prestate hash to avoid making invalid claims. If deploying your own contracts, chain operators must specify an absolute prestate hash taken from the `make reproducible-prestate` command during contract deployment, which will also build the required prestate json file. + + All governance approved releases use a tagged version of `op-program`. These can be rebuilt by checking out the version tag and running `make reproducible-prestate`. + + * There are two ways to specify the prestate to use: + * `--cannon-prestate`: specifies a path to a single Cannon pre-state Json file + * `--cannon-prestates-url`: specifies a URL to load pre-states from. This enables participating in games that use different prestates, for example due to a network upgrade. The prestates are stored in this directory named by their hash. + * Example final URL for a prestate: + * [https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json](https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json) + * This file contains the cannon memory state. + + + Challenger will refuse to interact with any games if it doesn't have the matching prestate. + Check this [guide](/operators/chain-operators/tutorials/absolute-prestate#generating-the-absolute-prestate) on how to generate a absolute prestate. + + + + + +### Create challenger startup script + +Create `scripts/start-challenger.sh`: + +```bash +#!/bin/bash +source .env + +# Path to the challenger binary +../optimism/op-challenger/bin/op-challenger \ + --trace-type permissioned,cannon \ + --l1-eth-rpc=$L1_RPC_URL \ + --l2-eth-rpc=$L2_RPC_URL \ + --l1-beacon=$L1_BEACON \ + --rollup-rpc=$ROLLUP_RPC_URL \ + --game-factory-address $GAME_FACTORY_ADDRESS \ + --datadir=$DATADIR \ + --cannon-bin=$CANNON_BIN \ + --cannon-rollup-config=$CANNON_ROLLUP_CONFIG \ + --cannon-l2-genesis=$CANNON_L2_GENESIS \ + --cannon-server=$CANNON_SERVER \ + --cannon-prestate=$CANNON_PRESTATE \ + --mnemonic "$MNEMONIC" \ + --hd-path "$HD_PATH" +``` + +## Initializing and starting the challenger + +### Start the challenger + +```bash +# Make sure you're in the challenger-node directory +cd challenger-node + +# Make script executable +chmod +x scripts/start-challenger.sh + +# Start challenger +./scripts/start-challenger.sh +``` + +### Verify challenger is running + +Monitor challenger logs to ensure it's operating correctly: + +```bash +# Check challenger logs +tail -f challenger-data/challenger.log + +# Or if running in foreground, monitor the output +``` + +The challenger should show logs indicating: + +* Successful connection to L1 and L2 nodes +* Loading of prestates and configuration +* Monitoring of dispute games + + + +### Docker Setup + +The Docker setup provides a containerized environment for running the challenger. This method uses the official Docker image that includes embedded `op-program` server and Cannon executable. + + + + First, create a `.env` file with your configuration values. This file will be used by Docker Compose to set up the environment variables: + + ```bash + # Create .env file with your actual values + cat > .env << 'EOF' + # L1 Configuration - Replace with your actual RPC URLs + L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + L1_BEACON=http://sepolia-cl-1:5051 + + # L2 Configuration - Replace with your actual node endpoints + L2_RPC_URL=http://localhost:8545 + ROLLUP_RPC_URL=http://localhost:8547 + + # Wallet configuration - Choose either mnemonic + HD path OR private key + MNEMONIC="test test test test test test test test test test test junk" + HD_PATH="m/44'/60'/0'/0/0" + + # Network configuration + NETWORK=op-sepolia + GAME_FACTORY_ADDRESS=0xYOUR_GAME_FACTORY_ADDRESS + EOF + ``` + **Important:** Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values. + + + + Each environment variable maps to a specific challenger configuration flag. Here's what each one does: + + + + * This is the HTTP provider URL for a standard L1 node, can be a full node. `op-challenger` will be sending many requests, so chain operators need a node that is trusted and can easily handle many transactions. + * Note: Challenger has a lot of money, and it will spend it if it needs to interact with games. That might risk not defending games or challenging games correctly, so chain operators should really trust the nodes being pointed at Challenger. + + + + + * This is needed just to get blobs from. + * In some instances, chain operators might need a blob archiver or L1 consensus node configured not to prune blobs: + * If the chain is proposing regularly, a blob archiver isn't needed. There's only a small window in the blob retention period that games can be played. + * If the chain doesn't post a valid output root in 18 days, then a blob archiver running a challenge game is needed. If the actor gets pushed to the bottom of the game, it could lose if it's the only one protecting the chain. + + + + + * This needs to be `op-geth` archive node, with `debug` enabled. + * Technically doesn't need to go to bedrock, but needs to have access to the start of any game that is still in progress. + + + + + * This needs to be an `op-node` archive node because challenger needs access to output roots from back when the games start. See below for important configuration details: + + 1. Safe Head Database (SafeDB) Configuration for op-node: + + * The `op-node` behind the `op-conductor` must have the SafeDB enabled to ensure it is not stateless. + + * To enable SafeDB, set the `--safedb.path` value in your configuration. This specifies the file path used to persist safe head update data. + + * Example Configuration: + + ``` + --safedb.path # Replace with your actual path + ``` + + + If this path is not set, the SafeDB feature will be disabled. + + + 2. Ensuring Historical Data Availability: + + * Both `op-node` and `op-geth` must have data from the start of the games to maintain network consistency and allow nodes to reference historical state and transactions. + * For `op-node`: Configure it to maintain a sufficient history of blockchain data locally or use an archive node. + * For `op-geth`: Similarly, configure to store or access historical data. + * Example Configuration: + + ``` + op-node \ + --rollup-rpc \ + --safedb.path + ``` + + + Replace `` with the URL of your archive node and `` with the desired path for storing SafeDB data. + + + + + + * Chain operators must specify a private key or use something else (like `op-signer`). + * This uses the same transaction manager arguments as `op-node` , batcher, and proposer, so chain operators can choose one of the following options: + * a mnemonic + * a private key + * `op-signer` endpoints + + + + + * This identifies the L2 network `op-challenger` is running for, e.g., `op-sepolia` or `op-mainnet`. + * When using the `--network` flag, the `--game-factory-address` will be automatically pulled from the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json). + * When cannon is executed, challenger needs the roll-up config and the L2 Genesis, which is op-geth's Genesis file. Both files are automatically loaded when Cannon Network is used, but custom networks will need to specify both Cannon L2 Genesis and Cannon rollup config. + * For custom networks not in the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json), the `--game-factory-address` and rollup must be specified, as follows: + + ``` + --cannon-rollup-config rollup.json \ + --cannon-l2-genesis genesis-l2.json \ + # use this if running challenger outside of the docker image + --cannon-server ./op-program/bin/op-program \ + # json or url, version of op-program deployed on chain + # if you use the wrong one, you will lose the game + # if you deploy your own contracts, you specify the hash, the root of the json file + # op mainnet are tagged versions of op-program + # make reproducible prestate + # challenger verifies that onchain + --cannon-prestate ./op-program/bin/prestate.json \ + # load the game factory address from system config or superchain registry + # point the game factory address at the dispute game factory proxy + --game-factory-address + ``` + + + These options vary based on which `--network` is specified. Chain operators always need to specify a way to load prestates and must also specify the cannon-server whenever the docker image isn't being used. + + + + + + * This is a directory that `op-challenger` can write to and store whatever data it needs. It will manage this directory to add or remove data as needed under that directory. + * If running in docker, it should point to a docker volume or mount point, so the data isn't lost on every restart. The data can be recreated if needed but particularly if challenger has executed cannon as part of responding to a game it may mean a lot of extra processing. + + + + + The pre-state is effectively the version of `op-program` that is deployed on chain. And chain operators must use the right version. `op-challenger` will refuse to interact with games that have a different absolute prestate hash to avoid making invalid claims. If deploying your own contracts, chain operators must specify an absolute prestate hash taken from the `make reproducible-prestate` command during contract deployment, which will also build the required prestate json file. + + All governance approved releases use a tagged version of `op-program`. These can be rebuilt by checking out the version tag and running `make reproducible-prestate`. + + * There are two ways to specify the prestate to use: + * `--cannon-prestate`: specifies a path to a single Cannon pre-state Json file + * `--cannon-prestates-url`: specifies a URL to load pre-states from. This enables participating in games that use different prestates, for example due to a network upgrade. The prestates are stored in this directory named by their hash. + * Example final URL for a prestate: + * [https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json](https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json) + * This file contains the cannon memory state. + + + Challenger will refuse to interact with any games if it doesn't have the matching prestate. + Check this [guide](/operators/chain-operators/tutorials/absolute-prestate#generating-the-absolute-prestate) on how to generate a absolute prestate. + + + + + + Create a `docker-compose.yml` file that defines the challenger service: + + ```yaml + version: '3.8' + + services: + challenger: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.5.0 + user: "1000" + volumes: + - ./challenger-data:/data + - ./rollup.json:/workspace/rollup.json:ro + - ./genesis-l2.json:/workspace/genesis-l2.json:ro + environment: + - L1_RPC_URL=${L1_RPC_URL} + - L1_BEACON=${L1_BEACON} + - L2_RPC_URL=${L2_RPC_URL} + - ROLLUP_RPC_URL=${ROLLUP_RPC_URL} + - MNEMONIC=${MNEMONIC} + - HD_PATH=${HD_PATH} + - NETWORK=${NETWORK} + - GAME_FACTORY_ADDRESS=${GAME_FACTORY_ADDRESS} + command: + - "op-challenger" + - "--l1-eth-rpc=${L1_RPC_URL}" + - "--l1-beacon=${L1_BEACON}" + - "--l2-eth-rpc=${L2_RPC_URL}" + - "--rollup-rpc=${ROLLUP_RPC_URL}" + - "--selective-claim-resolution" + - "--mnemonic=${MNEMONIC}" + - "--hd-path=${HD_PATH}" + - "--network=${NETWORK}" + - "--game-factory-address=${GAME_FACTORY_ADDRESS}" + - "--datadir=/data" + - "--cannon-prestate=/workspace/prestate-proof.json" + restart: unless-stopped + ports: + - "8548:8548" # If challenger exposes metrics endpoint + ``` + + + + Start the challenger service and monitor its logs: + + ```bash + # Start the challenger service + docker-compose up -d + + # View logs + docker-compose logs -f challenger + ``` + + + + + + +### Monitoring with op-dispute-mon + +Consider running [`op-dispute-mon`](/operators/chain-operators/tools/chain-monitoring#dispute-mon) for enhanced security monitoring: + +* Provides visibility into all game statuses for the last 28 days +* Essential for production challenger deployments + +## Next steps + +* Read the [OP-Challenger Explainer](/op-stack/fault-proofs/challenger) for additional context and FAQ +* Review the detailed [challenger specifications](https://specs.optimism.io/fault-proof/stage-one/honest-challenger-fdg.html) for implementation details +* If you experience any problems, reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions) diff --git a/docs/public-docs/chain-operators/guides/configuration/proposer.mdx b/docs/public-docs/chain-operators/guides/configuration/proposer.mdx new file mode 100644 index 0000000000000..d4d817d4f1530 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/configuration/proposer.mdx @@ -0,0 +1,506 @@ +--- +title: Proposer Configuration +description: Learn the OP Stack proposer configurations. +--- + +This page lists all configuration options for op-proposer. The op-proposer posts +output roots (proposals) to L1, making them available for verifiers. Withdrawals to L1 must reference an output root. +If the chain is running permissioned fault proofs, only the [designated proposer](/op-stack/protocol/privileged-roles) can submit output roots. +With [permissionless fault proofs](/op-stack/fault-proofs/explainer), anyone can make a proposal. + +## Proposer policy + +The proposer policy defines high-level constraints and responsibilities regarding how L2 output roots are posted to L1. Below are the [standard guidelines](/chain-operators/reference/standard-configuration) for configuring the proposer within the OP Stack. + +| Parameter | Description | Administrator | Requirement | Notes | +| ---------------- | ----------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------- | --------------------------------------------------------------- | +| Output Frequency | Defines how frequently L2 output roots are submitted to L1 (via the output oracle). | L1 Proxy Admin | **43,200 L2 blocks** (24 hours at 2s block times) or lower | It cannot be set to 0 (there must be some cadence for outputs). | + +### Example configuration + +``` +OP_PROPOSER_L1_ETH_RPC: YOUR_L1_RPC_URL_HERE +OP_PROPOSER_ROLLUP_RPC: YOUR_CHAINS_RPC_URL_HERE +OP_PROPOSER_GAME_FACTORY_ADDRESS: YOUR_CHAINS_GAME_FACTORY_ADDRESS_HERE +OP_PROPOSER_PROPOSAL_INTERVAL: 5h +OP_PROPOSER_WAIT_NODE_SYNC: true +OP_PROPOSER_ALLOW_NON_FINALIZED: "false" +OP_PROPOSER_POLL_INTERVAL: "20s" +OP_PROPOSER_NUM_CONFIRMATIONS: "1" +OP_PROPOSER_SAFE_ABORT_NONCE_TOO_LOW_COUNT: "3" +OP_PROPOSER_RESUBMISSION_TIMEOUT: "30s" +OP_PROPOSER_METRICS_ENABLED: "true" +OP_PROPOSER_METRICS_ADDR: 0.0.0.0 +OP_PROPOSER_METRICS_PORT: 7300 +``` + +Higher throughput chains can decrease the `proposal-interval` to allow users submit withdrawals more often. + +## All configuration variables + +### Output root generation + +#### poll-interval + +Delay between periodic checks on whether it is time to load an output root and propose it. The default value is 6s. + + + `--poll-interval=` + `--poll-interval=6s` + `OP_PROPOSER_POLL_INTERVAL=6s` + + +#### active-sequencer-check-duration + +The duration between checks to determine the active sequencer endpoint from which output roots will be derived. The +default value is `2m0s`. + + + `--active-sequencer-check-duration=` + `--active-sequencer-check-duration=2m0s` + `OP_PROPOSER_ACTIVE_SEQUENCER_CHECK_DURATION=2m0s` + + +### Output root submission + +#### proposal-interval + +Interval between submitting L2 output proposals when the dispute game factory +address is set. The default value is 0s. + + + `--proposal-interval=` + `--proposal-interval=0s` + `OP_PROPOSER_PROPOSAL_INTERVAL=0s` + + +#### allow-non-finalized + +Allow the proposer to submit proposals for L2 blocks from non-finalized L1 +blocks. The default value is false. + + + `--allow-non-finalized=` + `--allow-non-finalized=false` + `OP_PROPOSER_ALLOW_NON_FINALIZED=false` + + +#### game-factory-address + +Address of the DisputeGameFactory contract. This is where the proposer will submit output roots. + + + `--game-factory-address=` + `--game-factory-address=` + `OP_PROPOSER_GAME_FACTORY_ADDRESS=` + + +#### game-type + +Dispute game type to create via the configured DisputeGameFactory. The default +value is 0. + + + `--game-type=` + `--game-type=0` + `OP_PROPOSER_GAME_TYPE=0` + + +### Proposer startup + +#### wait-node-sync + +Indicates if, during startup, the proposer should wait for the rollup node to +sync to the current L1 tip before proceeding with its driver loop. The default +value is false. + + + `--wait-node-sync=` + `--wait-node-sync=false` + `OP_PROPOSER_WAIT_NODE_SYNC=false` + + +### Transaction manager + +#### fee-limit-multiplier + +The multiplier applied to fee suggestions to limit fee increases. The default +value is 5. + + + `--fee-limit-multiplier=` + `--fee-limit-multiplier=5` + `OP_PROPOSER_TXMGR_FEE_LIMIT_MULTIPLIER=5` + + +#### num-confirmations + +Number of confirmations to wait after sending a transaction. The default value +is 10. + + + `--num-confirmations=` + `--num-confirmations=10` + `OP_PROPOSER_NUM_CONFIRMATIONS=10` + + +#### resubmission-timeout + +Duration we will wait before resubmitting a transaction to L1. The default +value is 48s. + + + `--resubmission-timeout=` + `--resubmission-timeout=48s` + `OP_PROPOSER_RESUBMISSION_TIMEOUT=48s` + + +#### safe-abort-nonce-too-low-count + +Number of ErrNonceTooLow observations required to give up on a tx at a +particular nonce without receiving confirmation. The default value is 3. + + + `--safe-abort-nonce-too-low-count=` + `--safe-abort-nonce-too-low-count=3` + `OP_PROPOSER_SAFE_ABORT_NONCE_TOO_LOW_COUNT=3` + + +#### txmgr.fee-limit-threshold + +The minimum threshold (in GWei) at which fee bumping starts to be capped. The +default value is 100. + + + `--txmgr.fee-limit-threshold=` + `--txmgr.fee-limit-threshold=100` + `OP_PROPOSER_TXMGR_FEE_LIMIT_THRESHOLD=100` + + +#### txmgr.min-basefee + +Enforces a minimum base fee (in GWei) to assume when determining tx fees. The +default value is 1. + + + `--txmgr.min-basefee=` + `--txmgr.min-basefee=1` + `OP_PROPOSER_TXMGR_MIN_BASEFEE=1` + + +#### txmgr.min-tip-cap + +Enforces a minimum tip cap (in GWei) to use when determining tx fees. The +default value is 1. + + + `--txmgr.min-tip-cap=` + `--txmgr.min-tip-cap=1` + `OP_PROPOSER_TXMGR_MIN_TIP_CAP=1` + + +#### txmgr.not-in-mempool-timeout + +Timeout for aborting a tx send if the tx does not make it to the mempool. The +default value is 2m0s. + + + `--txmgr.not-in-mempool-timeout=` + `--txmgr.not-in-mempool-timeout=2m0s` + `OP_PROPOSER_TXMGR_TX_NOT_IN_MEMPOOL_TIMEOUT=2m0s` + + +#### txmgr.receipt-query-interval + +Frequency to poll for receipts. The default value is 12s. + + + `--txmgr.receipt-query-interval=` + `--txmgr.receipt-query-interval=12s` + `OP_PROPOSER_TXMGR_RECEIPT_QUERY_INTERVAL=12s` + + +#### txmgr.send-timeout + +Timeout for sending transactions. If 0 it is disabled. The default value is 0s. + + + `--txmgr.send-timeout=` + `--txmgr.send-timeout=0s` + `OP_PROPOSER_TXMGR_TX_SEND_TIMEOUT=0s` + + +### Wallet + +#### hd-path + +The HD path used to derive the sequencer wallet from the mnemonic. + + + `--hd-path=` + `--hd-path=` + `OP_PROPOSER_HD_PATH=` + + +#### mnemonic + +The mnemonic used to derive the wallets for the service. + + + `--mnemonic=` + `--mnemonic=` + `OP_PROPOSER_MNEMONIC=` + + +### RPC + +#### l1-eth-rpc + +HTTP provider URL for L1. + + + `--l1-eth-rpc=` + `--l1-eth-rpc=` + `OP_PROPOSER_L1_ETH_RPC=` + + +#### rollup-rpc + +HTTP provider URL for the rollup node. A comma-separated list enables the +active rollup provider. + + + `--rollup-rpc=` + `--rollup-rpc=` + `OP_PROPOSER_ROLLUP_RPC=` + + +#### rpc.addr + +rpc listening address. The default value is "0.0.0.0". + + + `--rpc.addr=` + `--rpc.addr=0.0.0.0` + `OP_PROPOSER_RPC_ADDR=0.0.0.0` + + +#### rpc.enable-admin + +Enable the admin API. The default value is false. + + + `--rpc.enable-admin=` + `--rpc.enable-admin=false` + `OP_PROPOSER_RPC_ENABLE_ADMIN=false` + + +#### rpc.port + +rpc listening port. The default value is 8545. + + + `--rpc.port=` + `--rpc.port=8545` + `OP_PROPOSER_RPC_PORT=8545` + + +### Logging + +#### log.color + +Color the log output if in terminal mode. The default value is false. + + + `--log.color=` + `--log.color=false` + `OP_PROPOSER_LOG_COLOR=false` + + +#### log.format + +Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', +'json-pretty'. The default value is text. + + + `--log.format=` + `--log.format=text` + `OP_PROPOSER_LOG_FORMAT=text` + + +#### log.level + +The lowest log level that will be output. The default value is INFO. + + + `--log.level=` + `--log.level=INFO` + `OP_PROPOSER_LOG_LEVEL=INFO` + + +### Metrics + +#### metrics.addr + +Metrics listening address. The default value is "0.0.0.0". + + + `--metrics.addr=` + `--metrics.addr=0.0.0.0` + `OP_PROPOSER_METRICS_ADDR=0.0.0.0` + + +#### metrics.enabled + +Enable the metrics server. The default value is false. + + + `--metrics.enabled=` + `--metrics.enabled=false` + `OP_PROPOSER_METRICS_ENABLED=false` + + +#### metrics.port + +Metrics listening port. The default value is 7300. + + + `--metrics.port=` + `--metrics.port=7300` + `OP_PROPOSER_METRICS_PORT=7300` + + +### pprof + +#### pprof.addr + +pprof listening address. The default value is "0.0.0.0". + + + `--pprof.addr=` + `--pprof.addr=0.0.0.0` + `OP_PROPOSER_PPROF_ADDR=0.0.0.0` + + +#### pprof.enabled + +Enable the pprof server. The default value is false. + + + `--pprof.enabled=` + `--pprof.enabled=false` + `OP_PROPOSER_PPROF_ENABLED=false` + + +#### pprof.path + +The pprof file path. If it is a directory, the path is `{dir}/{profileType}.prof` + + + `--pprof.path=` + `--pprof.path=` + `OP_PROPOSER_PPROF_PATH=` + + +#### pprof.port + +pprof listening port. The default value is 6060. + + + `--pprof.port=` + `--pprof.port=6060` + `OP_PROPOSER_PPROF_PORT=6060` + + +#### pprof.type + +pprof profile type. One of cpu, heap, goroutine, threadcreate, block, mutex, +allocs + + + `--pprof.type=` + `--pprof.type=` + `OP_PROPOSER_PPROF_TYPE=` + + +### Signer + +#### signer.address + +Address the signer is signing transactions for. + + + `--signer.address=` + `--signer.address=` + `OP_PROPOSER_SIGNER_ADDRESS=` + + +#### signer.endpoint + +Signer endpoint the client will connect to. + + + `--signer.endpoint=` + `--signer.endpoint=` + `OP_PROPOSER_SIGNER_ENDPOINT=` + + +#### signer.tls.ca + +tls ca cert path. The default value is "tls/ca.crt". + + + `--signer.tls.ca=` + `--signer.tls.ca=tls/ca.crt` + `OP_PROPOSER_SIGNER_TLS_CA=tls/ca.crt` + + +#### signer.tls.cert + +tls cert path. The default value is "tls/tls.crt". + + + `--signer.tls.cert=` + `--signer.tls.cert=tls/tls.crt` + `OP_PROPOSER_SIGNER_TLS_CERT=tls/tls.crt` + + +#### signer.tls.key + +tls key. The default value is "tls/tls.key". + + + `--signer.tls.key=` + `--signer.tls.key=tls/tls.key` + `OP_PROPOSER_SIGNER_TLS_KEY=tls/tls.key` + + +### Miscellaneous + +#### network-timeout + +Timeout for all network operations. The default value is 10s. + + + `--network-timeout=` + `--network-timeout=10s` + `OP_PROPOSER_NETWORK_TIMEOUT=10s` + + +#### help + +Show help. The default value is false. + + + `--help=` + `--help=false` + + +#### version + +Print the version. The default value is false. + + + `--version=` + `--version=false` + diff --git a/docs/public-docs/chain-operators/guides/configuration/rollup.mdx b/docs/public-docs/chain-operators/guides/configuration/rollup.mdx new file mode 100644 index 0000000000000..f7bdd0fdbb0b0 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/configuration/rollup.mdx @@ -0,0 +1,1227 @@ +--- +title: Rollup deployment configuration +description: Learn about the OP Stack rollup deployment configurations. +--- + + + This documentation is out of date. + + + + The Rollup configuration is an active work in progress and will likely evolve + significantly as time goes on. If something isn't working about your + configuration, you can refer to the [source code](https://github.com/ethereum-optimism/optimism/blob/develop/op-chain-ops/genesis/config.go). + + + + Standard configuration is the set of requirements for an OP Stack chain to be + considered a Standard Chain within the OP Stack. These requirements are + currently a draft, pending governance approval. For more details, please see + this [governance thread](https://gov.optimism.io/t/season-6-draft-standard-rollup-charter/8135) + and the actual requirements in the [OP Stack Configurability Specification](https://specs.optimism.io/protocol/configurability.html?utm_source=op-docs&utm_medium=docs). + + +## Deployment configuration values + +These values are provided to the deployment configuration JSON file when +deploying the L1 contracts. + +### Offset values + +These offset values determine when network upgrades (hardforks) activate on +your blockchain. + +#### l2GenesisRegolithTimeOffset + +L2GenesisRegolithTimeOffset is the number of seconds after genesis block that +Regolith hard fork activates. Set it to 0 to activate at genesis. Nil to +disable Regolith. + +* **Type:** Number of seconds +* **Default value:** nil +* **Recommended value:** "0x0" +* **Notes:** +* **Standard Config Requirement:** Network upgrade (hardfork) is activated. + +*** + +#### l2GenesisCanyonTimeOffset + +L2GenesisCanyonTimeOffset is the number of seconds after genesis block that +Canyon hard fork activates. Set it to 0 to activate at genesis. Nil to +disable Canyon. + +* **Type:** Number of seconds +* **Default value:** nil +* **Recommended value:** "0x0" +* **Notes:** +* **Standard Config Requirement:** Network upgrade (hardfork) is activated. + +*** + +#### l2GenesisDeltaTimeOffset + +L2GenesisDeltaTimeOffset is the number of seconds after genesis block that +Delta hard fork activates. Set it to 0 to activate at genesis. Nil to disable +Delta. + +* **Type:** Number of seconds +* **Default value:** nil +* **Recommended value:** "0x0" +* **Notes:** +* **Standard Config Requirement:** Network upgrade (hardfork) is activated. + +*** + +#### l2GenesisEcotoneTimeOffset + +L2GenesisEcotoneTimeOffset is the number of seconds after genesis block that +Ecotone hard fork activates. Set it to 0 to activate at genesis. Nil to disable +Ecotone. + +* **Type:** Number of seconds +* **Default value:** nil +* **Recommended value:** "0x0" +* **Notes:** +* **Standard Config Requirement:** Network upgrade (hardfork) is activated. + +*** + +#### l2GenesisFjordTimeOffset + +L2GenesisFjordTimeOffset is the number of seconds after genesis block that +Fjord hard fork activates. Set it to 0 to activate at genesis. Nil to +disable Fjord. + +* **Type:** Number of seconds +* **Default value:** nil +* **Recommended value:** "0x0" +* **Notes:** +* **Standard Config Requirement:** Network upgrade (hardfork) is activated. + +*** + +#### l2GenesisInteropTimeOffset + +L2GenesisInteropTimeOffset is the number of seconds after genesis block that +the Interop hard fork activates. Set it to 0 to activate at genesis. Nil to +disable Interop. + +* **Type:** Number of seconds +* **Default value:** nil +* **Recommended value:** +* **Notes:** Interoperability is still [experimental](https://specs.optimism.io/interop/overview.html?utm_source=op-docs&utm_medium=docs). +* **Standard Config Requirement:** Non-standard feature. + +*** + +#### l1CancunTimeOffset + +When Cancun activates. Relative to L1 genesis. + +* **Type:** Number of seconds +* **Default value:** None +* **Recommended value:** "0x0" +* **Notes:** +* **Standard Config Requirement:** Network upgrade (hardfork) is activated. + +*** + +### Admin addresses + +#### finalSystemOwner + +FinalSystemOwner is the L1 system owner. It owns any ownable L1 contracts. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** It is recommended to have a single admin + address to retain a common security model. +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** Must be Chain Governor or Servicer. +However, the L1 ProxyAdmin owner must be held by Optimism Security Council. +At the moment, this is L1 ProxyAdmin owner is transferred from the Chain +Governor or Servicer to [0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A](https://etherscan.io/address/0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A) + +*** + +#### proxyAdminOwner + +ProxyAdmin contract owner on the L2, which owns all of the Proxy contracts for +every predeployed contract in the range `0x42...0000` to `0x42...2048`. This +makes predeployed contracts easily upgradeable. + +* **Type:** L2 Address +* **Default value:** None +* **Recommended value:** It is recommended to have a single admin + address to retain a common security model. +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** + +*** + +### Proxy addresses + +#### l1StandardBridgeProxy + +L1StandardBridgeProxy represents the address of the L1StandardBridgeProxy on L1 +and is used as part of building the L2 genesis state. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** Implementation contract must be the most +up-to-date, governance-approved version of the OP Stack codebase, and, if the +chain has been upgraded in the past, that the previous versions were a standard +release of the codebase. + +*** + +#### l1CrossDomainMessengerProxy + +L1CrossDomainMessengerProxy represents the address of the +L1CrossDomainMessengerProxy on L1 and is used as part of building the L2 +genesis state. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** Implementation contract must be the most +up-to-date, governance-approved version of the OP Stack codebase, and, if the +chain has been upgraded in the past, that the previous versions were a standard +release of the codebase. + +*** + +#### l1ERC721BridgeProxy + +L1ERC721BridgeProxy represents the address of the L1ERC721Bridge on L1 and is +used as part of building the L2 genesis state. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** Implementation contract must be the most +up-to-date, governance-approved version of the OP Stack codebase, and, if the +chain has been upgraded in the past, that the previous versions were a standard +release of the codebase. + +*** + +#### systemConfigProxy + +SystemConfigProxy represents the address of the SystemConfigProxy on L1 and is +used as part of the derivation pipeline. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** Implementation contract must be the most +up-to-date, governance-approved version of the OP Stack codebase, and, if the +chain has been upgraded in the past, that the previous versions were a standard +release of the codebase. + +*** + +#### optimismPortalProxy + +OptimismPortalProxy represents the address of the OptimismPortalProxy on L1 and +is used as part of the derivation pipeline. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** Implementation contract must be the most +up-to-date, governance-approved version of the OP Stack codebase, and, if the +chain has been upgraded in the past, that the previous versions were a standard +release of the codebase. + +*** + +### Blocks + +These fields apply to L2 blocks: Their timing, when they need to be written to L1, and how they get written. + +#### l2BlockTime + +Number of seconds between each L2 block. Must be \< L1 block time (12 on mainnet and Sepolia). + +* **Type:** Number of seconds +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. Must be less than the L1 blocktime and a whole number. +* **Standard Config Requirement:** 1 or 2 seconds + +*** + +#### maxSequencerDrift + +How far the L2 timestamp can differ from the actual L1 timestamp. + +* **Type:** Number of seconds +* **Default value:** None +* **Recommended value:** 1800 +* **Notes:** Must not be `0`. 1800 (30 minutes) is the constant that takes +effect with the [Fjord activation](/op-stack/protocol/network-upgrades#fjord). +* **Standard Config Requirement:** + +*** + +#### sequencerWindowSize + +Maximum number of L1 blocks that a Sequencer can wait to incorporate the +information in a specific L1 block. For example, if the window is 10 then the +information in L1 block n must be incorporated by L1 block n+10. + +* **Type:** Number of blocks +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. 3600 (12 hours) is suggested. +* **Standard Config Requirement:** 3\_600 base layer blocks (12 hours for an + L2 on Ethereum, assuming 12 second L1 blocktime). This is an important value + for constraining the sequencer's ability to re-order transactions; higher + values would pose a risk to user protections. + +*** + +#### channelTimeout + +Maximum number of L1 blocks that a transaction channel frame can be considered +valid. A transaction channel frame is a chunk of a compressed batch of +transactions. After the timeout, the frame is dropped. + +* **Type:** Number of blocks +* **Default value:** 50 +* **Recommended value:** +* **Notes:** This default value was introduced in the [Granite network upgrade](/op-stack/protocol/network-upgrades#granite) +* **Standard Config Requirement:** + +*** + +#### p2pSequencerAddress + +Address of the key that the Sequencer uses to sign blocks on the p2p network. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** No requirement. + +*** + +#### batchInboxAddress + +Address that Sequencer transaction batches are sent to on L1. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Recommendation:** Convention is `versionByte` || +`keccak256(bytes32(chainId))[:19]`, where || denotes concatenation, +`versionByte` is `0x00`, and `chainId` is a `uint256`. +This is to cover the full range of chain ids, to the full `uint256` size. +Here's how you can get this address: + ```solidity + bytes32 hash = keccak256(abi.encodePacked(bytes32(uint256(chainId)))); + # [:19] means taking the first 19 bytes of the hash + # then preppending a version byte of zero: 0x00 + # `0x00{hash[:19]}` + ``` + +*** + +#### batchSenderAddress + +Address that nodes will filter for when searching for Sequencer transaction +batches being sent to the batchInboxAddress. Can be updated later via the +SystemConfig contract on L1. + +* **Type:** L1 Address +* **Default value:** Batcher, an address for which you own the private key. +* **Recommended value:** +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** No requirement. + +*** + +#### systemConfigStartBlock + +SystemConfigStartBlock represents the block at which the op-node should start +syncing from. It is an override to set this value on legacy networks where it +is not set by default. It can be removed once all networks have this value set +in their storage. + +* **Type:** L2 Block Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** The block where the SystemConfig was + initialized. + +*** + +#### daFootprintGasScalar +Scalar value which is multiplied by the `daUsageEstimate` to limit the total amount of estimated compressed transaction data that can fit into a block. +As of Jovian (Upgrade 17), the base fee update calculation now uses `gasMetered := max(gasUsed, blobGasUsed)` in place of the `gasUsed` value used before. +As a result, blocks with high DA usage may cause the base fee to increase in subsequent blocks. +* **Type:** L2 Block Number +* **Default value:** 400 +* **Recommended value:** 400 +* **Notes:** [Refer to the specs for more detail](https://specs.optimism.io/protocol/jovian/exec-engine.html#da-footprint-block-limit). +* **Standard Config Requirement:** + +*** + +### Chain Information + +#### l1StartingBlockTag + +Block tag for the L1 block where the L2 chain will begin syncing from. +It is generally recommended to use a finalized block to avoid issues with reorgs. + +* **Type:** Block hash +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. +* **Standard Config Requirement:** + +*** + +#### l1ChainID + +Chain ID of the L1 chain. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. 1 for L1 Ethereum mainnet, 11155111 for the + Sepolia test network, and See [here](https://chainlist.org/?testnets=true) + for other blockchains. +* **Standard Config Requirement:** 1 (Ethereum) + +*** + +#### l2ChainID + +Chain ID of the L2 chain. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. For security reasons, should be unique. Chains +should add their chain IDs to [ethereum-lists/chains](https://github.com/ethereum-lists/chains). +* **Standard Config Requirement:** Foundation-approved, globally unique value. + +*** + +#### l2GenesisBlockExtraData + +L2GenesisBlockExtraData is configurable extradata. Will default to +\[]byte("BEDROCK") if left unspecified. + +* **Type:** Number +* **Default value:** \[]byte("BEDROCK") +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### superchainConfigGuardian + +SuperchainConfigGuardian represents the GUARDIAN account in the +SuperchainConfig. Has the ability to pause withdrawals. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** [0x09f7150D8c019BeF34450d6920f6B3608ceFdAf2](https://etherscan.io/address/0x09f7150D8c019BeF34450d6920f6B3608ceFdAf2) +A 1/1 Safe owned by the Security Council Safe, with the [Deputy Guardian Module](https://specs.optimism.io/protocol/safe-extensions.html?utm_source=op-docs&utm_medium=docs#deputy-guardian-module) +enabled to allow the Optimism Foundation to act as Guardian. + +*** + +### Gas + +* **Standard Config Requirement:** Set such that Fee Margin is between 0 and + 50%. +* **Standard Config Requirement:** No higher than 200\_000\_000 gas. Chain + operators are driven to maintain a stable and reliable chain. When considering + a change to this value, careful deliberation is necessary. + +#### l2GenesisBlockGasLimit + +L2GenesisBlockGasLimit represents the chain's block gas limit. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. Must be greater than `MaxResourceLimit` + `SystemTxMaxGas`. +* **Standard Config Requirement:** + +*** + +#### l2GenesisBlockBaseFeePerGas + +L2GenesisBlockBaseFeePerGas represents the base fee per gas. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** L2 genesis block base fee per gas cannot be `nil`. +* **Standard Config Requirement:** + +*** + +#### eip1559Elasticity + +EIP1559Elasticity is the elasticity of the EIP1559 fee market. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. +* **Standard Config Requirement:** + +*** + +#### eip1559Denominator + +EIP1559Denominator is the denominator of EIP1559 base fee market. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. +* **Standard Config Requirement:** + +*** + +#### eip1559DenominatorCanyon + +EIP1559DenominatorCanyon is the denominator of EIP1559 base fee market when +Canyon is active. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** 250 +* **Notes:** Must not be `0` if Canyon is activated. +* **Standard Config Requirement:** + +*** + +#### gasPriceOracleBaseFeeScalar + +GasPriceOracleBaseFeeScalar represents the value of the base fee scalar used +for fee calculations. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Should not be `0`. +* **Standard Config Requirement:** + +*** + +#### gasPriceOracleBlobBaseFeeScalar + +GasPriceOracleBlobBaseFeeScalar represents the value of the blob base fee +scalar used for fee calculations. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Should not be `0`. +* **Standard Config Requirement:** + +*** + +#### minBaseFee + +Allows chain operators to specify a minimum base fee to help shorten the length of priority fee auctions. +The `minBaseFee` is an absolute minimum expressed in wei. +0 disables the `minBaseFee` entirely (disabled by default). + +* **Type:** Number +* **Default value:** 0 +* **Recommended value:** 100000 wei +* **Notes:** Setting the minimum too high may make transactions harder to get included for users. [See the specs for more detail](https://specs.optimism.io/protocol/jovian/exec-engine.html#minimum-base-fee). +* **Standard Config Requirement:** Must not be set higher than 10000000000 wei +*** + +### Proposal fields + +These fields apply to output root proposals. The +l2OutputOracleSubmissionInterval is configurable, see the section below for +guidance. + +#### l2OutputOracleStartingBlockNumber + +Block number of the first OP Stack block. Typically this should be zero, but +this may be non-zero for networks that have been upgraded from a legacy system +(like OP Mainnet). Will be removed with the addition of permissionless +proposals. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** 0 +* **Notes:** Should be `0` for new chains. +* **Standard Config Requirement:** + +*** + +#### l2OutputOracleStartingTimestamp + +Timestamp of the first OP Stack block. This MUST be the timestamp corresponding +to the block defined by the l1StartingBlockTag. Will be removed with the +addition of permissionless proposals. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** this MUST be the timestamp corresponding to the block defined by + the l1StartingBlockTag. +* **Standard Config Requirement:** + +*** + +#### l2OutputOracleSubmissionInterval + +Number of blocks between proposals to the L2OutputOracle. Will be removed with +the addition of permissionless proposals. + +* **Type:** Number of blocks +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. 120 (4 minutes) is suggested. +* **Standard Config Requirement:** + +*** + +#### finalizationPeriodSeconds + +Number of seconds that a proposal must be available to challenge before it is +considered finalized by the OptimismPortal contract. + +* **Type:** Number of seconds +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `0`. Recommend 12 on test networks, seven days on + production ones. +* **Standard Config Requirement:** 7 days. High security. Excessively safe + upper bound that leaves enough time to consider social layer solutions to a + hack if necessary. Allows enough time for other network participants to + challenge the integrity of the corresponding output root. + +*** + +#### l2OutputOracleProposer + +Address that is allowed to submit output proposals to the L2OutputOracle +contract. Will be removed when the OP Stack has permissionless proposals. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)` +* **Standard Config Requirement:** No requirement. This role is only active +when the OptimismPortal respected game type is `PERMISSIONED_CANNON`. The +L1ProxyAdmin sets the implementation of the `PERMISSIONED_CANNON` game type. +Thus, it determines the proposer configuration of the permissioned dispute game. + +*** + +#### l2OutputOracleChallenger + +Address that is allowed to challenge output proposals submitted to the +L2OutputOracle. Will be removed when the OP Stack has permissionless +challenges. + +* **Type:** L1 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)`. It is recommended to have a single admin + address to retain a common security model. +* **Standard Config Requirement:** + +*** + +### Fee recipients + +#### baseFeeVaultRecipient + +BaseFeeVaultRecipient represents the recipient of fees accumulated in the +BaseFeeVault. Can be an account on L1 or L2, depending on the +BaseFeeVaultWithdrawalNetwork value. + +* **Type:** L1 or L2 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)`. It is recommended to have a single admin + address to retain a common security model. +* **Standard Config Requirement:** + +*** + +#### l1FeeVaultRecipient + +L1FeeVaultRecipient represents the recipient of fees accumulated in the +L1FeeVault. Can be an account on L1 or L2, depending on the +L1FeeVaultWithdrawalNetwork value. + +* **Type:** L1 or L2 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)`. It is recommended to have a single admin + address to retain a common security model. +* **Standard Config Requirement:** + +*** + +#### sequencerFeeVaultRecipient + +SequencerFeeVaultRecipient represents the recipient of fees accumulated in the +SequencerFeeVault. Can be an account on L1 or L2, depending on the +SequencerFeeVaultWithdrawalNetwork value. + +* **Type:** L1 or L2 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be `address(0)`. It is recommended to have a single admin + address to retain a common security model. +* **Standard Config Requirement:** + +*** + +### Minimum fee withdrawal amounts + +Withdrawals to L1 are expensive and the minimum fee is to prevent overhead +costs of continuous tiny withdrawals. If the withdrawal execution costs more +than the fee-reward, then the fee Must not be collected economically. + +*** + +#### baseFeeVaultMinimumWithdrawalAmount + +BaseFeeVaultMinimumWithdrawalAmount represents the minimum withdrawal amount +for the BaseFeeVault. + +* **Type:** Number in wei +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### l1FeeVaultMinimumWithdrawalAmount + +L1FeeVaultMinimumWithdrawalAmount represents the minimum withdrawal amount for +the L1FeeVault. + +* **Type:** Number in wei +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### sequencerFeeVaultWithdrawalAmount + +SequencerFeeVaultMinimumWithdrawalAmount represents the minimum withdrawal +amount for the SequencerFeeVault. + +* **Type:** Number in wei +* **Recommended value:** +* **Default value:** None +* **Notes:** +* **Standard Config Requirement:** + +*** + +### Withdrawal network + +*** + +#### baseFeeVaultWithdrawalNetwork + +BaseFeeVaultWithdrawalNetwork represents the withdrawal network for the +BaseFeeVault. value of 0 will withdraw ETH to the recipient address on L1 and +a value of 1 will withdraw ETH to the recipient address on L2. + +* **Type:** Number representing network enum +* **Default value:** None +* **Recommended value:** +* **Notes:** Withdrawals to Ethereum are more expensive. +* **Standard Config Requirement:** + +*** + +#### l1FeeVaultWithdrawalNetwork + +L1FeeVaultWithdrawalNetwork represents the withdrawal network for the +L1FeeVault. A value of 0 will withdraw ETH to the recipient address on L1 and a +value of 1 will withdraw ETH to the recipient address on L2. + +* **Type:** Number representing network enum +* **Default value:** None +* **Recommended value:** +* **Notes:** Withdrawals to Ethereum are more expensive. +* **Standard Config Requirement:** + +*** + +#### sequencerFeeVaultWithdrawalNetwork + +SequencerFeeVaultWithdrawalNetwork represents the withdrawal network for the +SequencerFeeVault. A value of 0 will withdraw ETH to the recipient address on +L1 and a value of 1 will withdraw ETH to the recipient address on L2. + +* **Type:** Number representing network enum +* **Default value:** None +* **Recommended value:** +* **Notes:** Withdrawals to Ethereum are more expensive. +* **Standard Config Requirement:** + +*** + +### Fault proofs + +*** + +#### faultGameAbsolutePrestate + +FaultGameAbsolutePrestate is the absolute prestate of Cannon. This is computed +by generating a proof from the 0th -> 1st instruction and grabbing the prestate +from the output JSON. All honest challengers should agree on the setup state of +the program. + +* **Type:** Hash +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### faultGameMaxDepth + +FaultGameMaxDepth is the maximum depth of the position tree within the fault +dispute game. `2^{FaultGameMaxDepth}` is how many instructions the execution +trace bisection game supports. Ideally, this should be conservatively set so +that there is always enough room for a full Cannon trace. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### faultGameClockExtension + +FaultGameClockExtension is the amount of time that the dispute game will set +the potential grandchild claim's, clock to, if the remaining time is less than +this value at the time of a claim's creation. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### faultGameMaxClockDuration + +FaultGameMaxClockDuration is the maximum amount of time that may accumulate on +a team's chess clock before they may no longer respond. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### faultGameGenesisBlock + +FaultGameGenesisBlock is the block number for genesis. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### faultGameGenesisOutputRoot + +FaultGameGenesisOutputRoot is the output root for the genesis block. + +* **Type:** Hash +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### faultGameSplitDepth + +FaultGameSplitDepth is the depth at which the fault dispute game splits from +output roots to execution trace claims. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### faultGameWithdrawalDelay + +FaultGameWithdrawalDelay is the number of seconds that users must wait before +withdrawing ETH from a fault game. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### preimageOracleMinProposalSize + +PreimageOracleMinProposalSize is the minimum number of bytes that a large +preimage oracle proposal can be. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### preimageOracleChallengePeriod + +PreimageOracleChallengePeriod is the number of seconds that challengers have to +challenge a large preimage proposal. + +* **Type:** Number of Seconds +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### proofMaturityDelaySeconds + +ProofMaturityDelaySeconds is the number of seconds that a proof must be mature +before it can be used to finalize a withdrawal. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Should not be `0`. +* **Standard Config Requirement:** + +*** + +#### disputeGameFinalityDelaySeconds + +DisputeGameFinalityDelaySeconds is an additional number of seconds a dispute +game must wait before it can be used to finalize a withdrawal. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** Should not be `0`. +* **Standard Config Requirement:** + +*** + +#### respectedGameType + +RespectedGameType is the dispute game type that the OptimismPortal contract +will respect for finalizing withdrawals. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### useFaultProofs + +UseFaultProofs is a flag that indicates if the system is using fault proofs +instead of the older output oracle mechanism. + +* **Type:** Boolean +* **Default value:** None +* **Recommended value:** +* **Notes:** You should understand the implications of running a Fault Proof + chain. +* **Standard Config Requirement:** + +*** + +### Alt-DA Mode + +Alt-DA Mode enables seamless integration of various Data Availability (DA) +Layers, regardless of their commitment type, into the OP Stack. This allows +any chain operator to launch an OP Stack chain using their favorite DA Layer +for sustainably low costs. Learn more [here](/op-stack/features/experimental/alt-da-mode). + +*** + +#### useAltDA + +UseAltDA is a flag that indicates if the system is using op-alt-da + +* **Type:** bool +* **Recommended value:** +* **Default value:** None +* **Notes:** +* **Standard Config Requirement:** Non-standard feature. + +*** + +#### daCommitmentType + +DACommitmentType specifies the allowed commitment + +* **Type:** string +* **Default value:** None +* **Notes:** DACommitmentType must be either KeccakCommitment or +GenericCommitment. However, KeccakCommitment will be deprecated soon. +* **Recommended value:** GenericCommitment +* **Standard Config Requirement:** Non-standard feature. + +*** + +#### daChallengeWindow + +DAChallengeWindow represents the block interval during which the availability +of a data commitment can be challenged. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** DAChallengeWindow must not be 0 when using altda mode +* **Standard Config Requirement:** Non-standard feature. + +*** + +#### daResolveWindow + +DAResolveWindow represents the block interval during which a data availability +challenge can be resolved. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** DAChallengeWindow must not be 0 when using altda mode +* **Standard Config Requirement:** Non-standard feature. + +*** + +#### daBondSize + +DABondSize represents the required bond size to initiate a data availability +challenge. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** Non-standard feature. + +*** + +#### daResolverRefundPercentage + +DAResolverRefundPercentage represents the percentage of the resolving cost to +be refunded to the resolver such as 100 means 100% refund. + +* **Type:** Number +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** Non-standard feature. + +*** + +#### daChallengeProxy + +DAChallengeProxy represents the L1 address of the DataAvailabilityChallenge +contract. + +* **Type:** Address +* **Default value:** None +* **Recommended value:** +* **Notes:** Must not be address(0) when using altda mode +* **Standard Config Requirement:** Non-standard feature. + +*** + +### Interoperability + +*** + +#### useInterop + +UseInterop is a flag that indicates if the system is using interop. + +* **Type:** boolean +* **Default value:** None +* **Recommended value:** false +* **Notes:** Interoperability is still [experimental](https://specs.optimism.io/interop/overview.html?utm_source=op-docs&utm_medium=docs). +* **Standard Config Requirement:** Non-standard feature. + +*** + +### Governance + +*** + +#### enableGovernance + +EnableGovernance determines whether to include governance token predeploy. + +* **Type:** boolean +* **Default value:** None +* **Recommended value:** false +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### governanceTokenSymbol + +GovernanceTokenSymbol represents the ERC20 symbol of the GovernanceToken. + +* **Type**: string +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### governanceTokenName + +GovernanceTokenName represents the ERC20 name of the GovernanceToken + +* **Type**: string +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### governanceTokenOwner + +GovernanceTokenOwner represents the owner of the GovernanceToken. Has the +ability to mint and burn tokens. + +* **Type**: L2 Address +* **Default value:** None +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +### Miscellaneous + +*** + +#### fundDevAccounts + +FundDevAccounts determines whether to fund the dev accounts. Should only +be used during devnet deployments. + +* **Type**: Boolean +* **Default value:** +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### requiredProtocolVersion + +RequiredProtocolVersion indicates the protocol version that nodes are +recommended to adopt, to stay in sync with the network. + +* **Type**: String +* **Default value:** +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +#### recommendedProtocolVersion + +RecommendedProtocolVersion indicates the protocol version that nodes are +recommended to adopt, to stay in sync with the network. + +* **Type**: String +* **Default value:** +* **Recommended value:** +* **Notes:** +* **Standard Config Requirement:** + +*** + +### Deprecated + +*** + +#### (**DEPRECATED**) gasPriceOracleScalar + +GasPriceOracleScalar represents the initial value of the gas scalar in the +GasPriceOracle predeploy. Deprecated: Since Ecotone, this field is superseded +by GasPriceOracleBaseFeeScalar and GasPriceOracleBlobBaseFeeScalar. + +*** + +#### (**DEPRECATED**) gasPriceOracleOverhead + +GasPriceOracleOverhead represents the initial value of the gas overhead in the +GasPriceOracle predeploy. Deprecated: Since Ecotone, this field is superseded +by GasPriceOracleBaseFeeScalar and GasPriceOracleBlobBaseFeeScalar. + +*** + +#### (**DEPRECATED**) deploymentWaitConfirmations + +DeploymentWaitConfirmations is the number of confirmations to wait during +deployment. This is DEPRECATED and should be removed in a future PR. + +*** + +#### (**DEPRECATED**) numDeployConfirmations + +Number of confirmations to wait when deploying smart contracts to L1. diff --git a/docs/public-docs/chain-operators/guides/features/alt-da-mode-guide.mdx b/docs/public-docs/chain-operators/guides/features/alt-da-mode-guide.mdx new file mode 100644 index 0000000000000..c8a38dd64a147 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/alt-da-mode-guide.mdx @@ -0,0 +1,219 @@ +--- +title: How to run an Alt-DA mode chain +description: Learn how to configure and run an Alt-DA mode chain within the OP Stack. +--- + + + The Alt-DA Mode feature is currently in Beta within the MIT-licensed OP Stack. Beta features are built and reviewed by Optimism Collective core contributors, and provide developers with early access to highly requested configurations. + These features may experience stability issues, and we encourage feedback from our early users. + + +This guide provides a walkthrough for chain operators who want to run an Alt-DA Mode chain. See the [Alt-DA Mode Explainer](/op-stack/features/experimental/alt-da-mode) for a general overview of this OP Stack configuration. +An Alt-DA Mode OP Stack chain enables a chain operator to post and read data to any alternative data availability layer that has built a functioning OP Stack DA Server. + + + This page includes providers that meet specific [inclusion criteria](#inclusion-criteria), as outlined below. + + +## Prerequisite + +You should use at least the following compatible op\* versions when running your chain. + +* op-node/v1.9.1 +* op-proposer/v1.9.1 +* op-batcher/v1.9.1 +* [Latest version of op-geth](https://github.com/ethereum-optimism/op-geth/releases/latest) + + + + + DA Servers are not built or maintained by Optimism Collective Core Contributors. DA servers are maintained by third parties and run at your own risk. Please reach out to the team who built the DA Server you are trying to run with any questions or issues. + + + * Celestia's docs on how to run the [Celestia DA server](https://github.com/celestiaorg/op-plasma-celestia/blob/main/README.md) + * EigenDA's docs on how to run the [EigenDA DA server](https://github.com/Layr-Labs/op-plasma-eigenda/blob/main/README.md) + * Avail's docs on how to run the [AvailDA DA Server](https://docs.availproject.org/docs/build-with-avail/deploy-rollup-on-avail/Optimium) + * 0gDA's docs on how to run the [0gDA DA Server](https://github.com/0glabs/0g-da-op-plasma) + * Near DA's docs on how to run the [Near DA Server](https://github.com/Nuffle-Labs/data-availability/blob/84b484de98f58a91bf12c8abe8df27f5e753f63a/docs/OP-Alt-DA.md) + + + + * Spin up your OP chain as usual but set `--altda.enabled=true` and point both `op-batcher` and `op-node` to the DA server. + * No configuration changes are required for `op-geth` or `op-proposer`. + + ``` + Alt-DA (EXPERIMENTAL) + + + --altda.da-server value ($OP_NODE_ALTDA_DA_SERVER) + HTTP address of a DA Server + + --altda.enabled (default: false) ($OP_NODE_ALTDA_ENABLED) + Enable Alt-DA mode + + --altda.verify-on-read (default: true) ($OP_NODE_ALTDA_VERIFY_ON_READ) + Verify input data matches the commitments from the DA storage service + + ``` + + + + * Set `--altda.enabled=true` and `--altda.da-service=true`. + * Provide the URL for `--altda.da-server=$DA_SERVER_HTTP_URL`. + + ``` + --altda.da-server value ($OP_BATCHER_ALTDA_DA_SERVER) + HTTP address of a DA Server + + --altda.da-service (default: false) ($OP_BATCHER_ALTDA_DA_SERVICE) + Use DA service type where commitments are generated by the DA server + + --altda.enabled (default: false) ($OP_BATCHER_ALTDA_ENABLED) + Enable Alt-DA mode + + --altda.verify-on-read (default: true) ($OP_BATCHER_ALTDA_VERIFY_ON_READ) + Verify input data matches the commitments from the DA storage service + ``` + + After completing steps 1-3 above, you will have an Alt-DA mode chain up and running. + + + + * Chain operators are not posting everything to Ethereum, just commitments, so chain operators will need to determine fee scalars values to charge users. The fee scalar values are network throughput dependent, so values will need to be adjusted by chain operators as needed. + * Cost structure for Alt-DA Mode: The transaction data is split up into 128kb chunks and then submitted to your DA Layer. Then, 32 byte commitments are submitted (goes to batch inbox address) to L1 for each 128kb chunk. Then, figure out how much that costs relative to the number of transactions your chain is putting through. + * Set scalar values inside the deploy config. The example below shows some possible fee scalar values, calculated assuming negligible DA Layer costs, but will need to be adjusted up or down based on network throughput - as a reminder of how to set your scalar values, see [this section](/chain-operators/guides/features/blobs#update-your-scalar-values-for-blobs) of the docs. + + ``` + // Set in Deploy Config + "gasPriceOracleBaseFeeScalar": 7663, // Approximate commitment tx base cost + "gasPriceOracleBlobBaseFeeScalar": 0, // blobs aren't used for submitting the small data commitments + ``` + + + Some initial scalar values must be set early on in the deploy config in [Step 2](#configure-your-op-node). And then at a later point, chain operators can update the scalar values with an L1 transaction. + + + + +## For node operators (full and archive nodes) + +* Run a DA server as laid out in [Step 1](#setup-your-da-server) +* Provide the same `--altda.enabled=true, --altda.da-server...` on `op-node` as listed in [Step 2](#configure-your-op-node) + +## Inclusion criteria + +Alt DA teams who want to be featured on this page must meet the following criteria: + +* Functional [DA Server](https://specs.optimism.io/experimental/alt-da.html#da-server?utm_source=op-docs&utm_medium=docs), maintained in your own repo +* Supporting detailed documentation, to be referenced [here](#setup-your-da-server) +* Functioning OP Stack devnet using your DA server with linked configuration, contract addresses, and RPC address + +## Breaking changes: renaming Plasma Mode to Alt-DA Mode + +This feature has been renamed from Plasma Mode to Alt-DA Mode in the monorepo at: [0bb2ff5](https://github.com/ethereum-optimism/optimism/commit/0bb2ff57c8133f1e3983820c0bf238001eca119b). There are several breaking changes you should be aware of. These include changes to configuration file parameters, environment variables, and CLI commands. +Before proceeding with the migration, ensure you are using [OP Stack v1.9.1](https://github.com/ethereum-optimism/optimism/releases/tag/v1.9.1) or later. + +### Modify `rollup.json` config + +Update your `rollup.json` configuration file by replacing the old Plasma config with the new Alt-DA config. +There are two possible formats for the old Plasma config: + +### Legacy plasma config + +If your config looks like this: + +```json +"use_plasma": true, +"da_commitment_type": "GenericCommitment", +"da_challenge_contract_address": "0xAAA", +"da_challenge_window": 1000, +"da_resolve_window": 2000, +``` + +### Recent plasma config + +Or if it looks like this: + +```json +"plasma_config": { + "da_commitment_type": "GenericCommitment", + "da_challenge_contract_address": "0xAAA", + "da_challenge_window": 1000, + "da_resolve_window": 2000 +} +``` + +### New Alt-DA config + +Replace either of the above configurations with: + +```json +"alt_da": { + "da_commitment_type": "GenericCommitment", + "da_challenge_contract_address": "0xAAA", + "da_challenge_window": 1000, + "da_resolve_window": 2000 +} +``` + + + Only include fields in the new config that were present in your old config. + + +## Updating OP Stack runtime config parameters + +### CLI parameters + +Update the following CLI parameters for both `op-node` and `op-batcher`: + +| Former CLI param | Current CLI param | +| ------------------------- | ------------------------ | +| `--plasma.enabled` | `--altda.enabled` | +| `--plasma.da-server` | `--altda.da-server` | +| `--plasma.verify-on-read` | `--altda.verify-on-read` | +| `--plasma.da-service` | `--altda.da-service` | + +### Environment variables + +#### op-node + +| Former env var | Current env var | +| ------------------------------- | ------------------------------ | +| `OP_NODE_PLASMA_ENABLED` | `OP_NODE_ALTDA_ENABLED` | +| `OP_NODE_PLASMA_DA_SERVER` | `OP_NODE_ALTDA_DA_SERVER` | +| `OP_NODE_PLASMA_VERIFY_ON_READ` | `OP_NODE_ALTDA_VERIFY_ON_READ` | +| `OP_NODE_PLASMA_DA_SERVICE` | `OP_NODE_ALTDA_DA_SERVICE` | + +#### op-batcher + +| Former env var | Current env var | +| ---------------------------------- | --------------------------------- | +| `OP_BATCHER_PLASMA_ENABLED` | `OP_BATCHER_ALTDA_ENABLED` | +| `OP_BATCHER_PLASMA_DA_SERVER` | `OP_BATCHER_ALTDA_DA_SERVER` | +| `OP_BATCHER_PLASMA_VERIFY_ON_READ` | `OP_BATCHER_ALTDA_VERIFY_ON_READ` | +| `OP_BATCHER_PLASMA_DA_SERVICE` | `OP_BATCHER_ALTDA_DA_SERVICE` | + +#### op-alt-da (formerly op-plasma) daserver + +| Former env var | Current env var | +| ------------------------------------------ | -------------------------------------- | +| `OP_PLASMA_DA_SERVER_ADDR` | `OP_ALTDA_SERVER_ADDR` | +| `OP_PLASMA_DA_SERVER_PORT` | `OP_ALTDA_SERVER_PORT` | +| `OP_PLASMA_DA_SERVER_FILESTORE_PATH` | `OP_ALTDA_SERVER_FILESTORE_PATH` | +| `OP_PLASMA_DA_SERVER_GENERIC_COMMITMENT` | `OP_ALTDA_SERVER_GENERIC_COMMITMENT` | +| `OP_PLASMA_DA_SERVER_S3_BUCKET` | `OP_ALTDA_SERVER_S3_BUCKET` | +| `OP_PLASMA_DA_SERVER_S3_ENDPOINT` | `OP_ALTDA_SERVER_S3_ENDPOINT` | +| `OP_PLASMA_DA_SERVER_S3_ACCESS_KEY_ID` | `OP_ALTDA_SERVER_S3_ACCESS_KEY_ID` | +| `OP_PLASMA_DA_SERVER_S3_ACCESS_KEY_SECRET` | `OP_ALTDA_SERVER_S3_ACCESS_KEY_SECRET` | + +After making these changes, your system should be properly configured to use the new Alt-DA Mode. + + + Remember to thoroughly test your configuration in testnet before going mainnet. + + +## Next steps + +* Additional questions? See the FAQ section in the [Alt-DA Mode Explainer](/op-stack/features/experimental/alt-da-mode#faqs). +* For more detailed info on Alt-DA Mode, see the [specs](https://specs.optimism.io/experimental/alt-da.html?utm_source=op-docs&utm_medium=docs). +* If you experience any problems, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions). diff --git a/docs/public-docs/chain-operators/guides/features/blobs.mdx b/docs/public-docs/chain-operators/guides/features/blobs.mdx new file mode 100644 index 0000000000000..79bddf5532b6b --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/blobs.mdx @@ -0,0 +1,136 @@ +--- +title: Using Blobs +description: Learn how to switch to using blobs for your chain. +--- + +This guide walks you through how to switch to using blobs for your chain. +This feature was introduced in with the Ecotone network upgrade. + +## Switch to using blobs + + + + The first step to switching to submit data with Blobs is to calculate the + scalar values you wish to set for the formula to charge users fees. + To determine the scalar values to use for your chain, you can utilize this [fee parameter calculator](https://docs.google.com/spreadsheets/d/1V3CWpeUzXv5Iopw8lBSS8tWoSzyR4PDDwV9cu2kKOrs/edit) + to get a better estimate for scalar values on your chain. Input the average transaction per day your chain is processing, the types of transactions that occur on your chain, the [`OP_BATCHER_MAX_CHANNEL_DURATION`](/operators/chain-operators/configuration/batcher#setting-your--op_batcher_max_channel_duration) you have parameterized on your `op-batcher`, and the target margin you wish to charge users on top of your L1 costs. The following + information is tuned to a network like OP Mainnet. + For more details on fee scalar, see [Transaction Fees, Ecotone section](/op-stack/transactions/fees#ecotone). + + #### Adjust fees to change margins + + As a chain operator, you may want to scale your scalar values up or down either because the throughput of your chain has changed and you are either filling significantly more or less of blobs, or because you wish to simply increase your margin to cover operational expenses. + So, to increase or decrease your margin on L1 data costs, you would simply scale both the `l1baseFeeScalar` and the `l1blobBaseFeeScalar` by the same multiple. + + For example, if you wished to increase your margin on L1 data costs by \~10%, you would do: + + ``` + newBaseFeeScalar = prevBaseFeeScalar * 1.1 + newBlobBaseFeeScalar = prevBlobBaseFeeScalar * 1.1 + ``` + + + + Once you have determined your ideal `BaseFeeScalar` and `BlobBaseFeeScalar`, you will need to apply those values for your chain. The first step is to encode both values into a single value to be set in your L1 Config: + + You can set your Scalar Values to send transaction to the L1 SystemConfigProxy.setGasConfigEcotone + + ```bash + cast send \ + --private-key $GS_ADMIN_PRIVATE_KEY \ + --rpc-url $ETH_RPC_URL \ + \ + "setGasConfigEcotone(uint32,uint32)" \ + + ``` + + Check that the gas price oracle on L2 returns the expected values for `baseFeeScalar` and `blobBaseFeeScalar` (wait \~1 minute): + + + This is checked on L2, so ensure you are using an RPC URL for your chain. You'll also need to provide a `gas-price` to geth when making this call. + + + + + ```shell + cast call 0x420000000000000000000000000000000000000F 'baseFeeScalar()(uint256)' --rpc-url YOUR_L2_RPC_URL + ``` + + + + ```shell + cast call 0x420000000000000000000000000000000000000F 'blobBaseFeeScalar()(uint256)' --rpc-url YOUR_L2_RPC_URL + ``` + + + + + + Now that the fee config has been updated, you should immediately configure your batcher! + + + Your chain may be undercharging users during the time between updating the scalar values and updating the Batcher, so aim to do this immediately after. + + + Steps to configure the batcher: + + * Configure `OP_BATCHER_DATA_AVAILABILITY_TYPE=blobs`. The batcher will have to be restarted for it to take effect. + * Ensure your `OP_BATCHER_MAX_CHANNEL_DURATION` is properly set to maximize your fee savings. See [OP Batcher Max Channel Configuration](/operators/chain-operators/configuration/batcher#set-your--op_batcher_max_channel_duration) for more details. + * Optionally, you can configure your batcher to support multi-blobs. See [Multi-Blob Batcher Configuration](/operators/chain-operators/configuration/batcher#configure-your-batcher-to-use-multiple-blobs) for more details. + + + +## Switch back to using calldata + +As a chain operator, if the `blobBaseFee` is expensive enough and your chain is +not processing enough transactions to meaningfully fill blobs within your +configured batcher `OP_BATCHER_MAX_CHANNEL_DURATION`, you may wish to switch +back to posting data to calldata. Utilize the [fee parameter calculator](https://docs.google.com/spreadsheets/d/12VIiXHaVECG2RUunDSVJpn67IQp9NHFJqUsma2PndpE/edit) to inform whether your transactions will be cheaper if submitting blobs or if submitting calldata. Chains can follow these steps to switch from +blobs back to using calldata. + + + + If you are using calldata, then you can set your `BaseFeeScalar` similarly to + how you would have set "scalar" prior to Ecotone, though with a 5-10% bump to + compensate for the removal of the "overhead" component. + You can utilize this [fee parameter calculator](https://docs.google.com/spreadsheets/d/12VIiXHaVECG2RUunDSVJpn67IQp9NHFJqUsma2PndpE/edit) + to get a better estimate for scalar values on your chain. The following + information is tuned to a network like OP Mainnet. + + + Since the Pectra upgrade on L1, chains which exclusively use calldata DA need to scale up their BaseFeeScalar by 10/4. See [this notice](/notices/archive/pectra-changes). + + + Chains can update their fees to increase or decrease their margin. If using calldata, then `BaseFeeScalar` should be scaled to achieve the desired margin. + For example, to increase your L1 Fee margin by 10%: + + ``` + BaseFeeScalar = BaseFeeScalar * 1.1 + BlobBaseFeeScalar = 0 + ``` + + + + To set your scalar values, follow the same process as laid out in [Update your Scalar values for Blobs](#update-your-scalar-values-for-blobs). + + + + Now that the fee config has been updated, you will want to immediately configure your batcher. + + + Reminder, that your chain may be undercharging users during the time between updating the scalar values and updating the Batcher, so aim to do this immediately after. + + + * Configure `OP_BATCHER_DATA_AVAILABILITY_TYPE=calldata`. The batcher will have to be restarted for it to take effect. + * Ensure your `OP_BATCHER_MAX_CHANNEL_DURATION` is properly set to maximize savings. **NOTE:** While setting a high value here will lower costs, it will be less meaningful than for low throughput chains using blobs. See [OP Batcher Max Channel Configuration](/operators/chain-operators/configuration/batcher#set-your--op_batcher_max_channel_duration) for more details. + + + +## Use auto DA mode in your batcher +The batcher now supports automatically switching from blobs to calldata depending on which DA type is more affordable. This is an optimization which allows for a slightly better DA profit margin for your chain. +To enable this mode, set `OP_BATCHER_DATA_AVAILABILITY_TYPE=auto`. + +## Other considerations + +* For information on L1 Data Fee changes related to the Ecotone upgrade, visit the [Transaction Fees page](/op-stack/transactions/fees#ecotone). +* If you want to enable archive nodes, you will need to access a blob archiver service. You can use [Optimism's](/operators/node-operators/management/snapshots#op-mainnet-archive-node) or [run your own](/operators/chain-operators/tools/explorer#create-an-archive-node). diff --git a/docs/public-docs/chain-operators/guides/features/custom-gas-token-guide.mdx b/docs/public-docs/chain-operators/guides/features/custom-gas-token-guide.mdx new file mode 100644 index 0000000000000..16e5ee074bf6a --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/custom-gas-token-guide.mdx @@ -0,0 +1,253 @@ +--- +title: Deploy a Custom Gas Token chain +description: Learn how to deploy a Custom Gas Token chain using OP Deployer. +--- + +This guide provides instructions for chain operators who want to deploy a Custom Gas Token (CGT) chain. +See the [Custom Gas Token overview](/op-stack/features/custom-gas-token) for a general understanding of this OP Stack feature. + +A Custom Gas Token chain enables you to use any asset as the native fee currency instead of ETH. +This asset may be an existing L1 token, a representation of a token from another chain, or a newly defined asset (or new L2 token) created at genesis. + + +Custom Gas Token v2 is a new implementation that is not compatible with legacy CGT chains. +There is currently no migration path from legacy CGT to CGT v2, though one is planned to be put together. + + +## Prerequisites + +Before deploying a CGT chain, ensure you have: + +* **OP Deployer installed**: See [OP Deployer installation](/chain-operators/tools/op-deployer/installation) for setup instructions +* **L1 RPC endpoint**: Access to an Ethereum L1 node for deployment +* **Funded deployer account**: ETH for L1 gas costs during deployment +* **Token design**: Clear plan for your native asset (supply, distribution, bridging mechanism) +* **Security audit**: Thorough review of any authorized minters or bridge contracts you plan to deploy + +## Deployment steps + +Deploying a CGT chain follows the standard OP Stack deployment process with additional configuration for Custom Gas Token specific parameters. + + + + Create a new intent file using OP Deployer's [init command](/chain-operators/tools/op-deployer/usage/init): + + ```bash + op-deployer init \ + --l1-chain-id \ + --l2-chain-ids \ + --intent-type custom + ``` + + This creates an `intent.toml` file. + + + + Open your `intent.toml` file and add the Custom Gas Token configuration. Below is an example with CGT-specific fields: + + ```toml + configType = "custom" + l1ChainID = 11155111 + fundDevAccounts = false + useInterop = false + l1ContractsLocator = "tag://op-contracts/v6.0.0" + l2ContractsLocator = "tag://op-contracts/v6.0.0" + + [superchainRoles] + SuperchainProxyAdminOwner = "0xYourMultisigAddress" + SuperchainGuardian = "0xYourMultisigAddress" + ProtocolVersionsOwner = "0xYourMultisigAddress" + Challenger = "0xYourMultisigAddress" + + [[chains]] + id = "0x0000000000000000000000000000000000000000000000000000000000001234" + baseFeeVaultRecipient = "0xYourFeeRecipientAddress" + l1FeeVaultRecipient = "0xYourFeeRecipientAddress" + sequencerFeeVaultRecipient = "0xYourFeeRecipientAddress" + operatorFeeVaultRecipient = "0xYourFeeRecipientAddress" + eip1559DenominatorCanyon = 0 + eip1559Denominator = 0 + eip1559Elasticity = 0 + gasLimit = 60000000 + operatorFeeScalar = 0 + operatorFeeConstant = 0 + chainFeesRecipient = "0x0000000000000000000000000000000000000000" + minBaseFee = 0 + daFootprintGasScalar = 0 + + [chains.roles] + l1ProxyAdminOwner = "0xYourMultisigAddress" + l2ProxyAdminOwner = "0xYourMultisigAddress" + systemConfigOwner = "0xYourMultisigAddress" + unsafeBlockSigner = "0xYourSequencerAddress" + batcher = "0xYourBatcherAddress" + proposer = "0xYourProposerAddress" + challenger = "0xYourChallengerAddress" + + [chains.customGasToken] + name = "My Custom Token" + symbol = "MCT" + initialLiquidity = "0x..." # optional, default: type(uint248).max + liquidityControllerOwner = "0x..." # optional, default: L2ProxyAdminOwner + ``` + + ### Key CGT configuration fields + + | Field | Description | Required | + |-------|-------------|----------| + | `name` | Name of the native asset (e.g., "My Custom Token") | Yes | + | `symbol` | Symbol for the native asset (e.g., "MCT") | Yes | + | `initialLiquiditySupply` | Initial supply minted to NativeAssetLiquidity at genesis | No, default: type(uint248).max | + | `liquidityControllerOwner` | Manages the asset supply | No, `L2ProxyAdminOwner` will be used as default | + + + **Fee parameter calculation is critical**: Your `minBaseFee` and `operatorFee` must accurately account for L1 gas costs and DA fees denominated in your custom token. If set too low, your chain may not cover operational costs. If set too high, users will pay excessive fees. + + + + **Decimal support**: CGT v2 currently supports only 18-decimal tokens. Support for other decimals may be added in future releases. + + + + + Deploy your L1 contracts using OP Deployer's [apply command](/chain-operators/tools/op-deployer/usage/apply) + + ```bash + op-deployer apply \ + --l1-rpc-url \ + --private-key + ``` + + This deploys all necessary L1 contracts including `SystemConfig` with the `isCustomGasToken` flag enabled, which instructs L1 contracts to reject ETH-value transactions. + + + The deployment process will automatically deploy the CGT-specific predeploys (`NativeAssetLiquidity` and `LiquidityController`) during genesis initialization. + + + + + Optionally you can use OP Deployer's [verify command](/chain-operators/tools/op-deployer/usage/verify) to verify your L1 contracts on Blockscout or Etherscan. + + + + After L1 deployment completes, initialize your L2 genesis with OP Deployer's [apply command](/chain-operators/tools/op-deployer/usage/apply): + + ```bash + op-deployer apply \ + --deployment-target genesis + ``` + + The genesis configuration is applied based on your `initent.toml`. + + + + Start your OP Stack services to start sequencing your CGT chain: + + * Sequencer Execution Client + * Sequencer Consensus Client + * Batcher + * Proposer + * Challenger + + + + + Before going live, thoroughly test your CGT chain. The following are some key areas to check: + + **Verify flag alignment** + * Check L1 SystemConfig.isCustomGasToken() returns true + * Check L2 L1Block.isCustomGasToken() returns true + * Verify both flags match + + **Test native asset operations** + * Test minting native assets via authorized minter + * Test burning native assets + * Test fee payments in native token + * Verify fee vault accumulation + + + **Test ETH rejection** + * Attempt ETH deposit via OptimismPortal (should fail) + * Attempt ETH withdrawal via L2ToL1MessagePasser (should fail) + * Verify ETH operations are properly blocked + + + **Test bridge functionality** + + * Test deposits (L1 → L2 native asset minting) + * Test withdrawals (L2 native → L1 token) + * Verify proper locking/unlocking in liquidity contract + + + + +## Post-deployment considerations + +### Supply management + +After deployment, you can: + +* **Withdraw excess liquidity**: If genesis created more supply than needed, withdraw and burn via `L2ToL1MessagePasser` +* **Add new minters**: Authorize additional contracts to mint native assets as your ecosystem grows +* **Revoke minters**: Remove authorization from compromised or deprecated contracts +* **Implement rate limits**: Add safeguards to control minting velocity + +### Fee parameter adjustments + +Monitor your chain's operational costs and adjust fee parameters as needed: + +* **minBaseFee**: Adjust based on L1 gas costs and your token's value +* **operatorFee**: Adjust based on data availability costs +* Use `SystemConfig.setGasConfig()` to update parameters + +### Developer documentation + +Create clear documentation for your users covering: + +* How to acquire native assets (bridge, DEX, faucet, etc.) +* Bridge contract addresses and interfaces +* Fee structure and token economics +* Wallet configuration (RPC endpoints, chain ID, token metadata) + +## Troubleshooting + +### Flag mismatch errors + +**Symptom**: Transactions failing with "custom gas token mismatch" errors + +**Solution**: Verify that `SystemConfig.isCustomGasToken()` on L1 and `L1Block.isCustomGasToken()` on L2 return the same value. If mismatched, this indicates a critical configuration error. + +### Fee parameter issues + +**Symptom**: Chain operator losing money on transaction costs or users complaining about excessive fees + +**Solution**: Review and recalculate your `minBaseFee` and `operatorFee` parameters based on: +* Current L1 gas prices +* Your token's market value or peg +* Data availability costs +* Target fee structure for users + +### Liquidity depletion + +**Symptom**: Minting transactions failing due to insufficient liquidity + +**Solution**: +* Check `NativeAssetLiquidity` balance +* If depleted, this indicates an imbalance between minting and burning +* Review bridge logic to ensure burns are occurring correctly +* Consider increasing initial liquidity supply in future deployments + +### Unauthorized minting attempts + +**Symptom**: Unauthorized addresses attempting to mint native assets + +**Solution**: +* Review access control configuration on `LiquidityController` +* Ensure only audited and secured contracts are authorized +* Implement rate limiting if not already in place +* Consider revoking and re-authorizing with additional safeguards + +## Resources + +* [Custom Gas Token feature overview](/op-stack/features/custom-gas-token) +* [OP Deployer documentation](/chain-operators/tools/op-deployer/overview) diff --git a/docs/public-docs/chain-operators/guides/features/flashblocks-guide.mdx b/docs/public-docs/chain-operators/guides/features/flashblocks-guide.mdx new file mode 100644 index 0000000000000..4c2965af64805 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/flashblocks-guide.mdx @@ -0,0 +1,135 @@ +--- +title: "Flashblocks: A Guide for Chain Operators" +description: "Technical guide for chain operators on Flashblocks, covering components, operation, transaction flow, and leader selection." +--- + +Flashblocks is a `rollup-boost` module that accelerates confirmation times by dividing block construction into smaller, incremental sections. +This enables pre-confirmations and improves user experience, while finalization still occurs at standard block intervals. + +The Flashblocks setup involves three main components: + +* **`rollup-boost`**: Coordination layer with Flashblocks enabled +* **`op-rbuilder`**: Execution client and builder with Flashblocks support +* **`op-geth`**: Fallback builder, a standard EL node (can be `op-reth` as well) + +Flashblocks are streamed from the `op-rbuilder` to `rollup-boost` over WebSockets, minimizing latency between the sequencer and the pre-confirmed state. + +For full details on design choices, data structures, and invariants, see the [Flashblocks specification](https://specs.optimism.io/protocol/flashblocks.html). + +## Flashblocks components + +Flashblocks relies on several components working together: + +* **`op-node`**: Consensus layer, initiates `engine_forkchoiceUpdated` calls and leads block building. +* **`op-geth`**: Default block builder, produces standard blocks. +* **`op-rbuilder`**: Reth-based execution client that builds both standard blocks and flashblocks. + * Exposes flashblocks on a WebSocket stream (not recommended for public exposure). + * Each event on the stream corresponds to a 250 ms flashblock. +* **`rollup-boost`**: Sits between `op-node` and execution layers, coordinating block building in flashblocks mode. + * Validates `op-rbuilder` payloads against `op-geth` + * Falls back to `op-geth` if `op-rbuilder` diverges or lags +* **`flashblocks-websocket-proxy`**: Relays the flashblocks stream from the active sequencer to RPC providers. +* **`op-node-rbuilder`** *(optional)*: `op-node` pointing only to `op-rbuilder`, used for syncing at startup. +* **`op-conductor`** *(optional but recommended)*: Manages multiple sequencers, ensuring only one healthy leader streams blocks. + +See the [System architecture section](https://specs.optimism.io/protocol/flashblocks.html#system-architecture) to learn more. + +## Flashblocks lifecycle + +```mermaid +flowchart LR + subgraph Sequencer + ON[OP Node] + RB[Rollup Boost] + FEL[Fallback EL] + BB[Block Builder] + end + + subgraph Network + WSP[WebSocket Proxy] + end + + subgraph Clients + RPC[RPC Providers] + Users[End Users] + end + + ON --> RB + RB --> FEL + RB <--> BB + RB --> WSP + WSP --> RPC + RPC --> Users +``` + +* Single-sequencer setup. +* `op-node` coordinates block production through `rollup-boost`, targeting `op-rbuilder` instead of `op-geth`. +* `op-rbuilder` acts as a full execution client: it builds blocks, exposes Ethereum JSON-RPC, and processes transactions. +* In addition, `op-rbuilder` emits flashblocks every 250 ms, streamed over a WebSocket interface (e.g. `wss://`). +* These flashblocks give early visibility into transaction inclusion before the final regular block is sealed. + +For full details on construction rules, validation, and lifecycle steps, see [Flashblocks lifecycle in the specification](https://specs.optimism.io/protocol/flashblocks.html#flashblock-lifecycle). + +## How to set up and run rollup-boost + +Flashblocks relies on `rollup-boost` as the coordination layer for block building. +To run Flashblocks, you'll configure `rollup-boost` alongside your sequencer and execution clients. + +* [Running rollup-boost locally](https://rollup-boost.flashbots.net/operators/local.html) +* [Running rollup-boost in production](https://rollup-boost.flashbots.net/operators/production.html) +* [HA setup for rollup-boost](https://rollup-boost.flashbots.net/operators/ha-setup.html) + +### Single‑sequencer setup + +As suggested in the above links, in a single-sequencer setup, Flashblocks are streamed from `rollup-boost` (or `op-rbuilder`) to `flashblocks-websocket-proxy` by setting the following environment variable in flashblocks-websocket-proxy: + +```jsx +UPSTREAM_WS: ws:///ws +``` + +### HA‑compliant multi‑sequencer setup + +While Flashblocks can be enabled in a single-sequencer setup, we **highly recommend running a high-availability (HA) multi-sequencer setup** managed by [`op-conductor`](/chain-operators/tools/op-conductor). + +In an HA setup, multiple `op-conductor` instances form a [Raft](https://raft.github.io/) group. At any time, only one healthy sequencer acts as the active leader responsible for block building, while others remain in follower mode. Leadership changes automatically if the active sequencer becomes unhealthy. + +For Flashblocks, each sequencer (leader and followers) runs its own dedicated components, including `rollup-boost` and `op-rbuilder`. +Only the leader's `op-rbuilder` produces flashblocks; follower instances remain idle. + +In this setup, the connection between `rollup-boost` and the `flashblocks-websocket-proxy` is mediated by `op-conductor`. + +* `op-conductor` listens to Flashblocks from `rollup-boost` (or `op-rbuilder`). +* If it is the active leader, it forwards the Flashblocks to `flashblocks-websocket-proxy`. +* If it is not the leader, it does not forward anything. + +The rest of the data flow remains unchanged. + +### HA configuration + +**1. Configure `op-conductor`** to listen for Flashblocks and forward them if leader: + +```yaml +OP_CONDUCTOR_WEBSOCKET_SERVER_PORT: "8546" +OP_CONDUCTOR_ROLLUPBOOST_WS_URL: ws:///ws +OP_CONDUCTOR_ROLLUP_BOOST_ENABLED: "true" +OP_CONDUCTOR_EXECUTION_RPC: http://:8551 +``` + +* **Variable descriptions:** + * `OP_CONDUCTOR_WEBSOCKET_SERVER_PORT`: Port where `op-conductor` exposes Flashblocks if it is the leader. For example: `ws://:8546/ws`. + * `OP_CONDUCTOR_ROLLUPBOOST_WS_URL`: Direct URL of `rollup-boost` (or `op-rbuilder`) where Flashblocks are available. In a single-sequencer setup, this is the same URL you'd pass directly to `flashblocks-websocket-proxy`. + * `OP_CONDUCTOR_ROLLUP_BOOST_ENABLED`: Enables health checks for `rollup-boost` (and indirectly `op-rbuilder`) so leadership can fail over if either becomes unhealthy. + * `OP_CONDUCTOR_EXECUTION_RPC`: Execution RPC URL of `rollup-boost`. Same as `OP_NODE_L2_ENGINE_RPC` configured on `op-node`. + +**2. Configure `flashblocks-websocket-proxy`** to consume Flashblocks from all sequencer conductors: + +```yaml +UPSTREAM_WS: ws://:8546/ws,ws://:8546/ws,ws://:8546/ws +``` + +This way, the proxy always connects to the active leader via its `op-conductor`. + +Optional rate limits for `flashblocks-websocket-proxy`: + +* `PER_IP_CONNECTIONS_LIMIT`: Max connections allowed per client IP. +* `INSTANCE_CONNECTION_LIMIT`: Max total connections allowed per proxy instance. diff --git a/docs/public-docs/chain-operators/guides/features/setting-da-footprint.mdx b/docs/public-docs/chain-operators/guides/features/setting-da-footprint.mdx new file mode 100644 index 0000000000000..f2672c4707c1b --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/setting-da-footprint.mdx @@ -0,0 +1,136 @@ +--- +title: Set the DA Footprint Gas Scalar +description: Learn how to set a Data Availability (DA) Footprint on your OP-Stack Chain +--- + +## Overview + +| Parameter | Type | Default | Recommended | Units | +| ---------- | ---- | -------- | ------------ | ------------ | +| `daFootprintGasScalar` | Number | `0` (`400`) | `400` | gas per byte | + + +The default value of `0` is the same as setting the scalar to `400`. In order to effectively disable this feature, set the scalar to a very low value such as `1`. +See the [specs for more detail](https://specs.optimism.io/protocol/jovian/l1-attributes.html). + + +The Data Availability (DA) Footprint Block Limit was introduced in the [Jovian hardfork](https://docs.optimism.io/notices/upgrade-17) to limit the total amount of transaction data that can fit into a block based on a scaled estimate of the compressed size (or "data availability footprint") of that data. +When an OP Stack chain receives more calldata-heavy transactions than can fit into the L1s available blob space, the [Batcher can throttle the chains throughput](https://docs.optimism.io/chain-operators/guides/configuration/batcher#batcher-sequencer-throttling). + +However, continuous batcher throttling may cause the base fee to drop to the [minimum base fee](https://docs.optimism.io/chain-operators/guides/features/setting-min-base-fee), +causing unnecessary losses for the chain operator and negative user experiences such as priority fee auctions. +And without throttling, the batcher runs the risk of becoming overwhelmed with chain data to batch to the blob space. +Limiting the amount of (estimated compressed) calldata taken up by transactions in a block using their total DA Footprint can reduce the need for batcher throttling and its related issues. + +## DA Footprint Calculation + +For all [non-deposit transactions](https://docs.optimism.io/reference/glossary#deposited-transaction) processed by an OP Stack chain, a DA Footprint value is recorded alongside the transaction's gas usage. +The DA Footprint can be configured via the `daFootprintGasScalar` variable in the `SystemConfig` contract on the L1 chain. +The DA Footprint is automatically calculated for every transaction first by calculating a `daUsageEstimate` for that transaction: + +```python +daUsageEstimate = max( + minTransactionSize, + (intercept + fastlzCoef * tx.fastlzSize) // 1e6 + ) +``` + +where the `minTransactionSize`, `intercept`, `fastlzCoef`, and `tx.fastlzSize` are as specified in the [Fjord specs](https://specs.optimism.io/protocol/fjord/exec-engine.html#fees). +Then the `daUsageEstimate` is then multiplied by the `daFootprintGasScalar` to get the `daFootprint` for that individual transaction. + +```python + daFootprint += daUsageEstimate * daFootprintGasScalar +``` + +The `daFootprint` for all the transactions in a block are then added together to calculate that block's total `daFootprint`. +With the block's total `daFootprint` calculated: + + +- The block's total `daFootprint` must stay below its `gasLimit`. +- The `blobGasUsed` property of each block header is set to that block's `daFootprint`. +- The base fee update calculation then uses `gasMetered := max(gasUsed, blobGasUsed)` as a replacement for the `gasUsed` variable. + + +From Jovian, transaction receipts also record the transaction's DA Footprint in the receipt's `blobGasUsed` field, as well as the block's `daFootprintGasScalar` in a new field with the same name. + +## The DA Footprint Gas Scalar + + + - If no limit is set, or the value 0 is set for the `daFootprintGasScalar` in the `SystemConfig`, the default value of `400` is used + - In order to effectively disable this feature, set the scalar to a very low value such as `1`. + - Setting the `daFootprintGasScalar` too high may exclude too many transactions from your blocks. + - Setting the `daFootprintGasScalar` too low may prove ineffective at preventing batcher throttling or protecting against continuous DA heavy transactions. + - When a chain's gas limit is changed, the DA footprint scales proportionally by design. However, if you want to retain the same absolute DA footprint limit, then you must also scale the `daFootprintGasScalar` accordingly. + + +The _DA footprint gas scalar_ scales the _estimated DA usage in bytes_ to the gas dimension, and this scaled estimate of the DA usage is what we call the _DA footprint_. +This allows us to limit the estimated DA usage using the block's `gasLimit`: the effective limit of estimated DA usage per block is `gasLimit / daFootprintGasScalar` bytes. +So _increasing_ this scalar makes DA usage more gas-heavy, so _decreases_ the limit, and vice versa. + +This is closely related to how the calldata (floor) cost of `40` gas per non-zero byte limits the total amount of calldata in a block. +A DA footprint gas scalar of `400` effectively limits _incompressible_ calldata by a factor of `10` compared to its limit without a DA footprint block limit. +As such, the feature can be seen as an extension of the existing calldata limit. +But instead of repricing the calldata (floor) gas cost, the limit is accounted in parallel to EVM execution gas, and is based on the more relevant FastLZ-based DA usage estimate instead of simply counting zero and non-zero bytes. + +### Default Value `400` + +The default scalar of `400` was chosen so that it protects chains in worst-case DA spam scenarios, but has negligible to no impact during normal operation. +Careful [analyses](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/da-footprint-block-limit.md) have been done to estimate the impact on current OP Stack chains and pick the right default. +Only high-throughput chains would occasionally even see a small impact from the resulting DA footprint limit (as slightly faster rising base fees). The DA footprint limit is mostly invisible. +On mid to low-throughput chains, the feature is expected to have no impact under normal usage conditions. +It acts more like an insurance to protect against worst-case _incompressible_ DA spam. + +### How to Update the `daFootprintGasScalar` + +The steps below explain how to update the `daFootprintGasScalar` parameter on-chain using the `SystemConfig` contract. + + + + The [SystemConfig](https://specs.optimism.io/protocol/system-config.html) contract stores configurable protocol parameters such as gas limits and fee settings. + You can find its proxy address in the [state.json generated by op-deployer](https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-deployer-setup#deploy-l1-contracts). + + + + + The [SystemConfig owner](/op-stack/protocol/privileged-roles) is the only address that can make these changes. + + To update the `daFootprintGasScalar`, call the following method on the `SystemConfig` contract: + + `setDAFootprintGasScalar(uint16 daFootprintGasScalar) external onlyOwner;` + + Example (using [cast](https://getfoundry.sh/cast/reference/cast/)) to double the scalar, so lower the effective DA usage limit by half: + + ```bash title="Cast" + cast send -r "daFootprintGasScalar(uint16)" 800 + ``` + + + + After the transaction confirms, verify the current value by calling the following getter method on the `SystemConfig` contract: + + `function daFootprintGasScalar() external view returns (uint16);` + + Example (using [cast](https://getfoundry.sh/cast/reference/cast/)): + + ```bash title="cast" + cast call -r "daFootprintGasScalar()" + ``` + + And on your L2 chain you can query the scalar at the [L1Block predeploy](https://specs.optimism.io/protocol/predeploys.html#l1block) to confirm the new scalar has been propagated to the chain: + + ```bash title="cast" + cast call -r 0x4200000000000000000000000000000000000015 "daFootprintGasScalar()" + ``` + + + +### + +--- + +## References + +* [DA Footprint Configuration Spec](https://specs.optimism.io/protocol/jovian/system-config.html#da-footprint-configuration) +* [Jovian Upgrade Spec](https://specs.optimism.io/protocol/jovian/overview.html) +* [SystemConfig Contract Spec](https://specs.optimism.io/protocol/system-config.html) +* [Design Doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/da-footprint-block-limit.md) diff --git a/docs/public-docs/chain-operators/guides/features/setting-min-base-fee.mdx b/docs/public-docs/chain-operators/guides/features/setting-min-base-fee.mdx new file mode 100644 index 0000000000000..7c857c81ae62a --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/setting-min-base-fee.mdx @@ -0,0 +1,70 @@ +--- +title: Set the Minimum Base Fee +description: Learn how to set a minimum base fee on your OP-Stack Chain +--- + +## Overview + +| Parameter | Type | Default | Recommended | Max Allowed for Standard Chains | Units | +| ---------- | ---- | -------- | ------------ | ------------ | ------ | +| minBaseFee | Number | 0 (disabled) | 100,000 | 10,000,000,000 | wei | + +The Minimum Base Fee (`minBaseFee`) configuration was introduced in the [Jovian hardfork](https://docs.optimism.io/notices/upgrade-17). +This feature allows chain operators to specify a minimum L2 base fee to which can help avoid excessively long priority fee auctions that can occur when the base fee falls too low. + +When [batcher-sequencer throttling is active](https://docs.optimism.io/chain-operators/guides/configuration/batcher#batcher-sequencer-throttling) for long enough and the minBaseFee isn't enabled (or is zero), the base fee can drop all the way down to 1 wei. It can take a long time to recover back to a stable base fee. + + +The steps below explain how to update the `minBaseFee` parameter on-chain using the SystemConfig contract. + + + Setting the `minBaseFee` too high may make transactions harder to include for users. + + + +## How to Update the Minimum Base Fee + + + + The [SystemConfig](https://specs.optimism.io/protocol/system-config.html) contract stores configurable protocol parameters such as gas limits and fee settings. + You can find its proxy address in the [state.json generated by op-deployer](https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-deployer-setup#deploy-l1-contracts). + + + + + Note that the owner of the `SystemConfig` contract is the [System Config Owner address](/op-stack/protocol/privileged-roles), so this transaction must be sent from that address. + + To update the minimum base fee, call the following method on the `SystemConfig` contract: + + `setMinBaseFee(uint64 minBaseFee) external onlyOwner;` + + Example (using [cast](https://getfoundry.sh/cast/reference/cast/)): + + ```bash title="cast" + cast send "setMinBaseFee(uint64)" 100000 + ``` + + + + After the transaction confirms, verify the current value by calling the following getter method on the `SystemConfig` contract: + + `function minBaseFee() external view returns (uint64);` + + Example (using [cast](https://getfoundry.sh/cast/reference/cast/)): + + ```bash title="cast" + cast call "minBaseFee()" + ``` + + + +--- + +## References + +* [Minimum Base Fee Configuration Spec](https://specs.optimism.io/protocol/jovian/system-config.html#minimum-base-fee-configuration) +* [Jovian Upgrade Spec](https://specs.optimism.io/protocol/jovian/overview.html) +* [SystemConfig Contract Spec](https://specs.optimism.io/protocol/system-config.html) +* [Design Doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/minimum-base-fee.md) + + diff --git a/docs/public-docs/chain-operators/guides/features/setting-operator-fee.mdx b/docs/public-docs/chain-operators/guides/features/setting-operator-fee.mdx new file mode 100644 index 0000000000000..b401f210e4ac3 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/setting-operator-fee.mdx @@ -0,0 +1,90 @@ +--- +title: Set the Operator Fee +description: Learn how to configure the operator fee on your OP Stack Chain +--- + +## Overview + +| Parameter | Default | Recommended | Type | +| :---------------------- | :-----: | :------------- | :----------------------------| +| `operatorFeeScalar` | 0 | Chain-specific | uint32 scalar (scaled by 1e6)| +| `operatorFeeConstant` | 0 | Chain-specific | uint64 scalar (wei) | + +The **Operator Fee** was introduced in the **[Isthmus upgrade](https://docs.optimism.io/notices/upgrade-14#operator-fee)** and modified in the **[Jovian upgrade](https://docs.optimism.io/notices/upgrade-17#breaking-changes)**. +It allows OP Stack chain operators to charge an additional fee on transactions, on top of the **execution gas fee** (base fee + priority fee) and the **L1 data fee**. + +This mechanism gives operators two levers: + +- a **gas-proportional component** (`operatorFeeScalar`) that scales with gas used +- a **flat component** (`operatorFeeConstant`) + + + The operator fee is only applied on chains that have enabled the Isthmus upgrade. + + + + **Deposit transactions do not get charged operator fees.** + For all deposit transactions, regardless of the operator fee configuration, the operator fee is always zero. + + +--- + +## Operator Fee Formula + +The operator fee is calculated using the following formula, depending on the active fork. + +**After Isthmus:** + +```text +operatorFee = operatorFeeConstant + (operatorFeeScalar * gasUsed / 1e6) +``` + +**After Jovian:** + +```text +operatorFee = operatorFeeConstant + (operatorFeeScalar × gasUsed × 100) +``` + + + Setting operator fee values too high can significantly increase transaction costs and reduce user adoption. + The operator fee directly impacts UX and competitiveness and should be adjusted conservatively. + The default value for standard chains is 0, any other value is considered non-standard. + + +--- + +## How to Update the Operator Fee + +To update the operator fee parameters, call the following method on the `SystemConfig` contract from the `SystemConfigOwner`: + +`setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) external onlyOwner;` + +Example using cast: + + ```bash title="cast" + cast send --rpc-url --private-key \ + \ + "setOperatorFeeScalars(uint32,uint64)" \ + 500000 1000000000000 + ``` + +After the transaction confirms, verify the current configuration by calling the following: + + `function operatorFeeScalar() view returns (uint32);` + `function operatorFeeConstant() view returns (uint64);` + +Example using cast: + + ```bash title="cast" + cast call --rpc-url "operatorFeeScalar()" + cast call --rpc-url "operatorFeeConstant()" + ``` + +--- + +## References + +- [Operator Fee Overview – OP Stack Fee Docs](https://docs.optimism.io/op-stack/transactions/fees#operator-fee-component) +- [SystemConfig Operator Fee Parameter Configuration – OP Stack Specs](https://specs.optimism.io/protocol/isthmus/system-config.html#operator-fee-parameter-configuration) +- [Isthmus Operator Fee Details – OP Stack Specs](https://specs.optimism.io/protocol/isthmus/exec-engine.html#operator-fee) + diff --git a/docs/public-docs/chain-operators/guides/features/snap-sync.mdx b/docs/public-docs/chain-operators/guides/features/snap-sync.mdx new file mode 100644 index 0000000000000..9869d8b63a463 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/snap-sync.mdx @@ -0,0 +1,34 @@ +--- +title: Using snap sync for chain operators +description: Learn how to enable snap sync on your OP Stack chain. +--- + +Snap sync significantly improves the experience of syncing an OP Stack node. Snap sync is a native feature of go-ethereum that is now optionally enabled on `op-node` & `op-geth`. +Snap sync works by downloading a snapshot of the state from other nodes on the network and is then able to start executing blocks from the completed state rather than having to re-execute every single block. +This means that performing a snap sync is significantly faster than performing a full sync. + +* Snap sync enables node operators on your network to sync faster. +* Snap sync removes the need for nodes on your post Ecotone network to run a [blob archiver](/node-operators/guides/management/blobs). + +## Enable snap sync for chains + +To enable snap sync, chain operators need to spin up a node which is exposed to the network and has transaction gossip disabled. +This node will serve snap sync requests on the execution layer from other nodes on the network. + + +For snap sync, all `op-geth` nodes should expose port `30303` TCP and `30303` UDP to easily find other `op-geth` nodes to sync from. + * If you set the port with [`--discovery.port`](/node-operators/reference/op-geth-config#discovery-port), then you must open the port specified for UDP. + * If you set [`--port`](/node-operators/reference/op-geth-config#port), then you must open the port specified for TCP. + * The only exception is for sequencers and transaction ingress nodes. + + + + + * Expose port `30303` (`op-geth`'s default discovery port) to the internet on TCP and UDP. + * Disable transaction gossip with the [`--rollup.disabletxpoolgossip`](/node-operators/reference/op-geth-config#rollupdisabletxpoolgossip) flag + + + + * Follow the [Node operator snap sync guide](/node-operators/guides/management/snap-sync) to enable snap sync for your chain network. + + diff --git a/docs/public-docs/chain-operators/guides/features/switching-to-kona-proofs.mdx b/docs/public-docs/chain-operators/guides/features/switching-to-kona-proofs.mdx new file mode 100644 index 0000000000000..ee9465aa31124 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/features/switching-to-kona-proofs.mdx @@ -0,0 +1,220 @@ +--- +title: Switch to Kona Proofs +description: Learn how to switch your OP Stack chain to use Kona-based fault proofs as the respected game type. +--- + + + Kona proofs will become available if and when Optimism Governance approves [Upgrade 18](/notices/upgrade-18). + Until then, you can experiment with Kona proofs using [op-deployer/v0.6.0-rc.2](https://github.com/ethereum-optimism/optimism/releases/tag/op-deployer%2Fv0.6.0-rc.2), which allows you to upgrade from `op-contracts/v5.0.0` to `op-contracts/v6.0.0-rc.1`. + This smart contract release candidate will be finalized after governance approval. + + +## Overview + +| Parameter | Type | Current (typical) | Target when switching | Notes | +| ----------------------- | ------- | ----------------- | ---------------------- | ---------------------------------------------------- | +| Respected game type | Enum | `CANNON` (0) | `CANNON_KONA` (8) | Determines which game type is used for withdrawals | +| `OP_PROPOSER_GAME_TYPE` | Number | 0 | 8 | Proposer game type (must match respected game type) | +| `cannonPrestate` | Bytes32 | Set | Set | Must be a valid `cannon64` prestate hash | +| `cannonKonaPrestate` | Bytes32 | Set | Set | Must be a valid `cannon64-kona` prestate hash | + +Kona proofs use the `kona-client` fault proof program, which is a combination of `kona-node` and `op-reth`. This runs alongside the existing `op-program` (a combination of `op-node` and `op-geth`). Both programs: + +- Use Cannon as the FPVM, and +- Are used by the same dispute game implementation (`FaultDisputeGame.sol`). + +After the cannon+kona upgrade is deployed, both game types are available: + +- `CANNON` (0) — op-program +- `CANNON_KONA` (8) — kona-client + +By default, the respected game type remains `CANNON` (0), meaning only those games are used for withdrawals. This guide explains how to switch your chain so that Kona proofs (`CANNON_KONA`, 8) become the respected game type. + + + This guide assumes you have already completed the cannon+kona upgrade, including setting both `cannonPrestate` and `cannonKonaPrestate` in `OpChainConfig`, and that your fault proof infrastructure is healthy. + + +The high-level flow is: + +- Verify that cannon+kona support is correctly deployed on-chain. +- Confirm that your off-chain infra (especially `op-challenger`) is Kona-ready. +- Switch the respected game type to `CANNON_KONA` (8). +- Update `op-proposer` to post Kona games. +- Monitor the system. + +--- + +## How to Switch to Kona Proofs + + + + Before changing the respected game type, verify that your chain has been upgraded to support both `CANNON` and `CANNON_KONA` games. + + + The required upgrade is performed via `OPCM.upgrade` with an `OpChainConfig` that sets both `cannonPrestate` and `cannonKonaPrestate`. + + + 1. **Check `OpChainConfig` prestates** + + Ensure that the `OpChainConfig` used in your most recent upgrade included: + + - `cannonPrestate` pointing to a valid `cannon64` prestate. + - `cannonKonaPrestate` pointing to a valid `cannon64-kona` prestate. + + Both hashes must come from the standard prestates in the superchain registry and must embed an up-to-date chain config for your chain. + + 2. **Verify `DisputeGameFactory` implementations** + + After the cannon+kona upgrade, the `DisputeGameFactory` should have: + + - A non-zero implementation for `CANNON` (0), and + - A non-zero implementation for `CANNON_KONA` (8). + + Example (using [cast](https://getfoundry.sh/cast/reference/cast/)): + + ```bash title="cast" + # CANNON (0) implementation + cast call "gameImpl(uint8)" 0 + + # CANNON_KONA (8) implementation + cast call "gameImpl(uint8)" 8 + ``` + + Both calls should return non-zero addresses. + + + + Your challengers must be able to play both cannon and kona games before you change the respected game type. + + + 1. **Trace types** + + If you explicitly configure trace types, ensure that `cannon-kona` is included. A typical setting is: + + ```bash + OP_CHALLENGER_TRACE_TYPE="cannon,cannon-kona,permissioned" + # or + --trace-type=cannon,cannon-kona,permissioned + ``` + + 2. **Prestate URLs** + + Make sure `op-challenger` can fetch both cannon and cannon-kona prestates: + + - If they are at the same URL, use: + + ```bash + --prestates-url= + ``` + + - If they are at different locations, use: + + ```bash + --cannon-prestates-url= + --cannon-kona-prestates-url= + ``` + + 3. **Kona host binary** + + For operators not using the standard OP Labs `op-challenger` Docker images, you must also provide: + + ```bash + --cannon-kona-server=/path/to/kona-host + # or + OP_CHALLENGER_CANNON_KONA_SERVER=/path/to/kona-host + ``` + + The `kona-host` binary is available from the Kona repository (for example under `bin/host`). Build it from the same release as the `cannon-kona` prestate you configured. + + + + Once both on-chain and off-chain pieces are ready, you can switch the respected game type so that Kona proofs become the canonical path for withdrawals. + + + Changing the respected game type affects which games can be used to prove withdrawals. Only perform this step once you are confident in your Kona setup. + + + The respected game type is configured in the `AnchorStateRegistry` contract. + + 1. **Encode the calldata** + + Use `cast calldata` to encode a call to `setRespectedGameType(uint32)` with game type `8` (which corresponds to `CANNON_KONA`): + + ```bash title="cast" + CALLDATA=$(cast calldata "setRespectedGameType(uint32)" 8) + ``` + + 2. **Send the transaction** + + Send the transaction from the Guardian to the `AnchorStateRegistry`: + + ```bash title="cast" + cast send \ + --rpc-url \ + --private-key \ + \ + "$CALLDATA" + ``` + + 3. **Verify the new respected game type** + + After the transaction confirms, verify that the respected game type is now `8`: + + ```bash title="cast" + cast call \ + --rpc-url \ + \ + "respectedGameType()(uint32)" + ``` + + The returned value should be `8`, indicating that `CANNON_KONA` is now the respected game type. + + + + With `CANNON_KONA` set as the respected game type, your proposers must create Kona games. + + 1. **Update the proposer game type** + + Change your `op-proposer` configuration from game type 0 to 8: + + ```bash + # Before + OP_PROPOSER_GAME_TYPE=0 + # or + --game-type=0 + + # After switching to Kona + OP_PROPOSER_GAME_TYPE=8 + # or + --game-type=8 + ``` + + This change is not required as part of the initial cannon+kona upgrade; it is only required when you actually switch the respected game type to `CANNON_KONA`. + + 2. **Restart proposers** + + Restart your proposer processes so they pick up the new configuration. Verify in logs and metrics that they are now using game type 8. + + + + After switching to Kona proofs, closely monitor your chain to ensure everything is functioning as expected. + + 1. **Run test withdrawals** + + - Perform a few small test withdrawals and ensure that: + - New fault dispute games are created with game type `CANNON_KONA` (8). + - Your challengers are responding correctly using `kona-host`. + + 2. **Observe metrics and logs** + + - Monitor `op-proposer` and `op-challenger` logs for errors. + - Watch dispute game metrics (for example, game counts and step timings) for anomalies. + + + +--- + +## References + +- [OP Stack Fault Proofs Explainer](https://docs.optimism.io/op-stack/fault-proofs/explainer) +- [Fault Proof Specs](https://specs.optimism.io/fault-proof/index.html) \ No newline at end of file diff --git a/docs/public-docs/chain-operators/guides/management/best-practices.mdx b/docs/public-docs/chain-operators/guides/management/best-practices.mdx new file mode 100644 index 0000000000000..8f203686d426a --- /dev/null +++ b/docs/public-docs/chain-operators/guides/management/best-practices.mdx @@ -0,0 +1,83 @@ +--- +title: Chain operator best practices +description: Learn some best practices for managing the OP Stack's off-chain components. +--- + +The following information has some best practices around running the OP Stack's +off-chain components. + +## Correct release versions + +Chain and node operators should always run the latest production releases of the OP Stack's off-chain components. The latest notes and changelogs can be found on GitHub: + +* [OP monorepo releases](https://github.com/ethereum-optimism/optimism/releases) (includes `op-node` and other Go-based OP components) +* [op-geth releases](https://github.com/ethereum-optimism/op-geth/releases) (standalone repository) +* [op-contracts releases](https://github.com/ethereum-optimism/optimism/releases) (look for tags starting with `op-contracts/`) + +Some guidelines when picking versions: + +* **Production releases** are always tagged with `/v` for a specific OP component, for example: + * `op-node/v1.7.5` for the `op-node` + * `op-challenger/v1.0.0` for the `op-challenger` + +* **Monorepo releases** typically use a simple `v` format (e.g. `v1.7.7`) to indicate that all Go-based OP Stack components (in `op-*`) have been updated together. These do **not** include new L1 contract releases. + +* **Contracts releases** are tagged under `op-contracts/v` (e.g. `op-contracts/v1.6.0`) and contain updates to the Bedrock L1 contracts. + +* **`op-geth` versioning** includes upstream geth's version within its semver. For example, if upstream geth is at `v1.12.0`, an `op-geth` release might be `v1.101200.0`. The geth major version is used as our minor version (left-padded if needed), and the patch version is appended. + +Always consult release notes for additional details or upgrade instructions. + +## Keep deployment artifacts + +After deploying your contracts on Ethereum, you should keep a record of all the deployment artifacts. +This is will be all the [op-deployer](/chain-operators/tools/op-deployer/overview.mdx) artifacts, as well as the release tag and commit hash of `op-deployer` and `op-contracts`. +You will need these artifacts to add your chain to the [Superchain Registry](/op-stack/protocol/superchain-registry). + +## Incremental upgrade rollouts + +When upgrading your nodes, take a staggered approach. This means deploying the +upgrade gradually across your infrastructure and ensuring things work as +expected before making changes to every node. + +## Isolate your sequencer + +You can isolate your sequencer node, by not connecting it directly to the +internet. Instead, you could handle your ingress traffic behind a proxy. Have +the proxy forward traffic to replicas and have them gossip the transactions +internally. + +## Improve reliability of peer-to-peer transactions + +These flags can improve the reliability of peer-to-peer transactions from internal replica nodes and the sequencer node. + +For sequencer nodes: + +``` +GETH_TXPOOL_JOURNAL: "" +GETH_TXPOOL_JOURNALREMOTES: "false" +GETH_TXPOOL_NOLOCALS: "true" +``` + +For replica nodes: + +``` +GETH_TXPOOL_JOURNALREMOTES: "true" +GETH_TXPOOL_LIFETIME: "1h" +GETH_TXPOOL_NOLOCALS: "true" +``` + +For additional information about these flags, check out our [Execution Layer Configuration Options](/node-operators/reference/op-geth-config) doc. + +## Write your own runbooks + +Create custom runbooks to prepare for operating an OP Stack chain. +You'll want to setup metrics endpoints and monitoring on your key components of the system and create runbooks to execute when something is not behaving as expected. + +## Assumptions + +### op-proposer assumes archive mode + +The `op-proposer` currently assumes that `op-geth` is being run in archive +mode. This will likely be updated in a future network upgrade, but it is +necessary for L2 withdrawals at the moment. diff --git a/docs/public-docs/chain-operators/guides/management/gas-target-limit.mdx b/docs/public-docs/chain-operators/guides/management/gas-target-limit.mdx new file mode 100644 index 0000000000000..5b5f38147ac7a --- /dev/null +++ b/docs/public-docs/chain-operators/guides/management/gas-target-limit.mdx @@ -0,0 +1,79 @@ +--- +title: Changing gas target/limit +description: Learn how to change the gas target and limit on your OP Stack chain. +--- + +This guide covers how to optimize block processing performance at higher gas limits, test your infrastructure before activation, and safely change the gas target and/or limit on a live chain. +This guide assumes your OP Stack chain has the [Holocene features](/notices/archive/holocene-changes) activated. + +## Recommended config for best performance + +Some known ways to improve block processing performance: + +* **Use NVMe for storage** instead of local SSDs or other persistent disks +* **Use full nodes where possible** over archive nodes + +## Pre-activation testing + +To ensure your chain and supporting infrastructure can consistently process blocks up to your gas limit, you should run some performance tests. +You'll want to run benchmarks and perform careful analysis of the results to ensure your execution clients can handle the increased gas usage. + +## Changing the gas target/limit change + +How to change the gas target/limit on a live chain. + +### Relevant configuration parameters + +The gas configuration on an OP Stack chain use the same EIP-1559 parameters as Ethereum. +Where the **gas target** = **gas limit** / **elasticity**. +These values can be found in the `SystemConfig` contract. + +### Procedure + +Holocene introduces the ability to change the EIP-1559 parameters `elasticity` and `denominator` via the `SystemConfig`. This will allow you to set proper values for the `elasticity` and block `gasLimit`. + +**Example**: to double the *target* while keeping the block *limit* at 30Mgas/block: + + + + Retrieve the existing `eip1559Elasticity` and `eip1559Denominator` values: + + ```solidity + elasticity = SystemConfig.eip1559Elasticity() // 6 + denominator = SystemConfig.eip1559Denominator() // 50 + limit = SystemConfig.gasLimit() // 30_000_000 gas, 30Mgas + ``` + + Current target = `30Mgas / 6 = 5Mgas per block` + + + + Use value from previous step as input to set new params and reduce the `elasticity` from `6` to `3`: + ```solidity + SystemConfig.setEIP1559Params(denominator, 3) + ``` + New target = `30Mgas / 3 = 10Mgas per block` + + + +## Post-activation monitoring + +What to monitor after executing the gas changes. + +### SystemConfig contract values + +* `SystemConfig.gasLimit()` +* `SystemConfig.eip1559Denominator()` +* `SystemConfig.eip1559Elasticity()` + +### Block explorer + +For example, on [OP Mainnet](https://optimistic.etherscan.io/blocks): + +* Any block with `gasUsed > gasTarget` should cause the base fee to increase +* Any block with `gasUsed < gasTarget` should cause the base fee to decrease + +### Node performance + +* Verify nodes are processing blocks faster than the block period +* Monitor CPU/memory usage and p99 tail latency of block processing diff --git a/docs/public-docs/chain-operators/guides/management/key-management.mdx b/docs/public-docs/chain-operators/guides/management/key-management.mdx new file mode 100644 index 0000000000000..7160cdfd131ed --- /dev/null +++ b/docs/public-docs/chain-operators/guides/management/key-management.mdx @@ -0,0 +1,39 @@ +--- +title: Key management +description: A guide for chain operators on managing private keys on their chain, covering hot and cold wallets, and the use of an HSM. +--- + +This guide informs chain operators on important key management considerations. +There are certain [privileged roles](/op-stack/protocol/privileged-roles) that +need careful consideration. The privileged roles are categorized as hot wallets +or cold wallets. + +## Hot wallets + +The addresses for the `Batcher` and the `Proposer` need to have their private +keys online somewhere for a component of the system to work. If these addresses +are compromised, the system can be exploited. + +It is up to the chain operator to make the decision on how they want to manage +these keys. One suggestion is to use a Hardware Security Module (HSM) to provide +a safer environment for key management. Cloud providers oftentimes provide +Key Management Systems (KMS) that can work with your developer operations +configurations. This can be used in conjunction with the `eth_signTransaction` +RPC method. + + + You can take a look at the signer client [source code](https://github.com/ethereum-optimism/optimism/blob/develop/op-service/signer/client.go) + if you're interested in what's happening under the hood. + + +## Cold wallets + +The addresses for the cold wallets cannot be used without human intervention. +These can be set up as multisig contracts, so they can be controlled by groups +of community members and avoid a single point of failure. The signers behind a +multisig should probably also use a hardware wallet. + + + Refer to the [privileged roles](/op-stack/protocol/privileged-roles) documentation + for more information about these different addresses and their security concerns. + diff --git a/docs/public-docs/chain-operators/guides/management/operations.mdx b/docs/public-docs/chain-operators/guides/management/operations.mdx new file mode 100644 index 0000000000000..d17395f2604bd --- /dev/null +++ b/docs/public-docs/chain-operators/guides/management/operations.mdx @@ -0,0 +1,211 @@ +--- +title: Rollup operations +description: Learn basics of rollup operations, such as how to start and stop your rollup, get your rollup config, and how to add nodes. +--- + +This guide reviews the basics of rollup operations, such as how to start your rollup, stop your rollup, get your rollup config, and add nodes. + +## Stopping your rollup + +An orderly shutdown is done in the reverse order to the order in which components were started: + + + ```sh + curl -d '{"id":0,"jsonrpc":"2.0","method":"admin_stopBatcher","params":[]}' \ + -H "Content-Type: application/json" http://localhost:8548 | jq + ``` + + This way the batcher knows to save any data it has cached to L1. + Wait until you see `Batch Submitter stopped` in batcher's output before you stop the process. + + + + To stop the proposer, terminate the process directly. This can be done by: + + * Pressing **Ctrl+C** in the terminal running the process + * Using system commands like `kill -TERM ` to stop the process gracefully + + Ensure that the proposer process has terminated completely before proceeding to stop other components. + + + + This component is stateless, so you can just stop the process. + + + + Make sure you use **CTRL-C** to avoid database corruption. If Geth stops unexpectedly the database can be corrupted. This is known as an "[unclean shutdown](https://geth.ethereum.org/docs/fundamentals/databases#unclean-shutdowns)" and it can lead to a variety of problems for the node when it is restarted. + + + +## Starting your rollup + +To restart the blockchain, use the same order of components you did when you initialized it. + + + + + + + + + + + If `op-batcher` is still running and you just stopped it using RPC, you can start it with this command: + + ```sh + curl -d '{"id":0,"jsonrpc":"2.0","method":"admin_startBatcher","params":[]}' \ + -H "Content-Type: application/json" http://localhost:8548 | jq + ``` + + + + Start the proposer using the appropriate command. Here's an example: + + ```sh + ./bin/op-proposer \ + --poll-interval=12s \ + --rpc.port=8560 \ + --rollup-rpc=http://localhost:8547 \ + --l2oo-address=0xYourL2OutputOracleAddress \ + --private-key=$PROPOSER_PRIVATE_KEY \ + --l1-eth-rpc=$L1_RPC_URL + ``` + | Parameter | Description | + | ------------- | -------------------------------------------------------------- | + | poll-interval | How often to check for new output proposals (recommended: 12s) | + | rpc.port | Local RPC port for the proposer service | + | l2oo-address | The L2 Output Oracle contract address (0x-prefixed hex) | + | private-key | Private key for signing proposals | + | l1-eth-rpc | L1 network RPC endpoint URL | + + + +Synchronization takes time + +`op-batcher` might have warning messages similar to: + +``` +WARN [03-21|14:13:55.248] Error calculating L2 block range err="failed to get sync status: Post \"http://localhost:8547\": context deadline exceeded" +WARN [03-21|14:13:57.328] Error calculating L2 block range err="failed to get sync status: Post \"http://localhost:8547\": context deadline exceeded" +``` + +This means that `op-node` is not yet synchronized up to the present time. +Just wait until it is. + + + +## Getting your rollup config + +Use this tool to get your rollup config from `op-node`. This will only work if your chain is **already** in the [superchain-registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json) and `op-node` has been updated to pull those changes in from the registry. + + +This script will NOT work for chain operators trying to generate this data in order to submit it to the registry. + + + + + You'll need to run this tool: + + ``` + ./bin/op-node networks dump-rollup-config --network=op-sepolia + { + "genesis": { + "l1": { + "hash": "0x48f520cf4ddaf34c8336e6e490632ea3cf1e5e93b0b2bc6e917557e31845371b", + "number": 4071408 + }, + "l2": { + "hash": "0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d", + "number": 0 + }, + "l2_time": 1691802540, + "system_config": { + "batcherAddr": "0x8f23bb38f531600e5d8fddaaec41f13fab46e98c", + "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc", + "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0", + "gasLimit": 30000000 + } + }, + "block_time": 2, + "max_sequencer_drift": 600, + "seq_window_size": 3600, + "channel_timeout": 300, + "l1_chain_id": 11155111, + "l2_chain_id": 11155420, + "regolith_time": 0, + "canyon_time": 1699981200, + "delta_time": 1703203200, + "ecotone_time": 1708534800, + "batch_inbox_address": "0xff00000000000000000000000000000011155420", + "deposit_contract_address": "0x16fc5058f25648194471939df75cf27a2fdc48bc", + "l1_system_config_address": "0x034edd2a225f7f429a63e0f1d2084b9e0a93b538", + "protocol_versions_address": "0x79add5713b383daa0a138d3c4780c7a1804a8090", + "da_challenge_address": "0x0000000000000000000000000000000000000000", + "da_challenge_window": 0, + "da_resolve_window": 0, + "use_plasma": false + } + ``` + + + + Ensure that you are using the appropriate flag. + The `--network=op-sepolia` flag allows the tool to pick up the appropriate data from the registry, and uses the OPChains mapping under the hood. + + + +## Adding nodes + +To add nodes to the rollup, you need to initialize `op-node` and `op-geth`, similar to what you did for the first node. +You should *not* add an `op-batcher` because there should be only one. + + + + + + + + + + + ```bash + ~/op-geth/genesis.json + ~/optimism/op-node/rollup.json + ``` + + + + ```bash + cd ~/op-geth + openssl rand -hex 32 > jwt.txt + cp jwt.txt ~/optimism/op-node + ``` + + + + ```bash + cd ~/op-geth + ./build/bin/geth init --datadir=./datadir ./genesis.json + ``` + + + + If you do it this way, you won't have to wait until the transactions are written to L1. + If you already have peer to peer synchronization, add the new node to the `--p2p.static` list so it can synchronize. + + + + + **Important:** Make sure to configure the `--rollup.sequencerhttp` flag to point to your sequencer node. This HTTP endpoint is crucial because `op-geth` will route `eth_sendRawTransaction` calls to this URL. The OP Stack does not currently have a public mempool, so configuring this is required if you want your node to support transaction submission. + + + + + + + + +## Next steps + +* See the [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients) and [Execution Client Configuration](/node-operators/guides/configuration/execution-clients) guides for additional explanation or customization. +* If you experience difficulty at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions). diff --git a/docs/public-docs/chain-operators/guides/management/transaction-fees-101.mdx b/docs/public-docs/chain-operators/guides/management/transaction-fees-101.mdx new file mode 100644 index 0000000000000..630df1ecb41ba --- /dev/null +++ b/docs/public-docs/chain-operators/guides/management/transaction-fees-101.mdx @@ -0,0 +1,336 @@ +--- +title: Transaction Fees 101 +description: overview of fee-related parameters for chain operators and when/how to adjust them. +--- + +**NOTE:** The below document is an overview on fee-related parameters. It applies to the Jovian and following hardforks. For full details on OP Stack transaction fees, see [here](https://docs.optimism.io/op-stack/transactions/fees#transaction-fees-on-op-mainnet). + +## Total Fee + +On an OP stack chain a transaction **Total Fee** is made of three main components: + +`Total Fee = L2 Fee + L1 Fee + Operator Fee` + +Fees are gathered in dedicated contract vaults that collect the different fee components (for example `BaseFeeVault`, `SequencerFeeVault`, etc.). + +## 1. L2 Fee + +`L2 Fee = gasUsed * (baseFee + priorityFee)` + +### What it is and how it’s calculated + +The L2 fee is the EVM execution cost: `gasUsed` multiplied by the `baseFee` plus any `priorityFee`. + +- **gasUsed** is the total gas consumed during transaction execution and is measured by the EVM; the final `gasUsed` is recorded in the transaction receipt. +- **baseFee** is computed with the EIP-1559 mechanism and is **collected in the BaseFeeVault (not burned)** on OP Stack. +- **priorityFee** is set by the user to affect inclusion speed. + +### Recipient Vaults + +- `baseFee → BaseFeeVault` +- `priorityFee → SequencerFeeVault` + +### How to tweak it + +As a chain operator, you can tune L2 fee dynamics on the SystemConfig via: + +- `eip1559Denominator`: EIP-1559 max-change denominator, it appears in the denominator of the per-block base fee delta. Lower = faster response, so base fee adjust faster but results in more volatility. See the OP Mainnet values further down in the doc to have a reference. +- `eip1559Elasticity`: elasticity multiplier, sets the target relative to gasLimit. As `gas_target = gasLimit / elasticity`, a **smaller gas target** (larger elasticity) means the base fee will start increasing at a lower block gas usage (i.e., base fee increases sooner under load). See OP Mainnet values below for an example (`eip1559Elasticity = 2`). +- `gasLimit`: max gas per block. Lower `gasLimit` = less available gas per block (less supply), which makes base fees more sensitive to demand and can cause the base fee to move more easily. +- `minBaseFee`: minimum base floor on base fee, in wei (it can be 0). + +### Check current values + +```bash +export L1_RPC= +export SYSTEM_CONFIG= +export PK= + +cast call $SYSTEM_CONFIG "eip1559Denominator()" +cast call $SYSTEM_CONFIG "eip1559Elasticity()" +cast call $SYSTEM_CONFIG "minBaseFee()" +cast call $SYSTEM_CONFIG "gasLimit()" +``` + +### Set values + +```bash +cast send --rpc-url $L1_RPC --private-key $PK \ +$SYSTEM_CONFIG "setEIP1559Params(uint32,uint32)" +$SYSTEM_CONFIG "setMinBaseFee(uint64)" +$SYSTEM_CONFIG "setGasLimit(uint64)" +``` + +## 2. L1 Fee + +```ts +l1FeeScaled = + baseFeeScalar * 16 * l1BaseFee + + blobBaseFeeScalar * l1BlobBaseFee + +estimatedSizeScaled = + max( + minTransactionSize * 1e6, + intercept + fastlzCoef * fastlzSize + ) + +l1Fee = estimatedSizeScaled * l1FeeScaled / 1e12 +``` + +### What it is and how it’s calculated + +The L1 Fee charges for posting L2 data to L1. In short: we estimate the transaction’s compressed size (FastLZ) and multiply that estimated size by a scalar-weighted L1 price to compute the L1 fee. + +The formula contains two parts: + +- A **per-byte** component derived from the L1 per-byte price (`l1BaseFee`) scaled by `baseFeeScalar`. +- A **per-blob** component derived from the L1 blob base fee (`l1BlobBaseFee`) scaled by `blobBaseFeeScalar` (for blob-oriented data like EIP-4844 blobs). + +This combination lets the chain charge for both compressed calldata bytes and blob resources. + +### Recipient Vault + +- `l1Fee → L1FeeVault` + +### Why tweak it + +- Modify margin charged on top of expected costs +- Adjust FastLZ estimates to reflect costs +- Differentiate charges for per-byte vs per-blob costs to reflect actual L1 pricing + +### How to tweak it + +- `basefeeScalar`: scales the l1BaseFee contribution to L1 data fee (per-byte). +- `blobBaseFeeScalar`: scales the l1BlobBaseFee contribution (per-blob). + +### Check current values + +```bash +export L1_RPC= +export SYSTEM_CONFIG= +export PK= + +cast call $SYSTEM_CONFIG "basefeeScalar()" +cast call $SYSTEM_CONFIG "blobbasefeeScalar()" +``` + +### Set values + +```bash +cast send --rpc-url $L1_RPC --private-key $PK \ + $SYSTEM_CONFIG "setGasConfigEcotone(uint32,uint32)" +``` + +## 3. Operator Fee + +`Operator Fee = (gasUsed * operatorFeeScalar * 100) + operatorFeeConstant` + +### What it is and how it’s calculated + +The Operator fee is a discretionary, non‑standard fee charged per transaction. It has two components: + +- **flat constant** (operatorFeeConstant, wei). +- **per gas scalar** (operatorFeeScalar). + +### Recipient Vault + +- `operatorFee → OperatorFeeVault` + +### How to tweak it + +- `operatorFeeScalar`: per‑gas operator margin. +- `operatorFeeConstant`: flat per‑tx operator fee. + +> **IMPORTANT:** Any non‑zero operator fee makes the chain configuration non‑standard. + +#### Pre‑Jovian (Isthmus) formula + +```text +operatorFee = (gasUsed * operatorFeeScalar / 1e6) + operatorFeeConstant +``` + +### Check current values + +```bash +export L1_RPC= +export SYSTEM_CONFIG= +export PK= + +cast call $SYSTEM_CONFIG "operatorFeeScalar()" +cast call $SYSTEM_CONFIG "operatorFeeConstant()" +``` + +### Set values + +```bash +cast send --rpc-url $L1_RPC --private-key $PK \ +$SYSTEM_CONFIG "setOperatorFeeScalars(uint32,uint64)" +``` + +--- + +## OP Mainnet Values + +- `eip1559Elasticity` = 2 +- `eip1559Denominator` = 250 +- `minBaseFee` = 0 wei +- `basefeeScalar` = 5227 +- `blobBaseFeeScalar` = 1014213 +- `gasLimit` = 40,000,000 +- `operatorFeeScalar` = 0 +- `operatorFeeConstant` = 0 +- `daFootprintGasScalar` = 0* (0 is treated as 400. Set to 1 to disable it. See below) + +--- + +## DA footprint parameter + +The `daFootprintGasScalar` is an in‑protocol limit on estimated DA usage to prevent DA spam and priority fee auctions. + +A DA footprint block limit is introduced after Jovian to limit the total amount of estimated compressed transaction data that can fit into a block. For each transaction, a new resource called DA footprint is tracked, next to its gas usage. It is scaled to the gas dimension so that its block total can also be limited by the block gas limit, like a block's total gas usage. + +- Default value is **400** +- A value of **0** is treated as **400** +- Setting it to **1** disables DA footprint limiting + +For more details, see the [DA footprint documentation](https://docs.optimism.io/notices/archive/upgrade-17#block-header-changes). + +### Check current value + +```bash +export L1_RPC= +export SYSTEM_CONFIG= +export PK= + +cast call $SYSTEM_CONFIG "daFootprintGasScalar()" +``` + +### Set value + +```bash +cast send --rpc-url $L1_RPC --private-key $PK \ +$SYSTEM_CONFIG "setDAFootprintGasScalar(uint16)" +``` + +--- + +## Example Scenarios + +### Normal traffic / below EIP‑1559 target + +**What you’ll see** + +- If blocks stay below the EIP-1559 target, `baseFee` trends toward the `minBaseFee` floor. +- When L1 data usage is low, the L2 execution fee (`gasUsed * (baseFee + priorityFee)`) is typically the largest cost component. +- The L1 data fee remains low/steady if calldata per transaction is small. + +**What to tweak** + +- Usually nothing. +- If you need a revenue floor set/increase `minBaseFee`. + +**Tradeoff** + +- **Pros:** `minBaseFee` prevents prolonged low base fees and provides a predictable minimum (can be left at 0 to improve UX). +- **Cons:** If set too high, it raises inclusion cost for low-value transactions and can negatively impact UX/accessibility. + +--- + +### Busy blocks / congestion + +**What you’ll see** + +- `baseFee` rises when blocks exceed the target: `gas_target = gasLimit / eip1559Elasticity` +- Transactions with low priority tips may experience slower inclusion during congestion. + +**What to tweak** + +- `eip1559Elasticity` and `eip1559Denominator` : + - To increase responsiveness: decrease `eip1559Denominator` and/or increase `eip1559Elasticity` (smaller target → baseFee increases sooner). + - To decrease volatility: increase `eip1559Denominator` and/or reduce aggressiveness in `eip1559Elasticity`. + +**Tradeoff** + +- **Pros:** Faster base fee response better captures demand; elasticity provides direct control over the gas target. +- **Cons:** Smaller denominator and/or aggressive elasticity increases per-block swings and increases fee volatility. + +--- + +### Calldata‑heavy transactions + +**What you’ll see** + +- L1 data fee becomes a larger share of total cost for calldata heavy transactions (the L1 fee scales with compressed calldata size). +- Increasing `basefeeScalar` increases the per-byte L1 charge, making calldata heavy transactions more expensive. + +**What to tweak** + +- `basefeeScalar` and `blobBaseFeeScalar`: + - Increase them if you need to recover more L1/DA posting costs from data heavy transactions. + - Decrease them if you want to reduce user cost for calldata/blob-heavy workloads. + +**Tradeoff** + +- **Pros:** Enables proportional recovery of L1/DA posting costs. +- **Cons:** Penalizes calldata/blob-heavy dapps/users. Aggressive values can impact UX and reduce activity. + +--- + +### Predictable operator revenue + +**What you’ll see** + +- Introducing or increasing `operatorFeeScalar` raises the per-gas operator charge and increases predictable revenue per transaction. +- `operatorFeeConstant` adds a flat per-transaction fee regardless of gas usage. + +**What to tweak** + +- `operatorFeeScalar` and `operatorFeeConstant`: + - Use when you want direct, predictable operator revenue independent of congestion and L1 fee dynamics. + +**Tradeoff** + +- **Pros:** Direct and predictable revenue per transaction. +**Cons:** May discourage low-value transactions and changes transaction economics; should be used carefully. Any non-zero operator fee makes the chain configuration non-standard. + +--- + +### DA‑heavy workloads / DA-spam resistance + +**What you’ll see** + +- DA footprint can limit how much transaction data fits into a block through scaled accounting of compressed DA usage. +- Increasing `daFootprintGasScalar` raises the gas charged per DA-byte footprint, reducing DA capacity for the same byte footprint and deterring DA-heavy patterns. + +**What to tweak** + +- `daFootprintGasScalar`: + - Default is **400**, and `0` is treated as the default. + - Setting it to **1** disables the DA footprint limiting behavior. + +**Tradeoff** + +- **Pros:** Deters DA spam and makes DA costs explicit in block accounting. +- **Cons:** Increasing it reduces DA capacity and may throttle the batcher or exclude legitimate DA-heavy workloads if set too aggressively. + +--- + +## Summary + +1. Make sure to check your current hardfork, this doc applies to Jovian and following hardforks. +2. Check the current fee parameters on your SystemConfig contract. +3. Make small changes and observe: vault balances, inclusion times, dropped txs, DA throttling, etc. +4. Extensively test all changes on a staging/testnet. +5. Communicate changes to dapp/wallet teams (gas estimation will change). + +--- + +## References + +- [Transaction fees on OP mainnet](https://docs.optimism.io/op-stack/transactions/fees#transaction-fees-on-op-mainnet) +- [Operator fee](https://docs.optimism.io/op-stack/transactions/fees#operator-fee) +- [L1 data fee](https://docs.optimism.io/op-stack/transactions/fees#l1-data-fee) +- [SystemConfig interface](https://github.com/ethereum-optimism/optimism/blob/d181d5b197665df9b5efd66e4f76f09adf5c697f/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol#L8) +- [DA footprint](https://docs.optimism.io/notices/archive/upgrade-17#block-header-changes) +- [EIP-1559](https://docs.optimism.io/op-stack/protocol/differences#eip-1559-parameters) +- [OP Mainnet values](https://etherscan.io/address/0x229047fed2591dbec1eF1118d64F7aF3dB9EB290#readProxyContract) diff --git a/docs/public-docs/chain-operators/guides/management/troubleshooting.mdx b/docs/public-docs/chain-operators/guides/management/troubleshooting.mdx new file mode 100644 index 0000000000000..1c292ebfd0143 --- /dev/null +++ b/docs/public-docs/chain-operators/guides/management/troubleshooting.mdx @@ -0,0 +1,66 @@ +--- +title: Troubleshooting chain operations +description: Learn solutions to common problems when troubleshooting chain operations. +--- + +This page lists common troubleshooting scenarios and solutions for chain operators. + +## EvmError in contract deployment + +L1 smart contract deployment fails with the following error: + +```text +EvmError: Revert +``` + +### Solution + +The OP Stack uses deterministic smart contract deployments to guarantee that all contract addresses can be computed ahead of time based on a "salt" value that is provided at deployment time. +Each OP Stack chain must have a unique salt value to ensure that the contract addresses do not collide with other OP Stack chains. + +You can avoid this error by changing the salt used when deploying the L1 smart contracts. +The salt value is set by the `IMPL_SALT` environment variable when deploying the contracts. +The `IMPL_SALT` value must be a 32 byte hex string. + +You can generate a random salt value using the following command: + +```bash +export IMPL_SALT=$(openssl rand -hex 32) +``` + +## Failed to find the L2 Heads to start from + +`op-node` fails to execute the derivation process with the following error: + +```text +WARN [02-16|21:22:02.868] Derivation process temporary error attempts=14 err="stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch L2 block by hash 0x0000000000000000000000000000000000000000000000000000000000000000: failed to determine block-hash of hash 0x0000000000000000000000000000000000000000000000000000000000000000, could not get payload: not found" +``` + +### Solution + +This error can occur when the data directory for `op-geth` becomes corrupted (for example, as a result of a computer crash). +You will need to reinitialize the data directory. + +If you are following the tutorial for [Creating Your Own L2 Rollup](/chain-operators/tutorials/create-l2-rollup/create-l2-rollup), make sure to rerun the commands within the [Initialize `op-geth`](/operators/chain-operators/tutorials/create-l2-rollup#initialize-op-geth) section. + +If you are not following the tutorial, make sure to take the following steps: + +1. Stop `op-node` and `op-geth`. +2. Delete the corresponding `op-geth` data directory. +3. If running a Sequencer node, import the Sequencer key into the `op-geth` keychain. +4. Reinitialize `op-geth` with the `genesis.json` file. +5. Restart `op-geth` and `op-node`. + +## Batcher unable to publish transaction + +`op-batcher` fails to publish transactions with the following error: + +```text +INFO [03-21|14:22:32.754] publishing transaction service=batcher txHash=2ace6d..7eb248 nonce=2516 gasTipCap=2,340,741 gasFeeCap=172,028,434,515 +ERROR[03-21|14:22:32.844] unable to publish transaction service=batcher txHash=2ace6d..7eb248 nonce=2516 gasTipCap=2,340,741 gasFeeCap=172,028,434,515 err="insufficient funds for gas * price + value" +``` + +### Solution + +You will observe this error if the `op-batcher` runs out of ETH to publish transactions to L1. +This problem can be resolved by sending additional ETH to the `op-batcher` address. diff --git a/docs/public-docs/chain-operators/reference/architecture.mdx b/docs/public-docs/chain-operators/reference/architecture.mdx new file mode 100644 index 0000000000000..0a21378f9067b --- /dev/null +++ b/docs/public-docs/chain-operators/reference/architecture.mdx @@ -0,0 +1,128 @@ +--- +title: Network Design Example +description: This document describes an example network configuration for an OP Stack chain deployment. +--- + +This document describes an example configuration for an OP Stack network deployment, focusing on the network architecture and node configuration required for production usage. + +![OP Stack Network Design](/public/img/chain-operators/network-design-example.png) + +## Sequencers + +Sequencers receive transactions from the Tx Ingress Nodes via geth p2p gossip and work with the batcher and proposer to create new blocks. + +### Node Configuration + +* **Sequencer op-geth** can be either full or archive. Full nodes offer better performance but can't recover from deep L1 reorgs, so run at least one archive sequencer as a backup. +* **Sequencer op-node** should have p2p discovery disabled and only be statically peered with other internal nodes (or use [peer-management-service](https://github.com/ethereum-optimism/infra/tree/main/peer-mgmt-service) to define peering network). +* The **op-conductor** RPC can act as a leader-aware RPC proxy for op-batcher (proxies the necessary op-geth / op-node RPC methods if the node is the leader). +* Sequencers should have transaction journalling disabled. + +### op-node Configuration + +```yaml +OP_NODE_P2P_NO_DISCOVERY: "true" +OP_NODE_P2P_PEER_BANNING: "false" +OP_NODE_P2P_STATIC: "" +``` + +### op-geth Configuration + +```yaml +GETH_ROLLUP_DISABLETXPOOLGOSSIP: "false" +GETH_TXPOOL_JOURNAL: "" +GETH_TXPOOL_JOURNALREMOTES: "false" +GETH_TXPOOL_LIFETIME: "1h" +GETH_TXPOOL_NOLOCALS: "true" +GETH_NETRESTRICT: "10.0.0.0/8" # ex: restrict p2p to internal ips +``` + +## Tx Ingress Nodes + +These nodes receive `eth_sendRawTransaction` calls from the public and then gossip the transactions to the internal geth network. This allows the Sequencer to focus on block creation while these nodes handle transaction ingress. + +### Node Configuration + +* These can be either full or archive nodes. +* They participate in the internal tx pool p2p network to forward transactions to sequencers. + +### Configuration + +```yaml +GETH_ROLLUP_DISABLETXPOOLGOSSIP: "false" +GETH_TXPOOL_JOURNALREMOTES: "false" +GETH_TXPOOL_LIFETIME: "1h" +GETH_TXPOOL_NOLOCALS: "true" +GETH_NETRESTRICT: "10.0.0.0/8" # ex: restrict p2p to internal ips +``` + +## Archive RPC Nodes + +We recommend setting up some archive nodes for internal RPC usage, primarily used by the challenger, proposer, and security monitoring tools like [monitorism](/operators/chain-operators/tools/chain-monitoring#monitorism). + +### Node Configuration + +* Archive nodes are essential for accessing historical state data. +* You can also use these nodes for taking disk snapshots for disaster recovery. + +### Configuration + +```yaml +GETH_GCMODE: "archive" +GETH_DB_ENGINE: "pebble" +GETH_STATE_SCHEME: "hash" +``` + +## Full Snapsync Nodes + +These nodes provide peers for snap sync, using the snapsync bootnodes for peer discovery. + +### Configuration + +```yaml +GETH_GCMODE: "full" +GETH_DB_ENGINE: "pebble" +GETH_STATE_SCHEME: "path" +GETH_SYNCMODE: "snap" +``` + +## Snapsync Bootnodes + +These bootnodes facilitate peer discovery for public nodes using snapsync. + +### Node Configuration + +* The bootnode can be either a geth instance or the [geth bootnode tool](https://etclabscore.github.io/core-geth/core/alltools/). +* If you want to use geth for snapsync bootnodes, you may want to just make the Full Snapsync Nodes serve as your bootnodes as well. + +## P2P Bootnodes + +These are the op-node p2p network bootnodes. We recommend using the [geth bootnode tool](https://etclabscore.github.io/core-geth/core/alltools/) with discovery v5 enabled. + +## Public RPC + +Public RPC design is not listed in the above diagram but can be implemented very similarly to Tx Ingress Nodes, with the following differences: + +### Configuration Differences + +* Public RPC should **not** participate in the internal tx pool p2p network. + * While it is possible to run Public RPC from the same nodes that serve Tx Ingress and participate in tx pool gossip, there have been geth bugs in the past that leaked tx pool details on read RPCs, so it is a possible risk to consider. +* Public RPC [proxyd](https://github.com/ethereum-optimism/infra/tree/main/proxyd) should be run in `consensus_aware` routing mode and whitelist any RPCs you want to serve from op-geth. +* Public RPC nodes should likely be archive nodes. + +### About proxyd + +Proxyd is an RPC request router and proxy that provides the following capabilities: + +1. Whitelists RPC methods. +2. Routes RPC methods to groups of backend services. +3. Automatically retries failed backend requests. +4. Tracks backend consensus (latest, safe, finalized blocks), peer count and sync state. +5. Re-writes requests and responses to enforce consensus. +6. Load balances requests across backend services. +7. Caches immutable responses from backends. +8. Provides metrics to measure request latency, error rates, and the like. + +## Next steps + +* Learn more about using [proxyd](https://github.com/ethereum-optimism/infra/tree/main/proxyd) for your network. diff --git a/docs/public-docs/chain-operators/reference/components/op-supervisor.mdx b/docs/public-docs/chain-operators/reference/components/op-supervisor.mdx new file mode 100644 index 0000000000000..654dc70dff285 --- /dev/null +++ b/docs/public-docs/chain-operators/reference/components/op-supervisor.mdx @@ -0,0 +1,73 @@ +--- +title: OP-Supervisor +description: Learn the basics of OP-Supervisor. +--- + +OP Stack interop is in active development. Some features may be experimental. +OP-Supervisor is a service that verifies cross-chain messages and manages interoperability between chains in the OP Stack. Anyone is able to run OP-Supervisor but only chain operators and full-node operators are expected to run OP-Supervisor. The main information it contains about other blockchains is: + +* Log entries, which could be [initiating messages](/op-stack/interop/explainer#how-messages-get-from-one-chain-to-the-other) for cross-domain messages. +* Blockchain heads, which are the latest blocks at various levels of confidence and safety: + * Unsafe (the latest block available through the gossip protocol) + * Local-safe (the latest block written to L1) + * Cross-safe (the latest block written to L1, and for which all the dependencies are written to L1) + * Finalized (the latest block written to L1, and that L1 block is safe from reorgs) + +```mermaid + +graph LR + + classDef chain fill:#FFE + classDef transparent fill:none, stroke:none + + subgraph chain1[OP Stack chain #1] + node1[OP Node] + super1[OP-Supervisor] + geth1[Execution Engine] + node1<-->super1--->geth1<-->node1 + end + subgraph X[ ] + chain2[OP Stack chain #2] + chain3[OP Stack chain #3] + l1node[L1 Consensus Layer] + end + + chain2-->|log events|super1 + chain3-->|log events|super1 + l1node-->|block status|super1 + + class chain1,chain2,chain3 chain + class X transparent +``` + +To do this, OP-Supervisor has to have RPC access to all the chains in the dependency set (all those that can contain an initiating message whose destination is this blockchain). + +## How other components use OP-Supervisor + +* The execution client (typically `op-geth`) queries `op-supervisor` during block-building to verify if a message is sufficiently safe to include. + To do this, the execution client looks at every executing message and queries `op-supervisor` to see if there is a corresponding initiating message. + +* `op-node` queries cross-chain safety information and coordinates safety updates between OP stack nodes and `op-supervisor`. It uses the API provided by `op-supervisor` to: + * Retrieve the unsafe, local-safe, cross-safe, and finalized heads for other chains. + * Update the unsafe, local-safe, and finalized heads for its own chain. + * Attempt to promote blocks in its own chain to cross-safe status. + * Attempt to finalize L2 blocks based on L1 finality. + +### API + +Here are the most important API methods exposed by OP-Supervisor. +For a full listing of API names, see the [source code](https://github.com/ethereum-optimism/optimism/blob/develop/op-service/sources/supervisor_client.go). + +| Method(s) | Description | +| ----------------------------------------- | ------------------------------------------------------------------------------------- | +| `UnsafeView` and `SafeView` | Return the Local and Cross heads for their respective levels | +| `DerivedFrom` | OP Nodes use to check the L1 source of the Supervisor (needed for Safe Head tracking) | +| `UpdateLocalSafe` and `UpdateLocalUnsafe` | Tell the Supervisor when the Node's heads change | +| `Finalized` | Returns the Finalized Head | +| `UpdateFinalizedL1` | Signals to the Supervisor new finality signals | +| `CheckMessage` | Checks logs in the DB directly in tests | + +## Next steps + +* Learn [how ETH can move across chains](/op-stack/interop/superchain-eth-bridge) +* For more info about how OP Stack interoperability works under the hood, [check out the specs](https://specs.optimism.io/interop/overview.html?utm_source=op-docs&utm_medium=docs). diff --git a/docs/public-docs/chain-operators/reference/opcm.mdx b/docs/public-docs/chain-operators/reference/opcm.mdx new file mode 100644 index 0000000000000..ee5072fb129fe --- /dev/null +++ b/docs/public-docs/chain-operators/reference/opcm.mdx @@ -0,0 +1,23 @@ +--- +title: OP Contracts Manager +description: Learn how OP Contracts Manager deploys the OP Stack with one transaction. +--- + +The OP Contracts Manager is a contract that deploys the L1 contracts for an OP Stack chain in a single transaction. It provides a minimal set of user-configurable parameters to ensure that the resulting chain meets the standard configuration requirements. Additionally, as of [Upgrade 13](https://gov.optimism.io/t/upgrade-proposal-13-opcm-and-incident-response-improvements/9739), instances of OPCM can upgrade existing OP Stack chains. + +The version deployed is always a governance-approved contract release. The set of governance approved contract releases can be found on the Optimism Monorepo releases page, and is the set of releases named `op-contracts/vX.Y.Z`. It deploys the [Fault Proof System](/op-stack/fault-proofs/explainer), using the [PermissionedDisputeGame](/op-stack/protocol/smart-contracts#permissioneddisputegame). + +## Purpose + +OPCM simplifies the L1 contract deployments for new OP Stack chains. For each smart contract release there will be a new OPCM instance. It addresses three aspects of deploying the OP Stack's L1 contracts: + +1. **Deploy Shared Contracts.** Shared contracts are used between many OP chains, so this occurs only occasionally in production. +2. **Deploy Shared Implementation Contracts.** This occurs once per contracts release in production. +3. **Deploy OP Chain Contracts.** This occurs for every OP chain deployment in production. + +Additionally, after the Upgrade 13 network upgrade, OPCM instances will be used to upgrade existing OP Stack chains. + +## Learn more + +* Checkout the [OPCM specs](https://specs.optimism.io/experimental/op-contracts-manager.html?utm_source=op-docs&utm_medium=docs) +* Checkout the [OPCM design document](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/op-contracts-manager-arch.md) diff --git a/docs/public-docs/chain-operators/reference/standard-configuration.mdx b/docs/public-docs/chain-operators/reference/standard-configuration.mdx new file mode 100644 index 0000000000000..4df9510d33a90 --- /dev/null +++ b/docs/public-docs/chain-operators/reference/standard-configuration.mdx @@ -0,0 +1,107 @@ +--- +title: What makes a chain standard? +description: Learn what makes a chain standard, how op-deployer helps with standardization, and why being standard matters. +--- +The standard configuration within the OP Stack ensures that chains deployed in the OP Stack ecosystem adhere to a consistent set of technical and governance parameters. +This standardization is critical for OP Stack interoperability, network security, and ease of upgrading your chain. + +This guide provides an in-depth explanation of what defines a standard configuration, how the [op-deployer](/chain-operators/tools/op-deployer/overview) aids standardization, and why adhering to these standards is essential. + +## What is a Standard chain? + +A standard chain in the OP Stack refers to a rollup that adheres to the following principles: + +1. **Technical conformance:** + * Compliance with the consensus parameters, policy parameters, admin roles, and service roles defined in the specifications. + For more details, please see the [OP Stack Configurability Specification](https://specs.optimism.io/protocol/configurability.html?utm_source=op-docs&utm_medium=docs). + * Utilization of officially supported features and modules of the OP Stack. + +2. **Governance alignment:** + * Adherence to the [Standard Rollup Charter](/op-stack/protocol/blockspace-charter#the-standard-rollup-charter). + * Transparent and collaborative decision-making aligned with the OP Stack ecosystem. + +3. **Interoperability:** + * Maintaining compatibility with the OP Stack protocol level cross-chain interactions. + +Chains that deviate from these principles, such as introducing unsupported features, are considered non-standard configurations. + +## Role of op-deployer in standardization + +The [op-deployer](/chain-operators/tools/op-deployer/overview) is a powerful tool designed to automate and streamline the deployment of standard configuration-compliant chains. +Key features include: + +* **Default values:** + op-deployer provides default values that adhere to standard specifications. + + +* **Ease of customization within standards:** + The op-deployer tool allows for overriding default values. For example, you can override the L2 block time to 1s, which is standard. However, please ensure you know what you're doing when applying overrides because they may violate standard specifications. + +By using op-deployer, chain operators can reduce the complexity of chain deployment while ensuring alignment with OP Stack standards. + + +## Why standardization matters + +Standardization benefits the OP Stack ecosystem in several ways: + +* **Interoperability:** + A standard stack and security model makes your chain eligible for interactions between other standard chains, such as single block cross-chain messaging and token transfers. + +* **Simplified upgrade path:** + Reduces the complexity of upgrading your chain to the latest version. + + +* **Reduced Support Overhead:** + Minimizes the need for custom support by ensuring uniformity across deployments. + + +### Standard features + +* **Default system contracts:** + Core protocol contracts must use governance approved release implementations of the OP Stack to provide security and compatibility. + + +* **Specified sequencer configurations:** + Sequencer settings must follow prescribed parameters for transaction ordering and submission to maintain network stability. + +## What is Not Standard? + +Certain configurations are explicitly not part of the standard setup. For example: + +* **Modified system contracts:** + Any alterations to core system contracts break standardization and aren't supported in the official OP Stack specification. + +For a detailed list of standard configurations, refer to the [Standard rollup configuration page](/op-stack/protocol/blockspace-charter). + +## superchain-registry + +The [superchain-registry](/op-stack/protocol/superchain-registry) is the authoritative index of all OP Stack chains in the registry. It ensures: + +* **Transparency:** + All registered chains are publicly listed with their configurations. + +* **Registry levels:** + Chains listed in the registry are denoted with a [`superchain_level`](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/glossary.md#superchain-level-and-rollup-stage) which tells you which chains are standard. + +* **Community trust:** + Being part of the registry signals reliability and alignment with Optimism Collective principles. + +## Next Steps + +1. **Understand standards:** + Familiarize yourself with the [OP Stack specifications](https://specs.optimism.io/protocol/configurability.html?utm_source=op-docs&utm_medium=docs) and the Blockspace Charter. + +2. **Use op-deployer:** + Leverage [op-deployer](/chain-operators/tools/op-deployer/overview) to ensure your chain aligns with standard configurations. + +3. **Verify deployment with op-validator:** + Use [op-validator](/operators/chain-operators/tools/op-validator) to verify your chain's deployment. + +4. **Seek guidance:** + Consult the [developer support](https://github.com/ethereum-optimism/developers/discussions) team for clarifications on standardization. + +## References + +* [OP Stack Specifications](https://specs.optimism.io/protocol/configurability.html?utm_source=op-docs&utm_medium=docs) +* [Blockspace Charter](/op-stack/protocol/blockspace-charter) +* [superchain-registry](https://github.com/ethereum-optimism/superchain-registry) diff --git a/docs/public-docs/chain-operators/tools/chain-monitoring.mdx b/docs/public-docs/chain-operators/tools/chain-monitoring.mdx new file mode 100644 index 0000000000000..dd29e3644911e --- /dev/null +++ b/docs/public-docs/chain-operators/tools/chain-monitoring.mdx @@ -0,0 +1,121 @@ +--- +title: Chain monitoring options +description: Learn about onchain and offchain monitoring options for your OP Stack chain. +--- + +This explainer covers the basics of onchain and offchain monitoring options for your OP Stack chain. Onchain monitoring services allow chain operators to monitor the overall system and onchain events. +Offchain monitoring lets chain operators to monitor the operation and behavior of nodes and other offchain components. + +## Onchain monitoring services + +Onchain monitoring services provide insights into the overall system, helping chain operators track and monitor on-chain events. Some examples of onchain monitoring services include `monitorism` and `dispute-mon`. + +### `monitorism` + +Monitorism is a tooling suite that supports monitoring and active remediation actions for the OP Stack chain. Monitorism uses monitors as passive security providing automated monitoring for the OP Stack. They are used to monitor the OP stack and alert on specific events that could be a sign of a security incident. + +Currently, the list of monitors includes: + +Security integrity monitors: These are monitors necessary for making sure Bridges between L2 and L1 are safe and work as expected. These monitors are divided in two subgroups: + +* Pre-Faultproof Chain Monitors: + * Fault Monitor: checks for changes in output roots posted to the L2OutputOracle contract. When a change is detected, it reconstructs the output root from a trusted L2 source and looks for a match. + * Withdrawals Monitor: checks for new withdrawals that have been proven to the OptimismPortal contract. Each withdrawal is checked against the `L2ToL1MessagePasser` contract. +* Faultproof chain monitors: + * Faultproof Withdrawal: The Faultproof Withdrawal component monitors `ProvenWithdrawals` events on the `OptimismPortal` contract and performs checks to detect any violations of invariant conditions on the chain. If a violation is detected, the issue is logged, and a Prometheus metric is set for the event. This component is designed to work exclusively with chains that are already utilizing the Fault Proofs system. This is a new version of the deprecated `chain-mon`, `faultproof-wd-mon`. For detailed information on how the component works and the algorithms used, please refer to the component README. + +Security monitors: Those tools monitor other aspects of several contracts used in optimism: + +* Global Events Monitor: made for taking YAML rules as configuration and monitoring the events that are emitted on the chain. +* Liveness Expiration Monitor: monitors the liveness expiration on Safes. +* Balances Monitor: emits a metric reporting the balances for the configured accounts. +* Multisig Monitor: The multisig monitor reports the paused status of the OptimismPortal contract. If set, reports the latest nonce of the configured Safe address and the latest presigned nonce stored in One Password.. The latest presigned nonce is identified by looking for items in the configured vault that follow a `ready-.json` name. The highest nonce of this item name format is reported. +* Drippie Monitor: tracks the execution and executability of drips within a Drippie contract. +* Secrets Monitor: takes a Drippie contract as a parameter and monitors for any drips within that contract that use the `CheckSecrets` dripcheck contract. `CheckSecrets` is a dripcheck that allows a drip to begin once a specific secret has been revealed (after a delay period) and cancels the drip if a second secret is revealed. Monitoring these secrets is important, as their revelation may indicate that the secret storage platform has been compromised and someone is attempting to exfiltrate the ETH controlled by the drip. + +For more information on these monitors and how to use them, [check out the repo](https://github.com/ethereum-optimism/monitorism?tab=readme-ov-file#monitorism). + +### `dispute-mon` + +Chain operators should consider running `op-dispute-mon`. It's an essential security monitoring service that tracks game statuses, providing visibility over the last 28 days. + +`dispute-mon` is set up and built the same way as `op-challenger`. This means that you can run it the same way (run `make op-dispute-mon` in the directory). + +A basic configuration option would look like this: + +``` +OP_DISPUTE_MON_LOG_FORMAT=logfmt +OP_DISPUTE_MON_METRICS_ENABLED=true +OP_DISPUTE_MON_METRICS_ADDR=0.0.0.0 +OP_DISPUTE_MON_METRICS_PORT=7300 + +OP_DISPUTE_MON_L1_ETH_RPC=.. +OP_DISPUTE_MON_ROLLUP_RPC=.. +OP_DISPUTE_MON_GAME_FACTORY_ADDRESS=.. + +OP_DISPUTE_MON_HONEST_ACTORS=.. +``` + +`OP_DISPUTE_MON_HONEST_ACTORS` is a CSV (no spaces) list of addresses that are used for the honest `op-challenger` instances. + +Additional flags: + +* `OP_DISPUTE_MON_GAME_WINDOW`: This is the window of time to report on games. It should leave a buffer beyond the max game duration for bond claiming. If Fault Proof game parameters are not changes (e.g. MAX\_CLOCK\_DURATION), it is recommended to leave this as the default. +* `OP_DISPUTE_MON_MONITOR_INTERVAL`: The interval at which to check for new games. Defaults to 30 seconds currently. +* `OP_DISPUTE_MON_MAX_CONCURRENCY`: The max thread count. Defaults to 5 currently. + +You can find more info on `op-dispute-mon` on [the repo](https://github.com/ethereum-optimism/optimism/tree/develop/op-dispute-mon). + +## Offchain component monitoring + +Offchain monitoring allows chain operators to monitor the operation and behavior of nodes and other offchain components. Some of the more common components that you'll likely want to monitor include `op-node`, `op-geth`, `op-proposer`, `op-batcher`, and `op-challenger`. +The general steps for enabling offchain monitoring are pretty consistent for all the OP components: + +1. Expose the monitoring port by enabling the `--metrics.enabled` flag +2. Customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively +3. Use [Prometheus](https://prometheus.io/) to scrape data from the metrics port +4. Save the data in `influxdb` +5. Share the data with [Grafana](https://grafana.com/) to build your custom dashboard + +### `op-node` + +`op-node` metrics and monitoring is detailed in the [Node Metrics and Monitoring](/node-operators/guides/monitoring/metrics) guide. To enable metrics, pass the `--metrics.enabled` flag to `op-node` and follow the steps above for customization options. +See [this curated list](/operators/node-operators/management/metrics#important-metrics) for important metrics to track specifically for `op-node`. + +### `op-geth` + +To enable metrics, pass the `--metrics.enabled` flag to the op-geth. You can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively. + +### `op-proposer` + +To enable metrics, pass the `--metrics.enabled` flag to the op-proposer. You can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively. + +You can find more information about these flags in our [Proposer configuration doc](/operators/chain-operators/configuration/proposer#metricsenabled). + +### `op-batcher` + +To enable metrics, pass the `--metrics.enabled` flag to the op-batcher. You can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively. + +You can find more information about these flags in our [Batcher configuration doc](/operators/chain-operators/configuration/proposer#metricsenabled). + +### `op-challenger` + +The `op-challenger` operates as the *honest actor* in the fault dispute system and defends the chain by securing the `OptimismPortal` and ensuring the game always resolves to the correct state of the chain. +For verifying the legitimacy of claims, `op-challenger` relies on a synced, trusted rollup node as well as a trace provider (e.g., [Cannon](/op-stack/fault-proofs/cannon)). See the [OP-Challenger Explainer](/op-stack/fault-proofs/challenger) for more information on this service. + +To enable metrics, pass the `--metrics.enabled` flag to `op-challenger` and follow the steps above for customization options. + +``` + --metrics.addr value (default: "0.0.0.0") ($OP_CHALLENGER_METRICS_ADDR) + Metrics listening address + + --metrics.enabled (default: false) ($OP_CHALLENGER_METRICS_ENABLED) + Enable the metrics server + + --metrics.port value (default: 7300) ($OP_CHALLENGER_METRICS_PORT) + Metrics listening port +``` + +## Next steps + +* If you encounter difficulties at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions). diff --git a/docs/public-docs/chain-operators/tools/explorer.mdx b/docs/public-docs/chain-operators/tools/explorer.mdx new file mode 100644 index 0000000000000..be67b86f47b54 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/explorer.mdx @@ -0,0 +1,60 @@ +--- +title: Blockscout block explorer +description: Blockscout an open source block explorer for the OP Stack. +--- + +[Blockscout](https://www.blockscout.com/) is an open source block explorer that supports OP Stack chains. +Keep reading for a quick overview on how to deploy Blockscout for your OP Stack chain. + + + Check out the [Blockscout documentation](https://docs.blockscout.com) for up-to-date information on how to deploy and maintain a Blockscout instance. + + +## Dependencies + +* [Docker](https://docs.docker.com/get-docker/) + +## Create an archive node + +Blockscout needs access to an [archive node](https://www.alchemy.com/overviews/archive-nodes#archive-nodes) for your OP Stack chain to properly index transactions, blocks, and internal interactions. +If using `op-geth`, you can run a node in archive mode with the `--gcmode=archive` flag. + + + Archive nodes take up significantly more disk space than full nodes. + You may need to have 2-4 terabytes of disk space available (ideally SSD) if you intend to run an archive node for a production OP Stack chain. + 1-200 gigabytes of disk space may be sufficient for a development chain. + + +## Installation + +Blockscout can be started from its source code on GitHub. + +```sh +git clone https://github.com/blockscout/blockscout.git -b production-optimism +cd blockscout/docker-compose +``` + +## Configuration + +Review the configuration files within the `envs` directory and make any necessary changes. +In particular, make sure to review `envs/common-blockscout.env` and `envs/common-frontend.env`. + +## Starting Blockscout + +Start Blockscout with the following command: + +```sh +DOCKER_REPO=blockscout-optimism docker compose -f geth.yml up +``` + +## Usage + +### Explorer + +After Blockscout is started, browse to [http://localhost](http://localhost) to view the user interface. +Note that this URL may differ if you have changed the Blockscout configuration. + +### API + +Blockscout provides both a REST API and a GraphQL API. +Refer to the [API documentation](https://docs.blockscout.com/for-users/api) for more information. diff --git a/docs/public-docs/chain-operators/tools/op-conductor.mdx b/docs/public-docs/chain-operators/tools/op-conductor.mdx new file mode 100644 index 0000000000000..f2998c615030e --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-conductor.mdx @@ -0,0 +1,763 @@ +--- +title: OP Conductor +description: Learn what the op-conductor is and how to use it to create a highly available and reliable sequencer. +--- + +This page will teach you what the `op-conductor` service is and how it works on +a high level. It will also get you started on setting it up in your own +environment. + +## Enhancing sequencer reliability and availability + +The [op-conductor](https://github.com/ethereum-optimism/optimism/tree/develop/op-conductor) +is an auxiliary service designed to enhance the reliability and availability of +a sequencer within high-availability setups. By minimizing the risks +associated with a single point of failure, the op-conductor ensures that the +sequencer remains operational and responsive. + +### Assumptions + +It is important to note that the `op-conductor` does not incorporate Byzantine +fault tolerance (BFT). This means the system operates under the assumption that +all participating nodes are honest and act correctly. + +### Summary of guarantees + +The design of the `op-conductor` provides the following guarantees: + +* **No Unsafe Reorgs** +* **No Unsafe Head Stall During Network Partition** +* **100% Uptime with No More Than 1 Node Failure** + +## Design + +![op-conductor.](/public/img/builders/chain-operators/op-conductor.svg) + +**On a high level, `op-conductor` serves the following functions:** + +### Raft consensus layer participation + +* **Leader determination:** Participates in the Raft consensus algorithm to + determine the leader among sequencers. +* **State management:** Stores the latest unsafe block ensuring consistency + across the system. + +### RPC request handling + +* **Admin RPC:** Provides administrative RPCs for manual recovery scenarios, + including, but not limited to: stopping the leadership vote and removing itself + from the cluster. +* **Health RPC:** Offers health RPCs for the `op-node` to determine whether it + should allow the publishing of transactions and unsafe blocks. + +### Sequencer health monitoring + +* Continuously monitors the health of the sequencer (op-node) to ensure + optimal performance and reliability. + +### Control loop management + +* Implements a control loop to manage the status of the sequencer (op-node), + including starting and stopping operations based on different scenarios and + health checks. + +## Conductor state transition + +The following is a state machine diagram of how the op-conductor manages the +sequencers Raft consensus. + +![op-conductor-state-transition.](/public/img/builders/chain-operators/op-conductor-state-transition.svg) + +**Helpful tips:** To better understand the graph, focus on one node at a time, +understand what can be transitioned to this current state and how it can +transition to other states. This way you could understand how we handle the +state transitions. + +## Setup + +At OP Labs, op-conductor is deployed as a kubernetes statefulset because it +requires a persistent volume to store the raft log. This guide describes +setting up conductor on an existing network without incurring downtime. + +You can utilize the [op-conductor-ops](https://github.com/ethereum-optimism/infra/tree/main/op-conductor-ops) tool to confirm the conductor status between the steps. + +### Assumptions + +This setup guide has the following assumptions: + +* 3 deployed sequencers (sequencer-0, sequencer-1, sequencer-2) that are all + in sync and in the same vpc network +* sequencer-0 is currently the active sequencer +* You can execute a blue/green style sequencer deployment workflow that + involves no downtime (described below) +* conductor and sequencers are running in k8s or some other container + orchestrator (vm-based deployment may be slightly different and not covered + here) + +### Spin up op-conductor + + + + Deploy a conductor instance per sequencer with sequencer-1 as the raft cluster + bootstrap node: + + * suggested conductor configs: + + ```yaml + OP_CONDUCTOR_CONSENSUS_ADDR: '0.0.0.0' + OP_CONDUCTOR_CONSENSUS_ADVERTISED: '' + OP_CONDUCTOR_CONSENSUS_PORT: '50050' + OP_CONDUCTOR_EXECUTION_RPC: ':8545' + OP_CONDUCTOR_HEALTHCHECK_INTERVAL: '1' + OP_CONDUCTOR_HEALTHCHECK_MIN_PEER_COUNT: '2' # set based on your internal p2p network peer count + OP_CONDUCTOR_HEALTHCHECK_UNSAFE_INTERVAL: '5' # recommend a 2-3x multiple of your network block time to account for temporary performance issues + OP_CONDUCTOR_LOG_FORMAT: logfmt + OP_CONDUCTOR_LOG_LEVEL: info + OP_CONDUCTOR_METRICS_ADDR: 0.0.0.0 + OP_CONDUCTOR_METRICS_ENABLED: 'true' + OP_CONDUCTOR_METRICS_PORT: '7300' + OP_CONDUCTOR_NETWORK: '' + OP_CONDUCTOR_NODE_RPC: ':8545' + OP_CONDUCTOR_RAFT_SERVER_ID: 'unique raft server id' + OP_CONDUCTOR_RAFT_STORAGE_DIR: /conductor/raft + OP_CONDUCTOR_RPC_ADDR: 0.0.0.0 + OP_CONDUCTOR_RPC_ENABLE_ADMIN: 'true' + OP_CONDUCTOR_RPC_ENABLE_PROXY: 'true' + OP_CONDUCTOR_RPC_PORT: '8547' + ``` + + * sequencer-1 op-conductor extra config: + + ```yaml + OP_CONDUCTOR_PAUSED: "true" + OP_CONDUCTOR_RAFT_BOOTSTRAP: "true" + ``` + + + + + Pause `sequencer-0` &` sequencer-2` conductors with [conductor_pause](#conductor_pause) RPC request. + + + + Deploy an `op-node` config update to all sequencers that enables conductor. Use + a blue/green style deployment workflow that switches the active sequencer to + `sequencer-1`: + + * all sequencer op-node configs: + + ```yaml + OP_NODE_CONDUCTOR_ENABLED: "true" # this is what commits unsafe blocks to the raft logs + OP_NODE_RPC_ADMIN_STATE: "" # this flag can't be used with conductor + ``` + + + + Confirm `sequencer-1` is active and successfully producing unsafe blocks. + Because `sequencer-1` was the raft cluster bootstrap node, it is now committing + unsafe payloads to the raft log. + + + + Add voting nodes to cluster using [conductor_AddServerAsVoter](#conductor_addserverasvoter) + RPC request to the leader conductor (`sequencer-1`) + + + + + + Confirm cluster membership and sequencer state: + + * `sequencer-0` and `sequencer-2`: + 1. raft cluster follower + 2. sequencer is stopped + 3. conductor is paused + 4. conductor enabled in op-node config + + * `sequencer-1` + 1. raft cluster leader + 2. sequencer is active + 3. conductor is paused + 4. conductor enabled in op-node config + + + + + + Resume all conductors with [conductor\_resume](#conductor_resume) RPC request to + each conductor instance. + + + + + + Confirm all conductors successfully resumed with [conductor_paused](#conductor_paused) + + + + Trigger leadership transfer to `sequencer-0` using [conductor_transferLeaderToServer](#conductor_transferleadertoserver) + + + + + Confirm cluster membership and sequencer state: + + **`sequencer-1` and `sequencer-2`:** + 1. raft cluster follower + 2. sequencer is stopped + 3. conductor is active + 4. conductor enabled in op-node config + + **`sequencer-0`:** + 1. raft cluster leader + 2. sequencer is active + 3. conductor is active + 4. conductor enabled in op-node config + + + + + Deploy a config change to `sequencer-1` conductor to remove the + `OP_CONDUCTOR_PAUSED: true` flag and `OP_CONDUCTOR_RAFT_BOOTSTRAP` flag. + + + + +#### Blue/green deployment + +In order to ensure there is no downtime when setting up conductor, you need to +have a deployment script that can update sequencers without network downtime. + +An example of this workflow might look like: + +1. Query current state of the network and determine which sequencer is + currently active (referred to as "original" sequencer below). + From the other available sequencers, choose a candidate sequencer. +2. Deploy the change to the candidate sequencer and then wait for it to sync + up to the original sequencer's unsafe head. You may want to check peer counts + and other important health metrics. +3. Stop the original sequencer using `admin_stopSequencer` which returns the + last inserted unsafe block hash. Wait for candidate sequencer to sync with + this returned hash in case there is a delta. +4. Start the candidate sequencer at the original's last inserted unsafe block + hash. + 1. Here you can also execute additional check for unsafe head progression + and decide to roll back the change (stop the candidate sequencer, start the + original, rollback deployment of candidate, etc.) +5. Deploy the change to the original sequencer, wait for it to sync to the + chain head. Execute health checks. + +#### Post-conductor launch deployments + +After conductor is live, a similar canary style workflow is used to ensure +minimal downtime in case there is an issue with deployment: + +1. Choose a candidate sequencer from the raft-cluster followers +2. Deploy to the candidate sequencer. Run health checks on the candidate. +3. Transfer leadership to the candidate sequencer using + `conductor_transferLeaderToServer`. Run health checks on the candidate. +4. Test if candidate is still the leader using `conductor_leader` after some + grace period (ex: 30 seconds) + 1. If not, then there is likely an issue with the deployment. Roll back. +5. Upgrade the remaining sequencers, run healthchecks. + +### Configuration options + +It is configured via its [flags / environment variables](https://github.com/ethereum-optimism/optimism/blob/develop/op-conductor/flags/flags.go) + +#### --consensus.addr (`CONSENSUS_ADDR`) + +* **Usage:** Address to listen for consensus connections +* **Default Value:** 127.0.0.1 +* **Required:** yes + +#### --consensus.advertised (`CONSENSUS_ADVERTISED`) + +* **Usage:** Address to advertise for consensus connections +* **Default Value:** 127.0.0.1 +* **Required:** yes + +#### --consensus.port (`CONSENSUS_PORT`) + +* **Usage:** Port to listen for consensus connections +* **Default Value:** 50050 +* **Required:** yes + +#### --raft.bootstrap (`RAFT_BOOTSTRAP`) + + + For bootstrapping a new cluster. This should only be used on the sequencer + that is currently active and can only be started once with this flag, + otherwise the flag has to be removed or the raft log must be deleted before + re-bootstrapping the cluster. + + +* **Usage:** If this node should bootstrap a new raft cluster +* **Default Value:** false +* **Required:** no + +#### --raft.server.id (`RAFT_SERVER_ID`) + +* **Usage:** Unique ID for this server used by raft consensus +* **Default Value:** None specified +* **Required:** yes + +#### --raft.storage.dir (`RAFT_STORAGE_DIR`) + +* **Usage:** Directory to store raft data +* **Default Value:** None specified +* **Required:** yes + +#### --node.rpc (`NODE_RPC`) + +* **Usage:** HTTP provider URL for op-node +* **Default Value:** None specified +* **Required:** yes + +#### --execution.rpc (`EXECUTION_RPC`) + +* **Usage:** HTTP provider URL for execution layer +* **Default Value:** None specified +* **Required:** yes + +#### --healthcheck.interval (`HEALTHCHECK_INTERVAL`) + +* **Usage:** Interval between health checks +* **Default Value:** None specified +* **Required:** yes + +#### --healthcheck.unsafe-interval (`HEALTHCHECK_UNSAFE_INTERVAL`) + +* **Usage:** Interval allowed between unsafe head and now measured in seconds +* **Default Value:** None specified +* **Required:** yes + +#### --healthcheck.safe-enabled (`HEALTHCHECK_SAFE_ENABLED`) + +* **Usage:** Whether to enable safe head progression checks +* **Default Value:** false +* **Required:** no + +#### --healthcheck.safe-interval (`HEALTHCHECK_SAFE_INTERVAL`) + +* **Usage:** Interval between safe head progression measured in seconds +* **Default Value:** 1200 +* **Required:** no + +#### --healthcheck.min-peer-count (`HEALTHCHECK_MIN_PEER_COUNT`) + +* **Usage:** Minimum number of peers required to be considered healthy +* **Default Value:** None specified +* **Required:** yes + +#### --paused (`PAUSED`) + + + There is no configuration state, so if you unpause via RPC and then restart, + it will start paused again. + + +* **Usage:** Whether the conductor is paused +* **Default Value:** false +* **Required:** no + +#### --rpc.enable-proxy (`RPC_ENABLE_PROXY`) + +* **Usage:** Enable the RPC proxy to underlying sequencer services +* **Default Value:** true +* **Required:** no + +### RPCs + +Conductor exposes [admin RPCs](https://github.com/ethereum-optimism/optimism/blob/develop/op-conductor/rpc/api.go#L17) +on the `conductor` namespace. + +#### conductor_overrideLeader + +`OverrideLeader` is used to override the leader status, this is only used to +return true for `Leader()` & `LeaderWithID()` calls. It does not impact the +actual raft consensus leadership status. It is supposed to be used when the +cluster is unhealthy and the node is the only one up, to allow batcher to +be able to connect to the node so that it could download blocks from the +manually started sequencer. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_overrideLeader","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_overrideLeader --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_pause + +`Pause` pauses op-conductor. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_pause","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_pause --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_resume + +`Resume` resumes op-conductor. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_resume","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_resume --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_paused + +Paused returns true if the op-conductor is paused. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_paused","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_paused --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_stopped + +Stopped returns true if the op-conductor is stopped. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_stopped","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_stopped --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor\_sequencerHealthy + +SequencerHealthy returns true if the sequencer is healthy. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_sequencerHealthy","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_sequencerHealthy --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_leader + + + API related to consensus. + + +Leader returns true if the server is the leader. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_leader","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_leader --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_leaderWithID + + + API related to consensus. + + +LeaderWithID returns the current leader's server info. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_leaderWithID","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_leaderWithID --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_addServerAsVoter + + + API related to consensus. + + +AddServerAsVoter adds a server as a voter to the cluster. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_addServerAsVoter","params":[, , ],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_addServerAsVoter --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_addServerAsNonvoter + + + API related to consensus. + + +AddServerAsNonvoter adds a server as a non-voter to the cluster. non-voter +The non-voter will not participate in the leader election. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_addServerAsNonvoter","params":[, , ],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_addServerAsNonvoter --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_removeServer + + + API related to consensus. + + +RemoveServer removes a server from the cluster. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_removeServer","params":[, ],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_removeServer --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_transferLeader + + + API related to consensus. + + +TransferLeader transfers leadership to another server (resigns). + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_transferLeader","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_transferLeader --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_transferLeaderToServer + + + API related to consensus. + + +TransferLeaderToServer transfers leadership to a specific server. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_transferLeaderToServer","params":[, , ],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_transferLeaderToServer --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_clusterMembership + +ClusterMembership returns the current cluster membership configuration. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_clusterMembership","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_clusterMembership --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_active + + + API called by `op-node`. + + +Active returns true if the op-conductor is active (not paused or stopped). + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_active","params":[],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + cast rpc conductor_active --rpc-url http://127.0.0.1:8547 + ``` + + + +#### conductor_commitUnsafePayload + + + API called by `op-node`. + + +CommitUnsafePayload commits an unsafe payload (latest head) to the consensus +layer. This method is typically called by the op-node to commit execution payload envelopes. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"conductor_commitUnsafePayload","params":[{ + "executionPayload": { + "parentHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "feeRecipient": "0x4200000000000000000000000000000000000019", + "stateRoot": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "receiptsRoot": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "blockNumber": "0x64", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x12345678", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba", + "transactions": [] + } + }],"id":1}' \ + http://127.0.0.1:8547 + ``` + + + + ```sh + # Example with basic payload structure + cast rpc conductor_commitUnsafePayload \ + '{"executionPayload":{"parentHash":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","feeRecipient":"0x4200000000000000000000000000000000000019","stateRoot":"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890","receiptsRoot":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","blockNumber":"0x64","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x12345678","extraData":"0x","baseFeePerGas":"0x7","blockHash":"0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba","transactions":[]}}' \ + --rpc-url http://127.0.0.1:8547 + ``` + + + +## Next steps + +* Checkout [op-conductor-mon](https://github.com/ethereum-optimism/infra): + which monitors multiple op-conductor instances and provides a unified interface + for reporting metrics. +* Get familiar with [op-conductor-ops](https://github.com/ethereum-optimism/infra/tree/main/op-conductor-ops)to interact with op-conductor. diff --git a/docs/public-docs/chain-operators/tools/op-deployer/installation.mdx b/docs/public-docs/chain-operators/tools/op-deployer/installation.mdx new file mode 100644 index 0000000000000..53a94d0915115 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/installation.mdx @@ -0,0 +1,24 @@ +--- +title: Installation +description: Learn how to install OP Deployer from binaries or source. +--- + +OP Deployer can be installed both from pre-built binaries and from source. This guide will walk you through both +methods. + +## Install From Binaries + +Installing OP Deployer from pre-built binaries is the easiest and most preferred way to get started. To install from +binaries, download the latest release from the [releases page](https://github.com/ethereum-optimism/optimism/releases?q=op-deployer&expanded=true) and extract the binary to a directory in your +`$PATH`. + +## Install From Source + +To install from source, you will need Go, `just`, and `git`. Then, run the following: + +```shell +git clone git@github.com:ethereum-optimism/optimism.git # you can skip this if you already have the repo +cd optimism/op-deployer +just build +cp ./bin/op-deployer /usr/local/bin/op-deployer # or any other directory in your $PATH +``` diff --git a/docs/public-docs/chain-operators/tools/op-deployer/known-limitations.mdx b/docs/public-docs/chain-operators/tools/op-deployer/known-limitations.mdx new file mode 100644 index 0000000000000..2efc31c0dc01c --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/known-limitations.mdx @@ -0,0 +1,52 @@ +--- +title: Known Limitations +description: Known limitations and workarounds for OP Deployer. +--- + +OP Deployer is subject to some known limitations which we're working on addressing in future releases. + +## Tagged Releases on New Chains + + +**Fixed in all versions after v0.0.11.** + + +It is not currently possible to deploy chains using tagged contract locators (i.e., those starting with `tag://`) +anywhere except Sepolia and Ethereum mainnet. If you try to, you'll see an error like this: + +``` +####################### WARNING! WARNING WARNING! ####################### + +You are deploying a tagged release to a chain with no pre-deployed OPCM. +Due to a quirk of our contract version system, this can lead to deploying +contracts containing unaudited or untested code. As a result, this +functionality is currently disabled. + +We will fix this in an upcoming release. + +This process will now exit. + +####################### WARNING! WARNING WARNING! ####################### +``` + +Like the error says, this is due to a quirk of how we version our smart contracts. We currently follow a process +like this: + +1. We tag a release, like op-contracts/v1.8.0. +2. We update the [release notes](https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0) to reference which contracts are updated in that release. +3. We manually deploy the updated contract implementations. +4. We manually deploy a new OPCM to reference the newly-deployed implementations, as well as existing implementations + for any contracts that have not been updated. + +There's a flaw in this strategy, however. The release only includes the contracts that explicitly changed during +that release. **This means that any contract not referenced as "updated" in the release notes is "in-development," and +has not been audited or approved by governance.** Deploying all contracts from the release tag will therefore deploy a +combination of prod-ready and in-development code. To get the version of the contract that will *actually* run in prod, +OP Deployer would have to reference all previous releases to get the correct combination of contracts. + +For example, to deploy on Holesky you will need to deploy contracts from versions `op-contracts/v1.8.0`, `op-contracts/v1.6.0`, and `op-contracts/v1.3.0`. On +Sepolia and mainnet, we've been incrementally deploying implementation contracts so we just use the existing +implementations to work around this issue. + +We plan on addressing this in our next release. In the meantime, as a workaround you can use a non-tagged locator for +development chains, or use Sepolia or Ethereum mainnet as your L1. diff --git a/docs/public-docs/chain-operators/tools/op-deployer/overview.mdx b/docs/public-docs/chain-operators/tools/op-deployer/overview.mdx new file mode 100644 index 0000000000000..608c40331bdb6 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/overview.mdx @@ -0,0 +1,42 @@ +--- +title: OP Deployer +description: A CLI tool for deploying and upgrading smart contracts for OP Stack chains. +--- + +OP Deployer is a CLI tool that simplifies deploying and upgrading smart contracts for OP Stack chains. It also +exposes a suite of libraries that allow developers to easily manage smart contracts from their applications. + +## Goals + +### Declarative + +With OP Deployer, developers define their chain's desired configuration in a declarative configuration file. The tool +then makes the minimum number of smart contract calls required to make the deployment match the configuration. This +ensures that the implementation details of the deployment are abstracted away, and allows complex configurations to be +expressed cleanly without concern for the underlying deployment process. + +### Portable + +OP Deployer is designed to be small, portable, and easily installed. As such it is distributed as a standalone binary +with no additional dependencies. This allows it to be used in a variety of contexts, including as a CLI tool, in CI +pipelines, and as part of local development environments like [Kurtosis](https://github.com/ethpandaops/optimism-package). + +### Standard, But Extensible + +OP Deployer aims to make doing the right thing easy, and doing dangerous things hard. As such its configuration and +API are optimized for deploying and upgrading Standard OP Chains. However, it also exposes a lower-level set of +primitives and configuration directives which users can use to deploy more complex configurations if the need arises. + +## Development Status + + +OP Deployer is undergoing active development and has been used for several mainnet deployments. It is considered +production-ready. However, please keep in mind that **OP Deployer has not been audited** and that any chains +deployed using OP Deployer should be checked thoroughly for correctness prior to launch. + + +## Next Steps + +- [Installation](/chain-operators/tools/op-deployer/installation) - Install OP Deployer +- [Bootstrap](/chain-operators/tools/op-deployer/usage/bootstrap) - Deploy global singletons and implementation contracts +- [Architecture](/chain-operators/tools/op-deployer/reference/architecture/overview) - Understand OP Deployer's architecture diff --git a/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/engine.mdx b/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/engine.mdx new file mode 100644 index 0000000000000..2d8714a43e806 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/engine.mdx @@ -0,0 +1,128 @@ +--- +title: Scripting Engine +description: Learn about OP Deployer's in-memory EVM scripting engine. +--- + +One of OP Deployer's most powerful features is its in-memory EVM scripting engine. The scripting engine provides +similar capabilities to Forge: + +* It runs all on-chain calls in a simulated environment first, which allows the effects of on-chain calls to be + validated before they cost gas. +* It exposes Foundry cheatcodes, which allow for deep instrumentation and customization of the EVM environment. These + cheatcodes in turn allow OP Deployer to call into Solidity scripts. + +The scripting engine is really the heart of OP Deployer. Without it, OP Deployer would be nothing more than a thin +wrapper over Forge. The scripting engine enables: + +* Easy integration with existing Solidity-based tooling. +* Detailed stack traces when deployments fail. +* Fast feedback loops that prevent sending on-chain transactions that may fail. +* Live chain forking. + +For these reasons and more, the scripting engine is a critical part of OP Deployer's architecture. You will see that +almost all on-chain interactions initiated by OP Deployer use the scripting engine to call into a Solidity script. +The script then uses `vm.broadcast` to signal a transaction that should be sent on-chain. + +## Why Use Solidity Scripts? + +Solidity scripts are much more ergonomic than Go code for complex on-chain interactions. They allow for: + +* Easy integration with existing Solidity-based tooling and libraries. +* Simple ABI encoding/decoding. +* Clear separation of concerns between inter-contract calls, and the underlying RPC calls that drive them. + +The alternative is to encode all on-chain interactions in Go code. This is possible, but it is much more verbose and +requires writing bindings between Go and the Solidity ABI. These bindings are error-prone and difficult to maintain. + +## Engine Implementation + +The scripting engine is implemented in the `op-chain-ops/script` package. It extends Geth's EVM implementation with +Forge cheatcodes, and defines some tools that allow Go structs to be etched into the EVM's memory. Geth exposes +hooks that drive most of the engine's behavior. The best way to understand these further is to read the code. + +## Using the Engine + +OP Deployer uses the etching tooling described above to communicate between OP Deployer and the scripting engine. +Most Solidity scripts define an input contract, an output contract, and the script itself. The script reads data +from fields on the input contract, then sets fields on the output contract as it runs. OP Deployer defines the input +and output contracts as Go structs, like this: + +```go +package foo_script + +type FooInput struct { + Number uint64 + Bytes []byte +} + +type FooOutput struct { + Result uint64 + Bytes []byte +} +``` + +The input and output contracts are then "etched" into the EVM's memory, like this: + +```go +package foo_script + +// ... struct defs elided + +func Run(host *script.Host, input FooInput) (FooOutput, error) { + // Create a variable to hold our output + var output FooOutput + + // Make new addresses for our input/output contracts + inputAddr := host.NewScriptAddress() + outputAddr := host.NewScriptAddress() + + // Inject the input/output contracts into the EVM as precompiles + cleanupInput, err := script.WithPrecompileAtAddress[*FooInput](host, inputAddr, &input) + if err != nil { + return output, fmt.Errorf("failed to insert input precompile: %w", err) + } + defer cleanupInput() + + cleanupOutput, err := script.WithPrecompileAtAddress[*FooOutput](host, outputAddr, &output, + script.WithFieldSetter[*FooOutput]) + if err != nil { + return output, fmt.Errorf("failed to insert output precompile: %w", err) + } + defer cleanupOutput() + + // ... do stuff with the input/output contracts ... +} +``` + +The script engine will automatically generate getters and setters for the fields on the input and output contracts. +You can use the `evm:` struct tag to customize the behavior of these getters and setters. + +Finally, the script itself gets etched into the EVM's memory and executed, like this: + +```go +package foo_script + +type FooScript struct { + Run func(input, output common.Address) error +} + +func Run(host *script.Host, input FooInput) (FooOutput, error) { + // .. see implementation above... + + deployScript, cleanupDeploy, err := script.WithScript[FooScript](host, "FooScript.s.sol", "FooScript") + if err != nil { + return output, fmt.Errorf("failed to load %s script: %w", scriptFile, err) + } + defer cleanupDeploy() + + if err := deployScript.Run(inputAddr, outputAddr); err != nil { + return output, fmt.Errorf("failed to run %s script: %w", scriptFile, err) + } + + return output, nil +} +``` + +You may notice that the script is loaded from a file. To run the scripting engine, contract artifacts (**not** +source code) must exist somewhere on disk for the scripting engine to use. For more information on that, see the +[Artifacts Locators](/chain-operators/tools/op-deployer/reference/artifacts-locators) page. diff --git a/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/overview.mdx b/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/overview.mdx new file mode 100644 index 0000000000000..eb82ad17b0fb4 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/overview.mdx @@ -0,0 +1,15 @@ +--- +title: Architecture +description: Understand OP Deployer's architecture and internals. +--- + +This section details OP Deployer's architecture and internals. Unless you're contributing directly to OP Deployer, +you don't need to read this. + +* [Deployment Pipeline](/chain-operators/tools/op-deployer/reference/architecture/pipeline): Describes the stages of the deployment pipeline. +* [Scripting Engine](/chain-operators/tools/op-deployer/reference/architecture/engine): Describes the scripting engine that OP Deployer uses to interact + with the EVM. + +![Full architecture diagram](/public/img/chain-operators/op-deployer/full-architecture.png) + +*Full architecture diagram ([source](https://www.figma.com/board/bbp16y6ZwIkxzOoKhDu9kk/op-deployer-architecture?node-id=0-1&p=f&t=1Eg9JBP0RuVmdtsM-0))* diff --git a/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/pipeline.mdx b/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/pipeline.mdx new file mode 100644 index 0000000000000..bf303fa8ba7b8 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/reference/architecture/pipeline.mdx @@ -0,0 +1,57 @@ +--- +title: Deployment Pipeline +description: Learn about the stages of the OP Deployer deployment pipeline. +--- + +OP Deployer is architected as a pipeline where each stage is responsible for a single piece of the deployment process. +The pipeline consumes a configuration called an *intent* which describes the desired state of the chain, and +produces a file called the *state* which describes the current state of the chain during and after the deployment. +The steps of the pipeline are: + +1. Initialization +2. Shared Contracts Deployment +3. Implementations Deployment +4. OP Chain Deployment +5. Alt-DA Deployment +6. Dispute Game Deployment +7. L2 Genesis Generation +8. Setting Start Block + +State is written to disk after each state. This allows the pipeline to be restarted from any point in the event of a +recoverable error. + +We'll cover each of these stages in more detail below. + +## Initialization + +During this step, OP Deployer sets initial values for the pipeline based on the user's intent. These values will be +used by downstream stages. For example, if the user is deploying using an existing set of shared contracts, +those contracts will be inserted into the state during this step. + +## Shared Contracts/Implementations Deployment + +Next, the base contracts for the chain are deployed. This includes shared management contracts like +`SuperchainConfig` and `ProtocolVersions`, as well as implementation contracts that will be used for the OP Chain +deployment in the future like the OP Contracts Manager (OPCM). + +Most chains will be configured to use existing implementations. In this case, these steps will be skipped. + +## OP Chain Deployment + +The OP Chain itself is deployed during this step. Multiple chains will be deployed if they are specified in the +intent. The deployment works by calling into the OPCM, which will emit an event for each successfully-deployed chain. + +## Customizations Deployment + +The next two steps (Alt-DA and Dispute Game) deploy customizations. As their names imply, they deploy Alt-DA and +additional dispute game contracts. Typically, these steps will be skipped as they are mostly useful in testing. + +## L2 Genesis Generation + +This step generates the L2 Genesis file which is used to initialize the chain. This file is generated by calling +into `L2Genesis.sol`, and dumping the outputted state. + +## Setting Start Block + +Lastly, the start block is set to the current block number on L1. This is done last to ensure that the start block +is relatively recent, since the deployment process can take arbitrarily long. diff --git a/docs/public-docs/chain-operators/tools/op-deployer/reference/artifacts-locators.mdx b/docs/public-docs/chain-operators/tools/op-deployer/reference/artifacts-locators.mdx new file mode 100644 index 0000000000000..b21a7ad52b2c1 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/reference/artifacts-locators.mdx @@ -0,0 +1,23 @@ +--- +title: Artifacts Locators +description: Learn how OP Deployer uses artifacts locators to point to contract artifacts. +--- + +OP Deployer calls into precompiled contract artifacts. To make this work, OP Deployer uses artifacts locators to +point to the location of contract artifacts. While locators are nothing more than URLs, they do encode some +additional behaviors which are described here. + +## Locator Types + +Locators can be one of three types: + +* `tag://` locators, which point to a versioned contracts release. These resolve to a known URL. Artifacts + downloaded using a tagged locator are validated against a hardcoded checksum in the OP Deployer implementation. + This prevents tampering with the contract artifacts once they have been tagged. Additionally, tagged locators are + cached on disk to avoid repeated downloads. + * ex: `tag://op-contracts/v1.8.0-rc.4` +* `https://` locators, which point to a tarball of contract artifacts somewhere on the web. HTTP locators are cached + just like tagged locators are, but they are not validated against a checksum. + * ex: `https://` +* `file://` locators, which point to a directory on local disk containing the artifacts. + * ex: `file:///packages/contracts-bedrock/forge-artifacts` diff --git a/docs/public-docs/chain-operators/tools/op-deployer/reference/custom-deployments.mdx b/docs/public-docs/chain-operators/tools/op-deployer/reference/custom-deployments.mdx new file mode 100644 index 0000000000000..dca8e4be2974c --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/reference/custom-deployments.mdx @@ -0,0 +1,189 @@ +--- +title: Custom Deployments +description: Learn how to manage custom deployments with OP Deployer. +--- + +While OP Deployer was designed primarily for use with chains that are governed by Optimism, it also supports managing +custom deployments. This is particularly common for RaaS providers, whose customers often request deployments with +custom L1s (or L2s, in the case of L3s) or governance. This guide will walk you through the process of managing these +chains using OP Deployer. + + +Chains deployed in this way are not subject to Optimism Governance. They may be running customized or unaudited +code. Use at your own risk. + + +## Bootstrapping + +The first step to deploying a custom OP Stack is to bootstrap it onto an L1. This process will: + +* Deploy shared management contracts like `SuperchainConfig` and `SuperchainVersions`. +* Deploy contract implementations that will be shared among all OP Chains in this deployment. +* Set up ownership so that you can control the deployment. + +You will use the [`bootstrap`](/chain-operators/tools/op-deployer/usage/bootstrap) family of commands on `op-deployer` to do this. + +### Bootstrap Shared Contracts + +Every OP Chain belongs to a logical deployment group. This group consists of a set of shared contracts that control the behavior +of a group of OP Chains. This includes: + +* Pausing bridges +* Signaling which protocol versions are required and recommended + +You can deploy a new set of shared contracts for each OP Chain, or you can deploy them once and share across multiple OP Chains. +The choice is up to the deployer. Note that you **cannot** share a set of shared contracts across multiple L1s. + +To begin, bootstrap the shared contracts onto your chosen L1 with the following command: + +```shell +op-deployer bootstrap superchain \ + --l1-rpc-url="" \ + --private-key="" \ + --artifacts-locator="" \ + --outfile="" \ + --superchain-proxy-admin-owner="" \ + --protocol-versions-owner="" \ + --guardian="" +``` + +This will output a JSON file containing the addresses of the relevant contracts. Keep track of this file, as you will +need it in subsequent steps. + +We recommend the following these best practices when bootstrapping: + +1. Use Gnosis SAFEs for ownership roles like `guardian` and `superchain-proxy-admin-owner`. The owner **must** be a + smart contract to support future upgrades, so a SAFE is a sensible default. +2. Use a regular EOA as the deployer. It will not have any control over the deployment once the deployment completes. +3. Use a standard contracts tag (e.g. `tag://op-contracts/v2.0.0`). This will make upgrading easier. + +### Bootstrapping Implementations + +The smart contracts for an OP Chain are deployed using a factory that points to a set of predeployed implementations. +You must deploy the factory and the implementations every time you deploy to a new L1 and whenever new smart +contract versions are released. Implementations are deployed using `CREATE2`, so addresses may be reused if they +already exist on the L1. + + +You may need to use different versions of OP Deployer depending on which contracts version you are deploying. See the +[releases guide](/chain-operators/tools/op-deployer/reference/releases) for more information on picking the right release. + + +To deploy the implementations, use the following command: + +```shell +op-deployer bootstrap implementations \ + --artifacts-locator="" \ + --l1-rpc-url="" \ + --outfile="" \ + --mips-version="2" \ + --private-key="" \ + --protocol-versions-proxy="
" \ + --superchain-config-proxy="
" \ + --upgrade-controller="" +``` + +Similar to the `bootstrap superchain` command, this will output a JSON file containing the addresses of the relevant +contracts. Again, keep track of this file. + +The most important address in the implementations file is the OPCM, or OP Contracts Manager. This contract is the +factory that will deploy all the OP Chains belonging to this deployment group. It is also responsible for upgrading between +different contracts versions. Please keep the following **very important** invariants in mind with the OPCM: + +* There is a one-to-one mapping between each OPCM, and contracts version. +* Each OPCM is associated with **exactly one** deployment group. This means that you **must** deploy a new OPCM using the + `bootstrap implementations` command for each new deployment group you manage. + +## Deploying + +After bootstrapping the contracts and implementations, you can deploy your L2 chains with the `apply` command. You +will need to specify a `configType` of `standard-overrides` and set the `opcmAddress` field in your intent to the +address of the OPCM above. **Make sure you call the right OPCM.** Failing to call the right OPCM might lead to +deploying incorrect contract versions, or associating your chain with the wrong deployment. + + +Make sure that you use the same `l1ContractsLocator` and `l2ContractsLocator` as the ones used in the bootstrap +commands. Otherwise, you may run into deployment errors. + + +See the following config for an example: + +```toml +configType = "standard-overrides" +l1ChainID = 11155420 +opcmAddress = "0x..." +l1ContractsLocator = "tag://..." # must match the one used in bootstrap +l2ContractsLocator = "tag://..." + +[[chains]] +# Chain configs... +``` + +Once `apply` completes successfully, you can use the `inspect` family of commands to download your chain's L2 +genesis and rollup config files. + +## Upgrading + +You can upgrade between smart contract versions using the [`upgrade`](/chain-operators/tools/op-deployer/usage/upgrade) family of commands. As a prerequisite, +you must deploy OPCMs for each version you want to upgrade between using the `bootstrap implementations` command. + +You will need to use the correct sub-command for the version you are upgrading from. For example, if you are +upgrading from `v2.0.0` to `v3.0.0`, you will need to use the `upgrade v3.0.0` subcommand. + +Unlike the `bootstrap` and `apply` commands, the `upgrade` command does not interact with the chain directly. Instead, +it generates calldata that you can use with `cast`, Gnosis SAFE, or whatever tooling you use to manage your L1. + +To run the upgrade command, use the following: + + +Upgrading between non-standard contract versions is not supported. + + +```shell +op-deployer upgrade \ + --config \ + --l1-rpc-url="" +``` + +The contents of your config JSON should look something like this: + +```json +{ + "prank": "
", + "opcm": "
", + "chainConfigs": [ + { + "systemConfigProxy": "
", + "proxyAdmin": "
", + "absolutePrestate": "<32-byte hash of the chain's absolute prestate>" + } + ] +} +``` + +You will get output that looks something like this: + +```json +{ + "to": "", + "data": "", + "value": "0x0" +} +``` + +At this point, you will need to build a transaction that uses the calldata and calls the `upgrade()` method on the +OPCM. The exact method you use to do this will depend on your tooling. As an example, you can craft a transaction +for use with Gnosis SAFE CLI using the command below: + + +The Gnosis SAFE UI does not support the `--delegate` flag, so the CLI is required if you're using a Gnosis SAFE. + + +```shell +safe-cli send-custom 0 --private-key +--delegate +``` + +Note that no matter which method you use to broadcast the calldata, the call to OPCM **must** come from the smart +contract that owns the chain and the call must be via `DELEGATECALL`. If your upgrade command reverts, it is likely +due to one of these conditions not being met. diff --git a/docs/public-docs/chain-operators/tools/op-deployer/reference/releases.mdx b/docs/public-docs/chain-operators/tools/op-deployer/reference/releases.mdx new file mode 100644 index 0000000000000..f583283042bf3 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/reference/releases.mdx @@ -0,0 +1,115 @@ +--- +title: Releases +description: Learn about OP Deployer versioning and how to add support for new contract versions. +--- + +## Latest Releases + +The latest version of OP Deployer is always available at [releases](https://github.com/ethereum-optimism/optimism/releases?q=op-deployer&expanded=true). You should search for `op-deployer` to +exclude other packages. + +## Versioning + +For all releases after `v0.0.11`, each minor version of OP Deployer will support a single release of the +governance-approved smart contracts. If you want to deploy an earlier version of the contracts (which may be +dangerous!), you should use an earlier version of OP Deployer. This setup allows our smart contract developers to make +breaking changes on `develop`, while still allowing new chains to be deployed and upgraded using production-ready smart +contracts. + +If you deploy from an HTTPS or file [locator](/chain-operators/tools/op-deployer/reference/artifacts-locators), the deployment behavior will match the +contract's tag. For example, if version `v0.2.0` supports `v2.0.0` then the deployment will work as if you were +deploying `op-contracts/v2.0.0`. Typically, errors like `unknown selector: ` imply that you're using the wrong +version of OP Deployer for your contract artifacts. If this happens, we recommend trying different versions until you +get one that works. Note that this workflow is **not recommended** for production chains. + +## Version Backports + +From time to time, we may backport bugfixes from develop onto earlier versions of OP Deployer. The process for this is +as follows: + +1. If one doesn't exist already, make a new branch for the version lineage you're patching (e.g. `v0.2.x`). This branch + should be protected (not deletable) and should be based on the latest release of that lineage. The branch should be + named as follows: + `backports/op-deployer/`. +2. Open a PR with the backport against that branch. Be sure to reference the original commit in the backport. +3. Make and push a new tag on that lineage. + +Example for backporting fix(es) from `develop` and created a new release `op-deployer/v0.2.1`: + +``` +git checkout -b backports/op-deployer/v0.2.0 op-deployer/v0.2.0 +git push origin backports/op-deployer/v0.2.0 +git checkout -b fixes/deployer-v0.2.0 backports/op-deployer/v0.2.0 +git cherry-pick +git push origin fixes/deployer-v0.2.0 + +1. open pr from fixes/deployer-v0.2.0 targeting backports/op-deployer/v0.2.0 +2. merge the pr +3. push a new tag for op-deployer/v0.2.1 on backports/op-deployer/v0.2.0 branch (goreleaser will create the release) +``` + +## Adding Support for New Contract Versions + +Adding support for a new contract version is a multi-step process. Here's a high-level overview. For the sake of +simplicity we will assume you are adding support for a new `rc` release. + +### Step 1: Add Support on Develop + +**This section is designed for people developing OP Deployer itself.** + +First, you need to add support for the new contract version on the `develop` branch. This means ensuring that the +deployment pipeline supports whatever changes are required for the new version. Typically, this means passing in new +deployment variables, and responding to ABI changes in the Solidity scripts/OPCM. + +### Step 2: Add the Published Artifacts + +Run the following from the root of the monorepo: + +```bash +cd packages/contracts-bedrock +just clean +just build +bash scripts/ops/calculate-checksum.sh +# copy the outputted checksum +cd ../../op-deployer +just calculate-artifacts-hash +``` + +This will calculate the checksum of your artifacts as well as the hash of the artifacts tarball. OP Deployer uses +these values to download and verify tagged contract locators. + +Now, update `standard/standard.go` with these values so that the new artifacts tarball can be downloaded: + +```go +// Add a new const for your release + +const ContractsVXTag = "op-contracts/vX.Y.Z" + +var taggedReleases = map[string]TaggedRelease{ +// Other releases... +ContractsVXTag: { +ArtifactsHash: common.HexToHash(""), +ContentHash: common.HexToHash(""), +}, +} + +// Update the L1/L2 versions accordingly +func IsSupportedL1Version(tag string) bool { +return tag == ContractsVXTag +} +``` + +### Step 3: Update the SR With the New Release + +Add the new RC to the [standard versions](https://github.com/ethereum-optimism/superchain-registry/tree/main/validation/standard) in the superchain-registry. + +### Step 4: Update the Validation Package + +The SR is pulled into OP Deployer via the `validation` package. Update it by running the following command from the +root of the monorepo: + +```shell +go get -u github.com/ethereum-optimism/superchain-registry/validation@ +``` + +That should be it! diff --git a/docs/public-docs/chain-operators/tools/op-deployer/usage/apply.mdx b/docs/public-docs/chain-operators/tools/op-deployer/usage/apply.mdx new file mode 100644 index 0000000000000..615bd7e6dffc5 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/usage/apply.mdx @@ -0,0 +1,46 @@ +--- +title: Apply Command +description: Learn how to deploy your OP Chain based on the intent file. +--- + +Once you have [initialized](/chain-operators/tools/op-deployer/usage/init) your intent and state files, you can use the `apply` command to perform the +deployment. + +## Usage + +You can call the `apply` command like this: + +```shell +op-deployer apply \ + --workdir \ + <... additional arguments ...> +``` + +You will need to specify additional arguments depending on what you're trying to do. See below for a reference of each +supported CLI arg. + +## CLI Arguments + +### `--deployment-target` + +**Default:** `live` + +`--deployment-target` specifies where each chain should be deployed to. It can be one of the following values: + +* `live`: Deploys to a live L1. Concretely, this means that OP Deployer will send transactions identified by + `vm.broadcast` calls to L1. `--l1-rpc-url` and `--private-key` must be specified when using this target. +* `genesis`: Deploys to an L1 genesis file. This is useful for testing or local development purposes. You do not need to + specify any additional arguments when using this target. +* `calldata`: Deploys to a calldata file. This is useful for generating inputs to multisig wallets for future execution. +* `noop`: Doesn't deploy anything. This is useful for performing a dry-run of the deployment process prior to another + deployment target. + +### `--l1-rpc-url` + +Defines the RPC URL of the L1 chain to deploy to. + +### `--private-key` + +Defines the private key to use for signing transactions. This is only required for deployment targets that involve +sending live transactions. Note that ownership over each L2 is transferred to the proxy admin owner specified in the +intent after the deployment completes, so it's OK to use a hot key for this purpose. diff --git a/docs/public-docs/chain-operators/tools/op-deployer/usage/bootstrap.mdx b/docs/public-docs/chain-operators/tools/op-deployer/usage/bootstrap.mdx new file mode 100644 index 0000000000000..0c42a7d6561bd --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/usage/bootstrap.mdx @@ -0,0 +1,103 @@ +--- +title: Bootstrap Commands +description: Learn how to deploy global singletons and implementation contracts for new OP Stack deployments. +--- + + +If you are joining an existing OP Stack deployment, you can skip to the [`init`](/chain-operators/tools/op-deployer/usage/init) and [`apply`](/chain-operators/tools/op-deployer/usage/apply) commands to create your L2 chain(s). + + +Bootstrap commands are used to deploy global singletons and implementation contracts for new OP Stack deployments. +The deployed contracts can then be used with future invocations of `apply` so that new L2 chains can join that deployment. +Most users won't need to use these commands, since `op-deployer apply` will automatically use standard predeployed contracts for the L1/settlement-layer you are deploying on. However, you will need to use bootstrap commands if you're creating a new standalone deployment. + +There are several bootstrap commands available, which you can view by running `op-deployer bootstrap --help`. We'll +focus on the most important ones, which should be run in the sequence listed below. + +**It is safe to call these commands from a hot wallet.** None of the contracts deployed by these command are "ownable," +so the deployment address has no further control over the system. + +## Bootstrap Shared Contracts + +```shell +op-deployer bootstrap superchain \ + --l1-rpc-url="" \ + --private-key="" \ + --outfile="./.deployer/bootstrap_superchain.json" \ + --superchain-proxy-admin-owner="" \ + --protocol-versions-owner="" \ + --guardian="" +``` + +### CLI Arguments + +#### `--required-protocol-version`, `--recommended-protocol-version` (optional) + +Defaults to `OPStackSupport` value read from `op-geth`, but can be overridden by these flags. + +#### `--superchain-proxy-admin-owner`, `--protocol-versions-owner`, `--guardian` + +In a dev environment, these can all be hot wallet EOAs. In a production environment, `--guardian` should be an HSM (hardware security module) protected hot wallet and the other two should be multisig cold-wallets (e.g. Gnosis Safes). + +### Output + +This command will deploy several contracts, and output a JSON like the one below: + +```json +{ + "proxyAdminAddress": "0x269b95a33f48a9055b82ce739b0c105a83edd64a", + "superchainConfigImplAddress": "0x2f4c87818d67fc3c365ea10051b94f98893f3c64", + "superchainConfigProxyAddress": "0xd0c74806fa114c0ec176c0bf2e1e84ff0a8f91a1", + "protocolVersionsImplAddress": "0xbded9e39e497a34a522af74cf018ca9717c5897e", + "protocolVersionsProxyAddress": "0x2e8e4b790044c1e7519caac687caffd4cafca2d4" +} +``` + +## Bootstrap Implementations + +```shell +op-deployer bootstrap implementations \ + --l1-rpc-url="" \ + --outfile="./.deployer/bootstrap_implementations.json" \ + --private-key="" \ + --protocol-versions-proxy="" \ + --superchain-config-proxy="" \ + --superchain-proxy-admin="" \ + --challenger="" \ + --upgrade-controller="" +``` + +### Output + +This command will deploy implementations, blueprints, and the OPCM. Deployments are (for the most part) +deterministic, so contracts will only be deployed once per chain as long as the implementation and constructor args +remain the same. This applies to the `op-deployer apply` pipeline - that is, if someone else ran `op-deployer bootstrap implementations` +at some point on a given L1 chain, then the `apply` pipeline will re-use those implementations. + +The command will output a JSON like the one below: + +```json +{ + "opcmAddress": "0x82879934658738b6d5e8f781933ae7bbae05ba31", + "opcmContractsContainerAddress": "0x1e8de1574a2e085b7a292c760d90cf982d3c1a11", + "opcmGameTypeAdderAddress": "0xcab868d42d9088b86598a96d010db5819c19b847", + "opcmDeployerAddress": "0xf8b6718b28fa36b430334e78adaf97174fed818c", + "opcmUpgraderAddress": "0xa4d0a44890fafce541bdc4c1ca36fca1b5d22f56", + "opcmInteropMigratorAddress": "0xf0fca53bb450dd2230c7eb58a39a5dbfc8492fb6", + "opcmStandardValidatorAddress": "0x1364a02f64f03cd990f105058b8cc93a9a0ab2a1", + "delayedWETHImplAddress": "0x570da3694c06a250aea4855b4adcd09505801f9a", + "optimismPortalImplAddress": "0x1aa1d3fc9b39d7edd7ca69f54a35c66dcf1168f1", + "ethLockboxImplAddress": "0xe6e51fa10d481002301534445612c61bae6b3258", + "preimageOracleSingletonAddress": "0x1fb8cdfc6831fc866ed9c51af8817da5c287add3", + "mipsSingletonAddress": "0x7a8456ba22df0cb303ae1c93d3cf68ea3a067006", + "systemConfigImplAddress": "0x9f2b1fffd8a7aeef7aeeb002fd8477a4868e7e0a", + "l1CrossDomainMessengerImplAddress": "0x085952eb0f0c3d1ca82061e20e0fe8203cdd630a", + "l1ERC721BridgeImplAddress": "0xbafd2cae054ddf69af27517c6bea912de6b7eb8f", + "l1StandardBridgeImplAddress": "0x6abaa7b42b9a947047c01f41b9bcb8684427bf24", + "optimismMintableERC20FactoryImplAddress": "0xdd0b293b8789e9208481cee5a0c7e78f451d32bf", + "disputeGameFactoryImplAddress": "0xe7ab0c07ee92aae31f213b23a132a155f5c2c7cc", + "anchorStateRegistryImplAddress": "0xda4f46fad0e38d763c56da62c4bc1e9428624893", + "superchainConfigImplAddress": "0xdaf60e3c5ef116810779719da88410cce847c2a4", + "protocolVersionsImplAddress": "0xa95ac4790fedd68d9c3b30ed730afaec6029eb31" +} +``` diff --git a/docs/public-docs/chain-operators/tools/op-deployer/usage/init.mdx b/docs/public-docs/chain-operators/tools/op-deployer/usage/init.mdx new file mode 100644 index 0000000000000..c5a7f9d2e2375 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/usage/init.mdx @@ -0,0 +1,112 @@ +--- +title: Init Command +description: Learn how to initialize intent and state files for your OP Stack deployment. +--- + +The `init` command is used to create a new intent and state file in the specified directory. This command is the +starting point of each new deployment. + +## Usage + +The `init` command is used like this: + +```shell +op-deployer init \ + --l1-chain-id \ + --l2-chain-ids \ + --outdir \ + --intent-type +``` + +You should then see the following files appear in your output directory: + +``` +outdir +├── intent.toml +└── state.json +``` + +The `intent.toml` file is where you specify the configuration for your deployment. The `state.json` file is where OP +Deployer will output the current state of the deployment after each stage of the deployment. + +## Intent File + +Your intent should look something like this: + +```toml +configType = "standard" +l1ChainID = 11155420 +fundDevAccounts = false +useInterop = false +l1ContractsLocator = "tag://op-contracts/v1.8.0-rc.4" +l2ContractsLocator = "tag://op-contracts/v1.7.0-beta.1+l2-contracts" + +[superchainRoles] + proxyAdminOwner = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570" + protocolVersionsOwner = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570" + guardian = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570" + +[[chains]] + id = "0x0000000000000000000000000000000000000000000000000000000000002390" + baseFeeVaultRecipient = "0x0000000000000000000000000000000000000000" + l1FeeVaultRecipient = "0x0000000000000000000000000000000000000000" + sequencerFeeVaultRecipient = "0x0000000000000000000000000000000000000000" + operatorFeeVaultRecipient = "0x0000000000000000000000000000000000000000" + eip1559DenominatorCanyon = 250 + eip1559Denominator = 50 + eip1559Elasticity = 6 + + # Revenue Sharing Configuration + useRevenueShare = true + chainFeesRecipient = "0x0000000000000000000000000000000000000000" + + [chains.roles] + l1ProxyAdminOwner = "0x0000000000000000000000000000000000000000" + l2ProxyAdminOwner = "0x0000000000000000000000000000000000000000" + systemConfigOwner = "0x0000000000000000000000000000000000000000" + unsafeBlockSigner = "0x0000000000000000000000000000000000000000" + batcher = "0x0000000000000000000000000000000000000000" + proposer = "0x0000000000000000000000000000000000000000" + challenger = "0x0000000000000000000000000000000000000000" +``` + + +Before you can use your intent file for a deployment, you will need to update all zero values to whatever is +appropriate for your chain. For dev environments, it is ok to use all EOAs/hot-wallets. + + +## Revenue Sharing Configuration + +The `useRevenueShare` field controls whether your chain enables the revenue sharing system feature: + +* **`useRevenueShare = true`** (default for standard configurations): `FeeVault`s are upgraded and configured to use the `FeeSplitter` contract as the recipient, L2 as withdrawal network and `0` as the minimum withdrawal amount. The split logic is calculated using the `SuperchainRevSharesCalculator` contract. The `L1Withdrawer` contract is set to withdraw the OP portion of fees automatically. + +* **`useRevenueShare = false`**: `FeeSplitter` is deployed but initialized with zero address for the `sharesCalculator` field. No deployment is made for the `SuperchainRevSharesCalculator` and `L1Withdrawer` contracts. `FeeVault`s are upgraded but initialized using the custom configuration you provide. + +### Configuration Fields + +* `useRevenueShare` (optional): Enables or disables the revenue sharing system. Defaults to `true` for standard configurations, `false` for custom. +* `chainFeesRecipient` (required when `useRevenueShare = true`): Address that receives the chain operator's portion of fee revenue on L2. Must be able to receive ETH. + + +Since `useRevenueShare` defaults to `true` for standard configurations, you must either provide a `chainFeesRecipient` address OR explicitly set `useRevenueShare = false` to opt out. The deployment will fail validation if revenue sharing is enabled without a recipient. + + +## Production Setup + +In production environments, you should use a more secure setup with cold-wallet multisigs (e.g. Gnosis Safes) for the following: + +* `baseFeeVaultRecipient` +* `l1FeeVaultRecipient` +* `sequencerFeeVaultRecipient` +* `operatorFeeVaultRecipient` +* `l1ProxyAdminOwner` +* `l2ProxyAdminOwner` +* `systemConfigOwner` + +HSMs (hardware security modules) are recommended for the following hot-wallets: + +* `unsafeBlockSigner` +* `batcher` +* `proposer` +* `challenger` diff --git a/docs/public-docs/chain-operators/tools/op-deployer/usage/upgrade.mdx b/docs/public-docs/chain-operators/tools/op-deployer/usage/upgrade.mdx new file mode 100644 index 0000000000000..6ba6f7101d391 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/usage/upgrade.mdx @@ -0,0 +1,63 @@ +--- +title: Upgrade Command +description: Learn how to upgrade a chain from one version to another. +--- + +The `upgrade` command allows you to upgrade a chain from one version to another. It consists of several subcommands, one +for each upgrade version. Think of it like a database migration: each upgrade command upgrades a chain from exactly +one previous version to the next. A chain that is several versions behind can be upgrade to the latest version by +running multiple `upgrade` commands in sequence. + +Unlike the `bootstrap` or `apply` commands, `upgrade` does not directly interact with the chain. Instead, it generates +calldata. You can then use this calldata with `cast`, Gnosis SAFE, [`superchain-ops`](https://github.com/ethereum-optimism/superchain-ops), or whatever +tooling you use to manage your L1. + + +Your chain **must** be owned by a smart contract for `upgrade` to work because the OPCM can only be called via +`DELEGATECALL`. + + +## Usage + +Use the `upgrade` command like this: + +```shell +op-deployer upgrade \ + --config +``` + +Version `version` must be one of `v2.0.0` or `v3.0.0`. + +You can also provide an optional `--override-artifacts-url` flag if you want to point to a different set of artifacts +from the default. Setting this flag is not recommended. + +The config file should look like this: + +```json +{ + "prank": "
", + "opcm": "
", + "chainConfigs": [ + { + "systemConfigProxy": "
", + "proxyAdmin": "
", + "absolutePrestate": "<32-byte hash of the chain's absolute prestate>" + } + ] +} +``` + +You can specify multiple chains in the `chainConfigs` array. The CLI will generate calldata for all chains you +specify. Note that all of these chains must be upgradeable using the provided OPCM and `prank` address. + +The `upgrade` command will provide the following output: + +```json +{ + "to": "", + "data": "", + "value": "0x0" +} +``` + +You can then use the `data` field in the `to` field as input to `cast` or other tools. diff --git a/docs/public-docs/chain-operators/tools/op-deployer/usage/verify.mdx b/docs/public-docs/chain-operators/tools/op-deployer/usage/verify.mdx new file mode 100644 index 0000000000000..fce297fa4f901 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-deployer/usage/verify.mdx @@ -0,0 +1,188 @@ +--- +title: Verify Command +description: Learn how to verify deployed contract source code on block explorers. +--- + +Once you have deployed contracts via [`bootstrap`](/chain-operators/tools/op-deployer/usage/bootstrap) or [`apply`](/chain-operators/tools/op-deployer/usage/apply), you can use the `verify` command to verify the source code on block explorers like Etherscan or Blockscout. The command uses the `forge verify-contract` binary, which automatically handles constructor argument detection and source code verification. + +## Usage + +You can call the `verify` command like this: + +```shell +op-deployer verify \ + --l1-rpc-url \ + --input-file \ + --verifier-api-key \ + --artifacts-locator \ + --verifier etherscan +``` + +For Blockscout verification (uses default URLs for mainnet/sepolia, no API key required): + +```shell +op-deployer verify \ + --l1-rpc-url \ + --input-file \ + --artifacts-locator \ + --verifier blockscout +``` + +For custom block explorer verification (Etherscan v2-compatible, API key may be required): + +```shell +op-deployer verify \ + --l1-rpc-url \ + --input-file \ + --artifacts-locator \ + --verifier custom \ + --verifier-url +``` + +## CLI Arguments + +### `--l1-rpc-url` + +Defines the RPC URL of the L1 chain to deploy to (currently only supports mainnet and sepolia). + +### `--input-file` + +The full filepath to the input file. This can be either: + +* A simple JSON file with contract name/address pairs (output from `bootstrap superchain|implementations`) +* A complete `state.json` file (output from `apply`) + +The verifier automatically detects the file format and extracts all contracts. Unless the `--contract-name` flag is passed, all contracts in the input file will be verified. + +Example: + +```json +{ + "opcmAddress": "0x437d303c20ea12e0edba02478127b12cbad54626", + "opcmContractsContainerAddress": "0xf89d7ce62fc3a18354b37b045017d585f7e332ab", + "opcmGameTypeAdderAddress": "0x9aa4b6c0575e978dbe6d6bc31b7e4403ea8bd81d", + "opcmDeployerAddress": "0x535388c15294dc77a287430926aba5ba5fe6016a", + "opcmUpgraderAddress": "0x68a7a93750eb56dd043f5baa41022306e6cd50fa", + "delayedWETHImplAddress": "0x33ddc90167c923651e5aef8b14bc197f3e8e7b56", + "optimismPortalImplAddress": "0x54b75cb6f44e36768912e070cd9cb995fc887e6c", + "ethLockboxImplAddress": "0x05484deeb3067a5332960ca77a5f5603df878ced", + "preimageOracleSingletonAddress": "0xfbcd4b365f97cb020208b5875ceaf6de76ec068b", + "mipsSingletonAddress": "0xcc50288ad0d79278397785607ed675292dce37b1", + "systemConfigImplAddress": "0xfb24aa6d99824b2c526768e97b23694aa3fe31d6", + "l1CrossDomainMessengerImplAddress": "0x957c0bf84fe541efe46b020a6797fb1fb2eaa6ac", + "l1ERC721BridgeImplAddress": "0x62786d16978436f5d85404735a28b9eb237e63d0", + "l1StandardBridgeImplAddress": "0x6c9b377c00ec7e6755aec402cd1cfff34fa75728", + "optimismMintableERC20FactoryImplAddress": "0x3842175f3af499c27593c772c0765f862b909b93", + "disputeGameFactoryImplAddress": "0x70ed1725abb48e96be9f610811e33ed8a0fa97f9", + "anchorStateRegistryImplAddress": "0xce2206af314e5ed99b48239559bdf8a47b7524d4", + "superchainConfigImplAddress": "0x77008cdc99fb1cf559ac33ca3a67a4a2f04cc5ef", + "protocolVersionsImplAddress": "0x32e07ddb36833cae3ca1ec5f73ca348a7e9467f4" +} +``` + +### `--contract-name` (optional) + +Specifies a single contract name, matching a contract key within the input file, to verify. If not provided, all contracts in the input file will be verified. + +### `--artifacts-locator` + +The locator to forge-artifacts containing the output of the `forge build` command (i.e. compiled bytecode and solidity source code). This can be a local path (with a `file://` prefix), remote URL (with a `http://` or `https://` prefix), or standard contracts tag (with a `tag://op-contracts/v` prefix). + +### `--verifier` + +The block explorer(s) to use for verification. Supports multiple verifiers separated by commas. + +Options: + +* `etherscan` (default): Uses Etherscan for mainnet/sepolia +* `blockscout`: Uses default Blockscout URLs for mainnet/sepolia +* `custom`: For custom Etherscan v2-compatible instances (requires `--verifier-url`) + +Examples: + +* Single verifier: `--verifier etherscan` +* Multiple verifiers: `--verifier etherscan,blockscout` (verifies on both) + +### `--verifier-url` + +The verifier API URL. Usage varies by verifier type: + +* `etherscan`: Ignored (automatically determined from chain ID) +* `blockscout`: Optional (defaults to standard Blockscout URLs for mainnet/sepolia) +* `custom`: Required. Example: `https://etherscanv2.compat-api.example.com/api` + +## Output + +Output logs will be printed to the console and look something like the following. If the final results show `numFailed=0`, all contracts were verified successfully. + +```sh +INFO [03-05|15:56:55.900] Formatting etherscan verify request name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A +INFO [03-05|15:56:55.900] Opening artifact path=Proxy.sol/Proxy.json name=superchainConfigProxyAddress +INFO [03-05|15:56:55.905] contractName name=src/universal/Proxy.sol:Proxy +INFO [03-05|15:56:55.905] Extracting constructor args from initcode address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A argSlots=1 +INFO [03-05|15:56:56.087] Contract creation tx hash txHash=0x71b377ccc11304afc32e1016c4828a34010a0d3d81701c7164fb19525ba4fbc4 +INFO [03-05|15:56:56.494] Successfully extracted constructor args address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A +INFO [03-05|15:56:56.683] Verification request submitted name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A +INFO [03-05|15:57:02.035] Verification complete name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A +INFO [03-05|15:57:02.208] Formatting etherscan verify request name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768 +INFO [03-05|15:57:02.208] Opening artifact path=ProtocolVersions.sol/ProtocolVersions.json name=protocolVersionsImplAddress +INFO [03-05|15:57:02.215] contractName name=src/L1/ProtocolVersions.sol:ProtocolVersions +INFO [03-05|15:57:02.418] Verification request submitted name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768 +INFO [03-05|15:57:07.789] Verification complete name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768 +INFO [03-05|15:57:07.971] Contract is already verified name=protocolVersionsProxyAddress address=0x17C64430Fa08475D41801Dfe36bAFeE9667c6fA7 +INFO [03-05|15:57:07.971] --- COMPLETE --- +INFO [03-05|15:57:07.971] final results numVerified=4 numSkipped=1 numFailed=0 +``` + +## Automatic Verification + +You can automatically verify contracts after deployment by using the `--verify` flag with `apply` or `bootstrap` commands: + +```shell +op-deployer apply \ + --workdir ./.deployer \ + --l1-rpc-url \ + --private-key \ + --verify \ + --verifier-api-key +``` + +This will verify all deployed contracts at the end of the deployment process. + +### Multi-Verifier Deployment + +You can verify on multiple block explorers simultaneously: + +```shell +op-deployer bootstrap superchain \ + --l1-rpc-url \ + --private-key \ + --outfile ./superchain.json \ + --superchain-proxy-admin-owner \ + --protocol-versions-owner \ + --guardian \ + --verify \ + --verifier etherscan,blockscout \ + --verifier-api-key +``` + +This will: + +1. Deploy the shared contracts +2. Verify on Etherscan (using the API key) +3. Verify on Blockscout (no API key required) +4. Report combined results from both verifiers + +## Supported Contract Bundles + +The verify command now supports all contract bundles: + +* **Shared** contracts (from `bootstrap superchain`) +* **Implementations** contracts (from `bootstrap implementations`) +* **OpChain** contracts (from `apply` - including all chain-specific contracts) + +When using a `state.json` file from `apply`, the verifier automatically extracts and verifies contracts from all deployment stages. + +## Block Explorer Support + +The verification command supports both Etherscan and Blockscout block explorers through the forge binary, alongside any Etherscan v2 compatible APIs. diff --git a/docs/public-docs/chain-operators/tools/op-txproxy.mdx b/docs/public-docs/chain-operators/tools/op-txproxy.mdx new file mode 100644 index 0000000000000..a51c436551e36 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-txproxy.mdx @@ -0,0 +1,81 @@ +--- +title: OP Txproxy +description: A passthrough proxy service that can apply additional constraints on transactions prior to reaching the sequencer. +--- + +A [passthrough proxy](https://github.com/ethereum-optimism/infra/tree/main/op-txproxy) for the execution engine endpoint. This proxy does not forward all RPC traffic and only exposes a specific set of methods. Operationally, the ingress router should only re-route requests for these specific methods. + + + [proxyd](./proxyd) as an ingress router supports the mapping of specific methods to unique backends. + +## Methods + +### **eth_sendRawTransactionConditional** + +To safely expose this endpoint publicly, additional stateless constraints are applied. These constraints help scale validation rules horizontally and preemptively reject conditional transactions before they reach the sequencer. + +Various metrics are emitted to guide necessary adjustments. +#### Runtime shutoff + +This service can be configured with a flag or environment variable to reject conditional transactions without needing to interrupt the execution engine. This feature is useful for diagnosing issues. + +`--sendRawTxConditional.enabled (default: true) ($OP_TXPROXY_SENDRAWTXCONDITIONAL_ENABLED)` + +When disabled, requests will fail with the `-32003` (transaction rejected) json rpc error code with a message stating that the method is disabled. +#### Rate limits + +Even though the op-geth implementation of this endpoint includes rate limits, it is instead applied here to terminate these requests early. + +`--sendRawTxConditional.ratelimit (default: 5000) ($OP_TXPROXY_SENDRAWTXCONDITIONAL_RATELIMIT)` + +#### Stateless validation + +* Conditional cost is below the max +* Conditional values are valid (i.e min \< max) +* Transaction targets are only 4337 Entrypoint contracts + + + The motivating factor for this endpoint is to enable permissionless 4337 mempools, hence the restricted usage of this method to just [Entrypoint](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol) transactions. + + Please open up an issue if you'd like this restriction to be optional via configuration to broaden usage of this endpoint. + + +When the request passes validation, it is passed through to the configured backend URL + +`--sendRawTxConditional.backend ($OP_TXPROXY_SENDRAWTXCONDITIONAL_BACKENDS)` + + + Per the [specification](/op-stack/features/send-raw-transaction-conditional), conditional transactions are not gossiped between peers. Thus, if you use replicas in an active/passive sequencer setup, this request must be broadcasted to all replicas. + + [proxyd](./proxyd) as an egress router for this method supports this broadcasting functionality. + + +## How it works + +To start using `op-txproxy`, follow these steps: + + + + 1. Run the following command to build the binary + ```bash + make build + ``` + 2. This will build and output the binary under `/bin/op-txproxy`. + + The image for this binary is also available as a [docker artifact](https://us-docker.pkg.dev/oplabs-tools-artifacts/images/op-txproxy). + + + + The binary accepts configuration through CLI flags, which also settable via environment variables. Either set the flags explicitly when starting the binary or set the environment variables of the host starting the proxy. + + See [methods](#methods) on the configuration options available for each method. + + + + start the service with the following command + + ```bash + op-txproxy // ... with flags if env variables are not set + ``` + + diff --git a/docs/public-docs/chain-operators/tools/op-validator.mdx b/docs/public-docs/chain-operators/tools/op-validator.mdx new file mode 100644 index 0000000000000..9699ad2d771c4 --- /dev/null +++ b/docs/public-docs/chain-operators/tools/op-validator.mdx @@ -0,0 +1,99 @@ +--- +title: OP Validator +description: Learn how to use op-validator to validate chain configurations and deployments. +--- + +The `op-validator` is a tool for validating Standard OP Stack chain configurations and deployments to ensure they're in compliance with the [Standard Rollup Charter](/op-stack/protocol/blockspace-charter#the-standard-rollup-charter). +It works by calling into the `StandardValidator` smart contracts. +These then perform a set of checks, and return error codes for any issues found. + +These checks include: + +* Contract implementations and versions +* Proxy configurations +* System parameters +* Cross-component relationships +* Security settings + +## How to use op-validator + + + + ```bash + git clone https://github.com/ethereum-optimism/optimism.git + ``` + + + + ```bash + cd optimism/op-validator + just build + ``` + + + + ```bash + ./bin/op-validator validate v1.8.0 \ + --l1-rpc-url "https://ethereum-sepolia-rpc.publicnode.com" \ + --absolute-prestate "0x03f89406817db1ed7fd8b31e13300444652cdb0b9c509a674de43483b2f83568" \ + --proxy-admin "0x189aBAAaa82DfC015A588A7dbaD6F13b1D3485Bc" \ + --system-config "0x034edD2A225f7f429A63E0f1D2084B9E0A93b538" \ + --l2-chain-id "11155420" \ + --fail + ``` + + + + In the previous command, we provided a non-standard absolute prestate. The `op-validator` returned the following output to describe the discrepancies between the provided absolute prestate and the expected prestate in the L1 smart contracts: + + ``` + | ERROR | DESCRIPTION | + |-----------------|--------------------------------| + | PDDG-40 | Permissioned dispute game | + | | absolute prestate mismatch | + | PDDG-ANCHORP-40 | Permissioned dispute game | + | | anchor state registry root | + | | hash mismatch | + | PLDG-40 | Permissionless dispute game | + | | absolute prestate mismatch | + | PLDG-ANCHORP-40 | Permissionless dispute game | + | | anchor state registry root | + | | hash mismatch | + + Validation errors found + ``` + + + +## Usage + +The validator supports different protocol versions through subcommands: + +```bash +op-validator validate [version] [flags] +``` + +Choose a version based on your `op-contracts` version: + +* `v1.8.0` - For validating `op-contracts/1.8.0` +* `v2.0.0` - For validating `op-contracts/2.0.0` + +### Flags + +| Option | Description | Required/Optional | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | +| `--l1-rpc-url` | L1 RPC URL (can also be set via `L1_RPC_URL` environment variable) | Required | +| `--absolute-prestate` | Absolute prestate as hex string | Required | +| `--proxy-admin` | Proxy admin address as hex string. This should be a specific chain's proxy admin contract on L1. It is not the proxy admin owner or the superchain proxy admin. | Required | +| `--system-config` | System config proxy address as hex string | Required | +| `--l2-chain-id` | L2 chain ID | Required | +| `--fail` | Exit with non-zero code if validation errors are found (defaults to true) | Optional | + +### Example + +```bash +op-validator validate v2.0.0 \ + --l1-rpc-url "L1_RPC_URL" \ + --absolute-prestate "0x1234..." \ + --proxy-admin "0xabcd..." \ +``` diff --git a/docs/public-docs/chain-operators/tools/proxyd.mdx b/docs/public-docs/chain-operators/tools/proxyd.mdx new file mode 100644 index 0000000000000..fbca493e88bbe --- /dev/null +++ b/docs/public-docs/chain-operators/tools/proxyd.mdx @@ -0,0 +1,71 @@ +--- +title: proxyd +description: Learn about the proxyd service and how to configure it for use in the OP Stack. +--- + +`proxyd` is an important RPC request router and proxy used within the OP Stack infrastructure. It enables operators to efficiently route and manage RPC requests across multiple backend services, ensuring performance, fault tolerance, and security. + +## Key features +* RPC method whitelisting +* Backend request routing +* Automatic retries for failed backend requests +* Consensus tracking (latest, safe, and finalized blocks) +* Request/response rewriting to enforce consensus +* Load balancing across backend services +* Caching of immutable responses +* Metrics for request latency, error rates, and backend health + +## How it works + +To start using `proxyd`, follow these steps: + + + + * Run the following command to build the `proxyd` binary: + ```bash + make proxyd + ``` + * This will build the `proxyd` binary. No additional dependencies are required. + + + + * Create a configuration file to define your proxy backends and routing rules. + * Refer to [example.config.toml](https://github.com/ethereum-optimism/infra/blob/main/proxyd/example.config.toml) for a full list of options with commentary. + + + + Once the configuration file is ready, start the `proxyd` service using the following command: + + ```bash + proxyd + ``` + + + +## Consensus awareness + +Version 4.0.0 and later include consensus awareness to minimize chain reorganizations. + +Set `consensus_aware` to `true` in the configuration to enable: + +* Polling backends for consensus data (latest block, safe block, peer count, etc.). +* Resolving consensus groups based on healthiest backends +* Enforcing consensus state across client requests + +## Caching and metrics + +### Cacheable methods + +Certain immutable methods, such as `eth_chainId` and `eth_getBlockByHash`, can be cached using Redis to optimize performance. + +### Metrics + +Extensive metrics are available to monitor request latency, error rates, backend health, and more. These can be configured via `metrics.port` and `metrics.host` in the configuration file. + +## Next steps + +* Read about the [OP Stack chain architecture](/chain-operators/reference/architecture). +* Find out how you can support [snap sync](/chain-operators/guides/features/snap-sync). + on your chain. +* Find out how you can utilize [blob space](/chain-operators/guides/features/blobs) + to reduce the transaction fee cost on your chain. diff --git a/docs/public-docs/chain-operators/tutorials/absolute-prestate.mdx b/docs/public-docs/chain-operators/tutorials/absolute-prestate.mdx new file mode 100644 index 0000000000000..cdfd0e1b388d0 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/absolute-prestate.mdx @@ -0,0 +1,160 @@ +--- +title: "Generating absolute prestate and preimage files" +description: "A high-level guide on how to generate the absolute prestate and preimage necessary for running cannon/permissionless proofs." +--- + +# Overview + +Permissionless fault proofs are a critical component of the OP Stack's security model. They allow anyone to challenge invalid state proposals, ensuring the correctness of L2 to L1 withdrawals without relying on trusted third parties. To enable this functionality, chain operators must generate and maintain the necessary absolute prestate and preimage files. The absolute prestate is a commitment to the initial state of the fault proof program, and the preimage is the serialized binary representation of this program state. These files are essential for the op-challenger tool to participate in dispute games when challenging invalid claims. + +## Prerequisites + +Before starting, ensure you have: + +- [Docker](https://docs.docker.com/engine/install/) running + +## Official prestate hashes for superchain-registry chains + +The superchain-registry maintains official absolute prestate hashes for chains that are part of the registry. These prestates include the configurations of chains that were in the superchain-registry at the time the prestate was created. + + + Important: A prestate listed in the superchain-registry may not be suitable for your chain if: + + - Your chain was added to the registry after the prestate was created + - The configuration for your chain has been updated since the prestate was created + + Before using a prestate from the registry, verify that it contains the latest configuration for your chain. + + When in doubt, generating your own prestate with your specific chain configuration is the safest approach. + + +You can find the latest prestate tags in the [Superchain registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). + +## Generating the absolute prestate + + + + First, clone the Optimism monorepo and check out the appropriate [release tag](https://github.com/ethereum-optimism/optimism/tags) for op-program: + + ```shellscript + git clone https://github.com/ethereum-optimism/optimism.git + cd optimism + # For production chains, use the latest finalized release (not an rc) + git checkout op-program/v1.6.1 # Use the latest stable version + + ``` + + + For chains that are not included in the superchain-registry, you'll need to provide the chain rollup configuration file and the L2 genesis file. Add the `rollup.json` and `l2-genesis.json` to the monorepo at `optimism/op-program/chainconfig/configs/-rollup.json` and `optimism/op-program/chainconfig/configs/-genesis-l2.json` respectively. + + + Run the following command from the root of the monorepo: + + ```bash + make reproducible-prestate + ``` + + You should see the following logs at the end of the command’s output: + + ```log + + -------------------- Production Prestates -------------------- + + Cannon64 Absolute prestate hash: + 0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8 + + -------------------- Experimental Prestates -------------------- + + CannonInterop Absolute prestate hash: + 0x03fc3b4d091527d53f1ff369ea8ed65e5e17cc7fc98ebf75380238151cdc949c + + Cannon64Next Absolute prestate hash: + 0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8 + + ``` + + The output will display production and experimental prestate hashes: + + - **Production prestates**: Contains the `Cannon64` prestate, which is the current production absolute prestate hash for the 64-bit version of Cannon. This is the hash you should use for permissionless fault proofs. + - **Experimental prestates**: These contain prestates for versions that are in development and not yet ready for production use. + + + After generating the prestate, the preimage file will be located in `op-program/bin/prestate-mt64.bin.gz`. The exact name might vary based on the version. Rename this file to include the prestate hash: + + ```shellscript + cd op-program/bin + mv prestate-mt64.bin.gz [CANNON64_PRESTATE_HASH].bin.gz + ``` + + Replace `[CANNON64_PRESTATE_HASH]` with the actual `Cannon64` absolute prestate hash value from the output. This file needs to be uploaded to a location that's accessible by your op-challenger instances. + + + +## Deploying and configuring with the absolute prestate + +After generating the absolute prestate and preimage files, you'll need to: + + + + Upload the preimage file to a location accessible by your op-challenger instances + + + Configure the op-challenger to use the generated prestate. There are two ways to provide prestates: + + + + + + If your prestate files are hosted on a web server, you can simply provide the URL to the directory containing those files: + + ```bash + docker run -d --name op-challenger \ + -e OP_CHALLENGER_TRACE_TYPE=permissioned,cannon \ + -e OP_CHALLENGER_PRESTATES_URL= \ + -e OP_CHALLENGER_L1_ETH_RPC= \ + -e OP_CHALLENGER_GAME_FACTORY_ADDRESS= \ + -e OP_CHALLENGER_PRIVATE_KEY= \ + -e OP_CHALLENGER_NETWORK= \ + -e OP_CHALLENGER_CANNON_ROLLUP_CONFIG= \ + -e OP_CHALLENGER_CANNON_L2_GENESIS= \ + us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:latest + ``` + + + When using an HTTP URL, no volume mount is required. The challenger will download the prestate files as needed. + + + + If you have prestate files stored locally, you'll need to mount them as a volume and use the `file://` protocol: + + ```bash + docker run -d --name op-challenger \ + -e OP_CHALLENGER_TRACE_TYPE=permissioned,cannon \ + -e OP_CHALLENGER_PRESTATES_URL=file:///prestates \ + -e OP_CHALLENGER_L1_ETH_RPC= \ + -e OP_CHALLENGER_GAME_FACTORY_ADDRESS= \ + -e OP_CHALLENGER_PRIVATE_KEY= \ + -e OP_CHALLENGER_NETWORK= \ + -e OP_CHALLENGER_CANNON_ROLLUP_CONFIG= \ + -e OP_CHALLENGER_CANNON_L2_GENESIS= \ + -v /path/to/local/prestates:/prestates \ + us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:latest + ``` + + + When using local files, ensure your prestate files are in the mounted directory and properly named with their hash (e.g., `0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8.bin.gz`). + + + + + + - Ensure you're using the latest op-challenger version, see the [release page](https://github.com/ethereum-optimism/optimism/release). + - If your chain uses interoperability features, you'll need to add a `depsets.json` file to the `op-program/chainconfig/configs` directory. + - This file contains dependency set configurations in the same format as the op-supervisor's configs. You can extract this from your existing op-supervisor setup. + + +## Next Steps + +- Check out the [migrating to permissionless fault proofs guide](/chain-operators/tutorials/migrating-permissionless). +- Read the [Fault proofs explainer](/op-stack/fault-proofs/explainer). +- [Fault proofs explainer](/op-stack/fault-proofs/explainer) \ No newline at end of file diff --git a/docs/public-docs/chain-operators/tutorials/adding-derivation-attributes.mdx b/docs/public-docs/chain-operators/tutorials/adding-derivation-attributes.mdx new file mode 100644 index 0000000000000..22f6aaaf4b31b --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/adding-derivation-attributes.mdx @@ -0,0 +1,271 @@ +--- +title: Adding attributes to the derivation function +description: Learn how to modify the derivation function for an OP Stack chain to track the amount of ETH being burned on L1. +--- + + + OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use. + + OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support. + + +## Overview + +In this tutorial, you'll modify the Bedrock Rollup. Although there are many ways to modify the OP Stack, you're going to spend this tutorial modifying the Derivation function. Specifically, you're going to update the Derivation function to track the amount of ETH being burned on L1! Who's gonna tell [ultrasound.money](http://ultrasound.money) that they should replace their backend with an OP Stack chain? + +## Getting the idea + +Let's quickly recap what you're about to do. The `op-node` is responsible for generating the Engine API payloads that trigger `op-geth` to produce blocks and transactions. The `op-node` already generates a "system transaction" for every L1 block that relays information about the current L1 state to the L2 chain. You're going to modify the `op-node` to add a new system transaction that reports the total burn amount (the base fee multiplied by the gas used) in each block. + +Although it might sound like a lot, the whole process only involves deploying a single smart contract, adding one new file to `op-node`, and modifying one existing file inside `op-node`. It'll be painless. Let's go! + +## Deploy the burn contract + +You're going to use a smart contract on your Rollup to store the reports that the `op-node` makes about the L1 burn. Here's the code for your smart contract: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title L1Burn + * @notice L1Burn keeps track of the total amount of ETH burned on L1. + */ +contract L1Burn { + /** + * @notice Total amount of ETH burned on L1. + */ + uint256 public total; + + /** + * @notice Mapping of block numbers to total burn. + */ + mapping (uint64 => uint256) public reports; + + /** + * @notice Allows the system address to submit a report. + * + * @param _blocknum L1 block number the report corresponds to. + * @param _burn Amount of ETH burned in the block. + */ + function report(uint64 _blocknum, uint64 _burn) external { + require( + msg.sender == 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001, + "L1Burn: reports can only be made from system address" + ); + + total += _burn; + reports[_blocknum] = total; + } + + /** + * @notice Tallies up the total burn since a given block number. + * + * @param _blocknum L1 block number to tally from. + * + * @return Total amount of ETH burned since the given block number; + */ + function tally(uint64 _blocknum) external view returns (uint256) { + return total - reports[_blocknum]; + } +} +``` + +Deploy this smart contract to your L2 (using any tool you find convenient). Make a note of the address that the contract is deployed to because you'll need it in a minute. Simple! + +## Add the burn transaction + +Now you need to add logic to the `op-node` to automatically submit a burn report whenever an L1 block is produced. Since this transaction is very similar to the system transaction that reports other L1 block info (found in [l1\_block\_info.go](https://github.com/ethereum-optimism/optimism/blob/c9cd1215b76111888e25ee27a49a0bc0c4eeb0f8/op-node/rollup/derive/l1_block_info.go)), you'll use that transaction as a jumping-off point. + + + + ```bash + cd ~/optimism/op-node + ``` + + + + ```bash + touch rollup/derive/l1_burn_info.go + ``` + + + + ```go + package derive + + import ( + "bytes" + "encoding/binary" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/ethereum-optimism/optimism/op-node/eth" + ) + + const ( + L1BurnFuncSignature = "report(uint64,uint64)" + L1BurnArguments = 2 + L1BurnLen = 4 + 32*L1BurnArguments + ) + + var ( + L1BurnFuncBytes4 = crypto.Keccak256([]byte(L1BurnFuncSignature))[:4] + L1BurnAddress = common.HexToAddress("YOUR_BURN_CONTRACT_HERE") + ) + + type L1BurnInfo struct { + Number uint64 + Burn uint64 + } + + func (info *L1BurnInfo) MarshalBinary() ([]byte, error) { + data := make([]byte, L1BurnLen) + offset := 0 + copy(data[offset:4], L1BurnFuncBytes4) + offset += 4 + binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Number) + offset += 32 + binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Burn) + return data, nil + } + + func (info *L1BurnInfo) UnmarshalBinary(data []byte) error { + if len(data) != L1InfoLen { + return fmt.Errorf("data is unexpected length: %d", len(data)) + } + var padding [24]byte + offset := 4 + info.Number = binary.BigEndian.Uint64(data[offset+24 : offset+32]) + if !bytes.Equal(data[offset:offset+24], padding[:]) { + return fmt.Errorf("l1 burn tx number exceeds uint64 bounds: %x", data[offset:offset+32]) + } + offset += 32 + info.Burn = binary.BigEndian.Uint64(data[offset+24 : offset+32]) + if !bytes.Equal(data[offset:offset+24], padding[:]) { + return fmt.Errorf("l1 burn tx burn exceeds uint64 bounds: %x", data[offset:offset+32]) + } + return nil + } + + func L1BurnDepositTxData(data []byte) (L1BurnInfo, error) { + var info L1BurnInfo + err := info.UnmarshalBinary(data) + return info, err + } + + func L1BurnDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfig) (*types.DepositTx, error) { + infoDat := L1BurnInfo{ + Number: block.NumberU64(), + Burn: block.BaseFee().Uint64() * block.GasUsed(), + } + data, err := infoDat.MarshalBinary() + if err != nil { + return nil, err + } + source := L1InfoDepositSource{ + L1BlockHash: block.Hash(), + SeqNumber: seqNumber, + } + return &types.DepositTx{ + SourceHash: source.SourceHash(), + From: L1InfoDepositerAddress, + To: &L1BurnAddress, + Mint: nil, + Value: big.NewInt(0), + Gas: 150_000_000, + IsSystemTransaction: true, + Data: data, + }, nil + } + + func L1BurnDepositBytes(seqNumber uint64, l1Info eth.BlockInfo, sysCfg eth.SystemConfig) ([]byte, error) { + dep, err := L1BurnDeposit(seqNumber, l1Info, sysCfg) + if err != nil { + return nil, fmt.Errorf("failed to create L1 burn tx: %w", err) + } + l1Tx := types.NewTx(dep) + opaqueL1Tx, err := l1Tx.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("failed to encode L1 burn tx: %w", err) + } + return opaqueL1Tx, nil + } + ``` + + Feel free to take a look at this file if you're interested. It's relatively simple, mainly just defining a new transaction type and describing how the transaction should be encoded. + + + +## Insert the burn transactions + +Finally, you'll need to update `~/optimism/op-node/rollup/derive/attributes.go` to insert the new burn transaction into every block. You'll need to make the following changes: + + + + ```go + l1InfoTx, err := L1InfoDepositBytes(seqNumber, l1Info, sysConfig) + if err != nil { + return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err)) + } + ``` + + + + ```go + l1BurnTx, err := L1BurnDepositBytes(seqNumber, l1Info, sysConfig) + if err != nil { + return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err)) + } + ``` + + + + ```go + txs := make([]hexutil.Bytes, 0, 1+len(depositTxs)) + txs = append(txs, l1InfoTx) + ``` + + to + + ```go + txs := make([]hexutil.Bytes, 0, 2+len(depositTxs)) + txs = append(txs, l1InfoTx) + txs = append(txs, l1BurnTx) + ``` + + All you're doing here is creating a new burn transaction after every `l1InfoTx` and inserting it into every block. + + + +## Rebuild your op-node + +Before you can see this change take effect, you'll need to rebuild your `op-node`: + +```bash +cd ~/optimism/op-node +make op-node +``` + +Now start your `op-node` if it isn't running or restart your `op-node` if it's already running. You should see the change immediately — new blocks will contain two system transactions instead of just one! + +## Checking the result + +Query the `total` function of your contract, you should also start to see the total slowly increasing. Play around with the `tally` function to grab the amount of gas burned since a given L2 block. You could use this to implement a version of [ultrasound.money](http://ultrasound.money) that keeps track of things with an OP Stack as a backend. + +One way to get the total is to run these commands: + +```bash +export ETH_RPC_URL=http://localhost:8545 +cast call "total()" | cast --from-wei +``` + +## Conclusion + +With just a few tiny changes to the `op-node`, you were just able to implement a change to the OP Stack that allows you to keep track of the L1 ETH burn on L2. With a live Cannon Fault Proof System, you should not only be able to track the L1 burn on L2, you should be able to *prove* the burn to contracts back on L1. That's crazy! + +The OP Stack is an extremely powerful platform that allows you to perform a large amount of computation trustlessly. It's a superpower for smart contracts. Tracking the L1 burn is just one of the many, many wild things you can do with the OP Stack. If you're looking for inspiration or you want to see what others are building on the OP Stack, check out the OP Stack Hacks page. Maybe you'll find a project you want to work on, or maybe you'll get the inspiration you need to build the next killer smart contract. diff --git a/docs/public-docs/chain-operators/tutorials/adding-precompiles.mdx b/docs/public-docs/chain-operators/tutorials/adding-precompiles.mdx new file mode 100644 index 0000000000000..bf805347a32e6 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/adding-precompiles.mdx @@ -0,0 +1,163 @@ +--- +title: Adding a precompile +description: Learn how to run an EVM with a new precompile for OP Stack chain operations to speed up calculations that are not currently supported. +--- + + + OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use. + + OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support. + + +One possible use of OP Stack is to run an EVM with a new precompile for operations to speed up calculations that are not currently supported. In this tutorial, you'll make a simple precompile that returns a constant value if it's called with four or less bytes, or an error if it is called with more than that. + +To create a new precompile, the file to modify is [`op-geth/core/vm/contracts.go`](https://github.com/ethereum-optimism/op-geth/blob/optimism-history/core/vm/contracts.go). + + + + * add a structure named after your new precompile, and + * use an address that is unlikely to ever clash with a standard precompile and avoids the [EIP-7587](https://eips.ethereum.org/EIPS/eip-7587) reserved range (0x1337, for example): + + ```go + common.BytesToAddress([]byte{0x13,0x37}): &retConstant{}, + ``` + + + + ```go + type retConstant struct{} + + func (c *retConstant) RequiredGas(input []byte) uint64 { + return uint64(1024) + } + + var ( + errConstInvalidInputLength = errors.New("invalid input length") + ) + + func (c *retConstant) Run(input []byte) ([]byte, error) { + // Only allow input up to four bytes (function signature) + if len(input) > 4 { + return nil, errConstInvalidInputLength + } + + output := make([]byte, 6) + for i := 0; i < 6; i++ { + output[i] = byte(64+i) + } + return output, nil + } + ``` + + + + ```bash + cd ~/op-geth + make geth + ``` + + + + + + + + ```bash + cast call 0x0000000000000000000000000000000000001337 "whatever()" + cast call 0x0000000000000000000000000000000000001337 "whatever(string)" "fail" + ``` + + + +## How does it work? + +This is the precompile interface definition: + +```go +type PrecompiledContract interface { + RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use + Run(input []byte) ([]byte, error) // Run runs the precompiled contract +} +``` + +It means that for every precompile you need two functions: + +* `RequiredGas` which returns the gas cost for the call. This function takes an array of bytes as input, and returns a single value, the gas cost. +* `Run` which runs the actual precompile. This function also takes an array of bytes, but it returns two values: the call's output (a byte array) and an error. + +For every fork that changes the precompiles you have a [`map`](https://www.w3schools.com/go/go_maps.php) from addresses to the `PrecompiledContract` definitions: + +```go +// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum +// contracts used in the Berlin release. +var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + . + . + . + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{0x13,0x37}): &retConstant{}, +} +``` + +The key of the map is an address. You create those from bytes using `common.BytesToAddress([]byte{})`. In this case you have two bytes, `0x13` and `0x37`. Together you get the address `0x0…1337`. + +The syntax for a precompiled contract interface is `&{}`. + +The next step is to define the precompiled contract itself. + +```go +type retConstant struct{} +``` + +First you create a structure for the precompile. + +```go +func (c *retConstant) RequiredGas(input []byte) uint64 { + return uint64(1024) +} +``` + +Then you define a function as part of that structure. Here you just require a constant amount of gas, but of course the calculation can be a lot more sophisticated. + +```go +var ( + errConstInvalidInputLength = errors.New("invalid input length") +) + +``` + +Next you define a variable for the error. + +```go +func (c *retConstant) Run(input []byte) ([]byte, error) { +``` + +This is the function that actually executes the precompile. + +```go + + // Only allow input up to four bytes (function signature) + if len(input) > 4 { + return nil, errConstInvalidInputLength + } +``` + +Return an error if warranted. The reason this precompile allows up to four bytes of input is that any standard call (for example, using `cast`) is going to have at least four bytes for the function signature. + +`return a, b` is the way we return two values from a function in Go. The normal output is `nil`, nothing, because we return an error. + +```go + output := make([]byte, 6) + for i := 0; i < 6; i++ { + output[i] = byte(64+i) + } + return output, nil +} +``` + +Finally, you create the output buffer, fill it, and then return it. + +## Conclusion + +An OP Stack chain with additional precompiles can be useful, for example, to further reduce the computational effort required for cryptographic operations by moving them from interpreted EVM code to compiled Go code. diff --git a/docs/public-docs/chain-operators/tutorials/create-l2-rollup/code-setup.mdx b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/code-setup.mdx new file mode 100644 index 0000000000000..2d527b90e5e03 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/code-setup.mdx @@ -0,0 +1,42 @@ +--- +title: L2 Rollup Code Examples +description: Complete working code examples for the Create L2 Rollup tutorial +--- + +This page contains complete working code examples for the Create L2 Rollup tutorial. You can find all the code and configuration files in the [create-l2-rollup-example directory](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/). + + + For the complete working implementation, visit the [Create L2 Rollup code on GitHub](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/). + + +## Quick Start + +```bash +# Copy and configure environment +cp .example.env .env +# Edit .env with your values + +# Run the automated setup +make init # Download tools +make setup # Deploy and configure +make up # Start services +``` + +## Files + +* `.example.env` - Environment configuration template +* `docker-compose.yml` - Service orchestration +* `Makefile` - Automation commands +* `scripts/` - Setup and utility scripts +* `README.md` - Detailed documentation + +## About This Code + +This implementation provides: + +* Automated deployment of OP Stack L2 contracts +* Complete Docker-based service orchestration +* Working examples of all OP Stack components +* Production-ready configuration patterns + +For detailed setup instructions, see the [Create L2 Rollup tutorial](/chain-operators/tutorials/create-l2-rollup/create-l2-rollup). \ No newline at end of file diff --git a/docs/public-docs/chain-operators/tutorials/create-l2-rollup/create-l2-rollup.mdx b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/create-l2-rollup.mdx new file mode 100644 index 0000000000000..92ca9821e2d5f --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/create-l2-rollup.mdx @@ -0,0 +1,231 @@ +--- +title: Creating your own L2 rollup testnet +description: Learn how to deploy and orchestrate all OP Stack components for a complete testnet deployment. +--- + +Welcome to the complete guide for deploying your own OP Stack L2 rollup testnet. This multi-part tutorial will walk you through each component step-by-step, from initial setup to a fully functioning rollup. + + +This tutorial requires **intermediate-level experience working with EVM chains**. +You should be comfortable with concepts like smart contracts, private keys, RPC endpoints, gas fees, and command-line operations. +Basic familiarity with Docker is also recommended. + + +## What you'll build + +By the end of this tutorial, you'll have a complete OP Stack testnet with: + +* **L1 Smart Contracts** deployed on Sepolia testnet +* **Execution Client** (op-geth) processing transactions +* **Consensus Client** (op-node) managing rollup consensus +* **Batcher** (op-batcher) publishing transaction data to L1 +* **Proposer** (op-proposer) submitting state root proposals +* **Challenger** (op-challenger) monitoring for disputes + +## Quick setup (Recommended) + +If you want to get started quickly, you can use the complete working implementation provided in this repository. This automated setup handles all the configuration and deployment steps for you. + + + **Complete working example** + + A complete, working implementation is available in the [`create-l2-rollup-example/`](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/) directory. This includes all necessary scripts, Docker Compose configuration, and example environment files. + + +### Automated setup steps + +1. **Clone and navigate to the code directory:** + ```bash + git clone https://github.com/ethereum-optimism/docs.git + cd docs/create-l2-rollup-example + ``` + +2. **Configure your environment:** + ```bash + cp .example.env .env + # Edit .env with your L1_RPC_URL, PRIVATE_KEY, and other settings + ``` + +3. **Run the automated setup:** + ```bash + make init # Download op-deployer + make setup # Deploy contracts and generate configs + make up # Start all services + make test-l1 # Verify L1 connectivity + make test-l2 # Verify L2 functionality + ``` + +4. **Monitor your rollup:** + ```bash + make logs # View all service logs + make status # Check service health + ``` + +The automated setup uses the standard OP Stack environment variable conventions (prefixed with `OP_*`) and handles all the complex configuration automatically. + +## Manual setup (Step-by-Step) + +If you prefer to understand each component in detail or need custom configurations, follow the step-by-step guide below. + +## Before you start + +### Software dependencies + +| Dependency | Version | Version check command | +| ------------------------------------------------------------- | -------- | --------------------- | +| [git](https://git-scm.com/) | `^2` | `git --version` | +| [go](https://go.dev/) | `^1.21` | `go version` | +| [node](https://nodejs.org/en/) | `^20` | `node --version` | +| [pnpm](https://pnpm.io/installation) | `^8` | `pnpm --version` | +| [foundry](https://github.com/foundry-rs/foundry#installation) | `^0.2.0` | `forge --version` | +| [make](https://linux.die.net/man/1/make) | `^3` | `make --version` | +| [jq](https://github.com/jqlang/jq) | `^1.6` | `jq --version` | +| [direnv](https://direnv.net) | `^2` | `direnv --version` | +| [Docker](https://docs.docker.com/get-docker/) | `^24` | `docker --version` | + +### Notes on specific dependencies + + + Expand each dependency below for details + + + + + We recommend using the latest LTS version of Node.js (currently v20).\ + [`nvm`](https://github.com/nvm-sh/nvm) is a useful tool that can help you manage multiple versions of Node.js on your machine.\ + You may experience unexpected errors on older versions of Node.js. + + + + + We will use cast to generate wallet addresses in this guide. + + + + + Parts of this tutorial use [`direnv`](https://direnv.net) as a way of loading environment variables from `.envrc` files into your shell.\ + This means you won't have to manually export environment variables every time you want to use them.\ + `direnv` only ever has access to files that you explicitly allow it to see. + + After [installing `direnv`](https://direnv.net/docs/installation.html), you will need to **make sure that [`direnv` is hooked into your shell](https://direnv.net/docs/hook.html)**.\ + Make sure you've followed [the guide on the `direnv` website](https://direnv.net/docs/hook.html), then **close your terminal and reopen it** so that the changes take effect (or `source` your config file if you know how to do that). + + + Make sure that you have correctly hooked `direnv` into your shell by modifying your shell configuration file (like `~/.bashrc` or `~/.zshrc`).\ + If you haven't edited a config file then you probably haven't configured `direnv` properly (and things might not work later). + + + + + + Docker is used extensively in this tutorial for running various OP Stack components.\ + Make sure you have both Docker and Docker Compose installed and running on your system.\ + On Linux, you may need to [configure Docker to run without sudo](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user). + + + If you're using Docker Desktop, ensure it's running before starting the tutorial.\ + You can verify your installation with: + + ```bash + docker run hello-world + ``` + + + +### Get access to a sepolia node + +Since you're deploying your OP Stack chain to Sepolia, you'll need to have access to a Sepolia node. +You can either use a node provider like [Alchemy](https://www.alchemy.com/) (easier) or run your own Sepolia node (harder). + +### Required resources + +* **Sepolia ETH** - You'll need about 2-3 ETH: + * Start with [Superchain Faucet](https://console.optimism.io/faucet) (gives 0.05 ETH) + * Get more from: + * [Alchemy Faucet](https://sepoliafaucet.com/) + * [Infura Faucet](https://www.infura.io/faucet/sepolia) + * [Paradigm Faucet](https://faucet.paradigm.xyz/) + +* **L1 RPC URL** - An RPC endpoint to connect to the Sepolia network. You can get this from node providers like [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/). This is required so `op-deployer` and other services can read from and send transactions to L1. + + + **Testnet Only**: This guide is for **testnet deployment only**. + + + + **Follow in Order**: Each step builds on the previous one. Start with spinning up op-deployer and complete them sequentially for the best experience. + + +## Directory structure + +To keep your rollup deployment organized, we'll create a dedicated directory structure. All components will be set up within this structure: + +```bash +rollup/ +├── deployer/ # op-deployer files and contracts +├── sequencer/ # op-geth and op-node +├── batcher/ # op-batcher configuration +├── proposer/ # op-proposer setup +└── challenger/ # op-challenger files +``` + +Each component's documentation will show you how the directory structure evolves as you add files and configurations. + + + Throughout this tutorial, all file paths will be relative to this `rollup` directory structure. Make sure to adjust any commands if you use different directory names. + + +## Manual Tutorial Overview + +If you're following the manual setup path, this tutorial is organized into sequential steps that build upon each other: + + + + +Install op-deployer, deploy L1 contracts, and prepare your environment + +[Go to op-deployer setup →](/chain-operators/tutorials/create-l2-rollup/op-deployer-setup) + + + + +Set up and run op-geth and op-node (the execution and consensus layers) + +[Go to sequencer setup →](/chain-operators/tutorials/create-l2-rollup/op-geth-setup) + + + + +Configure and start op-batcher for L1 data publishing + +[Go to batcher setup →](/chain-operators/tutorials/create-l2-rollup/op-batcher-setup) + + + + +Set up op-proposer for state root submissions + +[Go to proposer setup →](/chain-operators/tutorials/create-l2-rollup/op-proposer-setup) + + + + +Configure op-challenger for dispute resolution monitoring + +[Go to challenger setup →](/chain-operators/tutorials/create-l2-rollup/op-challenger-setup) + + + +## Ready to Start? + +Now that you understand what you'll be building, let's begin with the first step! + + + Already have your dependencies? Get started and spin up op-deployer + + +*** + +## Need Help? + +* **Issues**: Report bugs on GitHub diff --git a/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-batcher-setup.mdx b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-batcher-setup.mdx new file mode 100644 index 0000000000000..9915d5142e2f4 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-batcher-setup.mdx @@ -0,0 +1,429 @@ +--- +title: Spin up batcher +description: Learn how to set up and configure an OP Stack batcher to submit L2 transaction batches to L1. +--- + +After you have spun up your sequencer, you need to configure a batcher to submit L2 transaction batches to L1. + + + **Step 3 of 5**: This tutorial is designed to be followed step-by-step. Each step builds on the [previous one](/operators/chain-operators/tutorials/create-l2-rollup/op-geth-setup). + + + + **Automated Setup Available** + + For a complete working setup with all components, check out the [automated approach](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/) in the code directory. + + +## Understanding the batcher's role + +The batcher (`op-batcher`) serves as a crucial component that bridges your L2 chain data to L1. Its primary responsibilities include: + +* **Batch submission**: Collecting L2 transactions and submitting them as batches to L1 +* **Data availability**: Ensuring L2 transaction data is available on L1 for verification +* **Cost optimization**: Compressing and efficiently packing transaction data to minimize L1 costs +* **Channel management**: Managing data channels for optimal batch submission timing + +The batcher reads transaction data from your sequencer and submits compressed batches to the `BatchInbox` contract on L1. + +## Prerequisites + +Before setting up your batcher, ensure you have: + +**Running infrastructure:** + +* An operational sequencer node +* Access to a L1 RPC endpoint + +**Network information:** + +* Your L2 chain ID and network configuration +* L1 network details (chain ID, RPC endpoints) +* `BatchInbox` contract address from your deployment + +For setting up the batcher, we recommend using Docker as it provides a consistent and isolated environment. Building from source is also available for more advanced users. + + + + If you prefer containerized deployment, you can use the official Docker images and do the following: + + + + + + ```bash + # Create your batcher directory inside rollup + cd ../ # Go back to rollup directory if you're in sequencer + mkdir batcher + cd batcher + + # Copy configuration files from deployer + cp ../deployer/.deployer/state.json . + + # Extract the BatchInbox address + BATCH_INBOX_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].systemConfigProxyAddress') + echo "BatchInbox Address: $BATCH_INBOX_ADDRESS" + ``` + + + + + + + **OP Stack Standard Variables** + + The batcher uses OP Stack standard environment variables following the OP Stack conventions. These are prefixed with `OP_BATCHER_` for batcher-specific settings. + + + ```bash + # Create .env file with your actual values + cat > .env << 'EOF' + # L1 Configuration - Replace with your actual RPC URLs + OP_BATCHER_L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + # Private key - Replace with your actual private key + OP_BATCHER_PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY + + # L2 Configuration - Should match your sequencer setup + OP_BATCHER_L2_ETH_RPC=http://op-geth:8545 + OP_BATCHER_ROLLUP_RPC=http://op-node:8547 + + # Contract addresses - Extract from your op-deployer output + OP_BATCHER_BATCH_INBOX_ADDR=YOUR_ACTUAL_BATCH_INBOX_ADDRESS + + # Batcher configuration + OP_BATCHER_POLL_INTERVAL=1s + OP_BATCHER_SUB_SAFETY_MARGIN=6 + OP_BATCHER_NUM_CONFIRMATIONS=1 + OP_BATCHER_SAFE_ABORT_NONCE_TOO_LOW_COUNT=3 + OP_BATCHER_MAX_CHANNEL_DURATION=1 + OP_BATCHER_DATA_AVAILABILITY_TYPE=calldata + + # RPC configuration + OP_BATCHER_RPC_PORT=8548 + EOF + ``` + + **Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values. + + + + + + + This configuration assumes your sequencer is running in a Docker container named `sequencer-node` on the same `op-stack` network. + Make sure your sequencer is running before starting the batcher. + + + ```yaml + + services: + op-batcher: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:v1.13.2 + volumes: + - .:/workspace + working_dir: /workspace + ports: + - "8548:8548" + env_file: + - .env + networks: + - sequencer-node_default + command: > + op-batcher + --rpc.addr=0.0.0.0 + --rpc.enable-admin + --resubmission-timeout=30s + --log.level=info + --log.format=json + restart: unless-stopped + + networks: + sequencer-node_default: + external: false + ``` + + + + + + ```bash + # Make sure your sequencer network exists + # Start the batcher + docker-compose up -d + + # View logs + docker-compose logs -f op-batcher + ``` + + + + + + ```bash + # Check container status + docker-compose ps + ``` + + + + + + ```bash + rollup/ + ├── deployer/ # From previous step + │ └── .deployer/ # Contains genesis.json and rollup.json + ├── sequencer/ # From previous step + └── batcher/ # You are here + ├── state.json # Copied from deployer + ├── .env # Environment variables + └── docker-compose.yml # Docker configuration + ``` + + + + + Your batcher is now operational and will continuously submit L2 transaction batches to L1! + + + + + To ensure you're using the latest compatible versions of OP Stack components, always check the official [releases page](https://github.com/ethereum-optimism/optimism/releases). + + Look for the latest `op-batcher/v*` release that's compatible with your sequencer setup. + + + This guide uses `op-batcher/v1.13.2` which is compatible with op-node/v1.13.3 and op-geth/v1.101511.1 from the sequencer setup. + Always check the [release notes](https://github.com/ethereum-optimism/optimism/releases) for compatibility information. + + + + + + + ```bash + # If you don't already have the optimism repository from the sequencer setup + git clone https://github.com/ethereum-optimism/optimism.git + cd optimism + + # Checkout the latest release tag + git checkout op-batcher/v1.13.2 + + # Build op-batcher + cd op-batcher + just + + # Binary will be available at ./bin/op-batcher + ``` + + + + + + Run this command to verify the installation: + + ```bash + ./bin/op-batcher --version + ``` + + + + + + For advanced configuration options and fine-tuning your batcher, including: + + * Batch submission policies + * Channel duration settings + * Data availability types (blobs vs calldata) + * Transaction throttling + * Network timeouts + + Check out the [Batcher Configuration Reference](/chain-operators/guides/configuration/batcher). + This will help you optimize your batcher's performance and cost-efficiency. + + + + + + Create your batcher working directory: + + ```bash + # Create batcher directory inside rollup + cd ../ # Go back to rollup directory + mkdir batcher + cd batcher + + # Create scripts directory + mkdir scripts + ``` + + Your final directory structure should look like: + + ```bash + rollup/ + ├── deployer/ # From previous step + │ └── .deployer/ # Contains state.json + ├── optimism/ # Contains op-batcher binary + ├── sequencer/ # From previous step + └── batcher/ # You are here + ├── state.json # Copied from deployer + ├── .env # Environment variables + └── scripts/ # Startup scripts + └── start-batcher.sh + ``` + + + + + + Extract the `BatchInbox` contract address from your op-deployer output: + + ```bash + # Make sure you're in the rollup/batcher directory + cd rollup/batcher + + # Copy the deployment state file from deployer + cp ../deployer/.deployer/state.json . + + # Extract the BatchInbox address + BATCH_INBOX_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].systemConfigProxyAddress') + echo "BatchInbox Address: $BATCH_INBOX_ADDRESS" + ``` + + + The batcher submits transaction batches to the `BatchInbox` contract on L1. This contract is responsible for accepting and storing L2 transaction data. + + + + + + + Create your `.env` file with the actual values: + + ```bash + # Create .env file with your actual values + # L1 Configuration - Replace with your actual RPC URL + L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + + # L2 Configuration - Should match your sequencer setup + L2_RPC_URL=http://op-geth:8545 + ROLLUP_RPC_URL=http://op-node:8547 + + # Contract addresses - Extract from your op-deployer output + BATCH_INBOX_ADDRESS=YOUR_ACTUAL_BATCH_INBOX_ADDRESS + + # Private key - Replace with your actual private key + BATCHER_PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY + + # Batcher configuration + POLL_INTERVAL=1s + SUB_SAFETY_MARGIN=6 + NUM_CONFIRMATIONS=1 + SAFE_ABORT_NONCE_TOO_LOW_COUNT=3 + RESUBMISSION_TIMEOUT=30s + MAX_CHANNEL_DURATION=25 + + # RPC configuration + BATCHER_RPC_PORT=8548 + ``` + + **Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values! + + + + + + Get a private key from your wallet that will be used for submitting batches to L1. This account needs sufficient ETH to pay for L1 gas costs. + + + The batcher account needs to be funded with ETH on L1 to pay for batch submission transactions. Monitor this account's balance regularly as it will consume ETH for each batch submission. + + + + + + ### Batcher configuration + + Create `scripts/start-batcher.sh` in the same directory: + + ```bash + #!/bin/bash + + source .env + + # Path to the op-batcher binary we built + ../../optimism/op-batcher/bin/op-batcher \ + --l2-eth-rpc=$L2_RPC_URL \ + --rollup-rpc=$ROLLUP_RPC_URL \ + --poll-interval=$POLL_INTERVAL \ + --sub-safety-margin=$SUB_SAFETY_MARGIN \ + --num-confirmations=$NUM_CONFIRMATIONS \ + --safe-abort-nonce-too-low-count=$SAFE_ABORT_NONCE_TOO_LOW_COUNT \ + --resubmission-timeout=$RESUBMISSION_TIMEOUT \ + --rpc.addr=0.0.0.0 \ + --rpc.port=$BATCHER_RPC_PORT \ + --rpc.enable-admin \ + --max-channel-duration=$MAX_CHANNEL_DURATION \ + --l1-eth-rpc=$L1_RPC_URL \ + --private-key=$BATCHER_PRIVATE_KEY \ + --batch-type=1 \ + --data-availability-type=blobs \ + --log.level=info + ``` + + ### Batcher parameters explained + + * **`--poll-interval`**: How frequently the batcher checks for new L2 blocks to batch + * **`--sub-safety-margin`**: Number of confirmations to wait before considering L1 transactions safe + * **`--max-channel-duration`**: Maximum time (in L1 blocks) to keep a channel open + * **`--batch-type`**: Type of batch encoding (1 for span batches, 0 for singular batches) + * **`--data-availability-type`**: Whether to use blobs or calldata for data availability + + ### Starting the batcher + + ### Start the batcher + + ```bash + # Make the script executable + chmod +x scripts/start-batcher.sh + + # Start the batcher + ./scripts/start-batcher.sh + ``` + + + For detailed cost analysis and optimization strategies, refer to the [Transaction fees documentation](/op-stack/transactions/fees). + + + Your batcher is now operational and will continuously submit L2 transaction batches to L1! + + + + + + + + **Understanding common startup messages** + + When starting your batcher, you might see various log messages: + + * `Added L2 block to local state`: Normal operation, shows the batcher processing blocks + * `SetMaxDASize RPC method unavailable`: Expected if the `op-geth` version used doesn't support this method. + * `context canceled` errors during shutdown: Normal cleanup messages + * `Failed to query L1 tip`: Can occur during graceful shutdowns + + Most of these messages are part of normal operation. For detailed explanations of configuration options and troubleshooting, see the [Batcher configuration reference](/chain-operators/guides/configuration/batcher). + + +## What's Next? + +Excellent! Your batcher is publishing transaction data to L1. The next step is to set up the proposer to submit state root proposals. + + + **Next**: Configure and start op-proposer to submit L2 state roots to L1 for withdrawal verification. + + +*** + +## Need Help? + +* **Monitoring Guide**: [Chain Monitoring](/chain-operators/tools/chain-monitoring) diff --git a/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-challenger-setup.mdx b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-challenger-setup.mdx new file mode 100644 index 0000000000000..8ceb7a4e3410e --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-challenger-setup.mdx @@ -0,0 +1,598 @@ +--- +title: Spin up challenger +description: Learn how to configure challenger for your OP Stack chain. +--- + +After you have spun up your sequencer, batcher, and proposer, the final step is to configure a challenger to monitor and respond to disputes. The challenger is the security component that ensures the integrity of your rollup by monitoring dispute games and responding to invalid claims. + + + **Step 5 of 5**: This tutorial is designed to be followed step-by-step. + Each step builds on the previous one, and this is the last part of the tutorial. + + + + **Automated Setup Available** + + For a complete working setup with all components including automated prestate generation, check out the [automated approach](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/) in the code directory. + + +This guide provides step-by-step instructions for setting up the configuration and monitoring options for `op-challenger`. The challenger is a critical fault proofs component that monitors dispute games and challenges invalid claims to protect your OP Stack chain. + +See the [OP-Challenger explainer](/op-stack/fault-proofs/challenger) for a general overview of this fault proofs feature. + +The challenger is responsible for: + +* Monitoring dispute games created by the fault proof system +* Challenging invalid claims in dispute games +* Defending valid state transitions +* Resolving games when possible + +## Prerequisites + +### Essential requirements + +Before configuring your challenger, complete the following steps: + + + + + + +The challenger needs the absolute prestate to participate in dispute games. Here's how to generate it: + +1. **Clone and checkout the correct version**: + ```bash + git clone https://github.com/ethereum-optimism/optimism.git + cd optimism + git checkout op-program/v1.6.1 # Use the latest stable version + git submodule update --init --recursive + ``` + +2. **Copy your chain configuration**: + ```bash + # Assuming you're in rollup/challenger/optimism directory + # Replace with your actual L2 chain ID + cp ../deployer/.deployer/rollup.json op-program/chainconfig/configs/-rollup.json + cp ../deployer/.deployer/genesis.json op-program/chainconfig/configs/-genesis.json + + # Your directory structure should look like: + # rollup/ + # ├── deployer/ + # │ └── .deployer/ + # │ ├── rollup.json # Source file + # │ └── genesis.json # Source file + # └── optimism/ # You are here + # └── op-program/ + # └── chainconfig/ + # └── configs/ + # ├── -rollup.json # Destination + # └── -genesis.json # Destination + ``` + +3. **Generate the prestate**: + ```bash + make reproducible-prestate + ``` + You'll see output like: + ```bash + -------------------- Production Prestates -------------------- + Cannon64 Absolute prestate hash: + 0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8 + ``` + +4. **Prepare the preimage file**: + ```bash + cd op-program/bin + mv prestate-mt64.bin.gz 0x[CANNON64_PRESTATE_HASH].bin.gz + ``` + Replace `[CANNON64_PRESTATE_HASH]` with the actual hash from step 3. + + + * Use the `Cannon64` hash for production + * Keep this file accessible - you'll need it for the challenger setup + * For Superchain registry chains, you can find official prestates in the [registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml) + + + + + + +* L1 RPC endpoint (Ethereum, Sepolia, etc.) +* L1 Beacon node endpoint (for blob access) + + + + +* `prestate.json` - The absolute prestate file generated in step 1 +* `rollup.json` - Rollup configuration file from the `op-deployer` guide + + + +## Software installation + +For challenger deployment, we recommend using Docker as it provides a consistent and isolated environment. Building from source is also available for more advanced users. + + + + + ### Docker Setup + + The Docker setup provides a containerized environment for running the challenger. This method uses the official Docker image that includes embedded `op-program` server and Cannon executable. + + + + + ```bash + # Create your challenger directory inside rollup + cd ../ # Go back to rollup directory if you're in proposer + mkdir challenger + cd challenger + ``` + + + + + + **OP Stack Standard Variables** + + The challenger uses OP Stack standard environment variables following the OP Stack conventions. These are prefixed with `OP_CHALLENGER_` for challenger-specific settings. + + + ```bash + # Create .env file with your actual values + cat > .env << 'EOF' + # Core configuration (required) + OP_CHALLENGER_L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + OP_CHALLENGER_L1_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com + OP_CHALLENGER_PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY + + # L2 Configuration - Replace with your actual node endpoints + OP_CHALLENGER_L2_ETH_RPC=http://op-geth:8545 + OP_CHALLENGER_ROLLUP_RPC=http://op-node:8547 + + # OP Stack challenger configuration (optional - defaults provided) + OP_CHALLENGER_GAME_FACTORY_ADDRESS=YOUR_GAME_FACTORY_ADDRESS + OP_CHALLENGER_CANNON_L2_GENESIS=/workspace/genesis.json + OP_CHALLENGER_CANNON_ROLLUP_CONFIG=/workspace/rollup.json + + + # Prestate configuration - Replace with the file from 'make reproducible-prestate' + OP_CHALLENGER_CANNON_PRESTATE=/workspace/${PRESTATE_HASH}.bin.gz + EOF + ``` + + **Important:** Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values. + + + + + Create a `docker-compose.yml` file that defines the challenger service. The file mounts several important files: + * `prestate-proof-mt64.json` and `${PRESTATE_HASH}.bin.gz`: Prestate files required for dispute games (the PRESTATE_HASH comes from running `make reproducible-prestate`), replace `PRESTATE_HASH` with the actual hash + + ```yaml + + services: + challenger: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.5.1 + user: "1000" + volumes: + - ./challenger-data:/data + - ./rollup.json:/workspace/rollup.json:ro + - ./genesis-l2.json:/workspace/genesis-l2.json:ro + - ./prestate-proof-mt64.json:/workspace/prestate-proof.json:ro + - ./${PRESTATE_HASH}.bin.gz:/workspace/${PRESTATE_HASH}.bin.gz:ro + command: > + op-challenger run-trace + --trace-type=cannon + --datadir=/data + --log.level=info + --log.format=json + restart: unless-stopped + networks: + - sequencer-node_default + + networks: + sequencer-node_default: + external: false + + + ``` + + + + + Start the challenger service and monitor its logs: + + ```bash + # Start the challenger service + docker-compose up -d + + # View logs + docker-compose logs -f challenger + ``` + + + + + + + To ensure you're using the latest compatible versions of OP Stack components, always check the official releases page: + + [OP Stack releases page](https://github.com/ethereum-optimism/optimism/releases) + + Look for the latest `op-challenger/v*` release. The challenger version used in this guide (op-challenger/v1.5.0) is a verified stable version. + + Always check the release notes to ensure you're using compatible versions with your chain's deployment. + + ### Build and Configure + + Building from source gives you full control over the binaries. + + **Clone and build op-challenger** + + ```bash + # Clone the optimism monorepo + git clone https://github.com/ethereum-optimism/optimism.git + cd optimism + + # Check out the latest release of op-challenger + git checkout op-challenger/v1.5.0 + + # Install dependencies and build + just op-challenger + + # Binary will be available at ./op-challenger/bin/op-challenger + ``` + + ### Verify installation + + Check that you have properly installed the challenger component: + + ```bash + # Make sure you're in the optimism directory + ./op-challenger/bin/op-challenger --help + + # You should see the challenger help output with available commands and flags + ``` + + ## Configuration setup + + + The following steps require a Cannon binary and the op-program server. + Ensure these binaries are available at the paths you configure (`CANNON_BIN` and `CANNON_SERVER`). + + + + + + After building the binaries, create your challenger working directory: + + ```bash + # Create challenger directory inside rollup + cd ../ # Go back to rollup directory + mkdir challenger + cd challenger + + # Create necessary subdirectories + mkdir scripts + mkdir challenger-data + + # Verify the optimism directory is accessible + # Directory structure should look like: + # rollup/ + # ├── deployer/ (from previous step) + # ├── optimism/ (contains the built binaries) + # ├── sequencer/ (from previous step) + # ├── batcher/ (from previous step) + # ├── proposer/ (from previous step) + # └── challenger/ (you are here) + ``` + + + + + ```bash + # Copy configuration files to your challenger directory + # Adjust paths based on your deployment setup + cp /path/to/your/rollup.json . + cp /path/to/your/genesis-l2.json . + ``` + + + + + You'll need to gather several pieces of information before creating your configuration. Here's where to get each value: + + **L1 network access:** + + * L1 RPC URL: Your L1 node endpoint (Infura, Alchemy, or self-hosted) + * L1 Beacon URL: Beacon chain API endpoint for blob access + + **L2 network access:** + + * L2 RPC URL: Your op-geth archive node endpoint + * Rollup RPC URL: Your op-node endpoint with historical data + + **Challenger wallet:** + + * Private key for challenger operations (must be funded) + + **Network configuration:** + + * Game factory address from your contract deployment + * Network identifier (e.g., op-sepolia, op-mainnet, or custom) + + Copy and paste in your terminal, to create your env file. + + ```bash + # Create .env file with your actual values + cat > .env << 'EOF' + # L1 Configuration - Replace with your actual RPC URLs + L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + + # L2 Configuration - Replace with your actual node endpoints + L2_RPC_URL=http://localhost:8545 + ROLLUP_RPC_URL=http://localhost:8547 + L1_BEACON=http://sepolia-cl-1:5051 + + # Private key - Replace with your actual private key + PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY + + # Network configuration + NETWORK=op-sepolia + GAME_FACTORY_ADDRESS=YOUR_GAME_FACTORY_ADDRESS + + # Trace configuration + TRACE_TYPE=permissioned,cannon + + # Data directory + DATADIR=./challenger-data + + # Configuration files from your deployment + CANNON_ROLLUP_CONFIG=./rollup.json + CANNON_L2_GENESIS=./genesis-l2.json + + # Cannon configuration + # Prestate file - Generate this using the absolute prestate guide + # Run 'make reproducible-prestate' in the optimism repo to generate it + # The Cannon binary is a specific hash file that matches your deployment + CANNON_BIN=./0x.bin.gz + # The op-program server binary is built when you run 'just op-challenger' + CANNON_SERVER=../../optimism/op-program/bin/op-program + CANNON_PRESTATE=../../optimism/op-program/bin/prestate.json + + EOF + ``` + + **Important:** Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values. + + + + + + * This is the HTTP provider URL for a standard L1 node, can be a full node. `op-challenger` will be sending many requests, so chain operators need a node that is trusted and can easily handle many transactions. + * Note: Challenger has a lot of money, and it will spend it if it needs to interact with games. That might risk not defending games or challenging games correctly, so chain operators should really trust the nodes being pointed at Challenger. + + + + * This is needed just to get blobs from. + * In some instances, chain operators might need a blob archiver or L1 consensus node configured not to prune blobs: + * If the chain is proposing regularly, a blob archiver isn't needed. There's only a small window in the blob retention period that games can be played. + * If the chain doesn't post a valid output root in 18 days, then a blob archiver running a challenge game is needed. If the actor gets pushed to the bottom of the game, it could lose if it's the only one protecting the chain. + + + + * This needs to be `op-geth` archive node, with `debug` enabled. + * Technically doesn't need to go to bedrock, but needs to have access to the start of any game that is still in progress. + + + + * This needs to be an `op-node` archive node because challenger needs access to output roots from back when the games start. See below for important configuration details: + + 1. Safe Head Database (SafeDB) Configuration for op-node: + + * The `op-node` behind the `op-conductor` must have the SafeDB enabled to ensure it is not stateless. + * To enable SafeDB, set the `--safedb.path` value in your configuration. This specifies the file path used to persist safe head update data. + * Example Configuration: + + ``` + --safedb.path # Replace with your actual path + ``` + + + If this path is not set, the SafeDB feature will be disabled. + + + 2. Ensuring Historical Data Availability: + + * Both `op-node` and `op-geth` must have data from the start of the games to maintain network consistency and allow nodes to reference historical state and transactions. + * For `op-node`: Configure it to maintain a sufficient history of blockchain data locally or use an archive node. + * For `op-geth`: Similarly, configure to store or access historical data. + * Example Configuration: + + ``` + op-node \ + --rollup-rpc \ + --safedb.path + ``` + + + Replace `` with the URL of your archive node and `` with the desired path for storing SafeDB data. + + + + + * Chain operators must specify a private key or use something else (like `op-signer`). + * This uses the same transaction manager arguments as `op-node` , batcher, and proposer, so chain operators can choose one of the following options: + * a mnemonic + * a private key + * `op-signer` endpoints + + + + * This identifies the L2 network `op-challenger` is running for, e.g., `op-sepolia` or `op-mainnet`. + * When using the `--network` flag, the `--game-factory-address` will be automatically pulled from the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json). + * When cannon is executed, challenger needs the roll-up config and the L2 Genesis, which is op-geth's Genesis file. Both files are automatically loaded when Cannon Network is used, but custom networks will need to specify both Cannon L2 Genesis and Cannon rollup config. + * For custom networks not in the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json), the `--game-factory-address` and rollup must be specified, as follows: + + ``` + --cannon-rollup-config rollup.json \ + --cannon-l2-genesis genesis-l2.json \ + # use this if running challenger outside of the docker image + --cannon-server ./op-program/bin/op-program \ + # json or url, version of op-program deployed on chain + # if you use the wrong one, you will lose the game + # if you deploy your own contracts, you specify the hash, the root of the json file + # op mainnet are tagged versions of op-program + # make reproducible prestate + # challenger verifies that onchain + --cannon-prestate ./op-program/bin/prestate.json \ + # load the game factory address from system config or superchain registry + # point the game factory address at the dispute game factory proxy + --game-factory-address + ``` + + + These options vary based on which `--network` is specified. Chain operators always need to specify a way to load prestates and must also specify the cannon-server whenever the docker image isn't being used. + + + + + * This is a directory that `op-challenger` can write to and store whatever data it needs. It will manage this directory to add or remove data as needed under that directory. + * If running in docker, it should point to a docker volume or mount point, so the data isn't lost on every restart. The data can be recreated if needed but particularly if challenger has executed cannon as part of responding to a game it may mean a lot of extra processing. + + + + The pre-state is effectively the version of `op-program` that is deployed on chain. And chain operators must use the right version. `op-challenger` will refuse to interact with games that have a different absolute prestate hash to avoid making invalid claims. If deploying your own contracts, chain operators must specify an absolute prestate hash taken from the `make reproducible-prestate` command during contract deployment, which will also build the required prestate json file. + + All governance approved releases use a tagged version of `op-program`. These can be rebuilt by checking out the version tag and running `make reproducible-prestate`. + + * There are two ways to specify the prestate to use: + * `--cannon-prestate`: specifies a path to a single Cannon pre-state Json file + * `--cannon-prestates-url`: specifies a URL to load pre-states from. This enables participating in games that use different prestates, for example due to a network upgrade. The prestates are stored in this directory named by their hash. + * Example final URL for a prestate: + * [https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json](https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json) + * This file contains the cannon memory state. + + + Challenger will refuse to interact with any games if it doesn't have the matching prestate. + Check this [guide](/operators/chain-operators/tutorials/absolute-prestate#generating-the-absolute-prestate) on how to generate an absolute prestate. + + + + + + ### Create challenger startup script + + Create `scripts/start-challenger.sh`: + + ```bash + #!/bin/bash + source .env + + # Path to the challenger binary + ../../optimism/op-challenger/bin/op-challenger \ + --trace-type permissioned,cannon \ + --l1-eth-rpc=$L1_RPC_URL \ + --l2-eth-rpc=$L2_RPC_URL \ + --l1-beacon=$L1_BEACON \ + --rollup-rpc=$ROLLUP_RPC_URL \ + --game-factory-address $GAME_FACTORY_ADDRESS \ + --datadir=$DATADIR \ + --cannon-bin=$CANNON_BIN \ + --cannon-rollup-config=$CANNON_ROLLUP_CONFIG \ + --cannon-l2-genesis=$CANNON_L2_GENESIS \ + --cannon-server=$CANNON_SERVER \ + --cannon-prestate=$CANNON_PRESTATE \ + --private-key="$PRIVATE_KEY" + ``` + ### Start the challenger + + ```bash + # Make sure you're in the rollup/challenger directory + cd rollup/challenger + + # Make script executable + chmod +x scripts/start-challenger.sh + + # Start challenger + ./scripts/start-challenger.sh + ``` + + ### Verify challenger is running + + Monitor challenger logs to ensure it's operating correctly: + + ```bash + # Check challenger logs + tail -f challenger-data/challenger.log + + # Or if running in foreground, monitor the output + ``` + + The challenger should show logs indicating: + + * Successful connection to L1 and L2 nodes + * Loading of prestates and configuration + * Monitoring of dispute games + + + + +### Monitoring with op-dispute-mon + +Consider running [`op-dispute-mon`](/operators/chain-operators/tools/chain-monitoring#dispute-mon) for enhanced security monitoring: + +* Provides visibility into all game statuses for the last 28 days +* Essential for production challenger deployments + +## Congratulations + +You've successfully completed the entire L2 rollup testnet tutorial! Your rollup is now fully operational with all components running: + +* **op-deployer** - L1 contracts deployed +* **Sequencer** - Processing transactions +* **Batcher** - Publishing data to L1 +* **Proposer** - Submitting state roots +* **Challenger** - Monitoring disputes + +## Connect your wallet to your chain + +You now have a fully functioning OP Stack Rollup with a Sequencer node running on `http://localhost:8545`. You can connect your wallet to this chain the same way you'd connect your wallet to any other EVM chain. + +## Get ETH on your chain + +Once you've connected your wallet, you'll probably notice that you don't have any ETH to pay for gas on your chain. + +The easiest way to deposit Sepolia ETH into your chain is to send ETH directly to the `L1StandardBridge` contract. + +### Get the L1StandardBridge address + +The `L1StandardBridge` proxy address can be found in your deployment state file. To get it, run: + +```bash +# From your project root +jq -r .l1StandardBridgeProxyAddress /.deployer/state.json +``` + +This will output the `L1StandardBridge` proxy address that you should use for deposits. Make sure to use the proxy address, not the implementation address. + +### Deposit ETH to your L2 + +Once you have the `L1StandardBridge` address, send a small amount of Sepolia ETH (0.1 or less) to that address from the wallet you want to use on L2. +This will trigger a deposit that will mint ETH into your wallet on L2. + + + It may take up to 5 minutes for the ETH to appear in your wallet on L2. + This delay is due to the time needed for the deposit transaction to be processed and finalized. + + +## See your rollup in action + +You can interact with your Rollup the same way you'd interact with any other EVM chain. +Send some transactions, deploy some contracts, and see what happens! + +## Need Help? + +* **OP Challenger Explainer**: [Fault Proofs Overview](/op-stack/fault-proofs/challenger) +* **Technical Specs**: [Honest Challenger Specification](https://specs.optimism.io/fault-proof/stage-one/honest-challenger-fdg.html) diff --git a/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-deployer-setup.mdx b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-deployer-setup.mdx new file mode 100644 index 0000000000000..654f19535cd5f --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-deployer-setup.mdx @@ -0,0 +1,426 @@ +--- +title: Deploy L1 contracts with op-deployer +description: Install op-deployer, prepare your environment, and deploy the L1 smart contracts for your rollup. +--- + +Welcome to the first step of creating your own L2 rollup testnet! In this section, you'll install the op-deployer tool and deploy the necessary L1 smart contracts for your rollup. + + + **Step 1 of 5**: This tutorial is designed to be followed step-by-step. Each step builds on the previous one. + + + + **Quick Setup Available** + + For a complete automated setup that includes op-deployer deployment, check out the [`code/`](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/) directory. The automated setup handles all contract deployment and configuration automatically. + + +## About op-deployer + +`op-deployer` simplifies the process of deploying the OP Stack. You define a declarative config file called an "**intent**," then run a command to apply it. `op-deployer` compares your chain's current state against the intent and makes the necessary changes to match. + +## Installation + +There are a couple of ways to install `op-deployer`: + + + + The recommended way to install `op-deployer` is to download the latest release from the monorepo's [release page](https://github.com/ethereum-optimism/optimism/releases). + + + **Quick Setup Available** + + For automated installation, you can use the download script from the [code directory](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/). This script automatically downloads the latest version for your system. + + + + + 1. Go to the [release page](https://github.com/ethereum-optimism/optimism/releases) + 2. Find the **latest** release that includes `op-deployer` (look for releases tagged with `op-deployer/v*`) + 3. Under **assets**, download the binary that matches your system: + + * For Linux: `op-deployer-linux-amd64` + * For macOS: + * Apple Silicon (M1/M2): `op-deployer-darwin-arm64` + * Intel processors: `op-deployer-darwin-amd64` + * For Windows: `op-deployer-windows-amd64.exe` + + + **Always download the latest version** to ensure you have the most recent features and bug fixes. + + + + Not sure which macOS version to use? + + * Open Terminal and run `uname -m` + * If it shows `arm64`, use the arm64 version + * If it shows `x86_64`, use the amd64 version + + + + + + 1. Create the rollup directory structure and enter the deployer directory: + + ```bash + # Create main rollup directory + mkdir rollup && cd rollup + + # Create and enter the deployer directory + mkdir deployer && cd deployer + ``` + + Your directory structure will now look like this: + ```bash + rollup/ + └── deployer/ # You are here + ``` + + 2. Move and rename the downloaded binary: + + + The downloaded file is likely in your Downloads folder: + + * macOS/Linux: `/Users/YOUR_USERNAME/Downloads` + * Windows WSL: `/mnt/c/Users/YOUR_USERNAME/Downloads` + + + ```bash + # Step 1: Extract the downloaded archive in the deployer directory + # Replace FILENAME with the actual downloaded file name (includes version and arch) + tar -xvzf /Users/USERNAME/Downloads/FILENAME.tar.gz + + # Step 2: Make the binary executable + # Replace FILENAME with the extracted binary name + chmod +x FILENAME + + # Step 3: Remove macOS quarantine attribute (fixes "can't be opened" warning) + sudo xattr -dr com.apple.quarantine FILENAME + + # Step 4: Move the binary to your PATH + # For Intel Macs: + sudo mv FILENAME /usr/local/bin/op-deployer + # For Apple Silicon Macs: + sudo mv FILENAME /opt/homebrew/bin/op-deployer + + # Step 5: Verify installation (should print version info) + op-deployer --version + ``` + + + **Pro Tip**: Use the automated download script from the [code directory](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/) to avoid manual version management. It automatically detects your platform and downloads the latest version. + + + + + + + + To install from source, you will need [Go](https://go.dev/doc/install), `just`, and `git`. + After installing all of that, run following: + + ```bash + git clone https://github.com/ethereum-optimism/optimism.git # you can skip this if you already have the repo + cd optimism/op-deployer + just build + cp ./bin/op-deployer /usr/local/bin/op-deployer # or any other directory in your $PATH + + # Verify installation + op-deployer --version + ``` + + + +## L1 network requirements + +Before deploying your L1 contracts, you'll need: + +**L1 RPC URL**: An Ethereum RPC endpoint for your chosen L1 network + + ```bash + # Examples: + # Sepolia (recommended for testing) + L1_RPC_URL=https://sepolia.infura.io/v3/YOUR-PROJECT-ID + # or https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY + + # Local network + L1_RPC_URL=http://localhost:8545 + ``` + + + For testing, we recommend using Sepolia testnet. You can get free RPC access from: + + * [Infura](https://infura.io) (create account, get API key) + * [Alchemy](https://alchemy.com) (create account, get API key) + * [Ankr](https://ankr.com) (create account, get API key) + + +## Generate deployment addresses + +Your rollup needs several addresses for different roles. Let's generate them first: + + + + + ```bash + # Create a address directory inside the deployer directory + mkdir -p address + cd address + ``` + + Your directory structure will now look like this: + ```bash + rollup/ + └── deployer/ + └── address/ # You are here + ``` + + + + + + ```bash + # Generate 8 new wallet addresses + for role in admin base_Fee_Vault_Recipient l1_Fee_Vault_Recipient sequencer_Fee_Vault_Recipient system_config unsafe_block_signer batcher proposer ; do + wallet_output=$(cast wallet new) + echo "$wallet_output" | grep "Address:" | awk '{print $2}' > ${role}_address.txt + echo "Created wallet for $role" + done + ``` + + This will save the various addresses for your intent file into files in your current directory. To view them later you can use `cat *_address.txt`. + + + + + + **Important**: + + * Save these address - you'll need them to operate your chain + * You can use any address for the purpose of testing, for production, use proper key management solutions (HSMs, multisigs addresses) + + + +## Create and configure intent file + +The intent file defines your chain's configuration. + + + + + Inside the `deployer` folder, run this command: + + ```bash + #You can use a 2-7 digit random number for your `` + op-deployer init \ + --l1-chain-id 11155111 \ + --l2-chain-ids \ + --workdir .deployer \ + --intent-type standard-overrides + ``` + + + + + `op-deployer` supports three intent types: + + * `standard`: Uses default OP Stack configuration, minimal customization + * `standard-overrides`: Recommended. Uses defaults but allows overriding specific values + * `custom`: Full customization, requires manual configuration of all values + + For most users, `standard-overrides` provides the best balance of simplicity and flexibility. + + + + + + + Edit `.deployer/intent.toml` with your generated addresses. The `op-deployer init` command automatically populates this file with sensible defaults. Update the addresses while keeping the auto-generated contract locator values: + + ```toml + configType = "standard-overrides" + l1ChainID = 11155111 # Sepolia + fundDevAccounts = false # Set to false for production/testnet + useInterop = false + opcmAddress = "0x3bb6437aba031afbf9cb3538fa064161e2bf2d78" # OPCM contract address on Sepolia + + # Contract locators - REQUIRED fields, automatically populated by op-deployer init + # Keep these default values unless you need specific contract versions (advanced use case) + l1ContractsLocator = "tag://op-contracts/v2.0.0" + l2ContractsLocator = "tag://op-contracts/v1.7.0-beta.1+l2-contracts" + + # Shared contract roles - only define if creating a standalone chain not part of the OP Stack ecosystem + # For standard OP Stack deployments, these are predefined and should not be set + # [superchainRoles] + # proxyAdminOwner = "0x..." # admin address + # protocolVersionsOwner = "0x..." # admin address + # guardian = "0x..." # admin address + + [[chains]] + id = "0x000000000000000000000000000000000000000000000000000000000016de8d" + baseFeeVaultRecipient = "0x..." # base_Fee_Vault_Recipient address + l1FeeVaultRecipient = "0x..." # l1_Fee_Vault_Recipient address + sequencerFeeVaultRecipient = "0x..." # sequencer_Fee_Vault_Recipient address + eip1559DenominatorCanyon = 250 + eip1559Denominator = 50 + eip1559Elasticity = 6 + [chains.roles] + l1ProxyAdminOwner = "0x1eb2ffc903729a0f03966b917003800b145f56e2" + l2ProxyAdminOwner = "0x2fc3ffc903729a0f03966b917003800b145f67f3" + systemConfigOwner = "0x..." # system_config address + unsafeBlockSigner = "0x..." # unsafe_block_signer address + batcher = "0x..." # batcher address + proposer = "0x..." # proposer address + challenger = "0xfd1d2e729ae8eee2e146c033bf4400fe75284301" + ``` + + + + **Global Settings:** + + * `l1ChainID`: The L1 network ID (11155111 for Sepolia) + * `fundDevAccounts`: Creates test accounts with ETH if true (set to false for production) + * `useInterop`: Enable interoperability features (false for standard deployments) + * `opcmAddress`: OP Contracts Manager (OPCM) contract address on the L1 network (automatically populated for supported networks) + + **Contract Locators (Required):** + + These fields are **required** and automatically populated by `op-deployer init` with default values compatible with your `op-deployer` version. + Removing them will cause the error: "Application failed: L1ContractsLocator undefined". + Keep the auto-generated values unless you specifically need different contract versions. + For version compatibility details, see the [op-deployer release notes](https://github.com/ethereum-optimism/optimism/releases). + + + **Shared Contract Roles (Advanced):** + + These are commented out because for standard OP Stack deployments, shared contract roles are predefined by the protocol. Only uncomment and define custom roles if you're creating a standalone chain not part of the OP Stack ecosystem. + + **Chain Configuration:** + + * `id`: Unique identifier for your chain + * `*FeeVaultRecipient`: Addresses receiving various protocol fees + * `eip1559*`: Parameters for dynamic gas price calculation + + **Chain Roles:** + + * `l1ProxyAdminOwner`: Can upgrade L1 contract implementations (usually same as superchain proxyAdminOwner) + * `l2ProxyAdminOwner`: Can upgrade L2 contract implementations + * `systemConfigOwner`: Manages system configuration parameters + * `unsafeBlockSigner`: Signs pre-confirmation blocks (can be same as batcher) + * `batcher`: Submits L2 transaction batches to L1 + * `proposer`: Submits L2 state roots to L1 for verification + * `challenger`: Monitors dispute games and defends valid states + + + + + + + Replace all `0x...` with actual addresses from your `addresses.txt` file. + Never use the default test mnemonic addresses in production or public testnets! + + +## Create environment file + +Before deploying, create a `.env` file in your `deployer` directory to store your environment variables: + +```bash +# Create .env file +cat << 'EOF' > .env +# Your L1 RPC URL (e.g., from Alchemy, Infura) +L1_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY + +# Private key for deployment. +# Get this from your self-custody wallet, like Metamask. +PRIVATE_KEY=WALLET_PRIVATE_KEY +EOF +``` + + + Never commit your `.env` file to version control. Add it to your `.gitignore`: + + ```bash + echo ".env" >> .gitignore + ``` + + +Load the environment variables: + +```bash +source .env +``` + +## Deploy L1 Contracts + +Now that your intent file and environment variables are configured, let's deploy the L1 contracts: + +```bash +op-deployer apply \ + --workdir .deployer \ + --l1-rpc-url $L1_RPC_URL \ + --private-key $PRIVATE_KEY +``` + +This will: + +1. Deploy all required L1 contracts +2. Configure them according to your intent file +3. Save deployment information to `.deployer/state.json` + + + The deployment can take 10-15 seconds and requires multiple transactions. + + +## Generate chain configuration + +After successful deployment, generate your chain configuration files: + +```bash +# Generate genesis and rollup configs +op-deployer inspect genesis --workdir .deployer > .deployer/genesis.json +op-deployer inspect rollup --workdir .deployer > .deployer/rollup.json +``` + +## What's Next? + +Great! You've successfully: + +1. Installed `op-deployer` using the `init` and `apply` command. +2. Created and configured your intent file +3. Deployed L1 smart contracts +4. Generated chain artifacts + +Your final directory structure should look like this: +```bash +rollup/ +└── deployer/ + ├── .deployer/ # Contains deployment state and configs + │ ├── genesis.json # L2 genesis configuration + │ ├── intent.toml # Your chain configuration + │ ├── rollup.json # Rollup configuration + │ └── state.json # Deployment state + ├── .env # Environment variables + └── address/ # Generated address pairs + ├── admin_address.txt + ├── base_Fee_Vault_Recipient_address.txt + ├── batcher_address.txt + ├── l1_Fee_Vault_Recipient_address.txt + ├── proposer_address.txt + ├── sequencer_Fee_Vault_Recipient_address.txt + ├── system_config_address.txt + └── unsafe_block_signer_address.txt + +``` + +Now you can move on to setting up your sequencer node. + + + **Next**: Set up op-geth and op-node, essential building blocks of the execution and consensus layers in your rollup. + + +*** + +## Need Help? + +* **op-deployer Repository**: [GitHub](https://github.com/ethereum-optimism/optimism/tree/develop/op-deployer/cmd/op-deployer) +* **OPCM Documentation**: [OP Contracts Manager](/chain-operators/reference/opcm) diff --git a/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-geth-setup.mdx b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-geth-setup.mdx new file mode 100644 index 0000000000000..58f97f5c6f7a5 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-geth-setup.mdx @@ -0,0 +1,501 @@ +--- +title: Spin up sequencer +description: Set up and run op-geth and op-node, the execution and consensus layers for your rollup. +--- + +Now that you have op-deployer configured, it's time to spin up the sequencer for your rollup. This involves running both `op-geth` and `op-node` to create a functioning sequencer. + + + **Step 2 of 5**: This tutorial builds on [Spin up op-deployer](./op-deployer-setup). Make sure you've completed that first. + + +## What you'll set up + +The sequencer node consists of two core components: + +- `op-geth`: Execution layer that processes transactions and maintains state +- `op-node`: Consensus layer that orders transactions and creates L2 blocks + +The sequencer is responsible for: + +- Ordering transactions from users +- Building L2 blocks +- Signing blocks on the P2P network + +## Software installation + +For spinning up a sequencer, we recommend using Docker, as it provides a simpler setup and consistent environment. In this guide, building from source is also provided as an alternative for those who need more control and easier debugging. + + + + + + + If you prefer containerized deployment, you can use the official Docker images, and do the following: + + ```bash + # Create your sequencer directory inside rollup + cd ../ # Go back to rollup directory if you're in deployer + mkdir sequencer + cd sequencer + + # Copy configuration files from deployer + cp ../deployer/.deployer/genesis.json . + cp ../deployer/.deployer/rollup.json . + + # Generate JWT secret + openssl rand -hex 32 > jwt.txt + chmod 600 jwt.txt + ``` + + + + + ```bash + # Create .env file with your actual values + cat > .env << 'EOF' + # L1 Configuration - Replace with your actual RPC URLs + L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + L1_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com + + # Private keys - Replace with your actual private key + PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY + + # P2P configuration - Replace with your actual public IP + # Run `curl ifconfig.me` in a separate shell to obtain the value, then paste it below + P2P_ADVERTISE_IP=YOUR_ACTUAL_PUBLIC_IP + + EOF + ``` + + **Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values. + + + + + Create a `docker-compose.yml` file in the same directory: + + ```yaml + services: + op-geth: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101511.1 + volumes: + # Mount entire directory to avoid file mounting issues + - .:/workspace + working_dir: /workspace + ports: + - "8545:8545" + - "8546:8546" + - "8551:8551" + command: + - "--datadir=/workspace/op-geth-data" + - "--http" + - "--http.addr=0.0.0.0" + - "--http.port=8545" + - "--ws" + - "--ws.addr=0.0.0.0" + - "--ws.port=8546" + - "--authrpc.addr=0.0.0.0" + - "--authrpc.port=8551" + - "--authrpc.jwtsecret=/workspace/jwt.txt" + - "--syncmode=full" + - "--gcmode=archive" + - "--rollup.disabletxpoolgossip=true" + - "--http.vhosts=*" + - "--http.corsdomain=*" + - "--http.api=eth,net,web3,debug,txpool,admin" + - "--ws.origins=*" + - "--ws.api=eth,net,web3,debug,txpool,admin" + - "--authrpc.vhosts=*" + + op-node: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.13.5 + depends_on: + - op-geth + volumes: + - .:/workspace + working_dir: /workspace + ports: + - "8547:8547" + - "9222:9222" + environment: + - L1_RPC_URL=${L1_RPC_URL} + - L1_BEACON_URL=${L1_BEACON_URL} + - PRIVATE_KEY=${PRIVATE_KEY} + - P2P_ADVERTISE_IP=${P2P_ADVERTISE_IP} + command: + - "op-node" + - "--l1=${L1_RPC_URL}" + - "--l1.beacon=${L1_BEACON_URL}" + - "--l2=http://op-geth:8551" + - "--l2.jwt-secret=/workspace/jwt.txt" + - "--rollup.config=/workspace/rollup.json" + - "--sequencer.enabled=true" + - "--sequencer.stopped=false" + - "--sequencer.max-safe-lag=3600" + - "--verifier.l1-confs=4" + - "--p2p.listen.ip=0.0.0.0" + - "--p2p.listen.tcp=9222" + - "--p2p.listen.udp=9222" + - "--p2p.advertise.ip=${P2P_ADVERTISE_IP}" + - "--p2p.advertise.tcp=9222" + - "--p2p.advertise.udp=9222" + - "--p2p.sequencer.key=${PRIVATE_KEY}" + - "--rpc.addr=0.0.0.0" + - "--rpc.port=8547" + - "--rpc.enable-admin" + - "--log.level=info" + - "--log.format=json" + ``` + + + + + ```bash + # Make sure you're in the rollup/sequencer directory with all files copied + cd rollup/sequencer + + # Initialize op-geth using Docker + docker run --rm \ + -v $(pwd):/workspace \ + -w /workspace \ + us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101511.1 \ + init --datadir=./op-geth-data --state.scheme=hash ./genesis.json + ``` + + + + + ```bash + # Start both services + docker-compose up -d + + # View logs + docker-compose logs -f + ``` + + + + + ```bash + rollup/ + ├── deployer/ # From previous step + │ └── .deployer/ # Contains genesis.json and rollup.json + └── sequencer/ # You are here + ├── jwt.txt # Generated JWT secret + ├── genesis.json # Copied from deployer + ├── rollup.json # Copied from deployer + ├── .env # Environment variables + ├── docker-compose.yml # Docker configuration + ├── opnode_discovery_db/ # Created by Docker + ├── opnode_peerstore_db/ # Created by Docker + └── op-geth-data/ # Created by Docker + ├── geth/ # Geth data + └── keystore/ # Key files + ``` + + Your sequencer node is now operational and ready to process transactions. + + + + + + + To ensure you're using the latest compatible versions of OP Stack components, always check the official [release page](https://github.com/ethereum-optimism/optimism/releases). + + The main components you'll need for sequencer deployment are: + + - `op-node`: Look for the latest `op-node/v*` [release](https://github.com/ethereum-optimism/optimism/releases) + - `op-geth`: Look for the latest `op-geth/v*` [release](https://github.com/ethereum-optimism/op-geth/releases) + + + The versions used in this guide (**op-node/v1.13.5** and **op-geth/v1.101511.1**) are verified compatible versions. + + According to the **op-node v1.13.5** [release notes](https://github.com/ethereum-optimism/optimism/releases), this op-node version specifically corresponds to **op-geth v1.101511.1**. + Always check the release notes to ensure you're using compatible versions. + + + Building from source gives you full control over the binaries. + + + + + ```bash + # Clone the optimism monorepo + git clone https://github.com/ethereum-optimism/optimism.git + cd optimism + + # Checkout the latest release tag + git checkout op-node/v1.13.5 + + # Build op-node + cd op-node + just + + # Binary will be available at ./bin/op-node + ``` + + + + + ```bash + # Clone op-geth repository (in a separate directory) + git clone https://github.com/ethereum-optimism/op-geth.git + cd op-geth + + # Checkout to this release tag + git checkout v1.101511.1 + + # Build op-geth + make geth + + # Binary will be available at ./build/bin/geth + ``` + + + + + Check that you have properly installed the needed components. + + ```bash + # Make sure you're in the right directory + ./bin/op-node --version + ./build/bin/geth version + ``` + + + + ## Configuration setup + + + + + After building the binaries, you should have the following directory structure: + + ```bash + rollup/ + ├── deployer/ # From previous step + │ └── .deployer/ # Contains genesis.json and rollup.json + ├── optimism/ # Optimism monorepo + │ └── op-node/ + │ └── bin/ + │ └── op-node + └── op-geth/ + └── build/ + └── bin/ + └── geth + ``` + + Now create your sequencer working directory: + + ```bash + cd ../ + mkdir sequencer + cd sequencer + ``` + + + + ```bash + mkdir sequencer-node/bin + cp optimism/op-node/bin/op-node sequencer-node/bin/ + cp op-geth/build/bin/geth sequencer-node/bin/ + ``` + + + + + + ```bash + openssl rand -hex 32 > jwt.txt + chmod 600 jwt.txt + ``` + + + + + ```bash + mkdir scripts + cp ../deployer/.deployer/genesis.json . + cp ../deployer/.deployer/rollup.json . + ``` + + + + + You'll need to gather several pieces of information before creating your configuration. + + + + + You need access to the L1 network (Ethereum mainnet or Sepolia testnet) and its beacon node. + + **L1 RPC URL options:** + - **Infura**: [infura.io](https://infura.io) + - **Alchemy**: [alchemy.com](https://alchemy.com) + + **L1 Beacon URL options:** + - `https://ethereum-sepolia-beacon-api.publicnode.com` + - `https://ethereum-beacon-api.publicnode.com` + + + + For this basic sequencer setup, you only need a private key during op-node initialization. + + + + + ```bash + curl ifconfig.me + curl ipinfo.io/ip + ``` + + + + + - `8545`: op-geth HTTP RPC + - `8546`: op-geth WebSocket RPC + - `8551`: op-geth Auth RPC + - `8547`: op-node RPC + - `9222`: P2P networking + + + + + + ```bash + L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + L1_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com + SEQUENCER_ENABLED=true + SEQUENCER_STOPPED=false + PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY + P2P_LISTEN_PORT=9222 + P2P_ADVERTISE_IP=YOUR_ACTUAL_PUBLIC_IP + OP_NODE_RPC_PORT=8547 + OP_GETH_HTTP_PORT=8545 + OP_GETH_WS_PORT=8546 + OP_GETH_AUTH_PORT=8551 + JWT_SECRET=./jwt.txt + ``` + + + ## Sequencer specific configuration + + ### op-geth configuration for sequencer + + ```bash + #!/bin/bash + source .env + + ../../op-geth/build/bin/geth \ + --datadir=./op-geth-data \ + --http \ + --http.addr=0.0.0.0 \ + --http.port=$OP_GETH_HTTP_PORT \ + --http.vhosts="*" \ + --http.corsdomain="*" \ + --http.api=eth,net,web3,debug,txpool,admin,miner \ + --ws \ + --ws.addr=0.0.0.0 \ + --ws.port=$OP_GETH_WS_PORT \ + --ws.origins="*" \ + --ws.api=eth,net,web3,debug,txpool,admin,miner \ + --authrpc.addr=0.0.0.0 \ + --authrpc.port=$OP_GETH_AUTH_PORT \ + --authrpc.vhosts="*" \ + --authrpc.jwtsecret=$JWT_SECRET \ + --syncmode=full \ + --gcmode=archive \ + --rollup.disabletxpoolgossip=true \ + --rollup.sequencerhttp=http://localhost:$OP_NODE_RPC_PORT + ``` + + ### op-node configuration for sequencer + + ```bash + #!/bin/bash + source .env + + ../../optimism/op-node/bin/op-node \ + --l1=$L1_RPC_URL \ + --l1.beacon=$L1_BEACON_URL \ + --l2=http://localhost:$OP_GETH_AUTH_PORT \ + --l2.jwt-secret=$JWT_SECRET \ + --rollup.config=./rollup.json \ + --sequencer.enabled=$SEQUENCER_ENABLED \ + --sequencer.stopped=$SEQUENCER_STOPPED \ + --sequencer.max-safe-lag=3600 \ + --verifier.l1-confs=4 \ + --p2p.listen.ip=0.0.0.0 \ + --p2p.listen.tcp=$P2P_LISTEN_PORT \ + --p2p.listen.udp=$P2P_LISTEN_PORT \ + --p2p.advertise.ip=$P2P_ADVERTISE_IP \ + --p2p.advertise.tcp=$P2P_LISTEN_PORT \ + --p2p.advertise.udp=$P2P_LISTEN_PORT \ + --p2p.sequencer.key=$PRIVATE_KEY \ + --rpc.addr=0.0.0.0 \ + --rpc.port=$OP_NODE_RPC_PORT \ + --rpc.enable-admin \ + --log.level=info \ + --log.format=json + ``` + + ## Initializing and starting the sequencer + + + + + ```bash + cd rollup/sequencer + ../../op-geth/build/bin/geth init --datadir=./op-geth-data --state.scheme=hash ./genesis.json + ``` + + + + + ```bash + chmod +x scripts/start-op-geth.sh + chmod +x scripts/start-op-node.sh + ./scripts/start-op-geth.sh + ``` + + + + + ```bash + ./scripts/start-op-node.sh + ``` + + + + + ```bash + curl -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + http://localhost:8545 + + curl -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"admin_sequencerActive","params":[],"id":1}' \ + http://localhost:8547 + ``` + + + + Your sequencer node is now operational and ready to process transactions. + + + +## What's Next? + +Great! Your sequencer is running and processing transactions. The next step is to set up the batcher to publish transaction data to L1. + + + **Next**: Configure and start op-batcher to publish L2 transaction data to L1 for data availability. + + +--- + +## Need Help? + +- **Batcher Configuration**: [op-batcher Configuration Guide](/chain-operators/guides/configuration/batcher) +- **Best Practices**: [Chain Operator Best Practices](/chain-operators/guides/management/best-practices) diff --git a/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-proposer-setup.mdx b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-proposer-setup.mdx new file mode 100644 index 0000000000000..bb010ffca0bca --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/create-l2-rollup/op-proposer-setup.mdx @@ -0,0 +1,477 @@ +--- +title: Spin up proposer +description: Learn how to set up and configure an OP Stack proposer to post L2 state roots. +--- + +After you have spun up your sequencer and batcher, you need to attach a proposer to post your L2 state roots data back onto L1 so we can prove withdrawal validity. The proposer is a critical component that enables trustless L2-to-L1 messaging and creates the authoritative view of L2 state from L1's perspective. + + + **Step 4 of 5**: This tutorial is designed to be followed step-by-step. Each step builds on the previous one. + + + + **Automated Setup Available** + + For a complete working setup with all components, check out the [automated approach](https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/) in the code directory. + + +This guide assumes you already have a functioning sequencer, batcher, and the necessary L1 contracts deployed using [`op-deployer`](./op-deployer-setup). If you haven't set up your sequencer and batcher yet, please refer to the [sequencer guide](./op-geth-setup) and [batcher guide](./op-batcher-setup) first. + +To see configuration info for the proposer, check out the [configuration page](/chain-operators/guides/configuration/proposer). + +## Understanding the proposer's role + +The proposer (`op-proposer`) serves as a crucial bridge between your L2 chain and L1. Its primary responsibilities include: + +* **State commitment**: Proposing L2 state roots to L1 at regular intervals +* **Withdrawal enablement**: Providing the necessary commitments for users to prove and finalize withdrawals + +The proposer creates dispute games via the `DisputeGameFactory` contract. + +## Prerequisites + +Before setting up your proposer, ensure you have: + +**Running infrastructure:** + +* An operational sequencer node +* Access to a L1 RPC endpoint + +**Network information:** + +* Your L2 chain ID and network configuration +* L1 network details (chain ID, RPC endpoints) + +For setting up the proposer, we recommend using Docker as it provides a consistent and isolated environment. Building from source is also available as an option. + + + + If you prefer containerized deployment, you can use the official Docker images and do the following: + + + + + + ```bash + # Create a proposer directory inside rollup + cd ../ # Go back to rollup directory if you're in batcher + mkdir proposer + cd proposer + # inside the proposer directory, copy the state.json file from the op-deployer setup + # Copy configuration files from deployer + cp ../deployer/.deployer/state.json . + + # Extract the DisputeGameFactory address + GAME_FACTORY_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].DisputeGameFactoryProxy') + echo "DisputeGameFactory Address: $GAME_FACTORY_ADDRESS" + ``` + + + + + + + **OP Stack Standard Variables** + + The proposer uses OP Stack standard environment variables following the OP Stack conventions. These are prefixed with `OP_PROPOSER_` for proposer-specific settings. + + + ```bash + # Create .env file with your actual values + cat > .env << 'EOF' + # L1 Configuration - Replace with your actual RPC URLs + OP_PROPOSER_L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + + # L2 Configuration - Should match your sequencer setup + OP_PROPOSER_ROLLUP_RPC=http://op-node:8547 + + # Contract addresses - Extract from your op-deployer output + OP_PROPOSER_GAME_FACTORY_ADDRESS=YOUR_ACTUAL_GAME_FACTORY_ADDRESS + + # Private key - Replace with your actual private key + OP_PROPOSER_PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY + + # OP Stack proposer configuration (optional - defaults provided) + OP_PROPOSER_PROPOSAL_INTERVAL=3600s + OP_PROPOSER_GAME_TYPE=0 + OP_PROPOSER_POLL_INTERVAL=20s + OP_PROPOSER_ALLOW_NON_FINALIZED=true + OP_PROPOSER_WAIT_NODE_SYNC=true + EOF + ``` + + **Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values. + + + + + + + If you get "failed to dial address" errors, ensure your proposer is in the same Docker network as your sequencer. + + Common fixes: + + * Add `networks: - sequencer-node_default` to your proposer's docker-compose.yml + * Use service names like `op-geth:8545` and `op-node:8547` in your `.env` file + * Verify your sequencer network name with `docker network ls` + + + ```yaml + + services: + op-proposer: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:v1.10.0 + volumes: + - .:/workspace + working_dir: /workspace + ports: + - "8560:8560" + env_file: + - .env + command: > + op-proposer + --rpc.port=8560 + --log.level=info + --log.format=json + restart: unless-stopped + networks: + - sequencer-node_default + + networks: + sequencer-node_default: + external: false + + ``` + + + + + + ```bash + # Make sure your sequencer network exists + docker network create op-stack 2>/dev/null || true + + # Start the proposer + docker-compose up -d + + # View logs + docker-compose logs -f op-proposer + ``` + + + + + + ```bash + # Check container status + docker-compose ps + ``` + + + + + + ```bash + rollup/ + ├── deployer/ # From previous step + │ └── .deployer/ # Contains state.json + ├── sequencer/ # From previous step + ├── batcher/ # From previous step + └── proposer/ # You are here + ├── state.json # Copied from deployer + ├── .env # Environment variables + └── docker-compose.yml # Docker configuration + ``` + + + + + + + When you first start your proposer, you'll see several types of log messages: + + 1. **Initialization messages** (normal): + ``` + lvl=info msg="Initializing L2Output Submitter" + lvl=info msg="Connected to DisputeGameFactory" + lvl=info msg="Starting JSON-RPC server" + ``` + + 2. **Sync status messages** (expected during startup): + ``` + msg="rollup current L1 block still behind target, retrying" + current_l1=...:9094035 target_l1=9132815 + ``` + This is normal! It means: + * Your rollup is still syncing with L1 (e.g., Sepolia) + * The proposer is waiting until sync is closer to L1 tip + * You'll see the `current_l1` number increasing as it catches up + * Once caught up, the proposer will start submitting proposals + + Don't worry about the "retrying" messages - they show healthy progress as your rollup catches up to the latest L1 blocks. + + **Common log patterns:** + + * Startup: You'll see initialization messages as services start + * Sync: "block still behind target" messages while catching up + * Normal operation: Regular proposal submissions once synced + * Network: Connection messages to L1/L2 endpoints + + If you see errors about "failed to dial" or connection issues: + + * For source build: Verify your localhost ports and services + + + Your proposer is now operational and will continuously submit state roots to L1! + + + + + ### Finding the current stable releases + + To ensure you're using the latest compatible versions of OP Stack components, always check the official [releases page](https://github.com/ethereum-optimism/optimism/releases). + + Look for the latest `op-proposer/v*` release that's compatible with your sequencer setup. + + + This guide uses `op-proposer/v1.10.0` which is compatible with op-node/v1.13.3 and op-geth/v1.101511.1 from the sequencer setup. + Always check the [release notes](https://github.com/ethereum-optimism/optimism/releases) for compatibility information. + + Building from source gives you full control over the binaries. + + + + + + ```bash + # If you don't already have the optimism repository from the sequencer setup + git clone https://github.com/ethereum-optimism/optimism.git + cd optimism + + # Checkout the latest release tag + git checkout op-proposer/v1.10.0 + + # Build op-proposer + cd op-proposer + just + + # Binary will be available at ./bin/op-proposer + ``` + + + + + + Run this command to verify the installation: + + ```bash + ./bin/op-proposer --version + ``` + + + + + ## Configuration setup + + + The rest of this guide assumes you're using the **build-from-source** approach. + If you chose Docker, all the necessary configuration was covered in the Docker tab above. + + + + + + + Create your proposer working directory at the same level as your sequencer: + + ```bash + # Create proposer directory inside rollup + cd ../ # Go back to rollup directory + mkdir proposer + cd proposer + + # Create scripts directory + mkdir scripts + ``` + + + + + + Extract the `DisputeGameFactory` contract address from your op-deployer output: + + ```bash + # Make sure you're in the rollup/proposer directory + cd rollup/proposer + + # Copy the state.json from deployer + cp ../deployer/.deployer/state.json . + + # Extract the DisputeGameFactory address + GAME_FACTORY_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].disputeGameFactoryProxyAddress') + echo "DisputeGameFactory Address: $GAME_FACTORY_ADDRESS" + ``` + + + The proposer only needs the `DisputeGameFactory` address to submit proposals. + The `GAME_TYPE=0` represents the standard fault proof game type. + + + + + + + Create your `.env` file with the actual values: + + ```bash + # Create .env file with your actual values + # L1 Configuration - Replace with your actual RPC URL + L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY + + # L2 Configuration - Should match your sequencer setup + L2_RPC_URL=http://localhost:8545 + ROLLUP_RPC_URL=http://localhost:8547 + + # Contract addresses - Extract from your op-deployer output + GAME_FACTORY_ADDRESS=YOUR_ACTUAL_GAME_FACTORY_ADDRESS + + # Private key - Replace with your actual private key + PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY + + # Proposer configuration + PROPOSAL_INTERVAL=3600s + GAME_TYPE=0 + POLL_INTERVAL=20s + + # RPC configuration + PROPOSER_RPC_PORT=8560 + ``` + + **Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values! + + + + + + Get a private key from your wallet that will be used for submitting proposals to L1. This account needs sufficient ETH to pay for L1 gas costs. + + + The proposer account needs to be funded with ETH on L1 to pay for proposal submission transactions. Monitor this account's balance regularly as it will consume ETH for each proposal submission. + + + + + + ## Proposer configuration + + Create `scripts/start-proposer.sh`: + + ```bash + #!/bin/bash + + source .env + + # Path to the op-proposer binary we built + ../../optimism/op-proposer/bin/op-proposer \ + --poll-interval=$POLL_INTERVAL \ + --rpc.port=$PROPOSER_RPC_PORT \ + --rpc.enable-admin \ + --rollup-rpc=$ROLLUP_RPC_URL \ + --l1-eth-rpc=$L1_RPC_URL \ + --private-key=$PRIVATE_KEY \ + --game-factory-address=$GAME_FACTORY_ADDRESS \ + --game-type=$GAME_TYPE \ + --proposal-interval=$PROPOSAL_INTERVAL \ + --num-confirmations=1 \ + --resubmission-timeout=30s \ + --wait-node-sync=true \ + --log.level=info + ``` + + Your final directory structure should look like: + + ```bash + rollup/ + ├── deployer/ # From previous step + │ └── .deployer/ # Contains state.json + ├── optimism/ # Contains op-proposer binary + ├── sequencer/ # From previous step + ├── batcher/ # From previous step + └── proposer/ # You are here + ├── state.json # Copied from deployer + ├── .env # Environment variables + └── scripts/ # Startup scripts + └── start-proposer.sh + ``` + + ## Starting the proposer + + + + + + ```bash + # Make the script executable + chmod +x scripts/start-proposer.sh + + # Start the proposer + ./scripts/start-proposer.sh + ``` + + + + + + + When you first start your proposer, you'll see several types of log messages: + + 1. **Initialization messages** (normal): + ``` + lvl=info msg="Initializing L2Output Submitter" + lvl=info msg="Connected to DisputeGameFactory" + lvl=info msg="Starting JSON-RPC server" + ``` + + 2. **Sync status messages** (expected during startup): + ``` + msg="rollup current L1 block still behind target, retrying" + current_l1=...:9094035 target_l1=9132815 + ``` + This is normal! It means: + * Your rollup is still syncing with L1 (e.g., Sepolia) + * The proposer is waiting until sync is closer to L1 tip + * You'll see the `current_l1` number increasing as it catches up + * Once caught up, the proposer will start submitting proposals + + Don't worry about the "retrying" messages - they show healthy progress as your rollup catches up to the latest L1 blocks. + + **Common log patterns:** + + * Startup: You'll see initialization messages as services start + * Sync: "block still behind target" messages while catching up + * Normal operation: Regular proposal submissions once synced + * Network: Connection messages to L1/L2 endpoints + + If you see errors about "failed to dial" or connection issues: + + * For Docker: Check your network configuration and service names + + + Your proposer is now operational! + + + +## What's Next? + +Perfect! Your proposer is submitting state roots to L1. The final step is to set up the challenger to monitor and respond to disputes. + + + **Next**: Configure and start op-challenger to monitor disputes and maintain your rollup's security. + + +*** + +## Need Help? + +* **Proposer Configuration**: [op-proposer Configuration Reference](/chain-operators/guides/configuration/proposer) +* **Dispute Games**: [Deploying Dispute Games with OPCM](/chain-operators/tutorials/dispute-games) diff --git a/docs/public-docs/chain-operators/tutorials/dispute-games.mdx b/docs/public-docs/chain-operators/tutorials/dispute-games.mdx new file mode 100644 index 0000000000000..0d0207e5766c9 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/dispute-games.mdx @@ -0,0 +1,140 @@ +--- +title: Deploying new dispute games with OPCM +description: Learn how to deploy new dispute games to an OP Stack chain using OPCM +--- + +This guide provides instructions on how to deploy new dispute games to an OP Stack chain using the [OPCM (OP Contracts Manager)](/chain-operators/reference/opcm). This process is particularly relevant for teams looking to upgrade their chains to support permissionless dispute games. + +## Prerequisites + +Before you begin, ensure that: + +* Run op-contracts/v2.0.0 or higher on your chain +* Own the chain's L1 `ProxyAdmin` contract +* Install the Forge toolkit (see [Foundry docs](https://getfoundry.sh/)) + +## Understanding dispute games + +The OP Stack uses two types of dispute games: + +* **Permissioned dispute game**: Limited to specific proposer and challenger addresses +* **Permissionless dispute game**: Open to anyone to propose or challenge + + + In the Permissioned dispute game (PDG), the [challenger role](/op-stack/protocol/privileged-roles) is a protocol-level permission assigned to specific addresses, allowing them to initiate or respond to disputes. This role is distinct from the op-challenger service, which is an off-chain monitoring service responsible for automatically detecting discrepancies and submitting challenges. + While the op-challenger service typically operates using an address that has been assigned the challenger role, the protocol-level role itself can be independently assigned, regardless of whether the op-challenger service is in use. + Refer to the [OP Stack configurability spec](https://specs.optimism.io/protocol/configurability.html) for more details. + + +All chains deployed with `op-deployer` only initially include the permissioned dispute game. +This guide explains how to add the permissionless game. + +## The `addGameType` function + +The OPCM contract contains an `addGameType` function that handles the deployment of new dispute games. This function: + +1. Deploys a new dispute game implementation +2. Optionally deploys a new [`DelayedWETH`](https://github.com/ethereum-optimism/optimism/blob/6dc586d551f55b2b3a607cfcca9d3d272dec84d7/packages/contracts-bedrock/src/dispute/DelayedWETH.sol) contract +3. Registers the game with the `DisputeGameFactory` +4. Sets the initial bond amount + +### 1. Finding the correct OPCM instance + +Each OP Contracts release has its own OPCM instance. You must use the OPCM corresponding exactly to your chain's contract version. For example, if your system uses contracts version **v3.0.0**, you must use the OPCM for **v3.0.0**. + +To find the correct OPCM address: + +* For **Mainnet**, refer to the [standard-versions-mainnet.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-mainnet.toml) file in the superchain-registry. +* For **Sepolia**, refer to the [standard-versions-sepolia.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-sepolia.toml) file in the superchain-registry. + +These registry files contain mappings between OP contract versions and their corresponding OPCM addresses. + +### 2. Preparing the `addGameType` Call + +The `addGameType` function expects an array of `AddGameInput` structs. Here is the structure: + +```solidity +struct AddGameInput { + string saltMixer; + ISystemConfig systemConfig; + IProxyAdmin proxyAdmin; + IDelayedWETH delayedWETH; + GameType disputeGameType; + Claim disputeAbsolutePrestate; + uint256 disputeMaxGameDepth; + uint256 disputeSplitDepth; + Duration disputeClockExtension; + Duration disputeMaxClockDuration; + uint256 initialBond; + IBigStepper vm; + bool permissioned; +} +``` + +**Key parameters explained:** + +* **saltMixer:** A string used to create unique contract addresses +* **systemConfig:** The address of your chain's `SystemConfig` contract +* **proxyAdmin:** The Address of your chain's `ProxyAdmin` contract +* **delayedWETH:** The Address of the `DelayedWETH` contract (use zero address to deploy a new one) +* **disputeGameType:** For permissionless games, use `GameTypes.CANNON` +* **disputeAbsolutePrestate:** The absolute prestate hash for the game +* **permissioned:** Set to `false` for a permissionless game + +For a permissionless game, you'll generally want to mirror most parameters from your existing permissioned game, but set permissioned to `false` and use the GameType `CANNON`. + +3. Execute the addGameType function + The following is a template for calling the addGameType function using Forge's cast: + + + The most recommended way is to use a script to execute this call, rather than manual execution. + + +```bash +# Retrieve existing values from chain for reference +# Get permissioned game implementation +PERMISSIONED_GAME=$(cast call --rpc-url $RPC_URL $DISPUTE_GAME_FACTORY "gameImpls(uint32)" $PERMISSIONED_GAME_TYPE) + +# Retrieve parameters from existing permissioned game +ABSOLUTE_PRESTATE=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "absolutePrestate()") +MAX_GAME_DEPTH=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "maxGameDepth()") +SPLIT_DEPTH=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "splitDepth()") +CLOCK_EXTENSION=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "clockExtension()") +MAX_CLOCK_DURATION=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "maxClockDuration()") +VM=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "vm()") +ANCHOR_STATE_REGISTRY=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "anchorStateRegistry()") +L2_CHAIN_ID=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "l2ChainId()") + +# Create call data for addGameType function +# Note: Set delayedWETH to 0x0 to deploy a new one +CALLDATA=$(cast calldata "addGameType((string,address,address,address,uint32,bytes32,uint256,uint256,uint64,uint64,uint256,address,bool)[])" \ +"[(\ +\"unique_salt_mixer\",\ +$SYSTEM_CONFIG,\ +$PROXY_ADMIN,\ +0x0000000000000000000000000000000000000000,\ +$CANNON_GAME_TYPE,\ +$ABSOLUTE_PRESTATE,\ +$MAX_GAME_DEPTH,\ +$SPLIT_DEPTH,\ +$CLOCK_EXTENSION,\ +$MAX_CLOCK_DURATION,\ +$INITIAL_BOND,\ +$VM,\ +false\ +)]") + +# Execute the transaction +cast send --rpc-url $RPC_URL --private-key $PRIVATE_KEY $OPCM_ADDRESS $CALLDATA +``` + +4. Setting the respected game type + After deploying the permissionless dispute game, you'll need to update the respectedGameType in the OptimismPortal to start using it. + For detailed instructions on setting the respected game type and migrating your chain from permissioned to permissionless fault proofs, refer to the [migrating to permissionless fault proofs guide](/chain-operators/tutorials/migrating-permissionless). + +## Next Steps + +* For more detail on deploying new dispute games with OPCM, [see the docs](/chain-operators/tutorials/dispute-games). +* Learn about [absolute prestate](/chain-operators/tutorials/absolute-prestate) +* checkout the [migrating to permissionless fault proofs](/chain-operators/tutorials/migrating-permissionless) guide +* [Fault proofs explainer](/op-stack/fault-proofs/explainer) diff --git a/docs/public-docs/chain-operators/tutorials/integrating-da-layer.mdx b/docs/public-docs/chain-operators/tutorials/integrating-da-layer.mdx new file mode 100644 index 0000000000000..aa7d0f243ec8e --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/integrating-da-layer.mdx @@ -0,0 +1,43 @@ +--- +title: Integrating a new DA layer with Alt-DA +description: Learn how to add support for a new DA Layer within the OP Stack. +--- + + + The Alt-DA Mode feature is currently in Beta within the MIT-licensed OP Stack. Beta features are built and reviewed by Optimism Collective core contributors, and provide developers with early access to highly requested configurations. + These features may experience stability issues, and we encourage feedback from our early users. + + +[Alt-DA Mode](/op-stack/features/experimental/alt-da-mode) enables seamless integration of any DA Layer, regardless of their commitment type, into the OP Stack. After a DA Server is built for a DA Layer, any chain operator can launch an OP Stack chain using that DA Layer for sustainably low costs. + +## Build your DA server + +Our suggestion is for every DA Layer to build and maintain their own DA Server, with support from the OP Labs team along the way. The DA Server will need to be run by every node operator, so we highly recommend making your DA Server open source and MIT licensed. + + + + * It must point to the data on your layer (like block height / hash). + * It must be able to validate the data returned from the data (i.e., include a cryptographic commitment to the data like a hash, merkle proof, or polynomial commitment, this could be done against the block hash with a complex proof). + + + See the [specs](https://specs.optimism.io/experimental/alt-da.html?highlight=input-commitment-submission?utm_source=op-docs&utm_medium=docs#input-commitment-submission) for more info on commitment submission. + + + + + * Claim your [byte](https://github.com/ethereum-optimism/specs/discussions/135) + + + + * Write a simple HTTP server which supports `get` and `put` + * `put` is used by the batcher and can return the commitment to the batcher in the body. It should not return until the data is known to be submitted to your DA layer. + * `get` should fetch the data. If the data is not available, it should return a `404` not found. If there are other errors, a different error should be returned. + + + +## Run Alt-DA +Follow our guide on [how to operate an Alt-DA Mode chain](/chain-operators/guides/features/alt-da-mode-guide), except instead of using the S3 DA server, use the DA server that you built. + +## Next steps + +* For more detail on implementing the DA Server, [see the specification](https://specs.optimism.io/experimental/alt-da.html?utm_source=op-docs&utm_medium=docs#da-server). diff --git a/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade.mdx b/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade.mdx new file mode 100644 index 0000000000000..e1f3694940279 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade.mdx @@ -0,0 +1,70 @@ +--- +title: Upgrade L1 contracts using op-deployer +description: Learn about how to upgrade smart contracts using op-deployer +--- + +[`op-deployer`](/chain-operators/tools/op-deployer/overview) simplifies the process of deploying and upgrading the OP Stack. Using the `upgrade` command, you can upgrade a chain from one version to the next. + +It consists of several subcommands, one for each upgrade version. Think of it like a database migration: each upgrade command upgrades a chain from exactly one previous version to the next. A chain that is several versions behind can be upgraded to the latest version by running multiple upgrade commands in sequence. + +Unlike the bootstrap or apply commands, upgrade does not directly interact with the chain. Instead, it generates calldata. You can then use this calldata with cast, Gnosis SAFE, or whatever tooling you use to manage your L1. + +## Limitations of `upgrade` + +There are a few limitations to `upgrade`: + +Using the standard OP Contracts Manager currently requires you to be using the standard shared SuperchainConfig contract. If you're not using this, you will need to utilize the `bootstrap` command to deploy your own Superchain target, including your own `opcm` instance. + +## Using `upgrade` + + + + [Install `op-deployer`](/operators/chain-operators/tools/op-deployer#installation) from source or pre-built binary. + + + + Create a `config.json` file using the following example: + + ```json + { + "prank": "
", + "opcm": "
", + "chainConfigs": [ + { + "systemConfigProxy": "
", + "proxyAdmin": "
", + "absolutePrestate": "<32-byte hash of the chain's absolute prestate>" + } + ] + } + ``` + + The standard OPCM instances (`opcm` in the example above) and absolute prestates (`absolutePrestate`) are found in the superchain registry: + + * `opcm`: under `op_contracts_manager` for [Mainnet](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-mainnet.toml) and [Sepolia](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-sepolia.toml). + * `absolutePrestate`: the `hash` under your [chosen upgrade](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml) + + + + Run the following command: + + ```bash + op-deployer upgrade \ + --config + ``` + + `version` must be either major release `2.0` or higher. + + The output should look like: + + ```json + { + "to": "", + "data": "", + "value": "0x0" + } + ``` + + Now you have the calldata that can be executed onchain to perform the L1 contract upgrade. You should simulate this upgrade and make sure the changes are expected. You can reference the validation files of previously executed upgrade tasks in the [superchain-ops repo](https://github.com/ethereum-optimism/superchain-ops/blob/main/tasks/eth/022-holocene-fp-upgrade/NestedSignFromJson.s.sol) to see what the expected changes are. Once you're confident the state changes are expected, you can sign and execute the upgrade. + + diff --git a/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide.mdx b/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide.mdx new file mode 100644 index 0000000000000..64b1e916319d7 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide.mdx @@ -0,0 +1,106 @@ +--- +title: Upgrade using superchain-ops +description: Learn about using superchain-ops to upgrade your chain +--- + +This guide outlines the process for upgrading Optimism chains using the `superchain-ops` repository. It's intended primarily for OP Stack chains managed by the Security Council, those with the Foundation or Security Council as signers, and/or chains requiring a highly secure process. + +For chains that don't require the enhanced security of superchain-ops or security council signing, you can instead use [op-deployer](/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade) to upgrade your chain. + +For non-Optimism governed chains, you can use [op-deployer](/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade) and your own tooling to upgrade your chain. + +`superchain-ops` is a highly secure service designed for Optimism chains. It provides a structured and security-focused approach to chain upgrades. The process involves creating tasks that use predefined templates to generate the necessary upgrade transactions. + +## Who should use `superchain-ops` + +`superchain-ops` is primarily intended for: + +1. **OP Stack chains managed by the Security Council**: For standard chains managed by the Optimism Security Council, upgrades are typically handled through `superchain-ops`. + +2. **Chains with Foundation or Security Council as signers**: If your chain has the Foundation multi-sig or Security Council as signers, your upgrade tasks should go through `superchain-ops`. + +3. **Chains requiring a highly secure process**: For chains that prioritize security over automation, `superchain-ops` provides an intentionally manual workflow with thorough verification steps (e.g. EVM state diff inspection). + +For chains that don't fall into these categories, you'll need to generate appropriate call data for upgrades through other means or develop your own upgrade process for non-OPCM upgrades. + +## Understanding templates and tasks + +`superchain-ops` uses two key concepts: + +* **Templates**: Define what the upgrade is and contain the code for specific upgrade paths (e.g., [`op-contracts/v1.8.0` to `op-contracts/v2.0.0`](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/improvements/template/OPCMUpgradeV200.sol)). Templates are version-specific and live in the [/src/improvements/template](https://github.com/ethereum-optimism/superchain-ops/tree/main/src/improvements/template) directory. + +* **Tasks**: Use a template to define a specific upgrade transaction for a chain. Multiple tasks can use the same template. Tasks are organized by network (`eth` or `sep`) in the [/src/improvements/tasks](https://github.com/ethereum-optimism/superchain-ops/tree/main/src/improvements/tasks) directory. + +## General upgrade process + +The following process outlines how to upgrade a chain using `superchain-ops`, using the [`op-contracts/v1.8.0` to `op-contracts/v2.0.0`](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/improvements/template/OPCMUpgradeV200.sol) upgrade as an example. This same pattern applies to other OPCM-based upgrades (like [`op-contracts/v2.0.0` to `op-contracts/v3.0.0`](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/improvements/template/OPCMUpgradeV300.sol)). + +### Step 1: Clone the `superchain-ops` repository + +```bash +git clone https://github.com/ethereum-optimism/superchain-ops.git +cd superchain-ops/src/improvements +``` + +### Step 1a: One-time Install Dependencies Setup +Follow the 'Install Dependencies' instructions in the ['Quick Start'](https://github.com/ethereum-optimism/superchain-ops/blob/main/README.md#quick-start) section of the `README.md` file. + +### Step 2: Create a new task using the quick start + +```bash +just new task +``` + +Follow the prompts to select the appropriate template (e.g., `OPCMUpgradeV200` for a `op-contracts/v1.8.0` to `op-contracts/v2.0.0` upgrade) and provide the necessary details. + +This will create a new task directory containing a `config.toml` and `README` file. The config file will look like this: + +```bash +l2chains = [] # e.g. [{name = "OP Mainnet", chainId = 10}] +templateName = "OPCMUpgradeV200" +``` + +### Step 3: Configure the task + +You'll have to add additional properties to your `config.toml` file to fully configure your task. For example, when upgrading from `op-contracts/v1.8.0` to `op-contracts/v2.0.0`, you can look at a previous task for reference: [src/improvements/tasks/eth/002-opcm-upgrade-v200/config.toml](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/improvements/tasks/eth/002-opcm-upgrade-v200/config.toml): + + + This is an example task. You must figure out which values you'll need for your own specific task. Ensure you replace all addresses and other values in the example below. + + +```bash +l2chains = [ + {name = "Unichain", chainId = 130} +] + +templateName = "OPCMUpgradeV200" + +[opcmUpgrades] +absolutePrestates = [ + {absolutePrestate = "0x039facea52b20c605c05efb0a33560a92de7074218998f75bcdf61e8989cb5d9", chainId = 130}, +] + +[addresses] +OPCM = "0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76" +StandardValidatorV200 = "0xecabaeaa1d58261f1579232520c5b460ca58a164" +``` + +### Step 5: Simulate the task + +Before executing the upgrade, simulate it to ensure everything is configured correctly: + +```bash +just --dotenv-path $(pwd)/.env simulate [child-safe-name-depth-1] [child-safe-name-depth-2] +# Both [child-safe-name-depth-1] and [child-safe-name-depth-2] are optional. You'll only need to specify +# [child-safe-name-depth-2] if it's a nested safe and [child-safe-name-depth-2] if it has multiple levels of nesting. +# Omit both arguments if it's a single safe. +``` + +This will run through the upgrade process without actually executing the transaction. +For more information on the simulate command, please reference the [README](https://github.com/ethereum-optimism/superchain-ops/blob/main/README.md#quick-start). + +### Step 6: Execute or submit for review + +For OP Stack chains managed by the Security Council, submit a pull request to have your task reviewed. If your chain is not managed by the Security Council, execute the transaction yourself. + + diff --git a/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/upgrade-op-contracts-1-3-1-8.mdx b/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/upgrade-op-contracts-1-3-1-8.mdx new file mode 100644 index 0000000000000..47e6182f5e7d8 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/l1-contract-upgrades/upgrade-op-contracts-1-3-1-8.mdx @@ -0,0 +1,213 @@ +--- +title: Upgrading Smart Contracts from v1.3.0 to v1.8.0 +description: Learn about upgrading the smart contracts that make up the OP Stack. +--- + +This guide provides specific instructions for upgrading the OP Stack's Layer 1 contracts from `op-contracts/v1.3.0` to `op-contracts/v1.8.0`. This upgrade includes important changes to the system configuration and introduces the Fault Proof System. + +## Overview of the Holocene upgrade + +The Holocene upgrade is a protocol upgrade. Learn more about it in the [Holocene notice page](https://docs.optimism.io/notices/holocene-changes). This guide shows you how to take your OP Stack chain from the L2 Output Oracle System to a permissioned Fault Proof System contract version associated with the Holocene upgrade. + +After upgrading to OP Stack contracts v1.8.0 and enabling permissioned fault proofs, all pending (unfinalized) withdrawal proofs created on L1 are invalidated. This means that withdrawals must be manually reproven by users after the upgrade; the process is not automatic. + +### Formatting config files + +You need your chain's `deployments.json` and `deploy-config.json` + +Ensure both files are correctly formatted, using the exact field names shown in the below examples, with **ALL** values - particularly the relevant ones - updated for **your specific chain**: + +`deployments.json`: + +```json +{ + "AddressManager": "0xEF8115F2733fb2033a7c756402Fc1deaa56550Ef", + "L2OutputOracleProxy": "0x9E6204F750cD866b299594e2aC9eA824E2e5f95c", + "OptimismMintableERC20FactoryProxy": "0xc52BC7344e24e39dF1bf026fe05C4e6E23CfBcFf", + "L1StandardBridgeProxy": "0x3e2Ea9B92B7E48A52296fD261dc26fd995284631", + "ProxyAdmin": "0xD4ef175B9e72cAEe9f1fe7660a6Ec19009903b49", + "L1CrossDomainMessengerProxy": "0xdC40a14d9abd6F410226f1E6de71aE03441ca506", + "L1ERC721BridgeProxy": "0x83A4521A3573Ca87f3a971B169C5A0E1d34481c3", + "OptimismPortalProxy": "0x1a0ad011913A150f69f6A19DF447A0CfD9551054", + "SystemConfigProxy": "0xA3cAB0126d5F504B071b81a3e8A2BBBF17930d86" +} +``` + +`deploy-config.json`: + +```json +{ + "l1StartingBlockTag": "0x10aa183", + "l1ChainID": 1, + "l2ChainID": 7777777, + "l2BlockTime": 2, + "finalizationPeriodSeconds": 604800, + "controller": "0xEe729F57F0111FD0F660867d0F522f983202a5aF", + "baseFeeVaultRecipient": "0xe900b3Edc1BA0430CFa9a204A1027B90825ac951", + "l1FeeVaultRecipient": "0xe900b3Edc1BA0430CFa9a204A1027B90825ac951", + "sequencerFeeVaultRecipient": "0xe900b3Edc1BA0430CFa9a204A1027B90825ac951", + "l2GenesisBlockBaseFeePerGas": "0x3b9aca00", + "governanceTokenOwner": "0xC72aE5c7cc9a332699305E29F68Be66c73b60542", + "governanceTokenSymbol": "OP", + "governanceTokenName": "Optimism", + "maxSequencerDrift": 600, + "sequencerWindowSize": 3600, + "channelTimeout": 300, + "p2pSequencerAddress": "0x3Dc8Dfd0709C835cAd15a6A27e089FF4cF4C9228", + "optimismL2FeeRecipient": "0x63AA492609175d1824dD668BDadF0042E74b0fC8", + "batchInboxAddress": "0x6F54Ca6F6EdE96662024Ffd61BFd18f3f4e34DFf", + "batchSenderAddress": "0x625726c858dBF78c0125436C943Bf4b4bE9d9033", + "l2GenesisRegolithTimeOffset": "0x0", + "l2OutputOracleSubmissionInterval": 180, + "l2OutputOracleStartingTimestamp": -1, + "l2OutputOracleStartingBlockNumber": "0x0", + "l2GenesisBlockGasLimit": "0x1c9c380", + "fundDevAccounts": false, + "gasPriceOracleOverhead": 188, + "gasPriceOracleScalar": 684000, + "eip1559Denominator": 50, + "eip1559Elasticity": 6, + "optimismBaseFeeRecipient": "0xea4591A6e5a31CF0b822A4f563163CeeBeEe4eb1", + "optimismL1FeeRecipient": "0xdD7aCF916c3E3Fb959CA3bB29beFffcAD2e90be6", + "l2CrossDomainMessengerOwner": "0xA53EF9bBec25fdA4b6Da7EF5617565794369A2A5", + "gasPriceOracleOwner": "0x9c3651E0B3CE47A0b17d775077E3d9B712582be0", + +- RELEVANT VALUES + "systemConfigOwner": "0xC72aE5c7cc9a332699305E29F68Be66c73b60542", + "finalSystemOwner": "0xC72aE5c7cc9a332699305E29F68Be66c73b60542", + "superchainConfigGuardian": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A", + "portalGuardian": "0xC72aE5c7cc9a332699305E29F68Be66c73b60542", + "l2OutputOracleProposer": "0x48247032092e7b0ecf5dEF611ad89eaf3fC888Dd", + "l2OutputOracleOwner": "0xDA1F62857EA7f10444725c6c435235243D623540", + "proxyAdmin": "0x027860cA56cF779371461C14c3a483c94e1aA8a0", + "proxyAdminOwner": "0xb0cCdbD6fe09D2199171BE19450aF249250518A0", + "l2OutputOracleChallenger": "0xcA4571b1ecBeC86Ea2E660d242c1c29FcB55Dc72", + +- add the below default values, if you expect different values for your chain reach out to OP Labs support for clarification before proceeding + "useFaultProofs": true, + "faultGameMaxDepth": 73, + "faultGameSplitDepth": 30, + "faultGameWithdrawalDelay": 604800, + "faultGameMaxClockDuration": 302400, + "faultGameClockExtension": 10800, + +- You can update this to the latest absolute prestate hash in the superchain-registry; however, the permissioned Fault Proof System doesn't use this. This comes into play when you upgrade your chain to the permissionless Fault Proof System. + "faultGameAbsolutePrestate": "0x03925193e3e89f87835bbdf3a813f60b2aa818a36bbe71cd5d8fd7e79f52bafe", + +- add the below default values, if you expect different values for your chain, reach out to OP Labs support for clarification before proceeding + "faultGameGenesisBlock": 0, + "faultGameGenesisOutputRoot": "0xdead000000000000000000000000000000000000000000000000000000000000", + "respectedGameType": 1, + "preimageOracleMinProposalSize": 126000, + "preimageOracleChallengePeriod": 86400, + "proofMaturityDelaySeconds": 604800, + "disputeGameFinalityDelaySeconds": 302400, + "enableGovernance": false, + "systemConfigStartBlock": 0, + "requiredProtocolVersion": "0x0000000000000000000000000000000000000003000000010000000000000000", + "recommendedProtocolVersion": "0x0000000000000000000000000000000000000003000000010000000000000000", + +- make sure to add the below if not present already, these values won't matter but the script needs them to be present in the config + "sequencerFeeVaultWithdrawalNetwork": 0, + "baseFeeVaultWithdrawalNetwork": 0, + "l1FeeVaultWithdrawalNetwork": 0, + "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", + "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000" +} +``` + +Make sure that important addresses (like the `ProxyAdmin`, `ProxyAdminOwner`, all L1 addresses, `L2OO`) are actually up to date by cross checking the superchain-registry and on-chain (you can do this easily with [`op-fetcher`](https://github.com/ethereum-optimism/optimism/tree/develop/op-fetcher), especially for the `deployments.json`. + +## Step-by-step upgrade process + +### 1. Create your working directory + +Create a working directory: + +```shell +mkdir upgrade-dir +cd upgrade-dir +``` + +And then create an outputs directory: + +```shell +mkdir outputs +``` + +### 2. Copy config files + +Copy your `deployments.json` and `deploy-config.json` into the working directory + +### 3. Configure and deploy environment + +Create and update an `.env` file with the required information: + +```json +############################################## +# ↓ Required ↓ # +############################################## + +# Can be "mainnet" or "sepolia" +NETWORK= + +# Etherscan API key used to verify contract bytecode +ETHERSCAN_API_KEY= + +# RPC URL for the L1 network that matches $NETWORK +ETH_RPC_URL= + +# Private key used to deploy the new contracts for this upgrade +PRIVATE_KEY= + +# Base fee scalar for the SystemConfig +BASE_FEE_SCALAR= + +# Blob base fee scalar for the SystemConfig +BLOB_BASE_FEE_SCALAR= + +# Check if required files and folders exist +if [ ! -f "./deploy_config.json" ]; then + echo "Error: deploy_config.json not found" +fi +if [ ! -f "./deployments.json" ]; then + echo "Error: deployments.json not found" +fi +if [ ! -d "./outputs" ]; then + echo "Error: outputs folder not found" +fi +``` + +### 4. Run the deployment process + +Run the deployment process with the following command: + +```docker + docker run -t + --env-file .env + -v ./deploy_config.json:/deploy_config.json + -v ./deployments.json:/deployments.json + -v ./outputs:/outputs + kfoplabs/upgrade-v1.3.0-v1.8.0-permissioned /deploy_config.json /deployments.json +``` + +### 5. Verify outputs + +The deployment should output four files: + +* `deploy.log` is a log of the deployment process +* `deployments.json` includes the newly deployed contract addresses +* `bundle.json` is the safe transaction bundle +* `transactions.json` is the summary of the executed deployment transactions +* `standard-addresses.json` is the addresses of `SystemConfigImpl`, `OptimismPortal2Impl`, `L1CrossDomainMessengerImpl`, `L1StandardBridgeImpl`, `L1ERC721BridgeImpl`, and `OptimismMintableERC20FactoryImpl` +* `validation.txt` is used for Tenderly state diff validation. Some info needed for the superchain-ops task might be missing from this file, and will instead be generated during the Tenderly simulation. + +### 6. Verify outputs + +Now you have the calldata that can be executed onchain to perform the L1 contract upgrade. You should simulate this upgrade and make sure the changes are expected. You can reference the validation files of previously executed upgrade tasks in the [superchain-ops repo](https://github.com/ethereum-optimism/superchain-ops/blob/main/tasks/eth/022-holocene-fp-upgrade/NestedSignFromJson.s.sol) to see what the expected changes are. Once you're confident the state changes are expected, you can sign and execute the upgrade. + +## Additional Resources + +* [superchain-ops Repository](https://github.com/ethereum-optimism/superchain-ops) +* [Optimism Monorepo](https://github.com/ethereum-optimism/optimism) diff --git a/docs/public-docs/chain-operators/tutorials/migrating-permissionless.mdx b/docs/public-docs/chain-operators/tutorials/migrating-permissionless.mdx new file mode 100644 index 0000000000000..4715418bfd90e --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/migrating-permissionless.mdx @@ -0,0 +1,373 @@ +--- +title: Migrating to permissionless fault proofs on OP Stack +description: A high-level guide for transitioning from permissioned to permissionless fault proofs on an OP Stack. +--- + +This guide provides a high-level overview for chain operators looking to transition their OP Stack from permissioned to permissionless fault proofs. +It's designed to be accessible for technical decision makers while providing sufficient detail for implementation teams. + +## Overview + +The OP Stack architecture uses Fault Proofs to ensure the validity of withdrawals from L2 to L1. +Transitioning from permissioned to permissionless proofs represents a significant security upgrade, allowing any participant to propose and challenge state output roots. +Permissioned games previously relied on a single trusted validator, this is typically the proposer which is configured in the PermissionedDisputeGame, and is usually the network's only sequencer. + +This migration involves several key components: + +* Configuring security-critical dispute [monitoring services](/chain-operators/tools/chain-monitoring) +* Deploying and configuring smart contracts using [op-deployer](/chain-operators/tools/op-deployer/overview) +* Testing the new system before activation +* Setting the respected game type to permissionless fault proofs, specifically using the [`FaultDisputeGame`](https://github.com/ethereum-optimism/optimism/blob/6e563de4a847c54ddd4e6d2e38bc21e8f6067680/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol) + +## Prerequisites + +Before beginning this transition, your chain should: + +* Be running a standard OP Stack implementation +* It's recommended to use the latest contracts version, minimum required [**v5.0.0**](https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv5.0.0). +* Be operating with the required infrastructure services including [`op-challenger`](/op-stack/fault-proofs/challenger) and [`op-dispute-mon`](/operators/chain-operators/tools/chain-monitoring#dispute-mon). + +## Migration steps + +The process of migrating from permissioned to permissionless fault proofs involves four main phases: configuring off-chain dispute components, deploying the necessary smart contracts, testing the system thoroughly, and finally switching the chain to use permissionless proofs. Each step builds on the previous one to ensure a smooth and secure transition. + +Let's begin with configuring the dispute components that will interact with the new permissionless game. + +## 1. Configure the dispute components + +The `op-challenger` and `op-dispute-mon` services are critical security components that participate in the dispute game process to challenge invalid proposals and monitor active games. + +### Upgrade to the latest `op-challenger` + +Upgrade to the [latest release](https://github.com/ethereum-optimism/optimism/releases), which contains important improvements to simplify the upgrade process. + +You can use the official Docker image for reliability and ease of deployment: + +```bash +# Pull a specific stable release version +docker pull us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger: +``` + +Then run the image, for example: + +```bash +# Run with all required flags +docker run -d --name op-challenger \ + -e OP_CHALLENGER_TRACE_TYPE=permissioned,cannon \ + -e OP_CHALLENGER_PRESTATES_URL= \ + -e OP_CHALLENGER_L1_ETH_RPC= \ + -e OP_CHALLENGER_GAME_FACTORY_ADDRESS= \ + -e OP_CHALLENGER_PRIVATE_KEY= \ + -e OP_CHALLENGER_NETWORK= \ + -v /path/to/local/prestates:/prestates \ + us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:latest + +# Replace all placeholder values with your actual configuration + +``` + +Replace `` with your actual prestates URL. + +If your deployment requires building from source, you can alternatively use: + +```bash +git clone https://github.com/ethereum-optimism/optimism -b op-challenger/v1.3.3 --recurse-submodules +cd optimism +make op-challenger +``` + +### Update network configuration + +Configure `op-challenger` to load your chain configuration. +Even if your chain is not included in the [superchain-registry](/op-stack/protocol/superchain-registry), you can specify a custom configuration: + +```bash +# For chains in the registry +--network + +# For chains not in the registry, provide a path to your rollup configuration +- `rollup.json` - Your rollup configuration +- `genesis-l2.json` - Your L2 genesis file +``` + +### Enable cannon trace type + +Configure `op-challenger` to support both permissioned and permissionless games by setting: + +```bash +--trace-type permissioned,cannon +``` + +Or by setting the environment variable: + +``` +OP_CHALLENGER_TRACE_TYPE=permissioned,cannon +``` + +### Configure prestates access + +Replace the `--cannon-prestate` flag with `--prestates-url`, which points to a source containing all required prestates: + +```bash +--prestates-url +``` + +The URL can use `http`, `https`, or `file` protocols. Each prestate should be named as `.json` or `.bin.gz`. + +### Building required prestates for chains not in the superchain-registry + +You'll need to deploy two new dispute game contracts with the new absolute prestate: + +1. `FaultDisputeGame` +2. `PermissionedDisputeGame` + + + The initial prestate used for permissioned games doesn't include the necessary chain configuration for the Fault Proof System. The assumption is that the chain operator, the single permissioned actor, will not challenge their own games. So the absolute prestate on the initial `PermissionedDisputeGame` will never be used. + When deploying a new chain, you must first deploy the L1 contracts and then generate the chain genesis file and rollup configuration files. + These are inputs to the creation of the absolute prestate and this circular dependency is the reason chains cannot be deployed directly to the permissionless Fault Proof System. + + +For chains not in the superchain-registry, you need to build custom prestates with your chain's configuration: + +The prestate will typically be generated at: + +* `op-program/bin/prestate.json` (for older versions) +* `op-program/bin/prestate.bin.gz` (for intermediate versions) +* `op-program/bin/prestate-mt64.bin.gz` (for chains upgraded to Cannon MT64, starting from [upgrade 14](/notices/upgrade-14#whats-included-in-upgrade-14)) + + + Post-upgrade 14, chains are expected to use `prestate-mt64.bin.gz` due to the Fault Proof VM contract upgrade to `cannon-mt64`. + The older `prestate.bin.gz` will eventually be deprecated but is temporarily retained until all chains complete the upgrade. + + +### Ensure sufficient funds for bonds + +Bonds are required for both permissioned and permissionless games. +However, with permissioned games, you typically don't post claims regularly, making bond requirements less noticeable. +In contrast, the challenger in permissionless games will frequently need to post bonds with each claim it makes. +Therefore, ensure your challenger has sufficient funds available. + +As a general guideline: + +* Maintain a minimum balance of 50 ETH +* Have access to a large pool of ETH for potential attack scenarios +* Implement monitoring to ensure sufficient funds are always available + +### Ensure there is disk space for the challenger to use + +`op-challenger`, particularly in permissionless mode, should have access to disk. About 50GB is enough to cover a couple of `invalid_ games`. Though the storage requirements will increase if the challenger needs to respond to more invalid dispute games. +Nominally, the challenger does not use disk space as long as there aren’t any invalid proposals being made. + +One way to gain more confidence that the op-challenger was configured correctly is to operate the op-challenger in “runner” mode. +Using the same op-challenger configuration, invoke the `op-challenger run-trace -run cannon` subcommand. +This will run op-challenger in a mode where it runs the op-program in a Cannon VM on live L2 blocks to ensure those were configured correctly. +This command runs forever, checking every couple of L2 blocks. But it suffices to let it run until it has completed one loop and kill it. +Wait you see `Successfully verified output root` in the logs before shutting it down. +Any errors indicate a misconfiguration. + +### Set up `op-dispute-mon` + +Ensure `op-dispute-mon` is properly configured by following the [steps](/operators/chain-operators/tools/chain-monitoring#dispute-mon) in the documentation. + +## 2. Deploy and configure smart contracts using OPCM + +This section requires privileged actions by the `ProxyAdminOwner` and the `Guardian` role. + +### Understanding ProxyAdmin Owner and Guardian roles + +This migration requires actions by privileged roles in your system: + +* The **ProxyAdmin Owner** has the authority to upgrade proxy contracts. +* The **Guardian** has emergency powers like pausing withdrawals and changing the respected game type. + +For detailed information about privileged roles and their security implications, refer to the [privileged roles documentation](/op-stack/protocol/privileged-roles). + +### Adding the PermissionlessDisputeGame to a chain + +To enable the permissionless dispute game, you must call the `addGameType()`function on the `OPContractsManager.sol` (OPCM) contract. + +This function adds the permissionless dispute game to the system. + +This method will: + +1. Deploy the `FaultDisputeGame` contract +2. Setup the `DelayedWethProxy` for the new game +3. Reinitialize the `AnchorStateRegistry` to add the new game type. + +See a high‐level implementation from this [docs](/chain-operators/tutorials/dispute-games) or [this superchain-ops template](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/template/AddGameTypeTemplate.sol). + +## 3. Testing off-chain agents + +After you've set the permissionless `FaultDisputeContract` implementations on the `DisputeGameFactory` and before you set the respected game type to it (game type 0), you can test `op-challenger` and `op-dispute-mon` to ensure they are working correctly with permissionless games. + +There are a number of useful `op-challenger` subcommands that can be used for testing, particularly `list-games`, `list-claims` and `create-game`. See the [README](https://github.com/ethereum-optimism/optimism/tree/develop/op-challenger#subcommands) and `op-challenger --help` output for further details. The two tests below are basic sanity tests: + +### Test defending valid proposals + +Create a valid proposal using the permissionless game type `0`: + +1. Ensure the proposal is from a block at or before the `safe` head: + + ```bash + cast block --rpc-url safe + + ``` + +2. Get a valid output root (from op-node): + + ```bash + cast rpc --rpc-url optimism_outputAtBlock \ + $(cast 2h ) | jq -r .outputRoot + + ``` + +3. Create a test game: + + ```bash + ./op-challenger/bin/op-challenger create-game \ + --l1-eth-rpc= \ + --game-factory-address \ + --l2-block-num \ + --output-root \ + + + ``` + +4. Verify: + * `op-challenger` logs a message showing the game is in progress + * `op-challenger` doesn't post a counter claim (as this is a valid proposal) + * `dispute-mon` includes the new game with `status="agree_defender_ahead"` + +### Test countering invalid claims + +Post an invalid counter claim to the valid proposal created above: + +```bash +./op-challenger/bin/op-challenger move \ + --l1-eth-rpc \ + --game-address \ + --attack \ + --parent-index 0 \ + --claim 0x0000000000000000000000000000000000000000000000000000000000000000 \ + + +``` + +Verify that `op-challenger` posts a counter-claim to the invalid claim. You can view claims using: + +```bash +./op-challenger/bin/op-challenger list-claims \ + --l1-eth-rpc \ + --game-address + +``` + +There should be 3 claims in the game after this test. + +## Switch to permissionless proofs + +After completing all previous steps and verifying their successful operation, you need to update the `respectedGameType` in the `AnchorStateRegistry`. This requires execution through the appropriate privileged role (typically the Guardian). + +You have two main options for executing this step: + +### Option 1: Execute using a multisig + +If your privileged role (such as the Guardian) is controlled by a multisig or DAO governance system, use the provided JSON payload (`input.json`): + + + + ```json + { + "chainId": "", + "metadata": { + "name": "Deputy Guardian - Enable Permissionless Dispute Game", + "description": "This task updates the `respectedGameType` in the `AnchorStateRegistry` to `CANNON` (game type 0), enabling users to permissionlessly propose outputs as well as for anyone to participate in the dispute of these proposals. This action requires all in-progress withdrawals to be re-proven against a new `FaultDisputeGame` that was created after this update occurs." + }, + "transactions": [ + { + "metadata": { + "name": "Update `respectedGameType` in the `AnchorStateRegistry`", + "description": "Updates the `respectedGameType` to `CANNON` in the `AnchorStateRegistry`, enabling permissionless proposals and challenging." + }, + "to": "", + "value": "0x0", + "data": "0x7fc485040000000000000000000000000000000000000000000000000000000000000000", + "contractMethod": { + "type": "function", + "name": "setRespectedGameType", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "0" + } + } + ] + } + + ``` + + +* **Submit this JSON payload** through your interface (e.g., Safe transaction builder). +* **Simulate** this transaction using Tenderly before execution to ensure the expected state changes: + * The `respectedGameType` in the `AnchorStateRegistry` should change from `1` (PERMISSIONED) to `0` (CANNON). + +*** + +### Option 2: Direct execution via `cast` (Forge CLI) + +Alternatively, if you're executing directly from a single-privileged wallet or want quicker execution, use the following `cast` commands: + +1. First, encode the transaction calldata using `cast calldata`: + +```bash +CALLDATA=$(cast calldata "setRespectedGameType(uint32)" 0) +``` + +2. Send the transaction: + +```bash +# Execute the transaction +cast send --rpc-url --private-key "$CALLDATA" +``` + +3. After execution, verify the respected game type: + +```bash +cast call --rpc-url "respectedGameType()(uint32)" +# The value should now be 0 (CANNON) + +``` + +### Post-execution configuration: + +After updating, configure `op-proposer` to create proposals using the permissionless `CANNON` game type: + +Via command-line argument: + +```bash +--game-type 0 +``` + +Or via environment variable: + +```bash +OP_PROPOSER_GAME_TYPE=0 +``` + + + This action requires all in-progress withdrawals to be re-proven against a new `FaultDisputeGame` created after this update occurs. + + +## Next steps + +* For more detail on deploying new dispute games with OPCM, [see the docs](/chain-operators/tutorials/dispute-games). +* Deploy new dispute games with OPCM via [this tutorial](/chain-operators/tutorials/dispute-games). +* Generate an absolute prestate using the [absolute prestate guide](/chain-operators/tutorials/absolute-prestate). +* Understand fault proofs in the [Fault proofs explainer](/op-stack/fault-proofs/explainer). diff --git a/docs/public-docs/chain-operators/tutorials/modifying-predeploys.mdx b/docs/public-docs/chain-operators/tutorials/modifying-predeploys.mdx new file mode 100644 index 0000000000000..fa67e0def28f0 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/modifying-predeploys.mdx @@ -0,0 +1,81 @@ +--- +title: Modifying predeployed contracts +description: Learn how to modify predeployed contracts for an OP Stack chain by upgrading the proxy. +--- + + + OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use. + + OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks — be prepared to get your hands dirty and to work without support. + + +OP Stack blockchains have a number of [predeployed contracts](https://github.com/ethereum-optimism/optimism/blob/129032f15b76b0d2a940443a39433de931a97a44/packages/contracts-bedrock/src/constants.ts) that provide important functionality. +Most of those contracts are proxies that can be upgraded using the `proxyAdminOwner` which was configured when the network was initially deployed. + +## Before you begin + +In this tutorial, you learn how to modify predeployed contracts for an OP Stack chain by upgrading the proxy. The predeploys are controlled from a predeploy called [`ProxyAdmin`](https://github.com/ethereum-optimism/optimism/blob/129032f15b76b0d2a940443a39433de931a97a44/packages/contracts-bedrock/contracts/universal/ProxyAdmin.sol), whose address is `0x4200000000000000000000000000000000000018`. +The function to call is [`upgrade(address,address)`](https://github.com/ethereum-optimism/optimism/blob/129032f15b76b0d2a940443a39433de931a97a44/packages/contracts-bedrock/contracts/universal/ProxyAdmin.sol#L205-L229). +The first parameter is the proxy to upgrade, and the second is the address of a new implementation. + +## Modify the legacy `L1BlockNumber` contract + +For example, the legacy `L1BlockNumber` contract is at `0x420...013`. +To disable this function, we'll set the implementation to `0x00...00`. +We do this using the [Foundry](https://book.getfoundry.sh/) command `cast`. + + + + * Set these addresses as variables in your terminal. + + ```sh + L1BLOCKNUM=0x4200000000000000000000000000000000000013 + PROXY_ADMIN=0x4200000000000000000000000000000000000018 + ZERO_ADDR=0x0000000000000000000000000000000000000000 + ``` + + * Set `PRIVKEY` to the private key of your ADMIN address. + + * Set `ETH_RPC_URL`. If you're on the computer that runs the blockchain, use this command. + + ```sh + export ETH_RPC_URL=http://localhost:8545 + ``` + + + + See that when you call the contract you get a block number, and twelve seconds later you get the next one (block time on L1 is twelve seconds). + + ```sh + cast call $L1BLOCKNUM 'number()' | cast --to-dec + sleep 12 && cast call $L1BLOCKNUM 'number()' | cast --to-dec + ``` + + + + ```sh + L1BLOCKNUM_IMPLEMENTATION=`cast call $L1BLOCKNUM "implementation()" | sed 's/000000000000000000000000//'` + echo $L1BLOCKNUM_IMPLEMENTATION + ``` + + + + ```sh + cast send --private-key $PRIVKEY $PROXY_ADMIN "upgrade(address,address)" $L1BLOCKNUM $ZERO_ADDR + ``` + + + + ```sh + cast call $L1BLOCKNUM 'implementation()' + cast call $L1BLOCKNUM 'number()' + ``` + + + + ```sh + cast send --private-key $PRIVKEY $PROXY_ADMIN "upgrade(address,address)" $L1BLOCKNUM $L1BLOCKNUM_IMPLEMENTATION + cast call $L1BLOCKNUM 'number()' | cast --to-dec + ``` + + diff --git a/docs/public-docs/chain-operators/tutorials/rewind-op-geth.mdx b/docs/public-docs/chain-operators/tutorials/rewind-op-geth.mdx new file mode 100644 index 0000000000000..37495f2f4a7e3 --- /dev/null +++ b/docs/public-docs/chain-operators/tutorials/rewind-op-geth.mdx @@ -0,0 +1,56 @@ +--- +title: How to rewind op-geth +description: Learn how to rewind an op-geth node to a previous chain head. +--- + +## Overview + +This tutorial teaches you how to rewind a single op-geth instance to a previous chain head. +This can be helpful to fix a divergent node, recreate archival state, or the safedb from an earlier chain state. + +## Rewind op-geth + +Follow these steps to rewind your op-geth node. + + + + Whatever CL clients are driving the op-geth instance need to be stopped to prevent them from interfering with the rewind later. + This would typically be a locally running `op-node` or `kona-node` application, a Docker container or a pod in a Kubernetes cluster. + + + Make sure you don't have something like ArgoCD syncing the app in question which would just scale the pod back up again. + + + + If you already know the block number/hash you want to rewind to, you can proceed to the next step. + + If you only know the *timestamp* you want to rewind to, you can find the corresponding block with + + ```bash + cast find-block 1741890000 -r + ``` + + Where `` points to your op-geth node’s RPC endpoint, e.g. `http://localhost:8545` if it was a local instance with the default port and `1741890000` is an example unix timestamp. You can use e.g. https://www.epochconverter.com/ to convert from human times to unix timestamps. + + + You need the JWT secret and *open* & *admin* port (default `8545` & `8551`). + Then you can use the `op-wheel` command from the [monorepo](https://github.com/ethereum-optimism/optimism/tree/develop/op-wheel) to issue the rewind. + + ```bash + # save JWT secret to env var + export JWT="YourJWTSecret123" + # assuming default ports 8545 and 8551 and local endpoint + go run ./op-wheel/cmd engine rewind \ + --engine.open http://localhost:8545 \ + --engine http://localhost:8551 \ + --engine.jwt-secret $JWT \ + --log.level DEBUG \ + --set-head --to 8460000 + ``` + + This would rewind the op-geth node to the block with number `8460000`. You should have determined the right block number in step 2. + + + You can now start your CL client again. It will sync from the rewound block head. + + \ No newline at end of file diff --git a/docs/public-docs/components/HomeCard.tsx b/docs/public-docs/components/HomeCard.tsx new file mode 100644 index 0000000000000..d537d38d87e57 --- /dev/null +++ b/docs/public-docs/components/HomeCard.tsx @@ -0,0 +1,134 @@ +import React from 'react'; + +interface CardListItemProps { + number?: string; + title: string; + description?: string; + href: string; + badge?: { + text: string; + variant: 'easy' | 'medium' | 'hard'; + }; +} + +export function CardListItem({ + number, + title, + description, + href, + badge +}: CardListItemProps) { + return ( + + {number && ( +
+ {number} +
+ )} +
+
+

{title}

+ {badge && ( + + {badge.text} + + )} +
+ {description && ( +

{description}

+ )} +
+
+ + + +
+
+ ); +} + +interface CardListProps { + children: React.ReactNode; +} + +export function CardList({ children }: CardListProps) { + return ( +
+ {children} +
+ ); +} + +interface HomeCardProps { + title: string; + content?: React.ReactNode + className?: string; + footerLink?: { + text: string; + href: string; + }; +} + +export function HomeCard({ + title, + content, + className = '', + footerLink +}: HomeCardProps) { + return ( +
+ +
+

{title}

+ {content} +
+ + {footerLink && ( + + )} +
+ ); +} + +interface HomeCardsProps { + children: React.ReactNode; + layout?: 'equal' | 'unequal'; + columns?: string; + gap?: string; +} + +export function HomeCards({ + children, + layout = 'equal', + columns, + gap = '2rem' +}: HomeCardsProps) { + const getGridColumns = () => { + if (columns) return columns; + + switch (layout) { + case 'equal': return '1fr 1fr'; + case 'unequal': return '1fr 2fr'; // 2:1 ratio + default: return '1fr 1fr'; + } + }; + + return ( +
+ {children} +
+ ); +} diff --git a/docs/public-docs/create-l2-rollup-example/.example.env b/docs/public-docs/create-l2-rollup-example/.example.env new file mode 100644 index 0000000000000..34df6ffd479fa --- /dev/null +++ b/docs/public-docs/create-l2-rollup-example/.example.env @@ -0,0 +1,62 @@ +# OP Stack L2 Rollup Environment Configuration +# Copy this file to .env and fill in your actual values +# This uses direnv for automatic environment loading + +# ========================================== +# REQUIRED: L1 Configuration (Sepolia testnet) +# ========================================== +# Option 1: Public endpoint (no API key required - recommended for testing) +L1_RPC_URL="https://ethereum-sepolia-rpc.publicnode.com" +L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com" + +# Option 2: Private endpoint (requires API key from Infura/Alchemy) +# L1_RPC_URL="https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY" +# L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com" + +# ========================================== +# REQUIRED: Private Key for Deployment & Operations +# ========================================== +# IMPORTANT: Never commit this to version control! +# Get this from your MetaMask or other self-custody wallet +# Remove the 0x prefix if present +PRIVATE_KEY="YOUR_PRIVATE_KEY_WITHOUT_0X_PREFIX" + +# ========================================== +# OPTIONAL: Public IP for P2P Networking +# ========================================== +# For local testing, defaults to 127.0.0.1 +# For production/testnet, run `curl ifconfig.me` to get your public IP +P2P_ADVERTISE_IP="127.0.0.1" + +# ========================================== +# OPTIONAL: Custom Configuration +# ========================================== +# Default L2 Chain ID (can be any number between 10000-99999 for testing) +L2_CHAIN_ID="16584" + +# L1 Beacon API URL for op-node (usually same provider as L1_RPC_URL) +L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com" + +# ========================================== +# ADVANCED: Component Environment Variables +# ========================================== +# These follow OP Stack conventions and can be overridden in the main .env file +# The setup script creates service-specific .env files with these OP_* prefixed variables + +# Batcher Environment Variables +OP_BATCHER_POLL_INTERVAL="1s" +OP_BATCHER_SUB_SAFETY_MARGIN="6" +OP_BATCHER_NUM_CONFIRMATIONS="1" +OP_BATCHER_SAFE_ABORT_NONCE_TOO_LOW_COUNT="3" + +# Proposer Environment Variables +OP_PROPOSER_PROPOSAL_INTERVAL="3600s" +OP_PROPOSER_GAME_TYPE="0" +OP_PROPOSER_POLL_INTERVAL="20s" + +# ========================================== +# DEVELOPMENT: Local Network (Alternative to Sepolia) +# ========================================== +# Uncomment these for local development with a local L1 network +# L1_RPC_URL="http://host.docker.internal:8545" +# L1_BEACON_URL="http://host.docker.internal:5052" diff --git a/docs/public-docs/create-l2-rollup-example/.gitignore b/docs/public-docs/create-l2-rollup-example/.gitignore new file mode 100644 index 0000000000000..b82e9ff7a5eae --- /dev/null +++ b/docs/public-docs/create-l2-rollup-example/.gitignore @@ -0,0 +1,45 @@ +# Environment files +.env +.env.local +**/.env + +# Deployment artifacts +deployer/ + +# Generated service directories +sequencer/ +batcher/ +proposer/ +challenger/ + +# Optimism monorepo +optimism/ + +# Downloaded binaries +op-deployer + +# Logs +*.log +logs/ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# Node modules (if any) +node_modules/ + +# Temporary files +tmp/ +temp/ diff --git a/docs/public-docs/create-l2-rollup-example/Makefile b/docs/public-docs/create-l2-rollup-example/Makefile new file mode 100644 index 0000000000000..c746703704171 --- /dev/null +++ b/docs/public-docs/create-l2-rollup-example/Makefile @@ -0,0 +1,89 @@ +# OP Stack L2 Rollup Makefile + +.PHONY: help setup up down logs clean reset + +# Default target +help: ## Show this help message + @echo "OP Stack L2 Rollup Management Commands:" + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +setup: ## Deploy L1 contracts and configure rollup components + mkdir -p deployer + @if [ ! -f op-deployer ]; then \ + echo "❌ Error: op-deployer not found. Run 'make download' first."; \ + exit 1; \ + fi + @if [ ! -f .env ]; then \ + echo "❌ Error: .env file not found. Copy .example.env to .env and configure it."; \ + exit 1; \ + fi + @echo "Running setup script..." + @./scripts/setup-rollup.sh + +up: ## Start all services + docker-compose up -d --wait + @make test-l2 + +down: ## Stop all services + docker-compose down + +logs: ## View logs from all services + docker-compose logs -f + +logs-%: ## View logs from a specific service (e.g., make logs-sequencer) + docker-compose logs -f $* + +status: ## Show status of all services + docker-compose ps + +restart: ## Restart all services + docker-compose restart + +restart-%: ## Restart a specific service (e.g., make restart-batcher) + docker-compose restart $* + +clean: ## Remove all containers and volumes + @# Create minimal env files if they don't exist to avoid docker-compose errors + @mkdir -p batcher proposer challenger sequencer + @echo "# Minimal env for cleanup" > batcher/.env 2>/dev/null || true + @echo "# Minimal env for cleanup" > proposer/.env 2>/dev/null || true + @echo "# Minimal env for cleanup" > challenger/.env 2>/dev/null || true + docker-compose down -v --remove-orphans 2>/dev/null || true + +reset: ## Complete reset - removes all data and redeploys + @echo "This will completely reset your L2 rollup deployment!" + @read -p "Are you sure? (y/N) " confirm && [ "$$confirm" = "y" ] || exit 1 + make clean + rm -rf deployer sequencer batcher proposer challenger + @echo "Reset complete. Run 'make setup' to redeploy." + +test-l2: ## Test L2 connectivity + @echo "Testing L2 RPC connection..." + @curl -s -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + http://localhost:8545 | jq -r '.result' 2>/dev/null && echo "✅ L2 connection successful" || echo "❌ L2 connection failed" + +test-l1: ## Test L1 connectivity + @echo "Testing L1 RPC connection..." + @if [ -f .env ]; then \ + set -a; source .env; set +a; \ + fi; \ + curl -s -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + "$$L1_RPC_URL" | jq -r '.result' 2>/dev/null && echo "✅ L1 connection successful" || echo "❌ L1 connection failed" + +deps: ## Install development dependencies with mise (optional) + mise install + +download: ## Download op-deployer binary + ./scripts/download-op-deployer.sh + +init: ## Initialize the project + ./scripts/download-op-deployer.sh + @if [ ! -f .env ]; then \ + cp .example.env .env; \ + echo "Please edit .env with your actual configuration values"; \ + else \ + echo ".env already exists, skipping copy"; \ + fi + @echo "Then run: make setup" diff --git a/docs/public-docs/create-l2-rollup-example/README.md b/docs/public-docs/create-l2-rollup-example/README.md new file mode 100644 index 0000000000000..330d079924279 --- /dev/null +++ b/docs/public-docs/create-l2-rollup-example/README.md @@ -0,0 +1,270 @@ +# Create L2 Rollup - Code Example + +This directory contains the complete working implementation that accompanies the [Create L2 Rollup tutorial](/chain-operators/tutorials/create-l2-rollup/create-l2-rollup). It provides automated deployment of an OP Stack L2 rollup testnet using official published Docker images. + +## Overview + +This implementation deploys a fully functional OP Stack L2 rollup testnet, including: + +- **L1 Smart Contracts** deployed on Sepolia testnet (via op-deployer) +- **Execution Client** (op-geth) processing transactions +- **Consensus Client** (op-node) managing rollup consensus +- **Batcher** (op-batcher) publishing transaction data to L1 +- **Proposer** (op-proposer) submitting state root proposals +- **Challenger** (op-challenger) monitoring for disputes + +## Prerequisites + +### Software Dependencies + +| Dependency | Version | Install Command | Purpose | +|------------|---------|-----------------|---------| +| [Docker](https://docs.docker.com/get-docker/) | ^24 | Follow Docker installation guide | Container runtime for OP Stack services | +| [Docker Compose](https://docs.docker.com/compose/install/) | latest | Usually included with Docker Desktop | Multi-container orchestration | +| [jq](https://github.com/jqlang/jq) | latest | `brew install jq` (macOS) / `apt install jq` (Ubuntu) | JSON processing for deployment data | +| [git](https://git-scm.com/) | latest | Usually pre-installed | Cloning repositories for prestate generation | + +### Recommended: Tool Management + +For the best experience with correct tool versions, we recommend installing [mise](https://mise.jdx.dev/): + +```bash +# Install mise (manages all tool versions automatically) +curl https://mise.jdx.dev/install.sh | bash + +# Install all required tools with correct versions +cd docs/create-l2-rollup-example +mise install +``` + +**Why mise?** It automatically handles tool installation and version management, preventing compatibility issues. + +### Network Access + +- **Sepolia RPC URL**: Get from [Infura](https://infura.io), [Alchemy](https://alchemy.com), or another provider +- **Sepolia ETH**: At least 2-3 ETH for contract deployment and operations +- **Public IP**: For P2P networking (use `curl ifconfig.me` to find your public IP) + +## Quick Start + +1. **Navigate to this code directory**: + ```bash + cd docs/create-l2-rollup-example + ``` + +2. **Configure environment variables**: + ```bash + cp .example.env .env + # Edit .env with your actual values (L1_RPC_URL, PRIVATE_KEY, L2_CHAIN_ID) + ``` + +3. **Download op-deployer**: + ```bash + make init # Download op-deployer + ``` + +4. **Deploy and start everything**: + ```bash + make setup # Deploy contracts and configure all services + make up # Start all services + ``` + +5. **Verify deployment**: + ```bash + make status # Check service health + make test-l1 # Verify L1 connectivity + make test-l2 # Verify L2 functionality + + # Or manually check: + docker-compose ps + docker-compose logs -f op-node + ``` + +## Environment Configuration + +Copy `.example.env` to `.env` and configure the following variables: + +```bash +# L1 Configuration (Sepolia testnet) +# Option 1: Public endpoint (no API key required) +L1_RPC_URL="https://ethereum-sepolia-rpc.publicnode.com" +L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com" + +# Option 2: Private endpoint (requires API key) +# L1_RPC_URL="https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY" +# L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com" + +# Private key for deployment and operations +# IMPORTANT: Never commit this to version control +PRIVATE_KEY="YOUR_PRIVATE_KEY_WITHOUT_0x_PREFIX" + +# Optional: Public IP for P2P networking (defaults to 127.0.0.1 for local testing) +P2P_ADVERTISE_IP="127.0.0.1" + +# Optional: Custom L2 Chain ID (default: 16584) +L2_CHAIN_ID="16584" +``` + +The `.env` file will be automatically loaded by Docker Compose. + +## Manual Setup (Alternative) + +For detailed manual setup instructions, see the [Create L2 Rollup tutorial](/chain-operators/tutorials/create-l2-rollup/create-l2-rollup). The tutorial provides step-by-step guidance for setting up each component individually if you prefer not to use the automated approach. + +## Directory Structure + +``` +create-l2-rollup-example/ +├── .example.env # Environment variables template +├── docker-compose.yml # Service orchestration +├── Makefile # Automation commands +├── scripts/ +│ ├── setup-rollup.sh # Automated deployment script +│ └── download-op-deployer.sh # op-deployer downloader +└── README.md # This file +``` + +**Generated directories** (created during deployment): + +``` +deployer/ # op-deployer configuration and outputs +├── .deployer/ # Deployment artifacts (genesis.json, rollup.json, etc.) +├── addresses/ # Generated wallet addresses +└── .env # Environment variables +batcher/ # op-batcher configuration +└── .env # Environment variables +proposer/ # op-proposer configuration +└── .env # Environment variables +challenger/ # op-challenger configuration +├── .env # Challenger-specific environment variables +└── data/ # Challenger data directory +sequencer/ # op-sequencer configuration +├── .env # op-sequencer environment variables +├── genesis.json # op-geth genesis file +├── jwt.txt # JWT secret for auth RPC +├── rollup.json # op-node rollup configuration +└── op-geth-data/ # op-geth data directory +``` + +## Service Ports + +| Service | Port | Description | +|---------|------|-------------| +| op-geth | 8545 | HTTP RPC endpoint | +| op-geth | 8546 | WebSocket RPC endpoint | +| op-geth | 8551 | Auth RPC for op-node | +| op-node | 8547 | op-node RPC endpoint | +| op-node | 9222 | P2P networking | + +## Monitoring and Logs + +```bash +# View all service logs +make logs + +# View specific service logs +docker-compose logs -f op-node +docker-compose logs -f op-geth + +# Check service health +make status + +# Restart all services +make restart + +# Restart a specific service +docker-compose restart op-batcher +``` + +## P2P Networking Configuration + +By default, this devnet disables P2P networking entirely to avoid validation warnings when running locally. The `--p2p.disable` flag is set in `docker-compose.yml` (line 26). + + +**For production deployments**, you must **remove** the `--p2p.disable` flag and configure P2P networking properly. P2P is essential for: +- Distributing newly sequenced blocks to other nodes in the network +- Enabling peer nodes to sync and validate your chain +- Supporting a decentralized network of nodes +- Network resilience and redundancy + + +### When to Enable P2P + +| Environment | P2P Networking | Reason | +|-------------|---------------|---------| +| **Local devnet** | Disabled (default) | Prevents P2P warnings when testing solo without peers | +| **Private testnet** | Disabled | No other nodes to connect with | +| **Public testnet** | Enabled | Other nodes need to receive blocks and sync | +| **Production mainnet** | Enabled | Required for network operation | + +### Enabling P2P for Production + +1. Open `docker-compose.yml` +2. Remove `--p2p.disable # For local devnet only...` +3. Add back the P2P configuration flags: + ```yaml + --p2p.listen.ip=0.0.0.0 + --p2p.listen.tcp=9222 + --p2p.listen.udp=9222 + --p2p.advertise.ip=${P2P_ADVERTISE_IP} + --p2p.advertise.tcp=9222 + --p2p.advertise.udp=9222 + --p2p.sequencer.key=${PRIVATE_KEY} + ``` +4. Ensure your P2P networking environment is properly configured: + - Set `P2P_ADVERTISE_IP` in `.env` to your public IP address (not 127.0.0.1) + - Ensure port 9222 (both TCP and UDP) is accessible from the internet + - Configure proper firewall rules to allow P2P traffic + - Consider setting up bootnodes for better peer discovery + +```bash +# Example: Quick enable P2P for testing +sed -i '' '/--p2p.disable/d' docker-compose.yml +# Then add back P2P flags manually in docker-compose.yml +docker-compose restart op-node +``` + +## Troubleshooting + +### Common Issues + +1. **Port conflicts**: Ensure ports 8545-8551 and 9222 are available +2. **Insufficient ETH**: Make sure your deployment wallet has enough Sepolia ETH +3. **Network timeouts**: Check your L1 RPC URL and network connectivity +4. **Docker issues**: Ensure Docker daemon is running and you have sufficient resources + +### Reset Deployment + +To reset and redeploy: + +```bash +# Stop all services and clean up +make clean + +# Re-run setup +make setup +make up +``` + +## Security Notes + +- **Never commit private keys** to version control +- **Use hardware security modules (HSMs)** for production deployments +- **Monitor gas costs** on Sepolia testnet +- **Backup wallet addresses** and deployment artifacts + +## About This Code + +This code example accompanies the [Create L2 Rollup tutorial](/chain-operators/tutorials/create-l2-rollup/create-l2-rollup) in the Optimism documentation. It provides a complete, working implementation that demonstrates the concepts covered in the tutorial. + +## Contributing + +This code is part of the Optimism documentation. For issues or contributions: + +- **Documentation issues**: Report on the main [Optimism repository](https://github.com/ethereum-optimism/optimism) +- **Code improvements**: Submit pull requests to the Optimism monorepo +- **Tutorial feedback**: Use the documentation feedback mechanisms + +## License + +This code is provided as-is for educational and testing purposes. See the Optimism monorepo for licensing information. diff --git a/docs/public-docs/create-l2-rollup-example/docker-compose.yml b/docs/public-docs/create-l2-rollup-example/docker-compose.yml new file mode 100644 index 0000000000000..fbd8c14c96a3b --- /dev/null +++ b/docs/public-docs/create-l2-rollup-example/docker-compose.yml @@ -0,0 +1,176 @@ +services: + # Sequencer (op-geth + op-node) + op-node: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.13.5 + container_name: op-node + volumes: + - ./sequencer:/workspace + - op_geth_data:/workspace/op-geth-data + working_dir: /workspace + env_file: + - .env + ports: + - "8547:8547" + - "9222:9222" + command: > + op-node + --l1=${L1_RPC_URL} + --l1.beacon=${L1_BEACON_URL} + --l2=http://op-geth:8551 + --l2.jwt-secret=/workspace/jwt.txt + --rollup.config=/workspace/rollup.json + --sequencer.enabled=true + --sequencer.stopped=false + --sequencer.max-safe-lag=3600 + --verifier.l1-confs=4 + --p2p.disable # For local devnet only. Remove this flag for production deployments. + --rpc.addr=0.0.0.0 + --rpc.port=8547 + --rpc.enable-admin + --log.level=info + --log.format=json + depends_on: + - op-geth + healthcheck: + test: ["CMD", "wget", "--post-data={\"jsonrpc\":\"2.0\",\"method\":\"optimism_syncStatus\",\"params\":[],\"id\":1}", "--header=Content-Type: application/json", "--quiet", "--tries=1", "--timeout=10", "--output-document=-", "http://localhost:8547"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 10s + + op-geth: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101511.1 + container_name: op-geth + volumes: + - ./sequencer:/workspace + - op_geth_data:/workspace/op-geth-data + working_dir: /workspace + env_file: + - .env + ports: + - "8545:8545" + - "8546:8546" + - "8551:8551" + entrypoint: ["/bin/sh", "-c"] + command: + - | + set -e + if [ ! -d /workspace/op-geth-data/geth/chaindata ]; then + echo "Initializing geth datadir..." + geth init --datadir=/workspace/op-geth-data --state.scheme=hash /workspace/genesis.json + fi + echo "Starting geth..." + exec geth --datadir=/workspace/op-geth-data --http --http.addr=0.0.0.0 --http.port=8545 --ws --ws.addr=0.0.0.0 --ws.port=8546 --authrpc.addr=0.0.0.0 --authrpc.port=8551 --authrpc.jwtsecret=/workspace/jwt.txt --syncmode=full --gcmode=archive --rollup.disabletxpoolgossip=true --http.vhosts=* --http.corsdomain=* --http.api=eth,net,web3,debug,txpool,admin --ws.origins=* --ws.api=eth,net,web3,debug,txpool,admin --authrpc.vhosts=* + + + # Batcher + batcher: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:v1.14.0 + container_name: op-batcher + volumes: + - ./batcher:/workspace + working_dir: /workspace + env_file: + - ./batcher/.env + - .env + environment: + - OP_BATCHER_L1_ETH_RPC=${L1_RPC_URL} + - OP_BATCHER_PRIVATE_KEY=${PRIVATE_KEY} + command: > + op-batcher + --rpc.addr=0.0.0.0 + --rpc.port=8548 + --rpc.enable-admin + --max-channel-duration=1 + --data-availability-type=calldata + --resubmission-timeout=30s + --log.level=info + --log.format=json + depends_on: + op-node: + condition: service_healthy + + # Proposer + proposer: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:v1.10.0 + container_name: op-proposer + volumes: + - ./proposer:/workspace + working_dir: /workspace + env_file: + - ./proposer/.env + - .env + environment: + - OP_PROPOSER_L1_ETH_RPC=${L1_RPC_URL} + - OP_PROPOSER_PRIVATE_KEY=${PRIVATE_KEY} + command: > + op-proposer + --rpc.port=8560 + --rollup-rpc=http://op-node:8547 + --allow-non-finalized=true + --wait-node-sync=true + --log.level=info + --log.format=json + depends_on: + op-node: + condition: service_healthy + + # Challenger + challenger: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.5.1 + container_name: op-challenger + volumes: + - ./challenger:/workspace + working_dir: /workspace + env_file: + - ./challenger/.env + - .env + environment: + - OP_CHALLENGER_L1_ETH_RPC=${L1_RPC_URL} + - OP_CHALLENGER_L1_BEACON=${L1_BEACON_URL} + - OP_CHALLENGER_PRIVATE_KEY=${PRIVATE_KEY} + command: > + op-challenger run-trace + --trace-type=cannon + --cannon-l2-genesis=/workspace/genesis.json + --cannon-rollup-config=/workspace/rollup.json + --l2-eth-rpc=http://op-geth:8545 + --rollup-rpc=http://op-node:8547 + --datadir=/workspace/data + --log.level=info + --log.format=json + depends_on: + op-node: + condition: service_healthy + + # Dispute Monitor + dispute-mon: + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-dispute-mon:v1.4.2-rc.1 + container_name: dispute-mon + volumes: + - ./dispute-mon:/workspace + working_dir: /workspace + ports: + - "7300:7300" + env_file: + - ./dispute-mon/.env + - .env + environment: + - OP_DISPUTE_MON_L1_ETH_RPC=${L1_RPC_URL} + - OP_DISPUTE_MON_ROLLUP_RPC=${ROLLUP_RPC} + - OP_DISPUTE_MON_HONEST_ACTORS=${PROPOSER_ADDRESS},${CHALLENGER_ADDRESS} + - OP_DISPUTE_MON_GAME_FACTORY_ADDRESS=${GAME_FACTORY_ADDRESS} + - OP_DISPUTE_MON_MONITOR_INTERVAL=10s + - OP_DISPUTE_MON_NETWORK=op-sepolia + - OP_DISPUTE_MON_LOG_LEVEL=debug + - OP_DISPUTE_MON_LOG_FORMAT=logfmt + - OP_DISPUTE_MON_METRICS_ADDR=0.0.0.0 + - OP_DISPUTE_MON_METRICS_ENABLED=true + - OP_DISPUTE_MON_METRICS_PORT=7300 + depends_on: + op-node: + condition: service_healthy + restart: unless-stopped + +volumes: + op_geth_data: diff --git a/docs/public-docs/create-l2-rollup-example/mise.toml b/docs/public-docs/create-l2-rollup-example/mise.toml new file mode 100644 index 0000000000000..d22bdca6eb131 --- /dev/null +++ b/docs/public-docs/create-l2-rollup-example/mise.toml @@ -0,0 +1,8 @@ +[tools] +"jq" = "latest" +"git" = "latest" +"docker" = "latest" +"foundry" = "latest" + +[settings] +experimental = true diff --git a/docs/public-docs/create-l2-rollup-example/scripts/download-op-deployer.sh b/docs/public-docs/create-l2-rollup-example/scripts/download-op-deployer.sh new file mode 100755 index 0000000000000..c49a7540c9da9 --- /dev/null +++ b/docs/public-docs/create-l2-rollup-example/scripts/download-op-deployer.sh @@ -0,0 +1,251 @@ +#!/bin/bash + +# Download op-deployer pre-built binary +# Based on the tutorial instructions + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Detect platform +detect_platform() { + case "$(uname -s)" in + Darwin) + case "$(uname -m)" in + arm64) + echo "darwin-arm64" + ;; + x86_64) + echo "darwin-amd64" + ;; + *) + log_error "Unsupported macOS architecture: $(uname -m)" + exit 1 + ;; + esac + ;; + Linux) + echo "linux-amd64" + ;; + *) + log_error "Unsupported platform: $(uname -s)" + exit 1 + ;; + esac +} + +# Download op-deployer +download_op_deployer() { + # Detect host platform + local os + local arch + case "$(uname -s)" in + Darwin) + os="darwin" + ;; + Linux) + os="linux" + ;; + *) + log_error "Unsupported OS: $(uname -s)" + exit 1 + ;; + esac + + case "$(uname -m)" in + aarch64|arm64) + arch="arm64" + ;; + x86_64|amd64) + arch="amd64" + ;; + *) + log_error "Unsupported architecture: $(uname -m)" + exit 1 + ;; + esac + local platform="$os-$arch" + local releases_url="https://api.github.com/repos/ethereum-optimism/optimism/releases" + + log_info "Detecting platform: $platform" + + # Find the latest op-deployer release + log_info "Finding latest op-deployer release..." + + # Get all releases and find the latest one with op-deployer assets + local latest_deployer_release + latest_deployer_release=$(curl -s "$releases_url?per_page=50" | jq -r '.[] | select(.tag_name | startswith("op-deployer/")) | .tag_name' | sort -V | tail -1) + + if [ -z "$latest_deployer_release" ]; then + log_error "Could not find any op-deployer releases" + exit 1 + fi + + local tag_name="$latest_deployer_release" + log_info "Found latest op-deployer release: $tag_name" + + # Get the release info + local release_info + release_info=$(curl -s "$releases_url/tags/$tag_name") + + if [ -z "$release_info" ] || [ "$release_info" = "null" ] || echo "$release_info" | jq -e '.message' >/dev/null 2>&1; then + log_error "Could not fetch release information for $tag_name" + exit 1 + fi + + local has_deployer_assets + has_deployer_assets=$(echo "$release_info" | jq -r '.assets[] | select(.name | contains("op-deployer")) | .name' | wc -l) + + if [ "$has_deployer_assets" -eq 0 ]; then + log_error "Release $tag_name does not have op-deployer assets" + exit 1 + fi + + log_info "Release $tag_name has op-deployer assets" + + # Check if this release has op-deployer assets for our platform + local asset_name + asset_name=$(echo "$release_info" | jq -r ".assets[] | select(.name | contains(\"op-deployer\") and contains(\"$platform\")) | .name") + + # If arm64 is not available, fall back to amd64 (Rosetta compatibility) + if [ -z "$asset_name" ] && [ "$platform" = "linux-arm64" ]; then + log_info "linux-arm64 not available, trying linux-amd64 (Rosetta compatible)" + platform="linux-amd64" + asset_name=$(echo "$release_info" | jq -r ".assets[] | select(.name | contains(\"op-deployer\") and contains(\"$platform\")) | .name") + fi + + if [ -z "$asset_name" ]; then + log_error "Could not find op-deployer asset for platform $platform in release $tag_name" + log_info "Available op-deployer assets in this release:" + echo "$release_info" | jq -r '.assets[] | select(.name | contains("op-deployer")) | .name' + log_info "Please check: https://github.com/ethereum-optimism/optimism/releases/tag/$tag_name" + exit 1 + fi + + local download_url="https://github.com/ethereum-optimism/optimism/releases/download/$tag_name/$asset_name" + + log_info "Downloading op-deployer $tag_name for $platform..." + log_info "From: $download_url" + + # Download the asset + if ! curl -L -o "op-deployer.tar.gz" "$download_url"; then + log_error "Failed to download op-deployer" + exit 1 + fi + + # Extract (assuming it's a tar.gz) + log_info "Extracting op-deployer..." + tar -xzf op-deployer.tar.gz + + # Find the binary (it might be in a subdirectory) + local binary_path + + # Look for the extracted directory first + local extracted_dir + extracted_dir=$(find . -name "op-deployer-*" -type d | head -1) + + if [ -n "$extracted_dir" ] && [ -f "$extracted_dir/op-deployer" ]; then + binary_path="$extracted_dir/op-deployer" + elif [ -f "op-deployer" ]; then + binary_path="op-deployer" + elif [ -f "op-deployer-$platform" ]; then + binary_path="op-deployer-$platform" + else + # Look for it anywhere (macOS compatible) + binary_path=$(find . -name "op-deployer*" -type f -perm +111 | head -1) + fi + + if [ -z "$binary_path" ]; then + log_error "Could not find op-deployer binary in extracted files" + ls -la + exit 1 + fi + + # Make executable and move to current directory + chmod +x "$binary_path" + mv "$binary_path" ./op-deployer + + # Cleanup + rm -f op-deployer.tar.gz + rm -rf "${binary_path%/*}" 2>/dev/null || true + + # Test the binary (only if we're downloading for the same platform) + local current_platform=$(detect_platform) + if [ "$platform" = "$current_platform" ]; then + if ! ./op-deployer --version >/dev/null 2>&1; then + log_error "Downloaded op-deployer binary appears to be invalid" + exit 1 + fi + log_success "op-deployer downloaded and ready: $(./op-deployer --version)" + else + # Cross-platform download - just verify the file exists and is executable + if [ ! -x "./op-deployer" ]; then + log_error "Downloaded op-deployer binary is not executable" + exit 1 + fi + log_success "op-deployer downloaded for $platform platform (cannot test on $current_platform host)" + fi +} + +# Alternative: Manual download instructions +show_manual_instructions() { + log_warning "Automatic download failed. Please download manually:" + echo "" + echo "1. Go to: https://github.com/ethereum-optimism/optimism/releases" + echo "2. Find the latest release that includes op-deployer" + echo "3. Download the appropriate binary for your system:" + echo " - Linux: op-deployer-linux-amd64" + echo " - macOS Intel: op-deployer-darwin-amd64" + echo " - macOS Apple Silicon: op-deployer-darwin-arm64" + echo "4. Extract and place the binary as './op-deployer'" + echo "5. Run: chmod +x ./op-deployer" + echo "" + exit 1 +} + +# Main execution +main() { + log_info "Downloading op-deployer..." + + if ! command -v curl &> /dev/null; then + log_error "curl is required but not installed" + exit 1 + fi + + if ! command -v jq &> /dev/null; then + log_error "jq is required but not installed" + exit 1 + fi + + # Try automatic download + if download_op_deployer; then + log_success "op-deployer setup complete!" + exit 0 + else + show_manual_instructions + fi +} + +# Run main function +main "$@" diff --git a/docs/public-docs/create-l2-rollup-example/scripts/setup-rollup.sh b/docs/public-docs/create-l2-rollup-example/scripts/setup-rollup.sh new file mode 100755 index 0000000000000..ab84cb7c4372a --- /dev/null +++ b/docs/public-docs/create-l2-rollup-example/scripts/setup-rollup.sh @@ -0,0 +1,538 @@ +#!/bin/bash + +# OP Stack L2 Rollup Setup Script +# This script automates the deployment of a complete L2 rollup testnet + +set -e + +# Check if running in Docker +if [ -f "/.dockerenv" ]; then + log_error "This script should not be run inside Docker. Run it on the host system." + exit 1 +fi + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +L1_CHAIN_ID=11155111 # Sepolia +L2_CHAIN_ID_DECIMAL=${L2_CHAIN_ID:-16584} # Default test chain ID (decimal) +L2_CHAIN_ID=$(printf "0x%064x" "$L2_CHAIN_ID_DECIMAL") # Convert to full 64-char hex format for TOML +P2P_ADVERTISE_IP=${P2P_ADVERTISE_IP:-127.0.0.1} # Default to localhost for local testing +WORKSPACE_DIR="$(pwd)" +ROLLUP_DIR="$WORKSPACE_DIR" +DEPLOYER_DIR="$ROLLUP_DIR/deployer" +SEQUENCER_DIR="$ROLLUP_DIR/sequencer" +BATCHER_DIR="$ROLLUP_DIR/batcher" +PROPOSER_DIR="$ROLLUP_DIR/proposer" +CHALLENGER_DIR="$ROLLUP_DIR/challenger" +DISPUTE_MON_DIR="$ROLLUP_DIR/dispute-mon" + +# Logging functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check prerequisites +check_prerequisites() { + log_info "Checking prerequisites..." + + if ! command -v op-deployer &> /dev/null; then + log_error "op-deployer not found. Please ensure it's downloaded" + log_info "Run: make download" + exit 1 + fi + + log_success "Prerequisites check passed" +} + +# Generate wallet addresses +generate_addresses() { + log_info "Generating wallet addresses..." + + mkdir -p "$DEPLOYER_DIR/addresses" + log_info "Created addresses directory: $DEPLOYER_DIR/addresses" + + cd "$DEPLOYER_DIR/addresses" + log_info "Changed to directory: $(pwd)" + + # Generate addresses for different roles using openssl + for role in admin base_fee_vault_recipient l1_fee_vault_recipient sequencer_fee_vault_recipient system_config unsafe_block_signer operator_fee_vault_recipient chain_fees_fee_recipient batcher proposer challenger ; do + # Generate a random 32-byte private key, ensuring it's not zero + private_key="" + while [ -z "$private_key" ] || [ "$private_key" = "0000000000000000000000000000000000000000000000000000000000000000" ]; do + private_key=$(openssl rand -hex 32) + done + + # For this demo, we'll create a fake but valid Ethereum address + # In a real scenario, you'd derive the actual Ethereum address from the private key + # Create a 40-character hex address (20 bytes) + address="0x$(echo "$private_key" | head -c 40)" + echo "$address" > "${role}_address.txt" + log_info "Created wallet for $role: $address" + done + + log_success "Wallet addresses generated" + log_info "Addresses generated successfully, continuing to init..." +} + +# Initialize op-deployer +init_deployer() { + log_info "Initializing op-deployer..." + + cd "$DEPLOYER_DIR" + + # Create .env file + cat > .env << EOF +L1_RPC_URL=$L1_RPC_URL +PRIVATE_KEY=$PRIVATE_KEY +EOF + + # Remove any existing .deployer directory for clean state + rm -rf .deployer + + # Initialize intent + op-deployer init \ + --l1-chain-id $L1_CHAIN_ID \ + --l2-chain-ids "$L2_CHAIN_ID_DECIMAL" \ + --workdir .deployer \ + --intent-type standard-overrides + + log_success "op-deployer initialized" +} + +# Update intent configuration +update_intent() { + log_info "Updating intent configuration..." + + # Read generated addresses + BASE_FEE_VAULT_ADDR=$(cat addresses/base_fee_vault_recipient_address.txt) + L1_FEE_VAULT_ADDR=$(cat addresses/l1_fee_vault_recipient_address.txt) + SEQUENCER_FEE_VAULT_ADDR=$(cat addresses/sequencer_fee_vault_recipient_address.txt) + SYSTEM_CONFIG_ADDR=$(cat addresses/system_config_address.txt) + UNSAFE_BLOCK_SIGNER_ADDR=$(cat addresses/unsafe_block_signer_address.txt) + BATCHER_ADDR=$(cat addresses/batcher_address.txt) + PROPOSER_ADDR=$(cat addresses/proposer_address.txt) + CHALLENGER_ADDR=$(cat addresses/challenger_address.txt) + OPERATOR_FEE_VAULT_ADDR=$(cat addresses/operator_fee_vault_recipient_address.txt) + CHAIN_FEES_RECIPIENT_ADDR=$(cat addresses/chain_fees_fee_recipient_address.txt) + + # Keep the default contract locators and opcmAddress from op-deployer init + + # Update only the chain-specific fields in the existing intent.toml + L2_CHAIN_ID_HEX=$(printf "0x%064x" "$L2_CHAIN_ID") + sed -i.bak "s|id = .*|id = \"$L2_CHAIN_ID_HEX\"|" .deployer/intent.toml + sed -i.bak "s|baseFeeVaultRecipient = .*|baseFeeVaultRecipient = \"$BASE_FEE_VAULT_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|l1FeeVaultRecipient = .*|l1FeeVaultRecipient = \"$L1_FEE_VAULT_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|sequencerFeeVaultRecipient = .*|sequencerFeeVaultRecipient = \"$SEQUENCER_FEE_VAULT_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|systemConfigOwner = .*|systemConfigOwner = \"$SYSTEM_CONFIG_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|unsafeBlockSigner = .*|unsafeBlockSigner = \"$UNSAFE_BLOCK_SIGNER_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|batcher = .*|batcher = \"$BATCHER_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|proposer = .*|proposer = \"$PROPOSER_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|challenger = .*|challenger = \"$CHALLENGER_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|fundDevAccounts = .*|fundDevAccounts = true|" .deployer/intent.toml + sed -i.bak "s|operatorFeeVaultRecipient = .*|operatorFeeVaultRecipient = \"$OPERATOR_FEE_VAULT_ADDR\"|" .deployer/intent.toml + sed -i.bak "s|chainFeesRecipient = .*|chainFeesRecipient = \"$CHAIN_FEES_RECIPIENT_ADDR\"|" .deployer/intent.toml + log_success "Intent configuration updated" +} + +# Deploy L1 contracts +deploy_contracts() { + log_info "Deploying L1 contracts..." + + cd "$DEPLOYER_DIR" + + op-deployer apply \ + --workdir .deployer \ + --l1-rpc-url "$L1_RPC_URL" \ + --private-key "$PRIVATE_KEY" + + log_success "L1 contracts deployed" +} + +# Generate chain configuration +generate_config() { + log_info "Generating chain configuration..." + + cd "$DEPLOYER_DIR" + + op-deployer inspect genesis --workdir .deployer "$L2_CHAIN_ID" > .deployer/genesis.json + op-deployer inspect rollup --workdir .deployer "$L2_CHAIN_ID" > .deployer/rollup.json + + log_success "Chain configuration generated" +} + +# Setup sequencer +setup_sequencer() { + log_info "Setting up sequencer..." + + mkdir -p "$SEQUENCER_DIR" + cd "$SEQUENCER_DIR" + + # Copy configuration files + cp "$DEPLOYER_DIR/.deployer/genesis.json" . + cp "$DEPLOYER_DIR/.deployer/rollup.json" . + + # Generate JWT secret + openssl rand -hex 32 > jwt.txt + chmod 600 jwt.txt + + # Create .env file + cat > .env << EOF +L1_RPC_URL=$L1_RPC_URL +L1_BEACON_URL=$L1_BEACON_URL +PRIVATE_KEY=$PRIVATE_KEY +P2P_ADVERTISE_IP=$P2P_ADVERTISE_IP +L2_CHAIN_ID=$L2_CHAIN_ID +EOF + + log_success "Sequencer setup complete" +} + +# Setup batcher +setup_batcher() { + log_info "Setting up batcher..." + + mkdir -p "$BATCHER_DIR" + cd "$BATCHER_DIR" + + # Copy state file + cp "$DEPLOYER_DIR/.deployer/state.json" . + INBOX_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].SystemConfigProxy') + + # Create .env file with OP_BATCHER prefixed variables + cat > .env << EOF +OP_BATCHER_L2_ETH_RPC=http://op-geth:8545 +OP_BATCHER_ROLLUP_RPC=http://op-node:8547 +OP_BATCHER_PRIVATE_KEY=$PRIVATE_KEY +OP_BATCHER_POLL_INTERVAL=1s +OP_BATCHER_SUB_SAFETY_MARGIN=6 +OP_BATCHER_NUM_CONFIRMATIONS=1 +OP_BATCHER_SAFE_ABORT_NONCE_TOO_LOW_COUNT=3 +OP_BATCHER_INBOX_ADDRESS=$INBOX_ADDRESS +EOF + + log_success "Batcher setup complete" +} + +# Setup proposer +setup_proposer() { + log_info "Setting up proposer..." + + mkdir -p "$PROPOSER_DIR" + cd "$PROPOSER_DIR" + + # Copy state file + cp "$DEPLOYER_DIR/.deployer/state.json" . + + # Extract dispute game factory address + GAME_FACTORY_ADDR=$(cat state.json | jq -r '.opChainDeployments[0].DisputeGameFactoryProxy') + + # Create .env file with OP_PROPOSER prefixed variables + cat > .env << EOF +OP_PROPOSER_GAME_FACTORY_ADDRESS=$GAME_FACTORY_ADDR +OP_PROPOSER_PRIVATE_KEY=$PRIVATE_KEY +OP_PROPOSER_POLL_INTERVAL=20s +OP_PROPOSER_GAME_TYPE=0 +OP_PROPOSER_PROPOSAL_INTERVAL=3600s +EOF + + log_success "Proposer setup complete" +} + +# Setup challenger +setup_challenger() { + log_info "Setting up challenger..." + + mkdir -p "$CHALLENGER_DIR" + cd "$CHALLENGER_DIR" + + # Copy configuration files + cp "$DEPLOYER_DIR/.deployer/genesis.json" . + cp "$DEPLOYER_DIR/.deployer/rollup.json" . + + # Extract dispute game factory address + GAME_FACTORY_ADDR=$(cat ../deployer/.deployer/state.json | jq -r '.opChainDeployments[0].DisputeGameFactoryProxy') + + # Check for existing prestate file + CHALLENGER_PRESTATE_FILE="" + if [ -d "$CHALLENGER_DIR" ]; then + # Look for any .bin.gz file in the challenger directory + EXISTING_PRESTATE=$(find "$CHALLENGER_DIR" -maxdepth 1 -name "*.bin.gz" -print -quit 2>/dev/null) + if [ -n "$EXISTING_PRESTATE" ]; then + CHALLENGER_PRESTATE_FILE=$(basename "$EXISTING_PRESTATE") + log_info "Found existing prestate file: $CHALLENGER_PRESTATE_FILE" + fi + fi + + # Ensure prestate file exists + if [ -z "$CHALLENGER_PRESTATE_FILE" ] || [ ! -f "$CHALLENGER_DIR/$CHALLENGER_PRESTATE_FILE" ]; then + log_error "Challenger prestate file not found in $CHALLENGER_DIR/" + log_error "Make sure generate_challenger_prestate runs before setup_challenger" + return 1 + fi + + # Create .env file with OP_CHALLENGER prefixed variables + cat > .env << EOF +OP_CHALLENGER_GAME_FACTORY_ADDRESS=$GAME_FACTORY_ADDR +OP_CHALLENGER_PRIVATE_KEY=$PRIVATE_KEY +OP_CHALLENGER_CANNON_PRESTATE=/workspace/$CHALLENGER_PRESTATE_FILE +EOF + + log_success "Challenger setup complete" +} + +# Validate main .env file for docker-compose +validate_main_env() { + log_info "Validating .env file..." + + # Load user-provided .env file + USER_ENV_FILE="$(dirname "$0")/../.env" + if [ ! -f "$USER_ENV_FILE" ]; then + log_error ".env file not found. Please run 'make init' first." + log_info "Run: make init" + return 1 + fi + + log_info "Loading .env file..." + set -a # automatically export all variables + source "$USER_ENV_FILE" + set +a + + # Validate required environment variables + if [ -z "$L1_RPC_URL" ]; then + log_error "L1_RPC_URL is not set. Please set it in your .env file." + log_info "Example: L1_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com" + return 1 + fi + + if [ -z "$L1_BEACON_URL" ]; then + log_error "L1_BEACON_URL is not set. Please set it in your .env file." + log_info "Example: L1_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com" + return 1 + fi + + if [ -z "$PRIVATE_KEY" ]; then + log_error "PRIVATE_KEY is not set. Please set it in your .env file." + log_info "Example: PRIVATE_KEY=0x..." + return 1 + fi + + if [ -z "$L2_CHAIN_ID" ]; then + log_error "L2_CHAIN_ID is not set. Please set it in your .env file." + log_info "Example: L2_CHAIN_ID=16584" + return 1 + fi + + # Set defaults for optional values + P2P_ADVERTISE_IP=${P2P_ADVERTISE_IP:-127.0.0.1} + + log_success ".env file validated" +} + +# Generate challenger prestate +generate_challenger_prestate() { + log_info "Generating challenger prestate..." + + # Get the chain ID from the rollup config + if [ -f "$DEPLOYER_DIR/.deployer/rollup.json" ]; then + CHAIN_ID=$(jq -r '.l2_chain_id' "$DEPLOYER_DIR/.deployer/rollup.json") + else + log_error "Could not find rollup.json to determine chain ID" + return 1 + fi + + log_info "Chain ID for prestate generation: $CHAIN_ID" + + # Create optimism directory for prestate generation + OPTIMISM_DIR="$ROLLUP_DIR/optimism" + if [ ! -d "$OPTIMISM_DIR" ]; then + log_info "Cloning Optimism repository..." + git clone https://github.com/ethereum-optimism/optimism.git "$OPTIMISM_DIR" + cd "$OPTIMISM_DIR" + + # Find the latest op-program tag + log_info "Finding latest op-program version..." + OP_PROGRAM_TAG=$(git tag --list "op-program/v*" | sort -V | tail -1) + if [ -z "$OP_PROGRAM_TAG" ]; then + log_error "Could not find any op-program tags" + return 1 + fi + log_info "Using op-program version: $OP_PROGRAM_TAG" + + git checkout "$OP_PROGRAM_TAG" + git submodule update --init --recursive + else + log_info "Optimism repository already exists, checking configuration..." + cd "$OPTIMISM_DIR" + + # Check if we're on the correct op-program tag + CURRENT_TAG=$(git describe --tags --exact-match 2>/dev/null || echo "") + LATEST_TAG=$(git tag --list "op-program/v*" | sort -V | tail -1) + + if [ "$CURRENT_TAG" != "$LATEST_TAG" ]; then + log_info "Updating to latest op-program version: $LATEST_TAG" + git checkout "$LATEST_TAG" + git submodule update --init --recursive + else + log_info "Already on correct op-program version: $CURRENT_TAG" + fi + fi + + # Copy configuration files + log_info "Copying chain configuration files..." + mkdir -p op-program/chainconfig/configs + cp "$DEPLOYER_DIR/.deployer/rollup.json" "op-program/chainconfig/configs/${CHAIN_ID}-rollup.json" + cp "$DEPLOYER_DIR/.deployer/genesis.json" "op-program/chainconfig/configs/${CHAIN_ID}-genesis-l2.json" + + # Generate prestate + log_info "Generating reproducible prestate..." + make reproducible-prestate + + # Extract the prestate hash from the JSON file + PRESTATE_HASH=$(jq -r '.pre' op-program/bin/prestate-proof-mt64.json) + if [ -z "$PRESTATE_HASH" ] || [ "$PRESTATE_HASH" = "null" ]; then + log_error "Could not extract prestate hash from prestate-proof-mt64.json" + return 1 + fi + + log_info "Prestate hash: $PRESTATE_HASH" + + # Move the prestate file + log_info "Moving prestate file to challenger directory..." + mkdir -p "$ROLLUP_DIR/challenger" + mv "op-program/bin/prestate-mt64.bin.gz" "$ROLLUP_DIR/challenger/${PRESTATE_HASH}.bin.gz" + + # Verify the prestate file was created successfully + if [ ! -f "$ROLLUP_DIR/challenger/${PRESTATE_HASH}.bin.gz" ]; then + log_error "Failed to create prestate file: $ROLLUP_DIR/challenger/${PRESTATE_HASH}.bin.gz" + return 1 + fi + + # Clean up + cd "$ROLLUP_DIR" + # rm -rf "$OPTIMISM_DIR" # Uncomment to clean up after successful generation + + log_success "Challenger prestate generation complete: $ROLLUP_DIR/challenger/${PRESTATE_HASH}.bin.gz" +} + +# Setup dispute monitor +setup_dispute_monitor() { + log_info "Setting up dispute monitor..." + + mkdir -p "$DISPUTE_MON_DIR" + cd "$DISPUTE_MON_DIR" + + # Get required addresses from state.json + GAME_FACTORY_ADDRESS=$(jq -r '.opChainDeployments[0].DisputeGameFactoryProxy' "$DEPLOYER_DIR/.deployer/state.json") + PROPOSER_ADDRESS=$(jq -r '.appliedIntent.chains[0].roles.proposer' "$DEPLOYER_DIR/.deployer/state.json") + CHALLENGER_ADDRESS=$(jq -r '.appliedIntent.chains[0].roles.challenger' "$DEPLOYER_DIR/.deployer/state.json") + + log_info "Game Factory: $GAME_FACTORY_ADDRESS" + log_info "Proposer: $PROPOSER_ADDRESS" + log_info "Challenger: $CHALLENGER_ADDRESS" + + # Create environment file for dispute monitor + cat > .env << EOF +# Rollup RPC Configuration +ROLLUP_RPC=http://op-node:8547 + +# Contract Addresses +OP_DISPUTE_MON_GAME_FACTORY_ADDRESS=$GAME_FACTORY_ADDRESS + +# Honest Actors +PROPOSER_ADDRESS=$PROPOSER_ADDRESS +CHALLENGER_ADDRESS=$CHALLENGER_ADDRESS + +# Network Configuration +OP_DISPUTE_MON_NETWORK=op-sepolia + +# Monitoring Configuration +OP_DISPUTE_MON_MONITOR_INTERVAL=10s +EOF + + # Create logs directory + mkdir -p logs + + log_success "Dispute monitor configuration created" + log_info "Dispute monitor will start with 'docker-compose up -d' from project root" + log_info "To verify it's working, run: curl -s http://localhost:7300/metrics | grep -E \"op_dispute_mon_(games|ignored)\" | head -10" +} + +# Add op-deployer to PATH if it exists in the workspace +if [ -f "$(dirname "$0")/../op-deployer" ]; then + OP_DEPLOYER_PATH="$(cd "$(dirname "$0")/.." && pwd)/op-deployer" + export PATH="$(dirname "$OP_DEPLOYER_PATH"):$PATH" + log_info "Added op-deployer to PATH: $OP_DEPLOYER_PATH" +fi + +# Main execution +main() { + log_info "Starting OP Stack L2 Rollup deployment..." + log_info "L2 Chain ID: $L2_CHAIN_ID" + log_info "L1 Chain ID: $L1_CHAIN_ID" + + # Clean start - remove any existing deployer directory + log_info "Cleaning up any existing deployment..." + rm -rf "$DEPLOYER_DIR" + mkdir -p "$DEPLOYER_DIR" + + validate_main_env + check_prerequisites + generate_addresses + init_deployer + update_intent + deploy_contracts + generate_config + setup_sequencer + setup_batcher + setup_proposer + generate_challenger_prestate + setup_challenger + setup_dispute_monitor + + log_success "OP Stack L2 Rollup deployment complete!" + log_info "Run 'docker-compose up -d' to start all services (including dispute monitor)" + log_info "Dispute monitor metrics: http://localhost:7300/metrics" +} + +# Handle command line arguments for standalone function calls +if [ $# -gt 0 ]; then + case "$1" in + "prestate"|"generate-prestate") + log_info "Running standalone prestate generation..." + + # Set up required variables for standalone execution + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + ROLLUP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + DEPLOYER_DIR="$ROLLUP_DIR/deployer" + + generate_challenger_prestate + exit $? + ;; + *) + log_error "Unknown command: $1" + log_info "Available commands: prestate" + exit 1 + ;; + esac +fi + +# Run main function if no arguments provided +main "$@" diff --git a/docs/public-docs/docs.json b/docs/public-docs/docs.json new file mode 100644 index 0000000000000..c7ac1520b7376 --- /dev/null +++ b/docs/public-docs/docs.json @@ -0,0 +1,2428 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "mint", + "name": "Optimism Documentation", + "colors": { + "primary": "#FF0420", + "light": "#FF0420", + "dark": "#FF0420" + }, + "favicon": "/public/favicon.png", + "metadata": { + "apple-touch-icon": "/public/apple-touch-icon.png" + }, + "seo": { + "indexHiddenPages": true, + "metatags": { + "canonical": "https://docs.optimism.io/", + "og:title": "Optimism Documentation", + "og:type": "website", + "twitter:card": "summary_large_image" + } + }, + "modeToggle": { + "default": "light", + "isHidden": false + }, + "contextual": { + "options": [ + "copy", + "chatgpt", + "claude" + ] + }, + "customCSS": [ + "/styles/homepage.css" + ], + "customJS": [ + "/mixpanel-custom.js" + ], + "integrations": { + "mixpanel": { + "projectToken": "807e42c62989ff12427a35baa12b6218" + } + }, + "feedback": { + "thumbsRating": true, + "suggestEdit": true, + "raiseIssue": true + }, + "footer": { + "socials": { + "twitter": "https://twitter.com/optimism", + "github": "https://github.com/ethereum-optimism" + } + }, + "redirects": [ + { + "source": "/pages/app-developers/get-started", + "destination": "/interop/get-started" + }, + { + "source": "/pages/app-developers/interop", + "destination": "/interop/explainer" + }, + { + "source": "/pages/app-developers/starter-kit", + "destination": "/interop/starter-kit" + }, + { + "source": "/app-developers/tools/data-and-dashboards", + "destination": "/app-developers/tools/data-and-dashboards/overview" + }, + { + "source": "/builders", + "destination": "/interop/get-started" + }, + { + "source": "/builders/dapp-developers", + "destination": "/app-developers" + }, + { + "source": "/builders/chain-operators", + "destination": "/operators/chain-operators" + }, + { + "source": "/builders/node-operators", + "destination": "/operators/node-operators" + }, + { + "source": "/builders/tools/rpc-providers", + "destination": "/app-developers/tools/connect/rpc-providers" + }, + { + "source": "/builders/tools/block-explorers", + "destination": "/app-developers/tools/block-explorers" + }, + { + "source": "/builders/tools/faucets", + "destination": "/app-developers/tools/build/faucets" + }, + { + "source": "/builders/tools/oracles", + "destination": "/app-developers/tools/build/oracles" + }, + { + "source": "/builders/tools/nft-tools", + "destination": "/app-developers/tools/" + }, + { + "source": "/builders/tools/account-abstraction", + "destination": "/app-developers/tools/build/account-abstraction" + }, + { + "source": "/builders/tools/analytics-tools", + "destination": "/app-developers/tools/build/analytics-tools" + }, + { + "source": "/builders/dapp-developers/dapp-developer-overview", + "destination": "/interop/get-started" + }, + { + "source": "/builders/dapp-developers/quick-start", + "destination": "/interop/starter-kit" + }, + { + "source": "/dapp-developers/creating-a-nft", + "destination": "/app-developers/tools/" + }, + { + "source": "/builders/dapp-developers/tutorials/first-contract", + "destination": "/interop/get-started" + }, + { + "source": "/builders/dapp-developers/tutorials/cross-dom-bridge-eth", + "destination": "/app-developers/tutorials/bridging/cross-dom-bridge-eth" + }, + { + "source": "/builders/dapp-developers/tutorials/cross-dom-bridge-erc20", + "destination": "/app-developers/tutorials/bridging/cross-dom-bridge-erc20" + }, + { + "source": "/builders/dapp-developers/tutorials/sdk-estimate-costs", + "destination": "/app-developers/tutorials/transactions/sdk-estimate-costs" + }, + { + "source": "/builders/dapp-developers/tutorials/sdk-trace-txns", + "destination": "/app-developers/tutorials/transactions/sdk-trace-txns" + }, + { + "source": "/builders/dapp-developers/tutorials/send-tx-from-eth", + "destination": "/app-developers/tutorials/transactions/send-tx-from-eth" + }, + { + "source": "/builders/dapp-developers/tutorials/cross-dom-solidity", + "destination": "/app-developers/tutorials/bridging/cross-dom-solidity" + }, + { + "source": "/builders/dapp-developers/bridging/basics", + "destination": "/app-developers/bridging/basics" + }, + { + "source": "/builders/dapp-developers/bridging/messaging", + "destination": "/app-developers/bridging/messaging" + }, + { + "source": "/builders/dapp-developers/bridging/standard-bridge", + "destination": "/app-developers/bridging/standard-bridge" + }, + { + "source": "/builders/dapp-developers/bridging/custom-bridge", + "destination": "/app-developers/bridging/custom-bridge" + }, + { + "source": "/builders/dapp-developers/contracts/account-abstraction", + "destination": "/app-developers/tools/build/account-abstraction" + }, + { + "source": "/builders/dapp-developers/contracts/cheap-dapp", + "destination": "/app-developers/contracts/optimization" + }, + { + "source": "/builders/dapp-developers/contracts/compatibility", + "destination": "/app-developers/contracts/compatibility" + }, + { + "source": "/builders/dapp-developers/contracts/system-contracts", + "destination": "/app-developers/contracts/system-contracts" + }, + { + "source": "/builders/chain-operators/overview", + "destination": "/" + }, + { + "source": "/builders/chain-operators/configuration/batcher", + "destination": "/operators/chain-operators/configuration/batcher" + }, + { + "source": "/builders/chain-operators/management/operations", + "destination": "/operators/chain-operators/management/operations" + }, + { + "source": "/builders/chain-operators/tools/proxyd", + "destination": "/operators/chain-operators/tools/proxyd" + }, + { + "source": "/operators/chain-operators/features/flashblocks", + "destination": "/chain-operators/guides/features/flashblocks-guide" + }, + { + "source": "/operators/chain-operators/features/flashblocks/apps", + "destination": "/op-stack/features/flashblocks" + }, + { + "source": "/chain-operators/reference/features/flashblocks/apps", + "destination": "/op-stack/features/flashblocks" + }, + { + "source": "/builders/chain-operators/tools/chain-monitoring", + "destination": "/operators/chain-operators/tools/chain-monitoring" + }, + { + "source": "/builders/chain-operators/tools/explorer", + "destination": "/operators/chain-operators/tools/explorer" + }, + { + "source": "/builders/node-operators/overview", + "destination": "/operators/node-operators/rollup-node" + }, + { + "source": "/builders/node-operators/management/snapshots", + "destination": "/operators/node-operators/management/snapshots" + }, + { + "source": "/builders/node-operators/management/metrics", + "destination": "/operators/node-operators/management/metrics" + }, + { + "source": "/operators/node-operators/tutorials/mainnet", + "destination": "/operators/node-operators/tutorials/run-node-from-source" + }, + { + "source": "/operators/node-operators/tutorials/testnet", + "destination": "/operators/node-operators/tutorials/run-node-from-source" + }, + { + "source": "/stack/dev-net", + "destination": "/" + }, + { + "source": "/stack/protocol/deposit-flow", + "destination": "/stack/transactions/deposit-flow" + }, + { + "source": "/stack/protocol/transaction-flow", + "destination": "/stack/transactions/transaction-flow" + }, + { + "source": "/stack/protocol/withdrawal-flow", + "destination": "/stack/transactions/withdrawal-flow" + }, + { + "source": "/stack/interop/transfer-superchainERC20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/stack/interop/architecture", + "destination": "/stack/interop/explainer#interoperability-architecture" + }, + { + "source": "/stack/interop/cross-chain-message", + "destination": "/stack/interop/explainer#how-messages-get-from-one-chain-to-the-other" + }, + { + "source": "/stack/interop/cross-chain/contract-calls", + "destination": "/stack/interop/tutorials/contract-calls" + }, + { + "source": "/stack/interop/cross-chain/event-contests", + "destination": "/stack/interop/tutorials/event-contests" + }, + { + "source": "/stack/interop/cross-chain/event-reads", + "destination": "/stack/interop/tutorials/event-reads" + }, + { + "source": "/stack/interop/cross-chain/security", + "destination": "/stack/interop/interop-security" + }, + { + "source": "/stack/interop/cross-chain", + "destination": "/stack/interop" + }, + { + "source": "/audit-reports/1", + "destination": "/op-stack/security/audit-reports" + }, + { + "source": "/audit-reports/_numQueuedTransactions", + "destination": "/op-stack/security/audit-reports" + }, + { + "source": "/stack/overview", + "destination": "/op-stack/protocol/getting-started" + }, + { + "source": "/stack/explainer", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/stack/beta-features/custom-gas-token", + "destination": "/notices/custom-gas-tokens-deprecation" + }, + { + "source": "/tokenlist", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/addresses", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/chain/networks", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/chain/addresses", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/chain/tokenlist", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/chain/getting-started", + "destination": "/app-developers/building-apps" + }, + { + "source": "/chain/security/privileged-roles", + "destination": "/op-stack/protocol/privileged-roles" + }, + { + "source": "/chain/differences", + "destination": "/stack/differences" + }, + { + "source": "/operators/chain-operators/features/custom-gas-token", + "destination": "/notices/custom-gas-tokens-deprecation" + }, + { + "source": "/identity/optimist-profile", + "destination": "/governance/gov-faq" + }, + { + "source": "/chain/identity/optimist-profile", + "destination": "/governance/gov-faq" + }, + { + "source": "/app-developers/tools/overview", + "destination": "/app-developers/tools" + }, + { + "source": "/app-developers/tools/ecosystem-overview", + "destination": "/app-developers/tools/" + }, + { + "source": "/resources/glossary", + "destination": "/connect/resources/glossary" + }, + { + "source": "/app-developers/app-developers/bridging/messaging", + "destination": "/app-developers/bridging/messaging" + }, + { + "source": "/app-developers/app-developers/bridging/standard-bridge", + "destination": "/app-developers/bridging/standard-bridge" + }, + { + "source": "/app-developers/app-developers/transactions/fees", + "destination": "/app-developers/transactions/fees" + }, + { + "source": "/app-developers/app-developers/bridging/basics", + "destination": "/app-developers/bridging/basics" + }, + { + "source": "/builders/notices/granite-changes", + "destination": "https://github.com/ethereum-optimism/docs/blob/ef619668ae44276edecdfd657157254b9809e2d6/pages/builders/notices/granite-changes.mdx" + }, + { + "source": "/builders/notices/fp-changes", + "destination": "https://github.com/ethereum-optimism/docs/blob/ef619668ae44276edecdfd657157254b9809e2d6/pages/builders/notices/fp-changes.mdx" + }, + { + "source": "/builders/notices/ecotone-changes", + "destination": "/stack/transactions/fees#ecotone" + }, + { + "source": "/get-started/design-principles", + "destination": "/stack/design-principles" + }, + { + "source": "/stack/interop/tools", + "destination": "/interop/tools" + }, + { + "source": "/stack/interop/op-supervisor", + "destination": "/interop/op-supervisor" + }, + { + "source": "/stack/interop/tools/supersim", + "destination": "/interop/tools/supersim" + }, + { + "source": "/stack/interop/tools/devnet", + "destination": "/interop/tools/devnet" + }, + { + "source": "/stack/interop/tools/rc-alpha", + "destination": "/interop/tools/rc-alpha" + }, + { + "source": "/stack/interop/compatible-tokens", + "destination": "/interop/compatible-tokens" + }, + { + "source": "/stack/interop/reading-logs", + "destination": "/interop/reading-logs" + }, + { + "source": "/stack/interop/message-passing", + "destination": "/interop/message-passing" + }, + { + "source": "/stack/interop/tutorials", + "destination": "/interop/tutorials" + }, + { + "source": "/stack/interop/reorg", + "destination": "/interop/reorg" + }, + { + "source": "/stack/interop/superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/stack/interop/tutorials/transfer-superchainERC20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/stack/interop/tutorials/bridge-crosschain-eth", + "destination": "/interop/tutorials/bridge-crosschain-eth" + }, + { + "source": "/stack/interop/tutorials/event-reads", + "destination": "/interop/tutorials/event-reads" + }, + { + "source": "/stack/interop/tutorials/message-passing", + "destination": "/interop/tutorials/message-passing" + }, + { + "source": "/stack/interop/tutorials/custom-superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/stack/interop/tutorials/relay-messages-cast", + "destination": "/interop/tutorials/relay-messages-cast" + }, + { + "source": "/stack/interop/tutorials/contract-calls", + "destination": "/interop/tutorials/contract-calls" + }, + { + "source": "/stack/interop/tutorials/event-contests", + "destination": "/interop/tutorials/event-contests" + }, + { + "source": "/stack/interop/tutorials/relay-messages-viem", + "destination": "/interop/tutorials/relay-messages-viem" + }, + { + "source": "/stack/interop/tutorials/deploy-superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/stack/interop/predeploy", + "destination": "/interop/predeploy" + }, + { + "source": "/stack/interop/explainer", + "destination": "/interop/explainer" + }, + { + "source": "/stack/interop/interop-security", + "destination": "/interop/interop-security" + }, + { + "source": "/stack/interop/superchain-weth", + "destination": "/interop/superchain-eth-bridge" + }, + { + "source": "/interop/superchain-weth", + "destination": "/interop/superchain-eth-bridge" + }, + { + "source": "/stack/interop", + "destination": "/interop/get-started" + }, + { + "source": "/app-developers/bridging/basics", + "destination": "/app-developers/guides/bridging/basics" + }, + { + "source": "/app-developers/bridging/custom-bridge", + "destination": "/app-developers/guides/bridging/custom-bridge" + }, + { + "source": "/app-developers/bridging/messaging", + "destination": "/app-developers/guides/bridging/messaging" + }, + { + "source": "/app-developers/bridging/standard-bridge", + "destination": "/app-developers/guides/bridging/standard-bridge" + }, + { + "source": "/app-developers/building-apps", + "destination": "/app-developers/guides/building-apps" + }, + { + "source": "/app-developers/get-started", + "destination": "/app-developers/quickstarts/get-started" + }, + { + "source": "/app-developers/interop", + "destination": "/app-developers/guides/interop" + }, + { + "source": "/app-developers/starter-kit", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/app-developers/testing-apps", + "destination": "/app-developers/guides/testing-apps" + }, + { + "source": "/app-developers/tools/build", + "destination": "/app-developers/build" + }, + { + "source": "/app-developers/tools/build/account-abstraction", + "destination": "/app-developers/tools/wallets/account-abstraction" + }, + { + "source": "/app-developers/tools/build/analytics-tools", + "destination": "/app-developers/tools/data/analytics-tools" + }, + { + "source": "/app-developers/tools/build/block-explorers", + "destination": "/app-developers/tools/block-explorers" + }, + { + "source": "/app-developers/tools/infrastructure/block-explorers", + "destination": "/app-developers/tools/block-explorers" + }, + { + "source": "/app-developers/tools/build/ecosystem-overview", + "destination": "/app-developers/tools/" + }, + { + "source": "/app-developers/tools/build/faucets", + "destination": "/app-developers/tools/faucets" + }, + { + "source": "/app-developers/tools/testing/faucets", + "destination": "/app-developers/tools/faucets" + }, + { + "source": "/app-developers/tools/build/nft-tools", + "destination": "/app-developers/tools/" + }, + { + "source": "/app-developers/tools/tools", + "destination": "/app-developers/tools/" + }, + { + "source": "/app-developers/tools/build/oracles", + "destination": "/app-developers/tools/data/oracles" + }, + { + "source": "/app-developers/tools/connect", + "destination": "/app-developers/guides/connect" + }, + { + "source": "/app-developers/tools/connect/networks", + "destination": "/app-developers/reference/networks" + }, + { + "source": "/app-developers/tools/connect/rpc-providers", + "destination": "/app-developers/reference/rpc-providers" + }, + { + "source": "/app-developers/tools/development/supersim", + "destination": "/app-developers/tools/supersim" + }, + { + "source": "/app-developers/tools/wallets/account-abstraction", + "destination": "/app-developers/tools/account-abstraction" + }, + { + "source": "/app-developers/transactions", + "destination": "/app-developers/guides/transactions" + }, + { + "source": "/app-developers/transactions/estimates", + "destination": "/app-developers/guides/transactions/estimates" + }, + { + "source": "/app-developers/transactions/fees", + "destination": "/op-stack/transactions/fees" + }, + { + "source": "/app-developers/guides/transactions/fees", + "destination": "/op-stack/transactions/fees" + }, + { + "source": "/app-developers/transactions/parameters", + "destination": "/app-developers/guides/transactions/parameters" + }, + { + "source": "/app-developers/transactions/statuses", + "destination": "/app-developers/guides/transactions/statuses" + }, + { + "source": "/app-developers/transactions/troubleshooting", + "destination": "/app-developers/guides/transactions/troubleshooting" + }, + { + "source": "/app-developers/tutorials/interop", + "destination": "/app-developers/guides/interoperability/get-started" + }, + { + "source": "/interop/message-expiration", + "destination": "/app-developers/guides/interoperability/message-expiration" + }, + { + "source": "/app-developers/quickstarts/get-started", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/app-developers/tutorials/interop/bridge-crosschain-eth", + "destination": "/app-developers/tutorials/bridging/bridge-crosschain-eth" + }, + { + "source": "/app-developers/tutorials/interop/contract-calls", + "destination": "/app-developers/tutorials/interoperability/contract-calls" + }, + { + "source": "/app-developers/tutorials/interop/deploy-superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/app-developers/tutorials/interop/event-contests", + "destination": "/app-developers/tutorials/interoperability/event-contests" + }, + { + "source": "/app-developers/tutorials/interop/event-reads", + "destination": "/app-developers/tutorials/interoperability/event-reads" + }, + { + "source": "/app-developers/tutorials/interop/relay-messages-cast", + "destination": "/app-developers/tutorials/tools/relay-messages-cast" + }, + { + "source": "/app-developers/tutorials/interop/relay-messages-viem", + "destination": "/app-developers/tutorials/tools/relay-messages-viem" + }, + { + "source": "/app-developers/tutorials/interop/transfer-superchainERC20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/app-developers/tutorials/supersim", + "destination": "/app-developers/tutorials/development/supersim" + }, + { + "source": "/app-developers/tutorials/supersim/chain-env", + "destination": "/app-developers/reference/tools/supersim/chain-env" + }, + { + "source": "/app-developers/tutorials/supersim/chain-env/chain-a", + "destination": "/app-developers/reference/tools/supersim/chain-a" + }, + { + "source": "/app-developers/tutorials/supersim/chain-env/chain-b", + "destination": "/app-developers/reference/tools/supersim/chain-b" + }, + { + "source": "/app-developers/tutorials/supersim/chain-env/included-contracts", + "destination": "/app-developers/reference/tools/supersim/included-contracts" + }, + { + "source": "/app-developers/tutorials/supersim/deposit-transactions", + "destination": "/app-developers/tutorials/bridging/deposit-transactions" + }, + { + "source": "/app-developers/tutorials/supersim/getting-started", + "destination": "/app-developers/tutorials/development/supersim/getting-started" + }, + { + "source": "/app-developers/tutorials/supersim/getting-started/first-steps", + "destination": "/app-developers/tutorials/development/supersim/first-steps" + }, + { + "source": "/app-developers/tutorials/supersim/getting-started/installation", + "destination": "/app-developers/tutorials/development/supersim/installation" + }, + { + "source": "/app-developers/tutorials/supersim/reference", + "destination": "/app-developers/reference/tools/reference" + }, + { + "source": "/app-developers/tutorials/supersim/reference/fork", + "destination": "/app-developers/reference/tools/supersim/fork" + }, + { + "source": "/app-developers/tutorials/supersim/reference/vanilla", + "destination": "/app-developers/reference/tools/supersim/vanilla" + }, + { + "source": "/connect/contribute", + "destination": "/reference/contribute/contribute" + }, + { + "source": "/connect/contribute/docs-contribute", + "destination": "https://github.com/ethereum-optimism/docs/blob/main/CONTRIBUTING.md" + }, + { + "source": "/connect/contribute/stack-contribute", + "destination": "https://github.com/ethereum-optimism/optimism/blob/develop/CONTRIBUTING.md" + }, + { + "source": "/connect/contribute/style-guide", + "destination": "https://github.com/ethereum-optimism/docs/blob/main/STYLE_GUIDE.md" + }, + { + "source": "/reference/contribute/style-guide", + "destination": "https://github.com/ethereum-optimism/docs/blob/main/STYLE_GUIDE.md" + }, + { + "source": "/connect/resources", + "destination": "/resources" + }, + { + "source": "/get-started/interop", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/concepts/interoperability/interop", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/get-started/op-stack", + "destination": "/concepts/stack/op-stack" + }, + { + "source": "/get-started/superchain", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/interop/compatible-tokens", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/interop/explainer", + "destination": "/op-stack/interop/explainer" + }, + { + "source": "/concepts/interoperability/explainer", + "destination": "/op-stack/interop/explainer" + }, + { + "source": "/interop/get-started", + "destination": "/app-developers/guides/interoperability/get-started" + }, + { + "source": "/interop/interop-security", + "destination": "/op-stack/security/interop-security" + }, + { + "source": "/concepts/security/interop-security", + "destination": "/op-stack/security/interop-security" + }, + { + "source": "/interop/message-passing", + "destination": "/app-developers/guides/interoperability/message-passing" + }, + { + "source": "/interop/op-supervisor", + "destination": "/chain-operators/reference/components/op-supervisor" + }, + { + "source": "/interop/predeploy", + "destination": "/app-developers/reference/contracts/interop/predeploy" + }, + { + "source": "/interop/reading-logs", + "destination": "/app-developers/guides/interoperability/reading-logs" + }, + { + "source": "/interop/estimate-costs", + "destination": "/app-developers/guides/interoperability/estimate-costs" + }, + { + "source": "/interop/reorg", + "destination": "/op-stack/interop/reorg" + }, + { + "source": "/concepts/architecture/reorg", + "destination": "/op-stack/interop/reorg" + }, + { + "source": "/interop/starter-kit", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/interop/superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/concepts/interoperability/superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/interop/superchain-eth-bridge", + "destination": "/op-stack/interop/superchain-eth-bridge" + }, + { + "source": "/concepts/interoperability/superchain-eth-bridge", + "destination": "/op-stack/interop/superchain-eth-bridge" + }, + { + "source": "/interop/tools", + "destination": "/app-developers/tools/tools" + }, + { + "source": "/interop/tools/devnet", + "destination": "/app-developers/tools/testing/devnet" + }, + { + "source": "/interop/tools/rc-alpha", + "destination": "/app-developers/tools/development/rc-alpha" + }, + { + "source": "/interop/tools/supersim", + "destination": "/app-developers/tools/development/supersim" + }, + { + "source": "/interop/tutorials", + "destination": "/app-developers/tutorials/tutorials" + }, + { + "source": "/interop/tutorials/bridge-crosschain-eth", + "destination": "/app-developers/tutorials/bridging/bridge-crosschain-eth" + }, + { + "source": "/interop/tutorials/contract-calls", + "destination": "/app-developers/tutorials/interoperability/contract-calls" + }, + { + "source": "/interop/tutorials/custom-superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/interop/tutorials/deploy-superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/interop/tutorials/event-contests", + "destination": "/app-developers/tutorials/interoperability/event-contests" + }, + { + "source": "/interop/tutorials/event-reads", + "destination": "/app-developers/tutorials/interoperability/event-reads" + }, + { + "source": "/interop/tutorials/message-passing", + "destination": "/app-developers/tutorials/interoperability/message-passing" + }, + { + "source": "/interop/tutorials/relay-messages-cast", + "destination": "/app-developers/tutorials/interoperability/relay-messages-cast" + }, + { + "source": "/interop/tutorials/relay-messages-viem", + "destination": "/app-developers/tutorials/interoperability/relay-messages-viem" + }, + { + "source": "/interop/tutorials/transfer-superchainERC20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/notices/blob-fee-bug", + "destination": "/notices/archive/blob-fee-bug" + }, + { + "source": "/notices/custom-gas-tokens-deprecation", + "destination": "/notices/archive/custom-gas-tokens-deprecation" + }, + { + "source": "/notices/holocene-changes", + "destination": "/notices/archive/holocene-changes" + }, + { + "source": "/notices/pectra-changes", + "destination": "/notices/archive/pectra-changes" + }, + { + "source": "/notices/sdk-deprecation", + "destination": "/notices/archive/sdk-deprecation" + }, + { + "source": "/notices/superchain-withdrawal-pause-test", + "destination": "/notices/archive/superchain-withdrawal-pause-test" + }, + { + "source": "/notices/upgrade-13", + "destination": "/notices/archive/upgrade-13" + }, + { + "source": "/notices/upgrade-14", + "destination": "/notices/archive/upgrade-14" + }, + { + "source": "/notices/upgrade-15", + "destination": "/notices/archive/upgrade-15" + }, + { + "source": "/notices/upgrade-16", + "destination": "/notices/archive/upgrade-16" + }, + { + "source": "/notices/upgrade-18", + "destination": "/notices/archive/upgrade-18" + }, + { + "source": "/notices/upgrade-17", + "destination": "/notices/archive/upgrade-17" + }, + { + "source": "/notices/fusaka-notice", + "destination": "/notices/archive/fusaka-notice" + }, + { + "source": "/notices/upgrade-16a", + "destination": "/notices/archive/upgrade-16a" + }, + { + "source": "/operators/chain-operators", + "destination": "/chain-operators" + }, + { + "source": "/operators/chain-operators/architecture", + "destination": "/chain-operators/reference/architecture" + }, + { + "source": "/operators/chain-operators/configuration", + "destination": "/chain-operators/guides/configuration" + }, + { + "source": "/operators/chain-operators/configuration/batcher", + "destination": "/chain-operators/guides/configuration/batcher" + }, + { + "source": "/operators/chain-operators/configuration/overview", + "destination": "/chain-operators/guides/configuration/overview" + }, + { + "source": "/operators/chain-operators/configuration/proposer", + "destination": "/chain-operators/guides/configuration/proposer" + }, + { + "source": "/operators/chain-operators/configuration/rollup", + "destination": "/chain-operators/guides/configuration/rollup" + }, + { + "source": "/operators/chain-operators/deploy", + "destination": "/chain-operators/guides/deploy" + }, + { + "source": "/operators/chain-operators/deploy/genesis", + "destination": "/chain-operators/guides/deployment/genesis" + }, + { + "source": "/operators/chain-operators/deploy/overview", + "destination": "/chain-operators/guides/deployment/overview" + }, + { + "source": "/operators/chain-operators/deploy/smart-contracts", + "destination": "/chain-operators/guides/deployment/smart-contracts" + }, + { + "source": "/operators/chain-operators/features", + "destination": "/chain-operators/reference/features" + }, + { + "source": "/operators/chain-operators/features/alt-da-mode", + "destination": "/chain-operators/guides/features/alt-da-mode-guide" + }, + { + "source": "/chain-operators/reference/features/alt-da-mode", + "destination": "/chain-operators/guides/features/alt-da-mode-guide" + }, + { + "source": "/operators/chain-operators/features/bridged-usdc-standard", + "destination": "/op-stack/features/bridged-usdc-standard" + }, + { + "source": "/operators/chain-operators/features/preinstalls", + "destination": "/chain-operators/reference/features/preinstalls" + }, + { + "source": "/operators/chain-operators/features/span-batches", + "destination": "/op-stack/features/span-batches" + }, + { + "source": "/chain-operators/reference/features/span-batches", + "destination": "/op-stack/features/span-batches" + }, + { + "source": "/operators/chain-operators/management", + "destination": "/chain-operators/guides/management" + }, + { + "source": "/operators/chain-operators/management/best-practices", + "destination": "/chain-operators/guides/best-practices" + }, + { + "source": "/operators/chain-operators/management/blobs", + "destination": "/chain-operators/guides/management/blobs" + }, + { + "source": "/operators/chain-operators/management/key-management", + "destination": "/chain-operators/guides/management/key-management" + }, + { + "source": "/operators/chain-operators/management/operations", + "destination": "/chain-operators/guides/management/operations" + }, + { + "source": "/operators/chain-operators/management/snap-sync", + "destination": "/chain-operators/guides/features/snap-sync" + }, + { + "source": "/operators/chain-operators/management/troubleshooting", + "destination": "/chain-operators/guides/troubleshooting" + }, + { + "source": "/operators/chain-operators/self-hosted", + "destination": "/" + }, + { + "source": "/operators/chain-operators/tools", + "destination": "/chain-operators/tools" + }, + { + "source": "/operators/chain-operators/tools/chain-monitoring", + "destination": "/chain-operators/tools/chain-monitoring" + }, + { + "source": "/operators/chain-operators/tools/explorer", + "destination": "/chain-operators/tools/explorer" + }, + { + "source": "/operators/chain-operators/tools/fee-calculator", + "destination": "/chain-operators/tools/fee-calculator" + }, + { + "source": "/operators/chain-operators/tools/op-challenger", + "destination": "/chain-operators/tools/op-challenger" + }, + { + "source": "/operators/chain-operators/tools/op-conductor", + "destination": "/chain-operators/tools/op-conductor" + }, + { + "source": "/operators/chain-operators/tools/op-deployer", + "destination": "/chain-operators/tools/op-deployer" + }, + { + "source": "/operators/chain-operators/tools/op-txproxy", + "destination": "/chain-operators/tools/op-txproxy" + }, + { + "source": "/operators/chain-operators/tools/op-validator", + "destination": "/chain-operators/tools/op-validator" + }, + { + "source": "/operators/chain-operators/tools/proxyd", + "destination": "/chain-operators/tools/proxyd" + }, + { + "source": "/operators/chain-operators/tutorials", + "destination": "/chain-operators/tutorials" + }, + { + "source": "/operators/chain-operators/tutorials/adding-derivation-attributes", + "destination": "/chain-operators/tutorials/adding-derivation-attributes" + }, + { + "source": "/operators/chain-operators/tutorials/adding-precompiles", + "destination": "/chain-operators/tutorials/adding-precompiles" + }, + { + "source": "/chain-operators/tutorials/chain-dev-net", + "destination": "/" + }, + { + "source": "/operators/chain-operators/tutorials/create-l2-rollup", + "destination": "/chain-operators/tutorials/create-l2-rollup" + }, + { + "source": "/operators/chain-operators/tutorials/dispute-games", + "destination": "/chain-operators/tutorials/dispute-games" + }, + { + "source": "/operators/chain-operators/tutorials/integrating-da-layer", + "destination": "/chain-operators/tutorials/integrating-da-layer" + }, + { + "source": "/operators/chain-operators/tutorials/migrating-permissionless", + "destination": "/chain-operators/tutorials/migrating-permissionless" + }, + { + "source": "/operators/chain-operators/tutorials/modifying-predeploys", + "destination": "/chain-operators/tutorials/modifying-predeploys" + }, + { + "source": "/operators/node-operators", + "destination": "/node-operators" + }, + { + "source": "/operators/node-operators/architecture", + "destination": "/node-operators/reference/architecture/architecture" + }, + { + "source": "/operators/node-operators/configuration", + "destination": "/node-operators/guides/configuration" + }, + { + "source": "/operators/node-operators/configuration/base-config", + "destination": "/node-operators/guides/configuration/consensus-clients" + }, + { + "source": "/operators/node-operators/configuration/consensus-config", + "destination": "/node-operators/guides/configuration/consensus-clients" + }, + { + "source": "/operators/node-operators/configuration/execution-config", + "destination": "/node-operators/guides/configuration/execution-clients" + }, + { + "source": "/operators/node-operators/json-rpc", + "destination": "/node-operators/reference/op-node-json-rpc" + }, + { + "source": "/node-operators/reference/json-rpc", + "destination": "/node-operators/reference/op-node-json-rpc" + }, + { + "source": "/operators/node-operators/management", + "destination": "/node-operators/guides/management" + }, + { + "source": "/operators/node-operators/management/blobs", + "destination": "/chain-operators/guides/management/blobs" + }, + { + "source": "/operators/node-operators/management/metrics", + "destination": "/node-operators/guides/monitoring/metrics" + }, + { + "source": "/operators/node-operators/management/regenesis-history", + "destination": "/node-operators/guides/management/regenesis-history" + }, + { + "source": "/operators/node-operators/management/snap-sync", + "destination": "/node-operators/guides/management/snap-sync" + }, + { + "source": "/operators/node-operators/management/snapshots", + "destination": "/node-operators/guides/management/snapshots" + }, + { + "source": "/operators/node-operators/management/troubleshooting", + "destination": "/node-operators/guides/troubleshooting" + }, + { + "source": "/operators/node-operators/network-upgrades", + "destination": "/op-stack/protocol/network-upgrades" + }, + { + "source": "/concepts/stack/network-upgrades", + "destination": "/op-stack/protocol/network-upgrades" + }, + { + "source": "/operators/node-operators/releases", + "destination": "/node-operators/reference/releases" + }, + { + "source": "/operators/node-operators/rollup-node", + "destination": "/node-operators/reference/architecture/rollup-node" + }, + { + "source": "/operators/node-operators/tutorials", + "destination": "/node-operators/tutorials" + }, + { + "source": "/operators/node-operators/tutorials/node-from-docker", + "destination": "/node-operators/tutorials/node-from-docker" + }, + { + "source": "/operators/node-operators/tutorials/node-from-source", + "destination": "/node-operators/tutorials/node-from-source" + }, + { + "source": "/operators/node-operators/tutorials/run-node-from-source", + "destination": "/node-operators/tutorials/run-node-from-source" + }, + { + "source": "/stack/beta-features", + "destination": "/concepts/stack/beta-features" + }, + { + "source": "/stack/beta-features/alt-da-mode", + "destination": "/op-stack/features/experimental/alt-da-mode" + }, + { + "source": "/stack/design-principles", + "destination": "/op-stack/protocol/design-principles" + }, + { + "source": "/stack/differences", + "destination": "/concepts/stack/differences" + }, + { + "source": "/stack/differences?utm_source=op-docs&utm_medium=docs", + "destination": "/concepts/stack/differences?utm_source=op-docs&utm_medium=docs" + }, + { + "source": "/stack/fact-sheet", + "destination": "/op-stack/introduction/fact-sheet" + }, + { + "source": "/stack/fault-proofs", + "destination": "/concepts/architecture/fault-proofs" + }, + { + "source": "/stack/fault-proofs/cannon", + "destination": "/op-stack/fault-proofs/cannon" + }, + { + "source": "/stack/fault-proofs/challenger", + "destination": "/op-stack/fault-proofs/challenger" + }, + { + "source": "/stack/fault-proofs/explainer", + "destination": "/op-stack/fault-proofs/explainer" + }, + { + "source": "/stack/fault-proofs/fp-components", + "destination": "/op-stack/fault-proofs/fp-components" + }, + { + "source": "/stack/fault-proofs/fp-security", + "destination": "/op-stack/fault-proofs/fp-security" + }, + { + "source": "/stack/fault-proofs/mips", + "destination": "/op-stack/fault-proofs/mips" + }, + { + "source": "/stack/features", + "destination": "/concepts/stack/features" + }, + { + "source": "/stack/features/send-raw-transaction-conditional", + "destination": "/op-stack/features/send-raw-transaction-conditional" + }, + { + "source": "/stack/getting-started", + "destination": "/concepts/stack/getting-started" + }, + { + "source": "/stack/opcm", + "destination": "/chain-operators/reference/opcm" + }, + { + "source": "/stack/public-devnets", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/stack/research", + "destination": "/concepts/research" + }, + { + "source": "/stack/research/block-time-research", + "destination": "/op-stack/research/block-time-research" + }, + { + "source": "/concepts/research/block-time-research", + "destination": "/op-stack/research/block-time-research" + }, + { + "source": "/stack/rollup", + "destination": "/concepts/architecture/rollup" + }, + { + "source": "/stack/rollup/derivation-pipeline", + "destination": "/op-stack/protocol/derivation-pipeline" + }, + { + "source": "/stack/rollup/outages", + "destination": "/op-stack/protocol/outages" + }, + { + "source": "/concepts/architecture/rollup/outages", + "destination": "/op-stack/protocol/outages" + }, + { + "source": "/concepts/architecture/rollups/outages", + "destination": "/op-stack/protocol/outages" + }, + { + "source": "/stack/rollup/overview", + "destination": "/concepts/architecture/rollup/overview" + }, + { + "source": "/stack/security", + "destination": "/concepts/security" + }, + { + "source": "/stack/security/audits-report", + "destination": "/op-stack/security/audit-reports" + }, + { + "source": "/stack/security/faq", + "destination": "/op-stack/security/faq-sec-model" + }, + { + "source": "/stack/security/faq-sec-model", + "destination": "/op-stack/security/faq-sec-model" + }, + { + "source": "/stack/security/pause", + "destination": "/op-stack/security/pause" + }, + { + "source": "/concepts/security/pause", + "destination": "/op-stack/security/pause" + }, + { + "source": "/stack/security/security-policy", + "destination": "/concepts/security/security-policy" + }, + { + "source": "/stack/smart-contracts", + "destination": "/op-stack/protocol/smart-contracts" + }, + { + "source": "/concepts/stack/smart-contracts", + "destination": "/op-stack/protocol/smart-contracts" + }, + { + "source": "/stack/transactions", + "destination": "/concepts/transactions" + }, + { + "source": "/stack/transactions/cross-domain", + "destination": "/op-stack/bridging/cross-domain" + }, + { + "source": "/concepts/bridging/cross-domain", + "destination": "/op-stack/bridging/cross-domain" + }, + { + "source": "/stack/transactions/deposit-flow", + "destination": "/op-stack/bridging/deposit-flow" + }, + { + "source": "/concepts/transactions/deposit-flow", + "destination": "/op-stack/bridging/deposit-flow" + }, + { + "source": "/concepts/transactions/fees", + "destination": "/op-stack/transactions/fees" + }, + { + "source": "/stack/transactions/fees", + "destination": "/op-stack/transactions/fees" + }, + { + "source": "/concepts/transactions/flashblocks", + "destination": "/op-stack/transactions/flashblocks" + }, + { + "source": "/stack/transactions/flashblocks", + "destination": "/op-stack/transactions/flashblocks" + }, + { + "source": "/concepts/transactions/forced-transaction", + "destination": "/op-stack/transactions/forced-transaction" + }, + { + "source": "/stack/transactions/forced-transaction", + "destination": "/op-stack/transactions/forced-transaction" + }, + { + "source": "/concepts/transactions/transaction-finality", + "destination": "/op-stack/transactions/transaction-finality" + }, + { + "source": "/stack/transactions/transaction-finality", + "destination": "/op-stack/transactions/transaction-finality" + }, + { + "source": "/concepts/transactions/transaction-flow", + "destination": "/op-stack/transactions/transaction-flow" + }, + { + "source": "/stack/transactions/transaction-flow", + "destination": "/op-stack/transactions/transaction-flow" + }, + { + "source": "/stack/transactions/withdrawal-flow", + "destination": "/op-stack/protocol/bridging/withdrawal-flow" + }, + { + "source": "/concepts/transactions/withdrawal-flow", + "destination": "/op-stack/protocol/bridging/withdrawal-flow" + }, + { + "source": "/superchain/addresses", + "destination": "/op-mainnet/network-information/op-addresses" + }, + { + "source": "/superchain/blockspace-charter", + "destination": "/op-stack/protocol/blockspace-charter" + }, + { + "source": "/concepts/blockspace-charter", + "destination": "/op-stack/protocol/blockspace-charter" + }, + { + "source": "/superchain/networks", + "destination": "/op-mainnet/network-information/connecting-to-op" + }, + { + "source": "/superchain/privileged-roles", + "destination": "/op-stack/protocol/privileged-roles" + }, + { + "source": "/superchain/standard-configuration", + "destination": "/chain-operators/reference/standard-configuration" + }, + { + "source": "/chain-operators/reference/superchain-registry", + "destination": "/op-stack/protocol/superchain-registry" + }, + { + "source": "/superchain/tokenlist", + "destination": "/app-developers/reference/tokens/tokenlist" + }, + { + "source": "/superchain/superchain-explainer", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/chain-operators/guides/troubleshooting", + "destination": "/chain-operators/guides/management/troubleshooting" + }, + { + "source": "/stack/smart-contracts/smart-contracts", + "destination": "/op-stack/protocol/smart-contracts#smart-contract-overview" + }, + { + "source": "/app-developers/build", + "destination": "/app-developers/guides/building-apps" + }, + { + "source": "/app-developers/guides/connect", + "destination": "/reference/networks" + }, + { + "source": "/developers/testing/public-devnets", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/notices/upgrade-14-123", + "destination": "/notices/archive/upgrade-14" + }, + { + "source": "/notices/pectra-fees", + "destination": "/notices/archive/pectra-changes" + }, + { + "source": "/node-operators/guides/configuration/consensus-config#configuration-options-reference", + "destination": "/node-operators/reference/op-node-config" + }, + { + "source": "/node-operators/guides/configuration/consensus-config", + "destination": "/node-operators/guides/configuration/consensus-clients" + }, + { + "source": "/node-operators/configuration/consensus-clients/op-node", + "destination": "/node-operators/guides/configuration/consensus-clients" + }, + { + "source": "/node-operators/guides/configuration/execution-config#nethermind", + "destination": "/node-operators/guides/configuration/execution-clients" + }, + { + "source": "/node-operators/guides/configuration/execution-config#configuration-options-reference", + "destination": "/node-operators/reference/op-geth-config" + }, + { + "source": "/node-operators/guides/configuration/execution-config", + "destination": "/node-operators/guides/configuration/execution-clients" + }, + { + "source": "/node-operators/configuration/execution-clients/op-geth", + "destination": "/node-operators/guides/configuration/execution-clients" + }, + { + "source": "/node-operators/configuration/execution-clients/nethermind", + "destination": "/node-operators/guides/configuration/execution-clients" + }, + { + "source": "/node-operators/guides/configuration/base-config", + "destination": "/node-operators/guides/configuration/consensus-clients" + }, + { + "source": "/node-operators/guides/base-config", + "destination": "/node-operators/guides/configuration/consensus-clients" + }, + { + "source": "/concepts/architecture/rollup/outages#sequencer-downtime-outages", + "destination": "/op-stack/protocol/outages#sequencer-downtime-outages" + }, + { + "source": "/concepts/architecture/rollup/outages#bypassing-the-sequencer", + "destination": "/op-stack/protocol/outages#bypassing-the-sequencer" + }, + { + "source": "/operators/chain-operators/features/flashblocks/chain-operators", + "destination": "/chain-operators/guides/features/flashblocks-guide" + }, + { + "source": "/stack/smart-contracts/op-deployer-upgrade", + "destination": "/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade" + }, + { + "source": "/concepts/stack/op-deployer-upgrade", + "destination": "/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade" + }, + { + "source": "/concepts/stack/upgrade-op-contracts-1-3-1-8", + "destination": "/chain-operators/tutorials/l1-contract-upgrades/upgrade-op-contracts-1-3-1-8" + }, + { + "source": "/concepts/stack/superchain-ops-guide", + "destination": "/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide" + }, + { + "source": "/chain-operators/tutorials/op-deployer-upgrade", + "destination": "/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade" + }, + { + "source": "/chain-operators/tutorials/superchain-ops-guide", + "destination": "/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide" + }, + { + "source": "/chain-operators/tutorials/upgrade-op-contracts-1-3-1-8", + "destination": "/chain-operators/tutorials/l1-contract-upgrades/upgrade-op-contracts-1-3-1-8" + }, + { + "source": "/stack/components", + "destination": "/op-stack/protocol/components" + }, + { + "source": "/governance/blockspace-charter", + "destination": "/op-stack/protocol/blockspace-charter" + }, + { + "source": "/app-developers/tutorials/tutorials", + "destination": "/app-developers/guides/building-apps" + }, + { + "source": "/core-contributors/reference/specs/mips", + "destination": "/op-stack/fault-proofs/mips" + }, + { + "source": "/concepts/architecture/rollup/overview", + "destination": "/op-stack/protocol/overview" + }, + { + "source": "/concepts/architecture/rollups/overview", + "destination": "/op-stack/protocol/overview" + }, + { + "source": "/concepts/architecture/rollup/derivation-pipeline", + "destination": "/op-stack/protocol/derivation-pipeline" + }, + { + "source": "/concepts/architecture/rollups/derivation-pipeline", + "destination": "/op-stack/protocol/derivation-pipeline" + }, + { + "source": "/operators/chain-operators/deploy/op-challenger", + "destination": "/op-stack/fault-proofs/challenger" + }, + { + "source": "/operators/chain-operators/tutorials/absolute-prestate", + "destination": "/chain-operators/tutorials/absolute-prestate" + }, + { + "source": "/chain-operators/guides/best-practices", + "destination": "/chain-operators/guides/management/best-practices" + }, + { + "source": "/operators/chain-operators/tutorials/create-l2-rollup/op-geth-setup", + "destination": "chain-operators/tutorials/create-l2-rollup/op-geth-setup" + }, + { + "source": "/operators/chain-operators/tutorials/create-l2-rollup/op-deployer-setup", + "destination": "chain-operators/tutorials/create-l2-rollup/op-deployer-setup" + }, + { + "source": "/operators/chain-operators/tutorials/create-l2-rollup/op-proposer-setup", + "destination": "chain-operators/tutorials/create-l2-rollup/op-proposer-setup" + }, + { + "source": "/operators/chain-operators/tutorials/create-l2-rollup/op-batcher-setup", + "destination": "chain-operators/tutorials/create-l2-rollup/op-batcher-setup" + }, + { + "source": "/operators/chain-operators/deploy/sequencer-node", + "destination": "/chain-operators/guides/deployment/sequencer-node" + }, + { + "source": "/app-developers/tools/testing/devnet", + "destination": "/app-developers/guides/building-apps" + }, + { + "source": "/chain/identity/contracts-eas", + "destination": "/op-stack/protocol/smart-contracts#eas-ethereum-attestation-service" + }, + { + "source": "/concepts/stack/op-stack", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/concepts/stack/fact-sheet", + "destination": "/op-stack/introduction/fact-sheet" + }, + { + "source": "/concepts/architecture/fault-proofs/explainer", + "destination": "/op-stack/fault-proofs/explainer" + }, + { + "source": "/concepts/architecture/fault-proofs/fp-components", + "destination": "/op-stack/fault-proofs/fp-components" + }, + { + "source": "/concepts/architecture/fault-proofs/mips", + "destination": "/op-stack/fault-proofs/mips" + }, + { + "source": "/concepts/architecture/fault-proofs/challenger", + "destination": "/op-stack/fault-proofs/challenger" + }, + { + "source": "/concepts/stack/getting-started", + "destination": "/op-stack/protocol/getting-started" + }, + { + "source": "/concepts/stack/differences", + "destination": "/op-stack/protocol/differences" + }, + { + "source": "/concepts/security/security-policy", + "destination": "/op-stack/security/security-policy" + }, + { + "source": "/concepts/security/audits-report", + "destination": "/op-stack/security/audit-reports" + }, + { + "source": "/concepts/security/faq-sec-model", + "destination": "/op-stack/security/faq-sec-model" + }, + { + "source": "/concepts/security/faq", + "destination": "/op-stack/security/faq-sec-model" + }, + { + "source": "/concepts/security/fp-security", + "destination": "/op-stack/fault-proofs/fp-security" + }, + { + "source": "chain-operators/guides/deployment/overview", + "destination": "/" + }, + { + "source": "/chain-operators/guides/deployment/smart-contracts", + "destination": "/chain-operators/tutorials/op-deployer-setup" + }, + { + "source": "/chain-operators/guides/deployment/genesis", + "destination": "/chain-operators/tools/op-deployer" + }, + { + "source": "/chain-operators/guides/deployment/validate-deployment", + "destination": "/chain-operators/tools/op-validator" + }, + { + "source": "/chain-operators/guides/deployment/sequencer-node", + "destination": "/chain-operators/tutorials/op-geth-setup" + }, + { + "source": "/chain-operators/guides/deployment/spin-batcher", + "destination": "/chain-operators/tutorials/op-batcher-setup" + }, + { + "source": "/chain-operators/guides/deployment/proposer-setup-guide", + "destination": "/chain-operators/tutorials/op-proposer-setup" + }, + { + "source": "/chain-operators/reference/features/flashblocks/chain-operators", + "destination": "/chain-operators/guides/features/flashblocks-guide" + }, + { + "source": "/chain-operators/reference/features/bridged-usdc-standard", + "destination": "/op-stack/features/bridged-usdc-standard" + }, + { + "source": "/chain-operators/reference/features/preinstalls", + "destination": "/op-stack/features/preinstalls" + }, + { + "source": "/chain-operators/reference/rpc/send-raw-transaction-conditional", + "destination": "/op-stack/features/send-raw-transaction-conditional" + }, + { + "source": "/chain-operators/reference/superchain-explainer", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/chain-operators/reference/privileged-roles", + "destination": "/op-stack/protocol/privileged-roles" + }, + { + "source": "/reference/components", + "destination": "/op-stack/protocol/components" + }, + { + "source": "chain-operators/guides/management/blobs", + "destination": "chain-operators/guides/features/blobs" + }, + { + "source": "chain-operators/guides/management/snap-sync", + "destination": "chain-operators/guides/features/snap-sync" + }, + { + "source": "/chain-operators/tools/fee-calculator", + "destination": "/op-stack/transactions/fees" + }, + { + "source": "/chain-operators/tools/op-challenger", + "destination": "chain-operators/guides/configuration/op-challenger-config-guide" + }, + { + "source": "/app-developers/tools/data-and-dashboards/overview", + "destination": "/app-developers/tools/data/data-and-dashboards" + }, + { + "source": "app-developers/tools/data-and-dashboards/data-glossary", + "destination": "/app-developers/tools/data/data-glossary" + }, + { + "source": "/reference/contribute/docs-contribute", + "destination": "https://github.com/ethereum-optimism/docs/blob/main/CONTRIBUTING.md" + }, + { + "source": "/reference/glossary", + "destination": "/op-stack/reference/glossary" + }, + { + "source": "/connect/resources/glossary", + "destination": "/op-stack/reference/glossary" + }, + { + "source": "/reference/contribute/stack-contribute", + "destination": "https://github.com/ethereum-optimism/optimism/blob/develop/CONTRIBUTING.md" + }, + { + "source": "/reference/public-devnets", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/reference/addresses", + "destination": "/op-mainnet/network-information/op-addresses" + }, + { + "source": "/reference/networks", + "destination": "/op-mainnet/network-information/connecting-to-op" + }, + { + "source": "/superchain/superchain-registry", + "destination": "/op-stack/protocol/superchain-registry" + }, + { + "source": "/node-operators/guides/management/snapshots", + "destination": "/op-mainnet/network-information/snapshots" + }, + { + "source": "/app-developers/quickstarts/starter-kit", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/app-developers/reference/tokens/compatible-tokens", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/app-developers/tutorials/tokens/custom-superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/app-developers/tutorials/tokens/deploy-superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/app-developers/tutorials/tokens/transfer-superchainERC20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/op-stack/interop/superchain-erc20", + "destination": "/op-stack/interop/interop" + }, + { + "source": "/superchain/introduction/superchain-explainer", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/superchain/superchain-information/superchain-registry", + "destination": "/op-stack/protocol/superchain-registry" + }, + { + "source": "/superchain/superchain-information/superchain-revenue-explainer", + "destination": "/op-stack/introduction/op-stack" + }, + { + "source": "/superchain/concepts/blockspace-charter", + "destination": "/op-stack/protocol/blockspace-charter" + }, + { + "source": "/app-developers/guides/superchain", + "destination": "/op-stack/introduction/op-stack" + } + ], + "interaction": { + "drilldown": false + }, + "navigation": { + "tabs": [ + { + "tab": "Chain Operators", + "groups": [ + { + "group": "Quickstart", + "pages": [ + "index" + ] + }, + { + "group": "Guides", + "pages": [ + { + "group": "Configuration", + "pages": [ + "chain-operators/guides/configuration/getting-started", + "chain-operators/guides/configuration/batcher", + "chain-operators/guides/configuration/proposer", + "chain-operators/guides/configuration/rollup", + "chain-operators/guides/configuration/op-challenger-config-guide" + ] + }, + { + "group": "Features", + "pages": [ + "chain-operators/guides/features/custom-gas-token-guide", + "chain-operators/guides/features/switching-to-kona-proofs", + "chain-operators/guides/features/setting-min-base-fee", + "chain-operators/guides/features/setting-operator-fee", + "chain-operators/guides/features/setting-da-footprint", + "chain-operators/guides/features/flashblocks-guide", + "chain-operators/guides/features/alt-da-mode-guide", + "chain-operators/guides/features/blobs", + "chain-operators/guides/features/snap-sync" + ] + }, + { + "group": "Management", + "pages": [ + "chain-operators/guides/management/best-practices", + "chain-operators/guides/management/gas-target-limit", + "chain-operators/guides/management/key-management", + "chain-operators/guides/management/operations", + "chain-operators/guides/management/transaction-fees-101", + "chain-operators/guides/management/troubleshooting" + ] + } + ] + }, + { + "group": "Tutorials", + "pages": [ + { + "group": "Create L2 Rollup", + "pages": [ + "chain-operators/tutorials/create-l2-rollup/create-l2-rollup", + "chain-operators/tutorials/create-l2-rollup/op-deployer-setup", + "chain-operators/tutorials/create-l2-rollup/op-geth-setup", + "chain-operators/tutorials/create-l2-rollup/op-batcher-setup", + "chain-operators/tutorials/create-l2-rollup/op-proposer-setup", + "chain-operators/tutorials/create-l2-rollup/op-challenger-setup" + ] + }, + { + "group": "L1 contract upgrades", + "pages": [ + "chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade", + "chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide", + "chain-operators/tutorials/l1-contract-upgrades/upgrade-op-contracts-1-3-1-8" + ] + }, + "chain-operators/tutorials/absolute-prestate", + "chain-operators/tutorials/adding-derivation-attributes", + "chain-operators/tutorials/adding-precompiles", + "chain-operators/tutorials/dispute-games", + "chain-operators/tutorials/integrating-da-layer", + "chain-operators/tutorials/migrating-permissionless", + "chain-operators/tutorials/modifying-predeploys", + "chain-operators/tutorials/rewind-op-geth" + ] + }, + { + "group": "Tools", + "pages": [ + "chain-operators/tools/chain-monitoring", + "chain-operators/tools/explorer", + "chain-operators/tools/op-conductor", + { + "group": "OP Deployer", + "pages": [ + "chain-operators/tools/op-deployer/overview", + "chain-operators/tools/op-deployer/installation", + { + "group": "Usage", + "pages": [ + "chain-operators/tools/op-deployer/usage/bootstrap", + "chain-operators/tools/op-deployer/usage/init", + "chain-operators/tools/op-deployer/usage/apply", + "chain-operators/tools/op-deployer/usage/verify", + "chain-operators/tools/op-deployer/usage/upgrade" + ] + }, + "chain-operators/tools/op-deployer/known-limitations", + { + "group": "Reference", + "pages": [ + { + "group": "Architecture", + "pages": [ + "chain-operators/tools/op-deployer/reference/architecture/overview", + "chain-operators/tools/op-deployer/reference/architecture/pipeline", + "chain-operators/tools/op-deployer/reference/architecture/engine" + ] + }, + "chain-operators/tools/op-deployer/reference/artifacts-locators", + "chain-operators/tools/op-deployer/reference/releases", + "chain-operators/tools/op-deployer/reference/custom-deployments" + ] + } + ] + }, + "chain-operators/tools/op-txproxy", + "chain-operators/tools/op-validator", + "chain-operators/tools/proxyd" + ] + }, + { + "group": "Reference", + "pages": [ + "chain-operators/reference/architecture", + "chain-operators/reference/opcm", + "chain-operators/reference/standard-configuration", + { + "group": "Components", + "pages": [ + "chain-operators/reference/components/op-supervisor" + ] + } + ] + } + ] + }, + { + "tab": "Node Operators", + "groups": [ + { + "group": "Getting Started", + "pages": [ + "node-operators/overview" + ] + }, + { + "group": "Guides", + "pages": [ + { + "group": "Configuration", + "pages": [ + "node-operators/guides/configuration/consensus-clients", + "node-operators/guides/configuration/execution-clients", + "node-operators/guides/configuration/legacy-geth" + ] + }, + { + "group": "Management", + "pages": [ + "node-operators/guides/management/archive-node", + "node-operators/guides/management/blobs", + "node-operators/guides/management/regenesis-history", + "node-operators/guides/management/snap-sync" + ] + }, + { + "group": "Monitoring", + "pages": [ + "node-operators/guides/monitoring/metrics" + ] + }, + "node-operators/guides/troubleshooting" + ] + }, + { + "group": "Tutorials", + "pages": [ + "node-operators/tutorials/node-from-docker", + "node-operators/tutorials/node-from-source", + "node-operators/tutorials/run-node-from-source", + "node-operators/tutorials/reth-historical-proofs" + ] + }, + { + "group": "Reference", + "pages": [ + { + "group": "Architecture", + "pages": [ + "node-operators/reference/architecture/architecture", + "node-operators/reference/architecture/rollup-node" + ] + }, + { + "group": "Features", + "pages": [ + "node-operators/reference/features/snap-sync" + ] + }, + "node-operators/reference/op-node-config", + "node-operators/reference/op-node-json-rpc", + "node-operators/reference/op-geth-config", + "node-operators/reference/op-geth-json-rpc", + "node-operators/reference/op-reth-config", + "node-operators/reference/op-reth-historical-proof-config", + "node-operators/reference/consensus-layer-sync" + ] + } + ] + }, + { + "tab": "App Developers", + "groups": [ + { + "group": "Quickstarts", + "pages": [ + "app-developers/quickstarts/actions" + ] + }, + { + "group": "Guides", + "pages": [ + "app-developers/guides/building-apps", + "app-developers/guides/testing-apps", + "app-developers/guides/configuring-actions" + ] + }, + { + "group": "Tutorials", + "pages": [ + { + "group": "Bridging", + "pages": [ + "app-developers/tutorials/bridging/cross-dom-bridge-erc20", + "app-developers/tutorials/bridging/cross-dom-solidity", + "app-developers/tutorials/bridging/standard-bridge-custom-token", + "app-developers/tutorials/bridging/standard-bridge-standard-token", + "app-developers/tutorials/bridging/cross-dom-bridge-eth", + "app-developers/tutorials/bridging/bridge-crosschain-eth", + "app-developers/tutorials/bridging/deposit-transactions" + ] + }, + { + "group": "Supersim", + "pages": [ + "app-developers/tutorials/development/supersim/first-steps", + "app-developers/tutorials/development/supersim/installation" + ] + }, + { + "group": "Interoperability", + "pages": [ + "app-developers/tutorials/interoperability/contract-calls", + "app-developers/tutorials/interoperability/event-contests", + "app-developers/tutorials/interoperability/event-reads", + "app-developers/tutorials/interoperability/manual-relay", + "app-developers/tutorials/interoperability/message-passing" + ] + }, + { + "group": "Transactions", + "pages": [ + "app-developers/tutorials/transactions/sdk-estimate-costs", + "app-developers/tutorials/transactions/sdk-trace-txns", + "app-developers/tutorials/transactions/send-tx-from-eth" + ] + } + ] + }, + { + "group": "Bridging", + "pages": [ + "app-developers/guides/bridging/basics", + "app-developers/guides/bridging/custom-bridge", + "app-developers/guides/bridging/messaging", + "app-developers/guides/bridging/standard-bridge" + ] + }, + { + "group": "Interoperability", + "pages": [ + "app-developers/guides/interoperability/get-started", + "app-developers/guides/interoperability/message-passing", + "app-developers/guides/interoperability/reading-logs", + "app-developers/guides/interoperability/message-expiration", + "app-developers/guides/interoperability/estimate-costs" + ] + }, + { + "group": "Transactions", + "pages": [ + "app-developers/guides/transactions/estimates", + "app-developers/guides/transactions/flashblocks-and-gas-usage", + "app-developers/guides/transactions/parameters", + "app-developers/guides/transactions/statuses", + "app-developers/guides/transactions/troubleshooting" + ] + }, + { + "group": "Tools", + "pages": [ + "app-developers/tools/faucets", + "app-developers/tools/block-explorers", + { + "group": "Data", + "pages": [ + "app-developers/tools/data/data-and-dashboards", + "app-developers/tools/data/data-glossary", + "app-developers/tools/data/analytics-tools", + "app-developers/tools/data/oracles" + ] + }, + "app-developers/tools/supersim", + "app-developers/tools/account-abstraction" + ] + }, + { + "group": "Reference", + "pages": [ + "app-developers/reference/networks", + "app-developers/reference/rpc-providers", + { + "group": "Actions SDK", + "pages": [ + "app-developers/reference/actions/integrating-wallets", + "app-developers/reference/actions/wallet-definitions", + "app-developers/reference/actions/lend-documentation" + ] + }, + { + "group": "Contracts", + "pages": [ + "app-developers/reference/contracts/interop/predeploy" + ] + }, + { + "group": "Tokens", + "pages": [ + "app-developers/reference/tokens/tokenlist" + ] + }, + { + "group": "Supersim", + "pages": [ + "app-developers/reference/tools/supersim/chain-a", + "app-developers/reference/tools/supersim/chain-b", + "app-developers/reference/tools/supersim/fork", + "app-developers/reference/tools/supersim/included-contracts", + "app-developers/reference/tools/supersim/vanilla" + ] + } + ] + } + ] + }, + { + "tab": "OP Stack", + "groups": [ + { + "group": "Introduction", + "pages": [ + "/op-stack/introduction/op-stack", + "/op-stack/introduction/fact-sheet" + ] + }, + { + "group": "Features", + "pages": [ + "/op-stack/features/flashblocks", + "/op-stack/features/custom-gas-token", + "/op-stack/features/bridged-usdc-standard", + "/op-stack/features/span-batches", + "/op-stack/features/send-raw-transaction-conditional" + ] + }, + { + "group": "Protocol Information", + "pages": [ + "/op-stack/protocol/overview", + "/op-stack/protocol/getting-started", + "/op-stack/protocol/design-principles", + "/op-stack/protocol/differences", + "/op-stack/protocol/components", + "/op-stack/protocol/derivation-pipeline", + "/op-stack/protocol/outages", + "/op-stack/protocol/network-upgrades", + "/op-stack/protocol/smart-contracts", + "/op-stack/protocol/privileged-roles", + "/op-stack/protocol/blockspace-charter", + "/op-stack/protocol/superchain-registry" + ] + }, + { + "group": "Bridging", + "pages": [ + "/op-stack/bridging/cross-domain", + "/op-stack/bridging/deposit-flow", + "/op-stack/bridging/withdrawal-flow" + ] + }, + { + "group": "Transactions", + "pages": [ + "/op-stack/transactions/fees", + "/op-stack/transactions/flashblocks", + "/op-stack/transactions/forced-transaction", + "/op-stack/transactions/transaction-finality", + "/op-stack/transactions/transaction-flow" + ] + }, + { + "group": "Fault Proofs", + "pages": [ + "op-stack/fault-proofs/explainer", + "/op-stack/fault-proofs/fp-security", + "op-stack/fault-proofs/fp-components", + "op-stack/fault-proofs/cannon", + "op-stack/fault-proofs/mips", + "op-stack/fault-proofs/challenger" + ] + }, + { + "group": "Security", + "pages": [ + "/op-stack/security/security-policy", + "/op-stack/security/audit-reports", + "/op-stack/security/faq-sec-model", + "/op-stack/security/pause", + "/op-stack/security/interop-security" + ] + }, + { + "group": "Research", + "pages": [ + "/op-stack/research/block-time-research" + ] + }, + { + "group": "Interoperability", + "pages": [ + "/op-stack/interop/interop", + "/op-stack/interop/explainer", + "/op-stack/interop/reorg", + "/op-stack/interop/superchain-eth-bridge" + ] + }, + { + "group": "Reference", + "pages": [ + "/op-stack/reference/glossary" + ] + } + ] + }, + { + "tab": "OP Mainnet", + "groups": [ + { + "group": "Network Information", + "pages": [ + "op-mainnet/network-information/connecting-to-op", + "op-mainnet/network-information/op-addresses", + "op-mainnet/network-information/snapshots" + ] + } + ] + }, + { + "tab": "How Optimism Evolves", + "groups": [ + { + "group": "How Optimism Evolves", + "pages": [ + "governance/protocol-upgrades", + "governance/capital-allocation", + "governance/evolution-and-experimentation", + "governance/gov-faq" + ] + } + ] + }, + { + "tab": "Notices", + "groups": [ + { + "group": "Notices", + "pages": [ + "notices/op-geth-deprecation", + { + "group": "Archive", + "pages": [ + "notices/archive/upgrade-18", + "notices/archive/upgrade-17", + "notices/archive/fusaka-notice", + "notices/archive/upgrade-16a", + "notices/archive/upgrade-16", + "notices/archive/blob-fee-bug", + "notices/archive/pectra-changes", + "notices/archive/pectra-fees", + "notices/archive/upgrade-15", + "notices/archive/upgrade-14", + "notices/archive/upgrade-13", + "notices/archive/holocene-changes", + "notices/archive/superchain-withdrawal-pause-test" + ] + } + ] + } + ] + } + ], + "global": { + "anchors": [ + { + "anchor": "Status", + "href": "https://status.optimism.io", + "icon": "signal-bars" + }, + { + "anchor": "Faucet", + "href": "https://console.optimism.io/faucet", + "icon": "gas-pump" + }, + { + "anchor": "Bridge", + "href": "https://app.optimism.io/bridge/deposit", + "icon": "bridge-water" + } + ] + } + }, + "logo": { + "light": "/public/logos/Lockup_docs_dark.svg", + "dark": "/public/logos/Lockup_docs_light.svg" + }, + "navbar": { + "links": [ + { + "label": "Blog", + "href": "https://www.optimism.io/blog" + }, + { + "label": "GitHub", + "href": "https://github.com/ethereum-optimism/docs" + } + ] + } +} diff --git a/docs/public-docs/governance/capital-allocation.mdx b/docs/public-docs/governance/capital-allocation.mdx new file mode 100644 index 0000000000000..a778104652b2f --- /dev/null +++ b/docs/public-docs/governance/capital-allocation.mdx @@ -0,0 +1,74 @@ +--- +title: "Capital Allocation" +description: "Learn how Optimism allocates capital for long-term success." +--- + +## How does Optimism ensure long-term success? + +Optimism strives to create a sustainable ecosystem flywheel. In this flywheel, revenue contributed by OP Chains to the Optimism Collective funds open-source development and drives ecosystem growth, which strengthens the OP Stack and attracts more end-users, apps, integration partners, and chains. + +In implementing this flywheel, Optimism uses a public decision making process designed to prevent short-term profit seeking at the expense of the platform, while ensuring organizations contributing the OP Stack remain accountable to tokenholders and customers. This process includes a capital allocation model, designed to avoid many of the common failure modes of corporate governance, aiming to ensure the product always remains at the cutting edge. + +For more details, please see the [Operating Manual.](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md) + +![capital alloction](/public/img/governance/capital-allocation.png) + +## How does Optimism generate revenue? + +OP Chains in the Superchain earn transaction fees whenever users send transactions onchain, including for payments, trading, identity, and other applications. Each chain also pays costs to publish its activity to Ethereum for security. + +OP Chains contribute a portion of their revenue to Optimism. This treasury is used to drive growth, provide shared infrastructure, and fund open source contributions that benefit the Superchain. + +Superchain member chains contribute the greater of: + +- 15% of net transaction fee profit (transaction fees earned on L2 - costs paid to Ethereum L1), or +- 2.5% of gross transaction fees + +OP Mainnet contributes 100% of its revenue to this shared treasury. + +You can find more information about revenue in the [Superchain Revenue Explainer documentation](/superchain/superchain-information/superchain-revenue-explainer). + +You can also find the list of wallets across L1 and OP Mainnet where the Optimism Collective Revenue earned sits [here](https://docs.google.com/spreadsheets/d/1f-uIW_PzlGQ_XFAmsf9FYiUf0N9l_nePwDVrw0D5MXY/edit?gid=155717474#gid=155717474), on the right-hand side of the Collective Contribution page. + +- OP Treasury Address for Foundation Allocated Budget: `0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26` + - This address hold the remaining OP tokens allocated to the Foundation, which the Foundation requires governance approval to access (via annual FND budget proposals). +- OP Treasury Address for Foundation Approved Budget: `0x2501c477D0A35545a387Aa4A3EEe4292A9a8B3F0` + - This is the Foundation's OP Treasury which is available for the Foundation to utilize as the Foundation's budget granted through the initial token allocation. Transactions from this wallet are typically internal operational movements per the Foundation's needs. + - Additional token may be moved from `0x2…a26` to `0x2…3F0` based on governance approval of budgets. +- OP Foundation Grants Wallet: `0x19793c7824Be70ec58BB673CA42D2779d12581BE` + - This Foundation wallet is used to make private OP grants. This is topped up from the OP Treasury Foundation Approved Budget wallet `0x2…B3F0` as needed. +- OP Foundation Locked Grants Wallet: `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F` + - This Foundation wallet is used to hold OP for one year lockups. This is topped up from the OP Foundation Grants Wallet `0x1...81BE` as needed. + +## How is the treasury managed? + +The treasury is currently stewarded by the Foundation, but is subject to oversight by key stakeholder groups, via Optimism's public decision making process. Specifically, tokenholders, chains, apps, and users are asked to oversee annual budgets, which enable the Foundation to deploy the treasury into initiatives aimed at generated the sustainable flywheel described above. + +Foundation Budget Reports can be found [here](https://gov.optimism.io/c/updates-and-announcements/foundation-budgets/) on the governance forum. + +The OP Token Unlock (Estimated) sheet can be found [here](https://docs.google.com/spreadsheets/d/1qVMhLmmch3s6XSbiBe8hgD4ntMkPIOhc1WrhsYsQc7M/edit?usp=sharing). + +You can find more information via the Operating Manual [here](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md). + +## What is Optimism's commitment to Open Source Software? + +Optimism has always been committed to funding open-source software. OP Labs, a core contributor to the OP Stack, is registered as a public benefit corporation with a mission to enhance and enshrine access to public goods. The OP Stack is MIT licensed and Optimism dedicates a significant portion of its treasury to funding public goods and open-source software. + +Optimism operates various grant programs to incentivize and reward open-source contributions. Grant programs can be found via [atlas.optimism.io](http://atlas.optimism.io). + +## What can I do with the OP Token? + +The OP token was created in May of 2022, with an initial supply of 4,294,967,296 OP tokens. The token was launched as a governance token to enable tokenholders to weigh in on technical and economic decisions that impact Optimism, such as protocol upgrades and capital allocation. The Optimism Foundation estimates the total supply of circulating OP tokens to increase as detailed in [this sheet](https://docs.google.com/spreadsheets/d/1qVMhLmmch3s6XSbiBe8hgD4ntMkPIOhc1WrhsYsQc7M/). + +Tokenholders can use OP to vote on: + +- Protocol Upgrades +- Token Allocations +- Adjusting Inflation +- Removing the Director of the Optimism Foundation +- Dissolutions +- Elections (and representative removal) +- Protecting the rights of tokenholders by consenting to any changes to the founding documents of the Optimism Foundation, if those changes would materially reduce their rights. +- Ratification of Governing Documents + +You can find full details of what Tokenholders can vote with the Operating Manual [here](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md). You can sign up to vote with your tokens [here](https://vote.optimism.io/delegates). diff --git a/docs/public-docs/governance/evolution-and-experimentation.mdx b/docs/public-docs/governance/evolution-and-experimentation.mdx new file mode 100644 index 0000000000000..0a971b125c68e --- /dev/null +++ b/docs/public-docs/governance/evolution-and-experimentation.mdx @@ -0,0 +1,59 @@ +--- +title: Evolution & Experimentation +description: Learn about Optimism's commitment to iterative improvement and experimentation. +--- + +## Evolution + +In our pursuit to design a new type of organization, Optimism's public decision making process has undergone significant evolution since its inception, reflecting Optimism's commitment to iterative improvement and experimentation. +Below is a summary of some of the key things we learned along the way. + +### Key Stakeholders + +- We've run multiple experiments to understand who our most engaged stakeholders are and how they participate in our public decision making process. +- **Tokenholders:** Anyone who holds OP can vote + - We've also run multiple delegation experiments aimed at getting if specific types of tokenholders (protocols, chains, and individual community members) more involved. Our main learning has been that tokenholders need strong incentive alignment to invest time in decision making processes and they want to be involved in low effort, high impact ways. + - While our system allows for delegation - whereby tokenholders can assign their votes to someone else to cast on their behalf - over time we've come to believe that delegation disrupts the incentives of token-weighted voting and that voting directly should be heavily encouraged. + - Tokenholders are asked to make decisions that would benefit from investor protections +- **Users, Apps, and Chains:** You must qualify to be a Citizen + - Citizenship started with a small initial group and expanded via a Web of Trust model. This model suffered from in-group dynamics (which were replicated here), resulting in many Citizens that were impacted by the decisions being made. + - We later ran targeted experiments to evaluate how community members, chains, and past grant recipients voted, ultimately resulting in our key stakeholder model. Our stakeholder models ensures chains, apps, and end-users are able to influence the decisions that impact them. + - Citizens are asked to make decisions that would benefit from consumer protections +- We've realized that input from different stakeholders is needed depending on the type of decision being made: + - **Preferences**: There is no absolute "right" answer and all stakeholders should have a say + - **Prediction**: There is a correct answer, which is only revealed in the future. Experts are best suited to make these decisions. + - When “experts” are needed, these decisions are made by Councils and Boards - such as the Developer Advisory Board and Security Council. These Councils and Boards are still ultimately accountable to key stakeholders. + - **Measurement**: This is best done objectively, by a computer, when possible, or by experts. + +### High Impact Inputs + +- Different decisions impact each stakeholder group in unique ways. Our approach has evolved from “everyone decides everything” to one that only asks stakeholders to make decisions that directly impact them. +- In many cases, a stakeholder doesn't need to make a decision directly, but should still have the ability to veto - or reject - a decision that disadvantages their stakeholder group. +- Stakeholders will also be able to express preferences and influence strategy via non-voting processes. +- We've outlined the different decisions here: Figma + +### The Core Set of Decisions + +- Governance minimization is a foundational principle of Optimism's collective decision making process. Our evolution has been one of continuously simplifying process, reducing structure, and further refining scope. +- We've learned over time that several decisions that used to be made publicly, actually benefit from more centralized decision making (CoCC, CFC, BB.) +- We believe the set of decisions that should be made collectively are those that: + - Reduce platform risk for customers and users of the protocol + - Prevent short-term profit seeking at the long-term expense of the platform +- Optimism has always been committed to supporting public goods, but the way we support public goods has evolved greatly over time. We started with a fully public grant making process, which gradually evolved to be more metrics-driven and programmatic approach, requiring less human input. We expect this to be a continued area of evolution and innovation. +- Our Decentralization Milestones outlines the remaining steps we hope to take to refine and further decentralize our public decision making process. + +## Experimentation + +Underpinning the learnings outlined above is a culture of experimentation. In our early days, our [iterative approach](https://gov.optimism.io/t/the-path-to-open-metagovernance/7728) sometimes involved a less-scientific, trial-and-error approach. Over time, we've realized a more rigorous, data-driven approach - leveraging controlled trials where possible - allows us to truly understand what works and what doesn't. + +A sample of our Research & Experiments findings are summarized in the table below. We often collaborate with academics, industry experts, and independent researchers. + +| **Topic** | **Research question** | **Methods** | **Key Takeaways** | **Write-up** | +| --- | --- | --- | --- | --- | +| **Airdrops** | Do airdrops drive prosocial behaviors like delegation? Do they increase retention among new users? | Regression discontinuity design (RDD) | Increased delegation esp among small wallets; Baseline reward increases retention but high activity bonuses decrease retention | - [Did OP Airdrop 2 Increase Governance Engagement?](https://gov.optimism.io/t/did-op-airdrop-2-increase-governance-engagement/7270)
- [Did OP Airdrop 5 Increase User Retention Rates? A Regression Discontinuity Analysis](https://gov.optimism.io/t/did-op-airdrop-5-increase-user-retention-rates-a-regression-discontinuity-analysis/9610) | +| **Citizenship** | How do we identify key stakeholders (eg, end users, app devs, or partner chains) and give them decision-making rights? | Voting data analysis, surveys, qualitative interviews | Experts no “better” at values questions but better at assessing impact; Guest voters don’t vote differently to existing set; 3 clear personas | - [Citizenship Learnings 2024](https://gov.optimism.io/t/citizenship-learnings-2024/9563) | +| **Deliberation** | How does participating in a deliberative process with direct policy implications change individual attitudes and behaviors? | Randomized experiment, instrumental variable regression | Deliberation increases knowledge and trust; No reduction in polarization when outcome is binding | - [When Is Deliberation Useful for Optimism Governance?](https://gov.optimism.io/t/when-is-deliberation-useful-for-optimism-governance/9142) | +| **Futarchy** | Do projects selected via Futarchy see greater increase in TVL than projects selected by existing Grants Council? | Time-series analysis, RDD, analysis of telegram, survey, and trading data | Futarchy grants produced more Superchain TVL after 3 months than Grants Council picks; Predictions notably overpriced; 400+ forecasters participated | - [Futarchy v1 Preliminary Findings](https://gov.optimism.io/t/futarchy-v1-preliminary-findings/10062) | +| **Public Goods Funding** | What voting designs lead to impactful grant allocation decisions? Does algorithmic/ metrics-based voting improve outcomes? | Voting data analysis, synthetic control method, surveys, qualitative feedback | Humans are bad at quantification and bias toward even distributions rather than reflecting value; Experts with context make better decisions for OSS; Individual bias about impact vs need is inevitable | - [Retro Funding 4: Learnings and Reflections](https://gov.optimism.io/t/retro-funding-4-learnings-and-reflections/9271)
- [Season 7 Retro Funding - Early Evidence on Developer Tooling Impact](https://gov.optimism.io/t/season-7-retro-funding-early-evidence-on-developer-tooling-impact/10162) | +| **Voter mobilization** | Do appeals to civic duty, economic self-interest, collective security, or decision authority increase tokenholder turnout? | Randomized multi-wave experiment | Economic and security (tangible stakes) were most effective in driving turnout; Repeated reminders are necessary to sustain increase in participation; catchy visuals and follow-ups important | - “What Drives Turnout in Digital Governance? Evidence from a Multi-stage Voter Mobilization Experiment among 34,328 Tokenholders” (Draft available upon request: eliza@optimism.io) + diff --git a/docs/public-docs/governance/gov-faq.mdx b/docs/public-docs/governance/gov-faq.mdx new file mode 100644 index 0000000000000..0288135a09b6e --- /dev/null +++ b/docs/public-docs/governance/gov-faq.mdx @@ -0,0 +1,498 @@ +--- +title: "FAQs" +description: "Frequently Asked Questions about how Optimism evolves." +--- + + + + All OP tokenholders, a key stakeholder group, are represented in governance via the Token House. The Token House uses token-weighted voting, giving influence proportional to OP token holdings. Tokenholders may vote themselves or assign their voting power to a “delegate.” The primary role of tokenholders is to express their financial interest in the evolution of the Superchain and to hold proposers accountable. Tokenholders may vote on: + + **Protocol Upgrades** + + Delegates have the power to veto decisions about protocol upgrades made by the Developer Advisory Board (DAB). This veto power serves as a critical check on technical changes, with the aim of ensuring they align with the interests of those who rely on the protocol. + + **Capital Allocation** + + Delegates participate in resource allocation decisions, including: + + - Approving the Collective Intent, missions, and budget + - Approving Governance Fund proposals + + **Representative Elections** + + Delegates elect members to the Councils and Boards and/or approve any alternative selection mechanisms + + **Ratification** + + Delegates may ratify core governing documents. + + For a full description of the voting mechanics for each of these proposal types, please refer to the [Operating Manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md). In future phases, the Token House may gain additional governance powers. + + + **Unlock Your Voting Power** + + OP token holders are able to vote on some of the most important decisions for the Collective. This empowers one of the Collective's key stakeholders to have a say in the development of the system. + + You can either vote yourself with your OP tokens, or you can delegate the voting power of your OP tokens to someone else to make decisions on your behalf. + + Get started at [vote.optimism.io/delegates](http://vote.optimism.io/delegates) + + + Users, Apps, and Chains, key stakeholder groups, are represented in governance via the Citizens’ House. The Citizens' House uses a ‘1 member, 1 vote’ model, so all members have the same level of influence. The primary role of Citizens is to express their preferences in the evolution of the Superchain and to hold proposers accountable. Citizens may vote on: + + ### **Protocol Upgrades** + + Citizens have the power to veto decisions about protocol upgrades made by the Developer Advisory Board (DAB). This veto power serves as a critical check on technical changes, ensuring they align with the interests of those who rely on the protocol. + + ### **Resource Allocation** + + Citizens participate in resource allocation decisions, including: + + - Approving the Collective Intent, missions, and budget + + ### **Representative Elections** + + Citizens elect representatives to the Developer Advisory Board, ensuring it remains accountable to their interests. + + ### **Ratification** + + Citizens may ratify core governing documents. + + For a full description of the voting mechanics for each of these proposal types, please refer to the [Operating Manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md). In future phases, the Citizens’ House may gain additional governance powers. + + + ## **Eligibility Framework and Principles** + + The Citizens' House relies on a carefully designed eligibility framework aimed at achieving representation from key stakeholders in the Superchain ecosystem. This framework is built on the following principles: + + 1.Those who are most impacted by Protocol Upgrades should have governance power over them + + 2.Those who contribute most to shared resources should have a voice in the governance of those shared resources + + Eligibility criteria include three distinct stakeholder groups within the Citizens' House: + + 1.Chains + + 2.Onchain Applications + + 3.Superchain End-Users + + ## **Citizenship Eligibility Criteria** + + Eligibility is recalculated at the beginning of every Season, and criteria are subject to change. + + ## **Chain Citizens** + + ### **Eligibility Criteria in Season 9** + + Chains are eligible to vote in the Citizens' House if they meet either of these criteria: + + - Account for at least 2% of the total revenue share contributed by all chains in the past Season + - Are among the top 15 chains by revenue contribution in the past Season + + Eligibility for Season 9 is calculated based on activity from 01 August 2025 - 31 December 2025. This approach aims to give chains with significant economic stake in the ecosystem a voice in governance, while the minimum number of seats (15) should mean broad representation even if revenue becomes concentrated. + + ## **App Citizens** + + ### **Eligibility Criteria** + + Onchain applications are eligible to join the Citizens' House if they meet either of these criteria: + + - Are responsible for at least 0.5% of the total gas used across the Superchain over the past Season + - Are among the top 100 apps by gas usage in the past Season + + Eligibility for Season 9 is calculated based on activity from 01 August 2025 - 31 December 2025 based on contract data registered by projects in OP Atlas. Projects/contracts not registered in OP Atlas cannot be considered at this time. This approach aims to give applications driving significant activity on the Superchain a voice in governance, while the minimum number of seats (100) should mean broad representation from the application ecosystem. + + ## **End-user Citizens** + + ### **Eligibility Criteria** + + Individual end-users are eligible to join the Citizens' House if they meet all of these criteria: + + - Had their first Superchain transaction before June 1, 2024 + - Have at least 2 Superchain transactions each month in at least 3 distinct months from 01 August 2025 - 31 December 2025 + - Can provide proof of personhood through [World ID](https://world.org/world-id) or [Passport](https://app.passport.xyz/) + + To check the eligibility of your address, navigate to [atlas.optimism.io/citizenship](http://atlas.optimism.io/citizenship) and link the address to your Atlas profile. These criteria are designed so that Citizens will be genuine, active users of the Superchain with sustained engagement over time, rather than one-time or sporadic users. + + ### **Selection Process** + + To register as an end-user Citizen, please visit https://atlas.optimism.io/citizenship + + Until Sybil-resistance mechanisms are more mature, the Optimism Foundation may suspend Citizens flagged as possible Sybils and request further verification of unique personhood. + + + The Token House and the Citizens' House together represent all key stakeholders of the Superchain: tokenholders, chains, apps, and end-users. Both houses vote on proposals when the interests of all stakeholders should be represented in a particular decision. Each house has a distinct voting mechanism which, when combined together, creates a system of checks and balances aimed at balancing competing interests. + + Voting happens on a regular schedule via three-week voting cycles. Regular voting Cycles begin on Thursday at 19:00p GMT (12p PST) and end on Wednesday at 19:00 GMT (12p PST). Protocol Upgrades may go through an accelerated process. You can view full details [here](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md). You can track cycles on [the governance calendar](https://calendar.google.com/calendar/u/0/r?cid=Y180aHVpNzBpdG0wODllN3Q4cTUwaGVoMWtub0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t). + + + Key stakeholders of the Superchain can protect their interests by participating in Optimism's public decision making process (governance). This process allows stakeholders to influence the future of the Superchain with limited day-to-day involvement. + + Key stakeholders will be asked to: + + - **Vote:** 1-2 times per year + - **Provide input:** 3-4 times per year + - **Veto:** only as needed + + Email notifications will be sent to all stakeholders whenever any of the above actions is possible. Stakeholders can also monitor activity directly at [vote.optimism.io](http://vote.optimism.io) or [atlas.optimism.io](http://atlas.optimism.io) + + **Token House Voters** + + 1. **Activity:** The social standard for being an active delegate is participating in 70% of all votes. + 2. **No self-dealing:** Voters are prohibited from approving and voting on their own proposals. Voters may not vote solely for their own candidacy in an election. In the case of approval/ranked choice elections, optimists may vote for themselves, so long as they also cast votes for the remaining elected positions. + 3. **Conflicts of Interest:** Any actual or reasonably anticipated conflicts of interest must be disclosed in writing and prominently displayed ahead of any voting (i.e. when approving proposal drafts, when running for an elected position, when making public recommendations). + + These guidelines help ensure that the Token House remains active and resistant to capture or manipulation. + + **Citizens House Voters** + + To maintain the integrity of the Citizens' House, several important rules govern participation: + + 1. **No Double Representation**: If you are an admin of a Citizen project or organization, you may not also join the Citizens' House as a Superchain user. + 2. **Organization Priority**: An organization and a project under that organization can never both get votes in the Citizens' House. If both are eligible, membership defaults to the organization. + 3. **No Multiple Accounts**: It is forbidden to create multiple accounts to attempt to get multiple votes in the Citizens' House as a Superchain user. In Season 8, Citizens will be manually reviewed for possible Sybil activity by the Optimism Foundation. + 4. **Seasonal Recalculation**: The eligibility criteria for being a member of the Citizens' House will be recalculated every Season and may change—being a Citizen now doesn't guarantee future Citizens' House membership. + + These rules help ensure that the Citizens' House remains balanced, representative, and resistant to capture or manipulation. + + You can view the Code of Conduct [here](https://gov.optimism.io/t/code-of-conduct/5751). + + + Funding public goods is core to Optimism's values and vision for a healthy ecosystem. Retroactive Public Goods Funding (Retro Funding) is an experimental grant program to reward public goods that have created impact in the Optimism ecosystem. Learn more at atlas.optimism.io + + + This guide covers [Ethereum Attestation Service ("EAS")](https://attest.sh/), an open-source public good that is included as a predeploy in the OP Stack. It also covers EAS contract addresses, how to read and write attestations, and indexing. + + + This guide covers Ethereum Attestation Service ("EAS"), an open-source public good that is included as a predeploy in the OP Stack. It also covers EAS contract addresses, how to read and write attestations, and indexing. + + **EAS contract addresses** + + The [Ethereum Attestation Service](https://docs.attest.sh/docs/welcome) is deployed on these addresses: + + | **Network** | **Attestation Contract** | **Schema Registry Contract** | + | ----------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | + | OP Sepolia | [0x4200000000000000000000000000000000000021](https://sepolia-optimism.etherscan.io/address/0x4200000000000000000000000000000000000021) | [0x4200000000000000000000000000000000000020](https://sepolia-optimism.etherscan.io/address/0x4200000000000000000000000000000000000020) | + | OP Mainnet | [0x4200000000000000000000000000000000000021](https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000021) | [0x4200000000000000000000000000000000000020](https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000020) | + + **How to read and write attestations** + + You can read and write attestations in several ways: + + - [EAS scan user interface (OP Mainnet)](https://optimism.easscan.org/) + - [EAS scan user interface (OP Sepolia)](https://optimism-sepolia.easscan.org/) + - [JavaScript SDK](https://docs.attest.sh/docs/developer-tools/eas-sdk) + - [Access directly onchain](https://github.com/ethereum-attestation-service/eas-contracts/blob/master/contracts/EAS.sol) (if you need to attest from a smart contract) + + **Indexing** + + Indexing is available via: + + - [GraphQL endpoint](https://docs.attest.sh/docs/developer-tools/api) + - [Ponder graph](https://github.com/ethereum-attestation-service/eas-ponder-graph) + - [Open source indexer](https://github.com/ethereum-attestation-service/eas-indexing-service) + + + Schemas define the structure and type of data that can be included in an attestation. + + Below you will find a list of relevant schemas that are being used on OP Mainnet. Schemas are built using the [Ethereum Attestation Service](https://docs.attest.sh/docs/welcome). + + ## General schemas + + - [\*\*Gitcoin Passport V1 scores schema UID](https://optimism.easscan.org/schema/view/0x6ab5d34260fca0cfcf0e76e96d439cace6aa7c3c019d7c4580ed52c6845e9c89):\*\* `0x6ab5d34260fca0cfcf0e76e96d439cace6aa7c3c019d7c4580ed52c6845e9c89` + - [**Superchain Faucet schema UID**](https://optimism.easscan.org/schema/view/0x98ef220cd2f94de79fbc343ef982bfa8f5b315dec6a08f413680ecb7085624d7): `0x98ef220cd2f94de79fbc343ef982bfa8f5b315dec6a08f413680ecb7085624d7` + + **Schemas related to project creation and Retro funding application** + + [**Project and organization identifier**](https://optimism.easscan.org/schema/view/0xff0b916851c1c5507406cfcaa60e5d549c91b7f642eb74e33b88143cae4b47d0) + + Used as the unique identifier for projects and organizations created on or after 23 August 2024. For projects created earlier, please see the archived section at the bottom of this page. + + | **Schema UID** | **`0xff0b916851c1c5507406cfcaa60e5d549c91b7f642eb74e33b88143cae4b47d0`** | + | -------------- | --------------------------------------------------------------------------------------------------------------- | + | Issuer | Attestations issued as part of Retro Funding sign up are issued by `0xF6872D315CC2E1AfF6abae5dd814fd54755fE97C` | + | farcasterID | The Farcaster id of the individual who created the project or organization | + | type | "Project" or "Organization" | + + [**Organization metadata**](https://optimism.easscan.org/schema/view/0xc2b376d1a140287b1fa1519747baae1317cf37e0d27289b86f85aa7cebfd649f) + + Used to associate metadata to an organization. Re-issued each time there is a change to metadata + + | **Schema UID** | **`0xc2b376d1a140287b1fa1519747baae1317cf37e0d27289b86f85aa7cebfd649f`** | + | -------------- | --------------------------------------------------------------------------------------------------------------- | + | Issuer | Attestations issued as part of Retro Funding sign up are issued by `0xF6872D315CC2E1AfF6abae5dd814fd54755fE97C` | + | Recipient | Null | + | RefUID | The attestation UID of the organization this metadata relates to | + | farcasterID | The Farcaster id of the individual who published the organization metadata | + | name | The name of the organization | + | projects | The array of projects that belong to this organization | + | parentOrgUID | The attestation UID of this organization's parent, in case it has one | + | metadataType | How the metadata can be accessed. 1 for ipfs, 2 for http | + | metadataUrl | The storage location where the metadata can be retrieved | + + [**Project metadata**](https://optimism.easscan.org/schema/view/0xe035e3fe27a64c8d7291ae54c6e85676addcbc2d179224fe7fc1f7f05a8c6eac) + + Used to associate metadata to a project. Re-issued each time there is a change to metadata. + + | **Schema UID** | **`0xe035e3fe27a64c8d7291ae54c6e85676addcbc2d179224fe7fc1f7f05a8c6eac`** | + | -------------------- | --------------------------------------------------------------------------------------------------------------- | + | Issuer | Attestations issued as part of Retro Funding sign up are issued by `0xF6872D315CC2E1AfF6abae5dd814fd54755fE97C` | + | Recipient | Null | + | projectRefUID | The attestation UID of the project this metadata relates to | + | farcasterID | The Farcaster id of the individual who published the project metadata | + | name | The name of the project | + | category | The category of the project | + | parentProject RefUID | The attestation UID of this project's parent project, in case it has a parent | + | metadataType | How the metadata can be accessed. 1 for ipfs, 2 for http | + | metadataUrl | The storage location where the metadata can be retrieved | + + [**Retro funding application**](https://optimism.easscan.org/schema/view/0x2169b74bfcb5d10a6616bbc8931dc1c56f8d1c305319a9eeca77623a991d4b80) + + Used to identify a project's application to a specific Retro Funding Round. This attestation is used for Retro Funding Round 6 and beyond. + + | **Schema UID** | **`0x2169b74bfcb5d10a6616bbc8931dc1c56f8d1c305319a9eeca77623a991d4b80`** | + | ----------------------- | ---------------------------------------------------------------------------------------------------------------- | + | Issuer | Attestations issued as part of Retro Funding sign up are issued by: `0xF6872D315CC2E1AfF6abae5dd814fd54755fE97C` | + | Recipient | Null | + | round | The round number for which this application was submitted | + | metadataType | How the metadata can be accessed. 1 for ipfs, 2 for http | + | metadataUrl | The storage location where the metadata can be retrieved | + | farcasterID | The individual that submitted this application on behalf of the project. | + | metadataSnapshot RefUID | The project metadata at the time the application was submitted. | + + [**Retro funding application approval/rejection**](https://optimism.easscan.org/schema/view/0x683b1b399d47aabed79c9aa8f2674729021174b6e5cce1e20675eab404fc82d6) + + Used to identify which Retro Funding applications have been approved or rejected. + + | **Schema UID** | **`0x683b1b399d47aabed79c9aa8f2674729021174b6e5cce1e20675eab404fc82d6`** | + | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Issuer | Currently, the Optimism Foundation issues these from the following address: `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F` | + | Recipient | Null | + | projectApplicationUID | The unique identifier of the projects Retro Funding application. | + | Status | The status of the Retro Funding application. | + | Reason | Identifier for the reason an application was rejected. 1 = "Duplicate Application", 2 = "Deceiving Badgeholders", 3 = "Spam", 4 = "Not meeting eligibility criteria" | + + [**Retro funding rewards**](https://optimism.easscan.org/schema/view/0x670ad6e6ffb842d37e050ea6d3a5ab308195c6f584cf2121076067e0d8adde18) + + Used to identify the reward amount each approved project received in a Retro Funding round + + | **Schema UID** | **`0x670ad6e6ffb842d37e050ea6d3a5ab308195c6f584cf2121076067e0d8adde18`** | + | -------------- | ---------------------------------------------------------------------------------------------------------------------------- | + | Issuer | Currently, the Optimism Foundation issues these from one the following address: `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F` | + | Recipient | Null | + | refUID | The UID of the Retro Funding application | + | projectRefUID | The unique identifier of the project | + | round | The retro round for which the project was rewarded | + | OPamount | The amount of OP awarded to the project | + + ## Schemas related to token house grants + + [**Token house grant approved**](https://optimism.easscan.org/schema/view/0x8aef6b9adab6252367588ad337f304da1c060cc3190f01d7b72c7e512b9bfb38) + + Issued by the Grants Council when a project is approved for a grant. Does not indicate that the grant has been completed. + + | **Schema UID** | **`0x8aef6b9adab6252367588ad337f304da1c060cc3190f01d7b72c7e512b9bfb38`** | + | ---------------- | --------------------------------------------------------------------------------- | + | Issuer | Currently issued by the Grants Council lead. | + | Recipient | The address where the tokens will be delivered once the grant has been completed. | + | refUID | Currently null | + | projectRefUID | The unique identifier of the project that was approved for the grant. | + | UserIncentivesOP | The OP amount approved for user incentives. | + | BuildersOP | The OP amount approved for the builder. | + | Season | The season (number) in which the grant was approved | + | Intent | The intent (number) to which the mission belongs | + | Mission | The name of the mission (in words) under which this grant was made. | + | Approval date | The date the grant was approved, in the following format MM/DD/YYYY | + | MetadataUrl | Currently null | + + ## Schemas related to roles and contributions + + [**Citizens**](https://optimism.easscan.org/schema/view/0xc35634c4ca8a54dce0a2af61a9a9a5a3067398cb3916b133238c4f6ba721bc8a) + + Citizen attestations were first issued in Season 6 and are used to represent Citizenship separately from the ability to vote in a specific Retro Round. The resolver contract checks that the issuer is the Foundation with following address `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F` + + | **Schema UID** | **`0xc35634c4ca8a54dce0a2af61a9a9a5a3067398cb3916b133238c4f6ba721bc8a`** | + | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | RefUID | In case the Citizen is a chain or an app, the refUID field will reference the organization/project id of the chain or app. If null, the Citizen is an end-user | + | FarcasterID | The Citizen's unique identifier | + | SelectionMethod | A Code representing the method through which the Citizen was selected. Codes beginning with the number 1 refer to various flavours of Web of Trust selection. | + + [**Retro funding voters**](https://optimism.easscan.org/schema/view/0x41513aa7b99bfea09d389c74aacedaeb13c28fb748569e9e2400109cbe284ee5) + + These attestations are voting Badges issued for Retro Round 5 and beyond. They are different from the [previous schema](https://optimism.easscan.org/schema/view/0xfdcfdad2dbe7489e0ce56b260348b7f14e8365a8a325aef9834818c00d46b31b) to include new fields like votingGroup, used to assign voters to sub-categories in the round. + + | **Schema UID** | **`0x41513aa7b99bfea09d389c74aacedaeb13c28fb748569e9e2400109cbe284ee5`** | + | --------------- | -------------------------------------------------------------------------- | + | FarcasterID | The voter's unique identifier | + | Round | The round number for which this voting Badge was valid | + | voterType | Guest or Citizen | + | votingGroup | Used to assign voters to subcategories in case the Round has subcategories | + | selectionMethod | The method in which this voter was selected | + + [**MetaGov contribution**](https://optimism.easscan.org/schema/view/0x84260b9102b41041692558a4e0cba6b7e5f9b813be56402c3db820c06dd4a5f1) + + | **Schema UID** | **`0x84260b9102b41041692558a4e0cba6b7e5f9b813be56402c3db820c06dd4a5f1`** | + | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Issuer | Currently, the Optimism Foundation issues these from one of the following addresses: `0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9` or `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F`. | + | Recipient | The address of the individual who made the contribution | + | refUID | The UID of the project, in case this contribution is represented as a project | + | FarcasterID | The id of the individual who made the contribution, if known | + | Impact | This field is not currently being used | + | Season | The season in which the contribution was made | + | Decision Module | The decision module to which the contribution relates | + | Contribution Type | The type of contribution | + | MetadataUrl | This field is not currently being used | + + [**Foundation mission request completed**](https://optimism.easscan.org/schema/view/0x649cc6df5af7561b66384405a62682c44e2428584d2f17a202ac3ef4506e2457) + + | **Schema UID** | **`0x649cc6df5af7561b66384405a62682c44e2428584d2f17a202ac3ef4506e2457`** | + | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Issuer | Currently, the Optimism Foundation issues these from one of the following addresses: `0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9` or `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F`. | + | projectRefUID | The UID of the project that represents the work completed as part of the Foundation Mission Request | + | OP Amount | The OP Amount that was awarded for the completion of this Mission Request | + | Season | The season in which this Mission Request was completed | + + [**Retro funding governance contribution**](https://optimism.easscan.org/schema/view/0x3743be2afa818ee40304516c153427be55931f238d961af5d98653a93192cdb3) + + | **Schema UID** | **`0x3743be2afa818ee40304516c153427be55931f238d961af5d98653a93192cdb3`** | + | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Issuer | Currently, the Optimism Foundation issues these from one of the following addresses: `0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9` or `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F`. | + | Recipient | The address of the individual who made the contribution | + | Rpgf_round | The round number for which this contribution was made | + | RetroPGF_Contribution | The type of contribution made | + + [**Governance contribution**](https://optimism.easscan.org/schema/view/0xef874554718a2afc254b064e5ce9c58c9082fb9f770250499bf406fc112bd315) + + Issued to those who held governance roles in the Collective, such as Grants Council members. + + | **Schema UID** | **`0xef874554718a2afc254b064e5ce9c58c9082fb9f770250499bf406fc112bd315`** | + | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Issuer | Currently, the Optimism Foundation issues these from one of the following addresses: `0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9` or `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F`. | + | Issuer | Currently, the Optimism Foundation issues these from one of the following addresses: `0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9` or `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F` | + | Recipient | The address of the individual who made the contribution | + | govSeason | The season the individual held the role | + | govRole | The role held by the individual | + + ## Archived schemas + + These schemas are no longer being actively issued, but capture valuable historical data. + + [**Retro funding application**](https://optimism.easscan.org/schema/view/0x88b62595c76fbcd261710d0930b5f1cc2e56758e155dea537f82bf0baadd9a32) + + Used to identify a project's application to a specific Retro Funding Round. This attestation was used for Retro Funding Rounds 4 and 5. + + | **Schema UID** | **`0x88b62595c76fbcd261710d0930b5f1cc2e56758e155dea537f82bf0baadd9a32`** | + | ----------------------- | ---------------------------------------------------------------------------------------------------------------- | + | Issuer | Attestations issued as part of Retro Funding sign up are issued by: `0xF6872D315CC2E1AfF6abae5dd814fd54755fE97C` | + | Recipient | Null | + | round | The round number for which this application was submitted | + | projectRefUID | The unique identifier of the project that submitted this application | + | farcasterID | The individual that submitted this application on behalf of the project. | + | metadataSnapshot RefUID | The project metadata at the time the application was submitted. | + + [**Retro funding badgeholders**](https://optimism.easscan.org/schema/view/0xfdcfdad2dbe7489e0ce56b260348b7f14e8365a8a325aef9834818c00d46b31b) + + These attestations are considered "voting Badges" and allow an individual to vote in any given iteration of Retro Funding. They were used up to and including Retro Round 4. + + | **Schema UID** | **`0xfdcfdad2dbe7489e0ce56b260348b7f14e8365a8a325aef9834818c00d46b31b`** | + | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | Issuer | Currently, the Optimism Foundation issues these from one of the following addresses: `0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9` or `0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F` | + | Recipient | The Badgeholder's address | + | rpgfRound | The round number for which this voting Badge was valid | + | referredBy | In early rounds, new Badges were issued by referral. This field captures the address of the referrer, if there was one | + | referredMethod | If this voting Badge was issued by referral, this field captures the referral method | + + [**Project identifier**](https://optimism.easscan.org/schema/view/0x7ae9f4adabd9214049df72f58eceffc48c4a69e920882f5b06a6c69a3157e5bd) + + Used as the unique identifier for projects created in the Collective before 23 August 2024. Attestations issued from this schema prior to 23 August 2024 are still used as the unique identifier for projects. New projects created after 23 August 2024 use the new entity identifier (see above). + + | **Schema UID** | **`0x7ae9f4adabd9214049df72f58eceffc48c4a69e920882f5b06a6c69a3157e5bd`** | + | -------------- | --------------------------------------------------------------------------------------------------------------- | + | Issuer | Attestations issued as part of Retro Funding sign up are issued by `0xF6872D315CC2E1AfF6abae5dd814fd54755fE97C` | + | Recipient | Null | + | farcasterID | The Farcaster id of the individual who created the project | + + - [\*\*RetroPGF 3 Approved Application schema UID](https://optimism.easscan.org/schema/view/0xebbf697d5d3ca4b53579917ffc3597fb8d1a85b8c6ca10ec10039709903b9277):\*\*`0xebbf697d5d3ca4b53579917ffc3597fb8d1a85b8c6ca10ec10039709903b9277`. Important: Remember to verify the attester address is `0x621477dBA416E12df7FF0d48E14c4D20DC85D7D9` + - [\*\*RetroPGF 3 Application schema UID](https://optimism.easscan.org/schema/view/0x76e98cce95f3ba992c2ee25cef25f756495147608a3da3aa2e5ca43109fe77cc):\*\* `0x76e98cce95f3ba992c2ee25cef25f756495147608a3da3aa2e5ca43109fe77cc` + - [\*\*RetroPGF 3 Lists schema UID](https://optimism.easscan.org/schema/view/0x3e3e2172aebb902cf7aa6e1820809c5b469af139e7a4265442b1c22b97c6b2a5):\*\* `0x3e3e2172aebb902cf7aa6e1820809c5b469af139e7a4265442b1c22b97c6b2a5` + - [**Season 4 Co-grant participant schema UID**](https://optimism.easscan.org/schema/view/0x401a80196f3805c57b00482ae2b575a9f270562b6b6de7711af9837f08fa0faf): `0x401a80196f3805c57b00482ae2b575a9f270562b6b6de7711af9837f08fa0faf`. Important: Remember to verify the attester address is `0x3C7820f2874b665AC7471f84f5cbd6E12871F4cC` or `0x2a0eB7cAE52B68e94FF6ab0bFcf0dF8EeEB624be` + - [\*\*Optimist Profile schema UID](https://optimism.easscan.org/schema/view/0xac4c92fc5c7babed88f78a917cdbcdc1c496a8f4ab2d5b2ec29402736b2cf929):\*\* `0xac4c92fc5c7babed88f78a917cdbcdc1c496a8f4ab2d5b2ec29402736b2cf929` + + + + Here you'll find the [Superchain Health Dashboard](https://docs.google.com/spreadsheets/d/1f-uIW_PzlGQ_XFAmsf9FYiUf0N9l_nePwDVrw0D5MXY/edit?gid=584971628#gid=584971628), below are relevant OP trackers, reports, and addresses. + + Governing Documentation + + - [Operating Manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md) + - [Working Constitution of the Optimism Collective](https://gov.optimism.io/t/working-constitution-of-the-optimism-collective/55) + - [Standard Rollup Charter](https://github.com/ethereum-optimism/OPerating-manual/blob/main/Standard%20Rollup%20Charter.md) + - [Law of Chains](https://gov.optimism.io/t/final-law-of-chains-v0-1/) + - [Decentralization Milestone Working Model](https://docs.google.com/spreadsheets/d/1IpL0oTd3AgNBu_eWdjP9EjbQfZjq-_Nd3yU1H2ke3vY/edit?gid=0#gid=0) + - [Decision Diagram Working Model](https://www.figma.com/board/iXqyKmLJeBeplKpJBHDI7G/PUBLIC%3A-Optimism-Decision-Diagram-Working-Model?node-id=0-1&node-type=canvas&t=QLiz1uM1DepwYyHy-0) + - [Optimist Expectations](https://gov.optimism.io/t/optimist-expectations/) + + **OP Trackers** + + - [Optimism GovFund Grants: Public Delivery Tracking](https://docs.google.com/spreadsheets/d/1Ul8iMTsOFUKUmqz6MK0zpgt8Ki8tFtoWKGlwXj-Op34/edit?gid=1179446718#gid=1179446718) + - [OP Token Unlock (Estimated)](https://docs.google.com/spreadsheets/d/1qVMhLmmch3s6XSbiBe8hgD4ntMkPIOhc1WrhsYsQc7M/edit?gid=470961921#gid=470961921) + + **Foundation Budget Reports** + + - These can be found [here](https://gov.optimism.io/c/updates-and-announcements/foundation-budgets/) on the governance forum. + + **Retroactive Public Goods Funding Round Results** + + - These can be found [here](https://retrofunding.optimism.io/round/results) on retrofunding.optimism.io. + + **Relevant Addresses** + + You can find the list of wallets across L1 and OP Mainnet where the Optimism Collective Revenue earned sits [here](https://docs.google.com/spreadsheets/d/1f-uIW_PzlGQ_XFAmsf9FYiUf0N9l_nePwDVrw0D5MXY/edit?gid=155717474#gid=155717474), on the right-hand side of the Collective Contribution page. + + - OP Treasury Address for Foundation Allocated Budget: 0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26 + - This address hold the remaining OP tokens allocated to the Foundation, which the Foundation requires governance approval to access (via annual FND budget proposals). + - OP Treasury Address for Foundation Approved Budget: 0x2501c477D0A35545a387Aa4A3EEe4292A9a8B3F0 + - This is the Foundation's OP Treasury which is available for the Foundation to utilize as the Foundation's budget granted through the initial token allocation. Transactions from this wallet are typically internal operational movements per the Foundation's needs. + - Additional token may be moved from 0x2…a26 to 0x2…3F0 based on governance approval of budgets. + - OP Foundation Grants Wallet: 0x19793c7824Be70ec58BB673CA42D2779d12581BE + - This Foundation wallet is used to make private OP grants. This is topped up from the OP Treasury Foundation Approved Budget wallet 0x2…B3F0 as needed. + - OP Foundation Locked Grants Wallet: 0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F + - This Foundation wallet is used to hold OP for one year lockups. This is topped up from the OP Foundation Grants Wallet 0x1...81BE as needed. + + **Optimism Governance Calendar** + + - You can find a link to the Governance Calendar [here](https://calendar.google.com/calendar/embed?src=c_fnmtguh6noo6qgbni2gperid4k%40group.calendar.google.com&ctz=Europe%2FBerlin). + + + The Optimism Foundation is a Cayman Islands foundation company. It operates to support the establishment of the Optimism Collective, the development of the Optimism ecosystem, and the technology that powers it. + + Consistent with the Collective's Working Constitution, the Foundation strives to: + + - Support the Collective with a formal legal entity, allowing the Foundation to: + - Enter into contracts with third parties, such as service providers. + - Administer intellectual property rights. + - Make required governmental reports and filings. + + **How does the Foundation work?** + + The Optimism Foundation is governed by a Board of Directors and a Supervisor. + + The Board of Directors currently consists of: Abbey Titcomb, Mark Tyneway, Brian Avello, and Jing Wang. The Board's role is to manage the business and affairs of the Foundation. + + The Supervisor is the Cayman Islands firm, DS Limited. Its role is to oversee the Foundation's directors and ensure the observance of their legal obligations. + + The Foundation also employs officers, contractors and service providers to execute on its operational and administrative aims. + + **How is the Foundation held accountable?** + + As a Cayman Islands foundation company, the Foundation is legally accountable to its governing documentation, which sets up the Foundation to defer to the will of the Optimism Collective and its governance. + + There are two governance proposal types specifically targeted towards ensuring that the Foundation and its personnel are accountable to the will of the Collective: + + - **Director removal** - the ability of governance to have a member of the Foundation's Board of Directors removed from service. + - **Rights protections** - a blocking vote, which enables governance to veto any proposed change to the Foundation's governing documents that would materially reduce the rights of OP token holders. + + More information on each of the above proposal types is contained in the [Operating Manual](https://github.com/ethereum-optimism/OPerating-manual). + + diff --git a/docs/public-docs/governance/protocol-upgrades.mdx b/docs/public-docs/governance/protocol-upgrades.mdx new file mode 100644 index 0000000000000..9950d6477d021 --- /dev/null +++ b/docs/public-docs/governance/protocol-upgrades.mdx @@ -0,0 +1,40 @@ +--- +title: Protocol Upgrades +description: Learn how the OP Stack stay up to date with the latest innovations. +--- + +## How does the OP Stack stay up to date with the latest innovation? + +The OP Stack is Optimism's open-source software for deploying next-generation onchain products. The software is licensed under the MIT license, meaning it can be freely used and forked by all parties. The OP Stack has a vibrant core developer community who contribute to the stack, ensuring it reflects the features and values protocol users care about. + +The Superchain is an ecosystem of chains running on Optimism's OP Stack, featuring some of the world's largest enterprises. Superchain networks benefit from shared security, upgrades and services provided by Optimism. Each chain maintains peak performance with access to innovative new features developed anywhere on the stack, continually strengthening the entire Superchain ecosystem. Chains may also configure components of the OP Stack to fit their regulatory and business needs while still benefiting from shared infrastructure and innovation. Learn more about the best way to build on the OP Stack for your business [here](https://www.optimism.io/compare). + +## How are new features added to the OP Stack? + +OP Labs and external contributors determine the feature roadmap. Based on discussions, protocol upgrades are drafted, which go through the protocol upgrade process. + +## What is the protocol upgrade process? + +The protocol upgrade process is designed to make sure the OP Stack does not change against the interests of the businesses building on the platform. This is a key benefit of crypto systems compared to their centralized alternatives. Platform risk is a common risk of [Web 2 platforms](https://a16zcrypto.com/posts/article/when-is-decentralizing-on-a-blockchain-valuable/) and is a key consideration for the largest partners building on the OP Stack. + +![protocol-upgrade-process](/public/img/governance/protocol-upgrade-process.png) + +Protocol upgrades are drafted by OP Labs or other core contributors to the OP Stack. Before they are implemented, they are reviewed by an independent group of developers (the Developer Advisory Board) to ensure the upgrade is well justified. + +After a proposal has been reviewed by the Developer Advisory Board, it enters a 7 day veto period. This allows all impacted stakeholders, namely tokenholders, chains, apps, and end-users to override the DAB's decision if they believe an upgrade disadvantages their interests. This is how platform risk is reduced for key stakeholders of the OP Stack. + +If a proposal is veto'd, it enters an appeals and discussion phase and can be resubmitted. + +For full details about the protocol upgrade process, please see the [Operating Manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md). + +Overall - contributors, tokenholders, chains, apps, and end-users all have a voice. Checks and balances exist so that no single entity (including OP Labs or the Foundation) can unilaterally dictate the future of the OP Stack. + +Platform risk is reduced by distributing veto power across stakeholder groups while maintaining an efficient core developer process. This ensures upgrades happen quickly but can be vetoed if they harm key stakeholders. + +## How can my chain specifically influence the feature roadmap? + +Superchain members who contribute revenue back to the Collective are consulted by OP Labs and core developers about the feature roadmap to ensure their voices are heard. All development happens in the open and chains are encouraged to participate and share their perspectives within various research and development repositories. + +## How does Optimism help my chain achieve decentralization? + +Permissionless Fault Proof OP Chains that have their upgrade keys managed by the Optimism Security Council are classified as Stage 1 in [L2Beat's framework](https://l2beat.com/stages). This distributed group delivers the benefit of security scrutinized upgrades that are managed by Optimism. diff --git a/docs/public-docs/index.mdx b/docs/public-docs/index.mdx new file mode 100644 index 0000000000000..8d63e37c2905f --- /dev/null +++ b/docs/public-docs/index.mdx @@ -0,0 +1,64 @@ +--- +title: "Deploy the OP Stack" +description: "Launch a scalable and customizable Layer 2 Rollup blockchain with Ethereum-grade security - powered by Optimism." +mode: "wide" +--- + + + +## Components + +The OP Stack is an open-source, modular, Ethereum Layer 2 rollup stack. +Before you deploy, it is important to understand the key components and how they come together to create your blockchain. + +- **L1 Smart contracts**: A set of smart contracts to be deployed on Ethereum to bridge between the L1 and L2 domains and manage aspects of the rollup. +- **Sequencer**: A single privileged node that accepts and derives user transactions on the network to construct the blockchain. +- **Batcher**: A sequencer service that publishes L2 transactions onto Ethereum. +Using Ethereum as a data availability layer, the OP Stack inherits Ethereum's security properties by allowing any node to derive the state of the L2 blockchain from L1. +- **Proposer**: A service responsible for publishing the L2 state root to Ethereum which enables user withdrawals of assets. +- **Challenger**: The challenger enforces network security by disputing invalid state roots that have been posted to Ethereum. + +## Deployment + +The following section will walk you through the sequence of steps a chain operator will follow to begin sequencing a chain. + + + + Using a CLI tool called [op-deployer](/chain-operators/tools/op-deployer/overview) you will configure your chain and then deploy the smart contracts on Ethereum. + + + After deploying the L1 smart contracts you will use [op-validator](/chain-operators/tools/op-validator) to verify that your deployment configurations match what you expect. + + + After deploying the L1 smart contracts, you will use [op-deployer](/chain-operators/tools/op-deployer/overview) to generate two files necessary to run nodes on the L2 network: + + * **Genesis file** (`genesis.json`): Initializes the execution client (`op-geth`) + * **Rollup configuration file** (`rollup.json`): Configures the consensus client (`op-node`) + + These files contain all the essential information your services need to interact with Ethereum and the system contracts you deployed. + + + To begin sequencing transactions and building blocks, you will then run an **execution client** and **consensus client** that come together as your **sequencer node**. + + + Next you will run `op-batcher` which will publish user transactions on Ethereum. + + + Then you will run `op-proposer` to publish the L2 state root on Ethereum to enable withdrawals back to Ethereum. + + + Finally you will run `op-challenger` to monitor and dispute any invalid L2 state roots that have been posted. + + + +## Next Steps + +Of course there is a lot more work to do ensure you're operating a highly available chain. +Take a look at some of the [chain operator best practices](/chain-operators/guides/management/best-practices) to get an idea of some of the things you'll need to keep in mind. diff --git a/docs/public-docs/keywords.config.yaml b/docs/public-docs/keywords.config.yaml new file mode 100644 index 0000000000000..2d181cfd8228c --- /dev/null +++ b/docs/public-docs/keywords.config.yaml @@ -0,0 +1,406 @@ +--- +# Keywords config file + +# Metadata Configuration for Documentation +metadata_rules: + # PERSONA + persona: + required: true + multiple: true + min: 1 + validation_rules: + - enum: + - app-developer + - node-operator + - chain-operator + - partner + - protocol-developer + - auditor + - governance-participant + description: "Must select at least one valid persona" + + # CONTENT TYPE + content_type: + required: true + multiple: false + validation_rules: + - enum: + - tutorial # step-by-step instructions + - landing-page # navigation and overview pages + - guide # general how-to content and concept exlainers + - reference # technical specifications and API docs + - troubleshooting # problem-solution focused + - notice # Technical updates: breaking changes, upgrades, deprecations + - announcement # Community/Governance updates: new programs, initiatives + description: "Must select exactly one content type" + + # TOPIC + topic: + required: true + multiple: false + validation_rules: + - pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$ + description: "Must be kebab-case, derived from page title" + examples: ["standard-bridge", "account-abstraction", "ecotone-upgrade"] + - unique: true + description: "Topic must be unique across all pages" + - max_length: 100 + description: "Topic should be concise" + + # CATEGORIES + categories: + required: true + multiple: true + min: 1 + max: 8 + validation_rules: + - no_duplicates: true + description: "Categories must not repeat" + - no_metadata_overlap: + fields: ["topic", "content_type", "persona"] + description: "Categories cannot repeat values used in topic, content_type, or persona" + values: + # Data Availability Layer + - eth-da + - alt-da + - permissionless-batch-submission + - block-times + - data-availability + - data-availability-layer + - blob-configuration + + # Sequencing Layer + - sequencer + - sequencer-pbs + - sequencer-decentralization + - sequencer-in-a-box + - op-batcher + - batcher-configuration + - outages + - downtime + - optimism-portal + - transaction-submission + - transaction-inclusion + - censorship-resistance + - disaster-recovery + - forced-transactions + - sequencing-layer + + # Derivation Layer + - rollup-node + - rollup-configuration + - consensus-client + - op-node + - fault-proofs + - fp-contracts + - dispute-game + - dispute-resolution + - fault-tolerance + - fault-proof-system + - contests + - span-batches + - virtual-machine + - op-challenger + - safe-head + - derivation + - derivation-pipeline + - derivation-layer + - batch-submission + - cannon + - zk + - op-workbench # Tool: derivation testing & simulation + + # Execution Layer + - op-geth + - op-reth + - op-erigon + - op-nethermind + - evm-equivalence + - precompiles + - predeploys + - preinstalls + - custom-gas-token + - gas + - gas-configuration + - fee-optimization + - node-configuration + - gas-optimization + - rpc + - mempool + - block-production + - block-execution + - transactions + - conditional-transactions + - l2-contracts + - transaction-management + - account-abstraction + - paymasters + - l1-data-fee + - execution-gas-fee + - execution-client + - eip-1559 + - sequencer-fee-vault + - dev-console # Tool: execution layer interaction + - execution-layer + + # Settlement Layer + - l1-contracts + - superchain-contracts + - op-contracts + - standard-bridge + - custom-bridge + - superchain-erc20 + - deposits + - proxy + - withdrawals + - teleportr + - bridging + - token-standard + - token-bridge + - eth-bridging + - superchain-eth-bridge + - superchain-token-bridge + - token-deployment + - event-reading + - block-safety + - dependency-set + - interoperability + - cross-chain-messaging + - cross-domain-messenger + - message-relaying + - message-passing + - message-validation + - transaction-flow + - l1-l2-communication + - l2-output-submission + - message-proving + - message-finalization + - l2-to-l1 + - token-transfers + - interoperable-assets + - reorgs + - op-deployer # Tool: contract deployment + - op-supervisor + - transaction-finality + - ethereum-consensus + - finality-guarantees + - transaction-lifecycle + - rollup-transactions + - state-commitment + - contract-addresses + - settlement-layer + - proposer-configuration + + # Governance Layer + - security-council + - guardian + - multisig + - governance + - optimism-collective + - superchain-ecosystem + - token-house + - citizens-house + - delegates + - voting + - audits + - privileged-roles + - security-policy + - security-model + - security-reviews + - vulnerability-assessment + - bug-bounty-program + - responsible-disclosure + - vulnerability-reporting + - safety-mechanisms + - pause + - emergency-response + - op-token + - blockspace-charters + - retro-funding + - revshare-enshrinement + - superchain + - superchain-registry + - contract-upgrades + - governance-layer + + # Cross-layer Development Tools + - architecture + - op-stack + - supersim # Tests across multiple layers + - devnet # Full-stack local environment + - performance-tooling # Stack-wide performance testing + - l1-deployment-upgrade-tooling # Cross-layer deployment + - l2-deployment-upgrade-tooling # Cross-layer deployment + - testnet-tooling # testnet-specific tools like faucets, block explorers, etc + - deployment-tooling # deployment-specific tools like OPCM + - testing-environment + - testing + - alphanets + - betanets + - l2-rollup + - chain-deployment + - layer2-scaling + - system-components + - smart-contracts + - deployment-artifacts + - local-development-environment + - genesis-configuration + - genesis-creation + - chain-configuration + - docker + + # Chain Management (Cross-layer) + - protocol + - infrastructure + - op-proposer + - op-supervisor + - op-conductor + - op-signer + - mcp + - mcp-l2 + - upgrade-standard-chains-stage-1 + - launch-new-chains-stage-1 + - automated-pause + - dispute-mon + - monitorism + - vertical-scaling + - chain-operation + + # Protocol Releases + - granite + - holocene + - isthmus + - canyon + - delta + - ecotone + - fjord + - network-upgrade + - hard-fork + - hardfork-activation + - protocol-upgrades + + # Infrastructure & Operations + - kubernetes-infrastructure + - devops-tooling + - artifacts-packaging + - peer-management-service + - node-management + - proxyd + - zdd-service + - snapman + - op-beat + - consensus-awareness + - load-balancing + - monitoring # General monitoring tools and services + - analytics # Analytics platforms and data analysis + - security-monitoring-response + - stability-monitoring + - security + - research + - beta-features + - experimental + - feature-testing + - decentralization + - modularity + - system-design + - system-configuration + - development-networks + - network-upgrades + - key-management + + # Development Languages & SDKs + - solidity + - typescript + - javascript + - go + - rust + - python + - foundry + - hardhat + - ethers + - viem + - web3js + - wagmi + - cli # Command Line Interfaces + - api # Application Programming Interfaces + + # Development Environments + - local-devnet + - testnet + - mainnet + + # Test Networks + - sepolia # Ethereum L1 testnet + - op-sepolia # OP Stack L2 testnet + - base-sepolia # Base testnet + - zora-sepolia # Zora testnet + + # IS_IMPORTED_CONTENT + is_imported_content: + required: true + multiple: false + validation_rules: + - enum: + - 'true' + - 'false' + description: "Must be either 'true' for imported/duplicate pages or 'false' for original pages" + + # TIMEFRAME + timeframe: + required_for: + - announcement + - notice + required: false + multiple: false + validation_rules: + # Component versions + - pattern: ^(op-\w+|cannon)/v\d+\.\d+\.\d+$ + description: "Component releases must be in format component/vX.Y.Z" + examples: ["op-node/v1.11.0", "op-batcher/v1.11.1"] + + # Release candidates + - pattern: ^(op-\w+)/v\d+\.\d+\.\d+-rc\.\d+$ + description: "Release candidates must be in format component/vX.Y.Z-rc.N" + examples: ["op-node/v1.11.0-rc.2"] + + # Alpha/Beta versions + - pattern: ^(op-\w+|cannon)/v\d+\.\d+\.\d+-(alpha|beta)\.\d+$ + description: "Alpha/Beta releases must be in format component/vX.Y.Z-{alpha|beta}.N" + examples: ["cannon/v1.4.0-alpha.1"] + + # Season format + - pattern: ^S[6-9]|S[1-9][0-9]+$ + description: "Season numbers must start with 'S' followed by a number 6 or greater" + examples: ["S6", "S7", "S8", "S10"] + + # Calendar year + - pattern: ^20(2[4-9]|[3-9][0-9])$ + description: "Years must be 2024 or later" + examples: ["2024", "2025", "2026"] + + # Half year + - pattern: ^20(2[4-9]|[3-9][0-9])H[1-2]$ + description: "Half years must be in format YYYYH1 or YYYYH2" + examples: ["2024H1", "2024H2", "2025H1"] + + # Quarter + - pattern: ^20(2[4-9]|[3-9][0-9])Q[1-4]$ + description: "Quarters must be in format YYYYQ1-Q4" + examples: ["2024Q1", "2024Q2", "2025Q3"] + + # Month + - pattern: ^20(2[4-9]|[3-9][0-9])-(0[1-9]|1[0-2])$ + description: "Months must be in format YYYY-MM" + examples: ["2024-01", "2024-12", "2025-06"] + + # Protocol upgrades + - enum: + - bedrock + - canyon + - delta + - ecotone + - fjord + - granite + - holocene + - isthmus + description: "Protocol upgrade names must match exactly" \ No newline at end of file diff --git a/docs/public-docs/node-operators/guides/configuration/consensus-clients.mdx b/docs/public-docs/node-operators/guides/configuration/consensus-clients.mdx new file mode 100644 index 0000000000000..ee88c5bcb5a80 --- /dev/null +++ b/docs/public-docs/node-operators/guides/configuration/consensus-clients.mdx @@ -0,0 +1,112 @@ +--- +title: Consensus client configuration +description: Learn how to configure consensus clients (op-node, kona-node) for your OP Stack node. +--- + +The consensus client (also called the rollup node) builds, relays, and verifies the canonical chain of blocks. +This guide covers configuration for the most popular consensus client implementations. + + +Always run your consensus client and execution client in a one-to-one configuration. +Don't run multiple execution client instances behind one consensus client, or vice versa. + + +## Consensus clients + +Choose the consensus client that best fits your needs: + + + + ## op-node configuration + + [op-node](https://github.com/ethereum-optimism/optimism/tree/develop/op-node) is the reference implementation of the OP Stack consensus client, written in Go. + + ### Minimal configuration + + The minimum required flags for running op-node: + + ```bash + op-node \ + --l1=https://ethereum-rpc-endpoint.example.com \ + --l1.beacon=https://ethereum-beacon-endpoint.example.com \ + --l2=http://localhost:8551 \ + --l2.jwt-secret=/path/to/jwt-secret.txt \ + --network=op-mainnet \ + --rpc.addr=0.0.0.0 \ + --rpc.port=9545 + ``` + + ### Sequencer configuration + + If you're running a sequencer node, use these additional flags: + + ```bash + --rpc.addr=0.0.0.0 \ + --rpc.port=9545 \ + --rpc.enable-admin \ + --sequencer.enabled \ + --sequencer.l1-confs=4 \ + --p2p.sequencer.key= + ``` + + + Keep your sequencer private key secure and never commit it to version control. + Use environment variables or secure key management systems in production. + + + ### l2.enginekind + + The kind of engine client, used to control the behavior of optimism in respect to different types of engine clients. + Supported values are `geth` (default) and `reth`. + + ### Complete reference + + For all available configuration options, see the [op-node configuration reference](/node-operators/reference/op-node-config). + + + + ## kona-node configuration + + [kona-node](https://github.com/op-rs/kona/tree/main/bin/node) is an experimental Rust-based implementation of the OP Stack consensus client. + + + kona-node is experimental and may not be production-ready. Use with caution. + + + ### Basic configuration + + kona-node follows a similar configuration pattern to op-node: + + ```bash + kona-node \ + --l1-rpc-url=https://ethereum-rpc-endpoint.example.com \ + --l2-rpc-url=http://localhost:8551 \ + --jwt-secret=/path/to/jwt-secret.txt \ + --network=op-mainnet + ``` + + ### JWT secret + + Generate the JWT secret using: + + ```bash + openssl rand -hex 32 > jwt-secret.txt + ``` + + ### Additional resources + + For more details on kona-node configuration, see: + - [kona-node GitHub repository](https://github.com/op-rs/kona/tree/main/bin/node) + - [kona documentation](https://op-rs.github.io/kona/) + + + +## JWT secret + +To communicate with execution client and enable the Engine API, you'll also need to generate a JWT secret file and enable the consensus client's authenticated RPC endpoint. +To generate the JWT secret file: + +```bash +openssl rand -hex 32 > jwt-secret.txt +``` + diff --git a/docs/public-docs/node-operators/guides/configuration/execution-clients.mdx b/docs/public-docs/node-operators/guides/configuration/execution-clients.mdx new file mode 100644 index 0000000000000..30763478cc610 --- /dev/null +++ b/docs/public-docs/node-operators/guides/configuration/execution-clients.mdx @@ -0,0 +1,132 @@ +--- +title: Execution client configuration +description: Learn how to configure execution clients (op-geth, op-reth, Nethermind) for your OP Stack node. +--- + +The execution client provides the EVM execution environment and processes transactions. +This guide covers configuration for the most popular execution client implementations. + + +Always run your execution client and consensus client in a one-to-one configuration. +Don't run multiple execution client instances behind one consensus client, or vice versa. + + +## Execution clients + +Choose the execution client that best fits your needs: + + + + ## op-geth configuration + + [op-geth](https://github.com/ethereum-optimism/op-geth) is a minimal fork of go-ethereum optimized for the OP Stack. + + + Although the Docker image is called `op-geth`, the actual binary is still named `geth` to minimize differences from go-ethereum. + See the [op-geth diff viewer](https://op-geth.optimism.io/?utm_source=op-docs&utm_medium=docs) for details. + + + ### Minimal configuration + + Minimumal configuration for op-geth: + + ```bash + geth \ + --datadir=/data/optimism \ + --http \ + --http.addr=0.0.0.0 \ + --http.port=8545 \ + --http.api=eth,net,web3 \ + --authrpc.jwtsecret=/path/to/jwt-secret.txt \ + --op-network=op-mainnet \ + --rollup.sequencerhttp=https://mainnet-sequencer.optimism.io/ \ + --rollup.disabletxpoolgossip + ``` + + This will utilize primarily the default settings i.e. snap sync mode, no WebSocket server, no metrics, etc. + + ### OP Stack specific flags + + - `--rollup.sequencerhttp`: HTTP endpoint of the sequencer for transaction submission + - `--rollup.disabletxpoolgossip`: Disables transaction pool gossip (recommended for replica nodes) + - `--rollup.historicalrpc`: Enables historical RPC endpoint for upgraded networks (OP Mainnet pre-bedrock archive nodes) + + This file must be identical for both op-geth and your consensus client. + + ### Complete reference + + For all available configuration options, see the [op-geth configuration reference](/node-operators/reference/op-geth-config). + + + + ## op-reth configuration + + [op-reth](https://github.com/paradigmxyz/reth) is a Rust-based execution client optimized for performance. + + ### Minimal configuration + + Minimum required flags for op-reth: + + ```bash + op-reth node \ + --chain optimism \ + --rollup.sequencer https://mainnet-sequencer.optimism.io \ + --http \ + --ws \ + --authrpc.port 9551 \ + --authrpc.jwtsecret /path/to/jwt.hex + ``` + + ### Complete reference + + For all available configuration options, see the [op-reth configuration reference](/node-operators/reference/op-reth-config). + If you are using the `op-rs` fork for historical proofs, see the [historical proof configuration](/node-operators/reference/op-reth-historical-proof-config). + + ### Additional resources + + For more details on op-reth configuration: + - [op-reth documentation](https://reth.rs/run/opstack) + - [op-reth configuration documentation](https://reth.rs/run/configuration) + + + + ## Nethermind configuration + + [Nethermind](https://github.com/NethermindEth/nethermind) is a .NET-based execution client with enterprise features. + + ### Installation + + Download Nethermind from the [releases page](https://github.com/NethermindEth/nethermind/releases) or build from source. + + ### Initialization + + Nethermind uses a configuration file approach. Create a configuration file or use command-line flags. + + ### Minimal configuration + + Minimum required flags for Nethermind: + + ```bash + nethermind \ + -c op-mainnet \ + --data-dir path/to/data/dir \ + --jsonrpc-jwtsecretfile path/to/jwt.hex + ``` + + ### Additional resources + + For more details on Nethermind configuration: + - [Nethermind documentation](https://docs.nethermind.io/get-started/running-node/l2-networks) + - [Nethermind configuration reference](https://docs.nethermind.io/fundamentals/configuration) + + + +## JWT secret + +To communicate with consensus client and enable the Engine API, you'll also need to generate a JWT secret file and enable the execution client's authenticated RPC endpoint. +To generate the JWT secret file: + +```bash +openssl rand -hex 32 > jwt-secret.txt +``` + diff --git a/docs/public-docs/node-operators/guides/configuration/legacy-geth.mdx b/docs/public-docs/node-operators/guides/configuration/legacy-geth.mdx new file mode 100644 index 0000000000000..2dbaf8ffb8602 --- /dev/null +++ b/docs/public-docs/node-operators/guides/configuration/legacy-geth.mdx @@ -0,0 +1,141 @@ +--- +title: Legacy Geth configuration +description: Learn how to configure Legacy Geth for serving historical execution traces on upgraded OP Stack networks. +--- + +Legacy Geth (`l2geth`) is required only for upgraded networks like OP Mainnet to serve historical execution traces from before the Bedrock upgrade. +If you're running a node that had a chain genesis after the Bedrock upgrade, you do not need Legacy Geth. + +## Historical execution vs. historical data routing + +Only requests for historical execution will be routed to Legacy Geth. +Everything else will be served by `op-geth` directly. +The term *historical execution* refers to RPC methods that need to execute transactions prior to bedrock (not just read data from the database): + +* `eth_call` +* `eth_estimateGas` +* `debug_traceBlockByNumber` +* `debug_traceBlockByHash` +* `debug_traceCall` +* `debug_traceTransaction` + +If you do not need these RPC methods for historical data, then you do not need to run Legacy Geth at all. + +## What is Legacy Geth? + +Legacy Geth is the old `l2geth` binary running against a preconfigured data directory. +It serves historical execution data from before the Bedrock upgrade. + +## Setup + +### 1. Download the preconfigured data directory + +Download and extract the Legacy Geth data directory, which is available at [datadirs.optimism.io](https://datadirs.optimism.io). + +```bash +# Download the data directory +curl -o legacy-geth-datadir.tar -sL + +# Extract it +tar -xvf legacy-geth-datadir.tar -C /data/legacy-geth +``` + +### 2. Configure Legacy Geth + +Run Legacy Geth with the minimum required configuration: + +```bash +USING_OVM=true \ + ETH1_SYNC_SERVICE_ENABLE=false \ + RPC_API=eth,rollup,net,web3,debug \ + RPC_ADDR=0.0.0.0 \ + RPC_CORS_DOMAIN=* \ + RPC_ENABLE=true \ + RPC_PORT=8545 \ + RPC_VHOSTS=* \ + geth --datadir /data/legacy-geth +``` + + +It is imperative that you specify the `USING_OVM=true` environment variable. +Failing to specify this will cause `l2geth` to return invalid execution traces or panic at startup. + + +### 3. Configure op-geth to route to Legacy Geth + +Update your op-geth configuration to route historical requests to Legacy Geth: + +```bash +geth \ + --datadir=/data/optimism \ + --rollup.historicalrpc=http://localhost:8545 \ + # ... other flags +``` + +The `--rollup.historicalrpc` flag tells op-geth where to route requests for historical execution. + +## Environment variables + +Legacy Geth accepts the following environment variables: + +| Variable | Description | Default | +|----------|-------------|---------| +| `USING_OVM` | **Required**. Enables OVM mode | N/A (must be set to `true`) | +| `ETH1_SYNC_SERVICE_ENABLE` | Enables L1 sync service | `true` (set to `false` for read-only) | +| `RPC_API` | Enabled RPC APIs | `eth,net,web3` | +| `RPC_ADDR` | RPC listening address | `localhost` | +| `RPC_CORS_DOMAIN` | CORS domains | `localhost` | +| `RPC_ENABLE` | Enable RPC server | `false` | +| `RPC_PORT` | RPC port | `8545` | +| `RPC_VHOSTS` | Virtual hosts | `localhost` | + +## Historical execution vs. historical data + +Only requests for **historical execution** will be routed to Legacy Geth. +Everything else will be served by op-geth directly. + +### Historical execution RPC methods + +These methods require transaction execution and are routed to Legacy Geth for pre-Bedrock blocks: + +- `eth_call` +- `eth_estimateGas` +- `debug_traceBlockByNumber` +- `debug_traceBlockByHash` +- `debug_traceCall` +- `debug_traceTransaction` + +### Historical data RPC methods + +These methods only read data from the database and are served by op-geth: + +- `eth_getBlockByNumber` +- `eth_getBlockByHash` +- `eth_getTransactionByHash` +- `eth_getTransactionReceipt` +- `eth_getLogs` + +## Troubleshooting + + + `l2geth` is based on an old version of geth where trace functionality is unstable. + It is no longer maintained and will not be updated. + + +### Legacy Geth won't start + +**Problem**: `l2geth` panics or returns errors on startup + +**Solution**: Ensure `USING_OVM=true` is set in your environment variables + +### Invalid execution traces + +**Problem**: Legacy Geth returns invalid or incorrect trace data + +**Solution**: Verify that `USING_OVM=true` is set and the data directory is complete and uncorrupted + +### op-geth not routing to Legacy Geth + +**Problem**: Historical execution requests are not being routed to Legacy Geth + +**Solution**: Check that `--rollup.historicalrpc` is set on op-geth with the correct URL to Legacy Geth diff --git a/docs/public-docs/node-operators/guides/management/archive-node.mdx b/docs/public-docs/node-operators/guides/management/archive-node.mdx new file mode 100644 index 0000000000000..cd0c2c7a81763 --- /dev/null +++ b/docs/public-docs/node-operators/guides/management/archive-node.mdx @@ -0,0 +1,118 @@ +--- +title: Running an archive node +description: Learn how to configure and run an archive node. +--- + +This guide shows you how to configure your node to run as an archive node. Archive nodes store the complete history of the blockchain, including all historical states. + +## Overview + +Archive nodes maintain the entire state history of the blockchain, allowing you to query any historical state at any block height. This is useful for: + +* Block explorers that need to provide historical data +* Analytics and data analysis applications +* Services that need to query historical state +* Debugging and auditing purposes + +Archive nodes use execution-layer sync but configure the execution client to retain all historical state data instead of pruning it. + +## Requirements + +* **OP Mainnet**: Requires the [bedrock datadir](/node-operators/guides/management/snapshots) +* **Other OP Stack networks**: No datadir required +* **Storage**: Archive nodes require significantly more disk space than regular nodes (several terabytes for OP Mainnet) +* **Sync time**: Archive sync with execution-layer mode is faster than full block-by-block execution + +## Configuration + +### Configuration for op-node + +Set the following flag on `op-node`: + +```shell +--syncmode=execution-layer +``` + + + The `--syncmode=execution-layer` flag is not the default setting and must be explicitly configured. + + +### Configuration for op-geth + +Set the following flags on `op-geth`: + +```shell +--syncmode=full +--gcmode=archive +``` + + + Both flags are not the default settings and must be explicitly configured. The `--syncmode=full` flag ensures every block is executed, and `--gcmode=archive` disables state pruning. + + +### Configuration for Nethermind + +Archive sync can be enabled by using the archive configuration for your network (configurations with `_archive` suffix): + +```shell +--config op-mainnet_archive +``` + + + Replace `op-mainnet_archive` with the appropriate archive configuration for your network (e.g., `op-sepolia_archive` for OP Sepolia). + + +## Archive mode with alternative clients + +Alternative execution clients such as `reth` and `op-erigon` are designed as archive nodes by default, which means they always maintain the complete history of the chain. When using these clients with execution-layer sync, they will automatically operate in archive mode. + +### Configuration for op-node with reth + +Set the following flags on `op-node`: + +```shell +--syncmode=execution-layer +--l2.enginekind=reth +``` + + + Both flags are not the default setting and must be explicitly configured on `op-node`. `reth` operates as an archive node by default. + + +### Configuration for op-node with op-erigon + +Set the following flags on `op-node`: + +```shell +--syncmode=execution-layer +--l2.enginekind=erigon +``` + + + Both flags are not the default setting and must be explicitly configured on `op-node`. `op-erigon` operates as an archive node by default. + + +## How archive sync works + +With execution-layer sync mode enabled: + +1. **Initial sync**: The node downloads block headers and data through the P2P network +2. **Block execution**: The node executes every block in the chain to build the complete state history +3. **State retention**: Unlike regular nodes, archive nodes never prune historical state data +4. **Faster than legacy**: While still executing all blocks, this is faster than the legacy consensus-layer sync because block data is retrieved via P2P instead of being derived from L1 + +## Storage considerations + +Archive nodes require substantial storage: + +* **OP Mainnet**: Several terabytes and growing +* **Other networks**: Varies by network age and activity +* **Growth rate**: Storage requirements increase continuously as new blocks are added +* **Recommendation**: Use fast SSD storage for optimal performance + +## Next steps + +* See the [Snap Sync guide](/node-operators/guides/management/snap-sync) for non-archive node configuration +* See the [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients) and [Execution Client Configuration](/node-operators/guides/configuration/execution-clients) guides for additional explanation or customization +* See the [Snapshots guide](/node-operators/guides/management/snapshots) for information about downloading the bedrock datadir +* If you experience difficulty at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions) diff --git a/docs/public-docs/node-operators/guides/management/blobs.mdx b/docs/public-docs/node-operators/guides/management/blobs.mdx new file mode 100644 index 0000000000000..72c428699c97b --- /dev/null +++ b/docs/public-docs/node-operators/guides/management/blobs.mdx @@ -0,0 +1,112 @@ +--- +title: Using blobs +description: Learn how to handle blobs for your node. +--- + +The proposed Ecotone upgrade impacts node operators because of the new Beacon +endpoint for `op-node`. Soon after the Ecotone activation, batch transactions +will be sent as 4844 blobs, and blobs can only be retrieved from Beacon nodes. +This means node operators will need access to a beacon node/consensus client +(i.e. Lighthouse, Lodestar, Nimbus, Prysm, Teku, etc.). + +## Preparing your node + +These steps are necessary for EVERY node operator: + + + + Update both your execution client and consensus client to the latest releases. + + + + * If you are operating a node for an OP Chain that has an entry in the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json), + the Ecotone activation date is handled automatically by both execution clients: + + + + The activation date is part of the node configuration. No action needed after upgrading to the latest release. + + + The activation date is included in the built-in chain configuration. No action needed after upgrading to the latest release. + + + + * For node operators of custom chains not included in the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json): + + + + Set the activation time in `rollup.json` (`ecotone_time`) and via CLI overrides + + + Use the chain configuration examples in `src/Nethermind/Chains` as templates for your custom chain configuration + + + + + When using `op-geth`, even if the ecotone activation is configured via the `rollup.json`, it still + needs to be configured separately via command line flag. + + + + + ```shell + --override.ecotone value (default: 0) ($OP_NODE_OVERRIDE_ECOTONE) + Manually specify the Ecotone fork timestamp, overriding the bundled setting + ``` + + + + ```shell + --override.ecotone value (default: 0) ($GETH_OVERRIDE_ECOTONE) + Manually specify the Optimism Ecotone fork timestamp, overriding the bundled + setting + ``` + + + + + + * All node operators must set an L1 beacon value in your `op-node` as soon as you update to the latest release. + + ```shell + --l1.beacon value ($OP_NODE_L1_BEACON) + HTTP endpoint Address of L1 Beacon-node. + ``` + + * Either run your own L1 beacon node like [Lighthouse](https://lighthouse-book.sigmaprime.io/run_a_node.html) or use a third-party beacon node RPC service, like [Quicknode](https://www.quicknode.com/docs/ethereum/eth-v1-beacon-genesis). + + + +## Configure a blob archiver + +There is a configurable `beacon-archiver` that will allow nodes to sync blob data that is older than 18 days - after blobs are 18 days old, normal beacon nodes will "prune" or remove them. If your node is already in sync with the head of the chain, you won't need to use a `beacon-archiver`. + +* If you're spinning up a new node, if you load it from a snapshot that's within 18 days (the amount of time until blobs are pruned) you will not need to use a `beacon-archiver` at all as long as your node does not fall offline for more than 18 days. +* If you're running a new node that is syncing more than 18 days (the amount of time until blobs are pruned) after Ecotone launch, then you will need to configure a `beacon-archiver` on the `op-node`. + +```shell +--l1.beacon-archiver value ($OP_NODE_L1_BEACON) + HTTP Endpoint of a Blob Archiver or an L1 Beacon-node that does not prune blobs +``` + +Choose one of the following options to access a beacon archiver endpoint: + +* **Option 1:** Run a beacon node with blobs pruning disabled. For example, you can run your own L1 beacon node like [Lighthouse](https://lighthouse-book.sigmaprime.io/run_a_node.html) and configure it to retain all blobs and use that endpoint here. + +```shell +# lighthouse +--prune-blobs=false +``` + +* **Option 2:** Run a `blob-archiver` and configure `op-node` to use the `blob-archiver` API service with `--l1.beacon-archiver`. + Running a `blob-archiver` is lighter weight than running a beacon node that does not prune blobs: [https://github.com/base-org/blob-archiver](https://github.com/base-org/blob-archiver). There is a configurable `beacon-archiver` that will allow nodes to sync blob data that is older than 18 days - after blobs are 18 days old, normal beacon nodes will "prune" or remove them. If your node is already in sync with the head of the chain, you won't need to use a `beacon-archiver`. + * If you're spinning up a new node, if you load it from a snapshot that's within 18 days (the amount of time until blobs are pruned) you will not need to use a `beacon-archiver` at all as long as your node does not fall offline for more than 18 days. + * If you're running a new node that is syncing more than 18 days (the amount of time until blobs are pruned) after Ecotone launch, then you will need to configure a `beacon-archiver` on the `op-node`. + +```shell +--l1.beacon-archiver value ($OP_NODE_L1_BEACON) + HTTP Endpoint of a Blob Archiver or an L1 Beacon-node that does not prune blobs +``` + +* **Option 3:** If you don't want to operate any Beacon infrastructure, you can use an external service that provides access to pruned Blobs. + diff --git a/docs/public-docs/node-operators/guides/management/regenesis-history.mdx b/docs/public-docs/node-operators/guides/management/regenesis-history.mdx new file mode 100644 index 0000000000000..73e5303059040 --- /dev/null +++ b/docs/public-docs/node-operators/guides/management/regenesis-history.mdx @@ -0,0 +1,69 @@ +--- +title: Accessing pre-regenesis history +description: Learn how to access pre-regenesis history using the Etherscan CSV exporting tool. +--- + +This tutorial explains how to access transaction history between 23 June 2021 and the final regenesis. +Because of our final regenesis on 11 November 2021, older transactions are not part of the current blockchain and do not appear on [Etherscan](https://explorer.optimism.io/?utm_source=op-docs&utm_medium=docs). + +## Dune access + +You can use a query on [Dune Analytics](https://dune.com), similar to [this query](https://dune.com/queries/354886?addr=%5Cx25E1c58040f27ECF20BBd4ca83a09290326896B3). +You have to log on with a Dune account, but their free tier is sufficient. + + + + + + + + + + + + + + + +Alternatively, to run custom queries in Dune, you can use the `optimism_legacy_ovm1` schema defined in [Dune Docs here](https://dune.com/docs/data-tables/?h=ovm#optimism). + +## Lost data directories + +Three data directories that were used by legacy L2Geth Sequencer instances +during the period of January 2021 to July 2021 had been errantly deleted during +an infra cleanup in August 2023. + +These data directories contained information about the effects of transactions, +once executed. This information can only be obtained by properly executing the +transaction chain. The most valuable data within these directories was (1) events +emitted by smart contracts during each transaction and (2) the success state of +the transaction (whether or not the transaction executed or reverted). This +information is valuable for tracking things like ETH transfers or ERC-20 token +transfers. Without this we can still know the final set of balances but the +intermediate balances become opaque. + +The transaction data for this period of time was published to a smart contract +on Ethereum called the [CanonicalTransactionChain](https://etherscan.io/address/0x5e4e65926ba27467555eb562121fac00d24e9dd2). +While it is theoretically possible to recover the data by downloading and +re-executing this chain of transactions from Ethereum, this is a labor intensive +and costly task that may not fully recover the data. The OP Labs team did +attempt data recovery efforts, including reaching out to several partners. + +### Impact + +No state, balances, or user assets were lost. Most of the impact is felt by data +providers who want complete data sets for analysis purposes and by individuals +who may want this information for tax purposes. + +Since this was very early during the history of OP Mainnet there are relatively +few transactions in this period and this data is infrequently requested. Most +requests for this data came from individuals who needed access to this information +for the 2021 tax season though this is mostly no longer relevant today (many +people who needed this data already retrieved it). + +### Going forward + +We recognize the inconvenience this has caused some of our community and their +users and we're sorry for the frustrations. In an effort to prevent similar +situations from happening again in the future, we are evaluating and updating +existing processes and frameworks. diff --git a/docs/public-docs/node-operators/guides/management/snap-sync.mdx b/docs/public-docs/node-operators/guides/management/snap-sync.mdx new file mode 100644 index 0000000000000..9ae40f396543b --- /dev/null +++ b/docs/public-docs/node-operators/guides/management/snap-sync.mdx @@ -0,0 +1,93 @@ +--- +title: Using snap sync for node operators +description: Learn how to use and enable snap sync on your node. +--- + +This guide shows you how to enable and configure Snap Sync for your node. For an overview of how snap sync works and its benefits, see the [Snap Sync feature page](/node-operators/reference/features/snap-sync). + +## Enable snap sync for your node + + +For snap sync, all `op-geth` nodes should expose port `30303` TCP and `30303` UDP to easily find other op-geth nodes to sync from. + * If you set the port with [`--discovery.port`](/node-operators/reference/op-geth-config#discoveryport), then you must open the port specified for UDP. + * If you set [`--port`](/node-operators/reference/op-geth-config#port), then you must open the port specified for TCP. + * The only exception is for sequencers and transaction ingress nodes. + + +Snap sync requires no datadir on OP Mainnet. With snap sync, `op-node` tells the execution client to snap sync, and then the execution client downloads the state at tip and once complete switches to inserting blocks one by one. + +### Configuration for op-node + +The following flag is not the default setting and must be explicitly configured on `op-node`: + +```shell +--syncmode=execution-layer +``` + +### Configuration for op-geth + +The following flag is the default setting for `op-geth` to enable snap sync, so you don't need to set it explicitly: + +```shell +--syncmode=snap +``` + +### Configuration for Nethermind + + + A single restart of `Nethermind` during Snap Sync may extend the sync time by up to 2 hours because `Nethermind` has to rebuild the caches by reading millions of values from the database. + + +```shell +--config op-mainnet +--Sync.SnapSync=true +``` + +## Enabling execution layer sync for alternative clients + +In addition to `op-geth` and `Nethermind`, you can enable execution-layer syncing with alternative execution clients such as `reth` and `op-erigon`. + +Unlike `op-geth` and `Nethermind`, `reth` and `op-erigon` are designed as archive nodes, which means they require the complete history of the chain. +However, these clients can still retrieve block headers and data through the P2P network instead of deriving each individual block, resulting in a faster initial sync. + +For OP Mainnet, the [bedrock datadir](/node-operators/guides/management/snapshots) is required. For other OP Stack networks, no datadir is required. + +### Configuration for op-node with reth + +Set the following flags on `op-node`: + +```shell +--syncmode=execution-layer +--l2.enginekind=reth +``` + + + Both flags are not the default setting and must be explicitly configured on `op-node`. + + +### Configuration for op-node with op-erigon + +Set the following flags on `op-node`: + +```shell +--syncmode=execution-layer +--l2.enginekind=erigon +``` + + + Both flags are not the default setting and must be explicitly configured on `op-node`. + + +## Alternative sync modes + +Snap sync is the recommended sync mode for most node operators, but other sync modes are available depending on your needs: + +* **Archive nodes**: If you need to maintain complete historical state, see the [Archive Node guide](/node-operators/guides/management/archive-node) +* **Consensus-layer sync**: For independent L1-based verification without P2P networking, see the [Consensus-Layer Sync reference](/node-operators/reference/consensus-layer-sync) + +## Next steps + +* See the [Snap Sync feature page](/node-operators/reference/features/snap-sync) for an overview of how snap sync works and its benefits. +* See the [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients) and [Execution Client Configuration](/node-operators/guides/configuration/execution-clients) guides for additional explanation or customization. +* To enable snap sync for your chain, see [Using Snap Sync for Chain Operators](/chain-operators/guides/features/snap-sync). +* If you experience difficulty at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions). diff --git a/docs/public-docs/node-operators/guides/monitoring/metrics.mdx b/docs/public-docs/node-operators/guides/monitoring/metrics.mdx new file mode 100644 index 0000000000000..2946ca2a84950 --- /dev/null +++ b/docs/public-docs/node-operators/guides/monitoring/metrics.mdx @@ -0,0 +1,66 @@ +--- +title: Node Metrics and Monitoring +description: Learn about the different metrics you can use to monitor the health of your node. +--- + +The Optimism `op-node` exposes a variety of metrics to help observe the health of the system and debug issues. Metrics are formatted for use with Prometheus and exposed via a metrics endpoint. The default metrics endpoint is `http://localhost:7300/metrics`. + +To enable metrics, pass the `--metrics.enabled` flag to the `op-node`. You can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively. + +## Important metrics + +To monitor the health of your node, you should monitor the following metrics: + +* `op_node_default_refs_number`: This metric represents the `op-node`'s current L1/L2 reference block number for different sync types. If it stops increasing, it means that the node is not syncing. If it goes backwards, it means your node is reorging. +* `op_node_default_peer_count`: This metric represents how many peers the `op-node` is connected to. Without peers, the `op-node` cannot sync unsafe blocks and your node will lag behind the sequencer as it will fall back to syncing purely from L1. +* `op_node_default_rpc_client_request_duration_seconds`: This metric measures the latency of RPC requests initiated by the `op-node`. This metric is important when debugging sync performance, as it will reveal which specific RPC calls are slowing down sync. This metric exposes one timeseries per RPC method. The most important RPC methods to monitor are: + * `engine_forkChoiceUpdatedV1`, `engine_getPayloadV1`, and `engine_newPayloadV1`: These methods are used to execute blocks on `op-geth`. If these methods are slow, it means that sync time is bottlenecked by either `op-geth` itself or your connection to it. + * `eth_getBlockByHash`, `eth_getTransactionReceipt`, and `eth_getBlockByNumber`: These methods are used by the `op-node` to fetch transaction data from L1. If these methods are slow, it means that sync time is bottlenecked by your L1 RPC. + +## Available metrics + +A complete list of available metrics is below: + +| METRIC | DESCRIPTION | LABELS | TYPE | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------ | --------- | +| op\_node\_default\_info | Pseudo-metric tracking version and config info | version | gauge | +| op\_node\_default\_up | 1 if the op node has finished starting up | | gauge | +| op\_node\_default\_rpc\_server\_requests\_total | Total requests to the RPC server | method | counter | +| op\_node\_default\_rpc\_server\_request\_duration\_seconds | Histogram of RPC server request durations | method | histogram | +| op\_node\_default\_rpc\_client\_requests\_total | Total RPC requests initiated by the opnode's RPC client | method | counter | +| op\_node\_default\_rpc\_client\_request\_duration\_seconds | Histogram of RPC client request durations | method | histogram | +| op\_node\_default\_rpc\_client\_responses\_total | Total RPC request responses received by the opnode's RPC client | method,error | counter | +| op\_node\_default\_l1\_source\_cache\_size | L1 Source cache size | type | gauge | +| op\_node\_default\_l1\_source\_cache\_get | L1 Source cache lookups, hitting or not | type,hit | counter | +| op\_node\_default\_l1\_source\_cache\_add | L1 Source cache additions, evicting previous values or not | type,evicted | counter | +| op\_node\_default\_l2\_source\_cache\_size | L2 Source cache size | type | gauge | +| op\_node\_default\_l2\_source\_cache\_get | L2 Source cache lookups, hitting or not | type,hit | counter | +| op\_node\_default\_l2\_source\_cache\_add | L2 Source cache additions, evicting previous values or not | type,evicted | counter | +| op\_node\_default\_derivation\_idle | 1 if the derivation pipeline is idle | | gauge | +| op\_node\_default\_pipeline\_resets\_total | Count of derivation pipeline resets events | | counter | +| op\_node\_default\_last\_pipeline\_resets\_unix | Timestamp of last derivation pipeline resets event | | gauge | +| op\_node\_default\_unsafe\_payloads\_total | Count of unsafe payloads events | | counter | +| op\_node\_default\_last\_unsafe\_payloads\_unix | Timestamp of last unsafe payloads event | | gauge | +| op\_node\_default\_derivation\_errors\_total | Count of derivation errors events | | counter | +| op\_node\_default\_last\_derivation\_errors\_unix | Timestamp of last derivation errors event | | gauge | +| op\_node\_default\_sequencing\_errors\_total | Count of sequencing errors events | | counter | +| op\_node\_default\_last\_sequencing\_errors\_unix | Timestamp of last sequencing errors event | | gauge | +| op\_node\_default\_publishing\_errors\_total | Count of p2p publishing errors events | | counter | +| op\_node\_default\_last\_publishing\_errors\_unix | Timestamp of last p2p publishing errors event | | gauge | +| op\_node\_default\_unsafe\_payloads\_buffer\_len | Number of buffered L2 unsafe payloads | | gauge | +| op\_node\_default\_unsafe\_payloads\_buffer\_mem\_size | Total estimated memory size of buffered L2 unsafe payloads | | gauge | +| op\_node\_default\_refs\_number | Gauge representing the different L1/L2 reference block numbers | layer,type | gauge | +| op\_node\_default\_refs\_time | Gauge representing the different L1/L2 reference block timestamps | layer,type | gauge | +| op\_node\_default\_refs\_hash | Gauge representing the different L1/L2 reference block hashes truncated to float values | layer,type | gauge | +| op\_node\_default\_refs\_seqnr | Gauge representing the different L2 reference sequence numbers | type | gauge | +| op\_node\_default\_refs\_latency | Gauge representing the different L1/L2 reference block timestamps minus current time, in seconds | layer,type | gauge | +| op\_node\_default\_l1\_reorg\_depth | Histogram of L1 Reorg Depths | | histogram | +| op\_node\_default\_transactions\_sequenced\_total | Count of total transactions sequenced | | gauge | +| op\_node\_default\_p2p\_peer\_count | Count of currently connected p2p peers | | gauge | +| op\_node\_default\_p2p\_stream\_count | Count of currently connected p2p streams | | gauge | +| op\_node\_default\_p2p\_gossip\_events\_total | Count of gossip events by type | type | counter | +| op\_node\_default\_p2p\_bandwidth\_bytes\_total | P2P bandwidth by direction | direction | gauge | +| op\_node\_default\_sequencer\_building\_diff\_seconds | Histogram of Sequencer building time, minus block time | | histogram | +| op\_node\_default\_sequencer\_building\_diff\_total | Number of sequencer block building jobs | | counter | +| op\_node\_default\_sequencer\_sealing\_seconds | Histogram of Sequencer block sealing time | | histogram | +| op\_node\_default\_sequencer\_sealing\_total | Number of sequencer block sealing jobs | | counter | diff --git a/docs/public-docs/node-operators/guides/troubleshooting.mdx b/docs/public-docs/node-operators/guides/troubleshooting.mdx new file mode 100644 index 0000000000000..fd831a42f57a5 --- /dev/null +++ b/docs/public-docs/node-operators/guides/troubleshooting.mdx @@ -0,0 +1,193 @@ +--- +title: Node Troubleshooting +description: Learn solutions to common problems to troubleshoot your node. +--- + +This page lists common troubleshooting scenarios and solutions for node operators. + +## 401 Unauthorized: Signature Invalid + +If you see a log that looks like this in `op-node`: + +``` +WARN [12-13|15:53:20.263] Derivation process temporary error attempts=80 err="stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch current L2 forkchoice state: failed to find the finalized L2 block: failed to determine L2BlockRef of finalized, could not get payload: 401 Unauthorized: signature is invalid +``` + +It means that the `op-node` is unable to authenticate with `execution client`'s authenticated RPC using the JWT secret. + +### Solution + +1. Check that the JWT secret is correct in both services. +2. Check that `execution client`'s authenticated RPC is enabled, and that the URL is correct. + +## 403 Forbidden: Invalid Host Specified + +If you see a log that looks like this in `op-node`: + +``` +{"err":"403 Forbidden: invalid host specified\n","lvl":"error","msg":"error getting latest header","t":"2022-12-13T22:29:18.932833159Z"} +``` + +It means that you have not whitelisted `op-node`'s host with `execution client`. + +### Solution + +1. Make sure that the `--authrpc.vhosts` parameter in `execution client` is either set to the correct host, or `*`. +2. Check that `execution client`'s authenticated RPC is enabled, and that the URL is correct. + +## Failed to Load P2P Config + +If you see a log that looks like this in `op-node`: + +``` +CRIT [12-13|13:46:21.386] Application failed message="failed to load p2p config: failed to load p2p discovery options: failed to open discovery db: mkdir /p2p: permission denied" +``` + +It means that the `op-node` lacks write access to the P2P discovery or peerstore directories. + +### Solution + +1. Make sure that the `op-node` has write access to the P2P directory. By default, this is `/p2p`. +2. Set the P2P directory to somewhere the `op-node` can access via the `--p2p.discovery.path` and `--p2p.peerstore.path` parameters. +3. Set the discovery path to `memory` to disable persistence via the `--p2p.discovery.path` and `--p2p.peerstore.path` parameters. + +## Wrong Chain + +If you see a log that looks like this in `op-node`: + +``` +{"attempts":183,"err":"stage 0 failed resetting: temp: failed to find the L2 Heads to start from: wrong chain L1: genesis: 0x4104895a540d87127ff11eef0d51d8f63ce00a6fc211db751a45a4b3a61a9c83:8106656, got 0x12e2c18a3ac50f74d3dd3c0ed7cb751cc924c2985de3dfed44080e683954f1dd:8106656","lvl":"warn","msg":"Derivation process temporary error","t":"2022-12-13T23:31:37.855253213Z"} +``` + +It means that the `op-node` is pointing to the wrong chain. + +### Solution + +1. Verify that the `op-node`'s L1 URL is pointing to the correct L1 for the given network. +2. Verify that the `op-node`'s rollup config/`--network` parameter is set to the correct network. +3. Verify that the `op-node`'s L2 URL is pointing to the correct instance of `execution client`, and that `execution client` is properly initialized for the given network. + +## Error: `eth_sendRawTransaction` Does Not Exist + +If an RPC call to your execution client (`op-geth`, Nethermind, etc.) returns a response like: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -32601, + "message": "the method eth_sendRawTransaction does not exist/is not available" + } +} +``` + +This `-32601` JSON-RPC error means the sequencer endpoint you configured does not expose `eth_sendRawTransaction`. The request path looks like: + +``` +Client → eth_sendRawTransaction → op-geth:8545 + ↓ + op-geth forwards to sequencer + ↓ + eth_sendRawTransaction → op-node:8547 + ↓ + ❌ ERROR: "method does not exist/is not available" + (op-node has no eth namespace!) +``` + +Because `op-node` only exposes rollup-specific RPC methods—there is no `eth_*` namespace—it cannot accept raw transaction submissions. When `op-geth` forwards the transaction to an `op-node` URL it immediately fails with `-32601`. + +This situation almost always happens when: + +* `--rollup.sequencerhttp` (or `ROLLUP_SEQUENCER_HTTP`) is pointed to your own `op-node` instead of the hosted public sequencer. +* The sequencer container was launched with that flag by mistake, so your node tries to submit transactions to itself. + +For canonical Optimism networks you must point `--rollup.sequencerhttp` at the public sequencer endpoint published in the network config. For user-deployed L2s that host their own sequencer, do not set this option at all so the execution client talks directly to your local sequencer and never forwards to an `op-node`. + +### Solution + +1. Confirm your execution client exposes the `eth` namespace on its HTTP API (`--http.api eth,net,engine,...` for geth/`op-geth`). If `eth` is disabled, raw transactions will be rejected before they reach the sequencer. +2. Inspect the environment/CLI flags of every component that speaks to the sequencer (`op-node`, `execution client`, `op-batcher`, `op-proposer`, scripts). The `--rollup.sequencerhttp` flag must point to the public sequencer URL for the chain (for example, `https://mainnet-sequencer.optimism.io`), **not** to your own `op-node`. +3. For user-deployed L2s where you run the sequencer yourself, leave `--rollup.sequencerhttp` unset so the client forwards locally and never falls back to an `op-node` endpoint. +4. If you templated docker-compose or systemd files, make sure `ROLLUP_SEQUENCER_HTTP` is not being overwritten by another `.env` file or compose override. Remove any accidental references to your local node. +5. Restart the affected services after correcting the flag so they pick up the new endpoint. The error should disappear as soon as they can reach the proper sequencer RPC. + +## Unclean Shutdowns + +If you see a log that looks like this in `execution client`: + +``` +WARN [03-05|16:18:11.238] Unclean shutdown detected booted=2023-03-05T11:09:26+0000 age=5h8m45s +``` + +It means `execution client` has experienced an unclean shutdown. The [geth docs](https://geth.ethereum.org/docs/fundamentals/databases#unclean-shutdowns) +say if Geth stops unexpectedly, the database can be corrupted. This is known as an +"unclean shutdown" and it can lead to a variety of problems for the node when +it is restarted. + +### For op-geth + +It is always best to shut down Geth gracefully, i.e. using a +shutdown command such as `ctrl-c`, `docker stop -t 300 ` or +`systemctl stop` (although please note that `systemctl stop` has a default timeout +of 90s - if Geth takes longer than this to gracefully shut down it will quit +forcefully. Update the `TimeoutSecs` variable in `systemd.service` to override this +value to something larger, at least 300s). + +This way, Geth knows to write all relevant information into the database to +allow the node to restart properly later. This can involve >1GB of information +being written to the LevelDB database which can take several minutes. + +**Solution** + +In most cases, `op-geth` can recover automatically from an unclean shutdown when you restart it. The warning message is informational and doesn't necessarily indicate a problem. + +However, if `op-geth` fails to restart properly after an unclean shutdown, you can use the `removedb` subcommand as a last resort: + +```bash +geth removedb --datadir= +``` + + + This command will delete all state database data. After running `removedb`, + you must manually restart the node to begin the resync process from the ancient database, + which can take significant time. Only use this when the node actually fails to restart, + not for every unclean shutdown warning. + + +### For Nethermind + +Unclean shutdowns in `Nethermind` can lead to database corruption. This typically happens when: + +* The node experiences hardware failures (disk failures, memory errors, overheating) +* Power cuts cause abrupt shutdowns +* The process is terminated without proper cleanup + +**Solutions** + +1. **Lock File Issues** + + If `Nethermind` complains about lock files after an unclean shutdown, run: + + ```bash + find /path/to/nethermind_db -type f -name 'LOCK' -delete + ``` + +2. **Block Checksum Mismatch** + + If you encounter block checksum mismatch errors, you can enable direct I/O: + + ```bash + --Db.UseDirectIoForFlushAndCompactions true + ``` + Note: This may impact performance. + +3. **Complete Resync** + + In cases of severe corruption, a full resync is recommended: + + ```bash + sudo systemctl stop nethermind + sudo rm -rf /path/to/nethermind_db/mainnet + sudo systemctl start nethermind + ``` diff --git a/docs/public-docs/node-operators/overview.mdx b/docs/public-docs/node-operators/overview.mdx new file mode 100644 index 0000000000000..595a4ecb274a7 --- /dev/null +++ b/docs/public-docs/node-operators/overview.mdx @@ -0,0 +1,50 @@ +--- +title: Node Operator Overview +description: Learn about running nodes on OP Stack networks. +--- + +## Overview + +This section of the documentation is dedicated to node operators who want to learn about configuring and running nodes on OP Stack networks. +Because the OP Stack is an open-source, modular, and extensible stack, there are many different clients, configurations, and requirements depending on your goals and the specific network you're targeting. +The information provided in this section covers standard configurations and features on the OP Stack. + +## Why Run a Node? + +Running your own node gives you the benefit of trustless verification, enhanced privacy, and gives you local access to the blockchain. +However, it also requires time and resources to set up and maintain. +So you should consider your goals and use cases before deciding to run a node because there are many third-party RPC providers available. + +## Node Architecture + +Regardless of which OP Stack network you're running a node for, all nodes share the same fundamental two-client architecture. +There is the consensus client and the execution client, which communicate via the Engine API with JWT authentication. + +### Consensus Layer (Rollup Node) + +The consensus layer builds, relays and verifies the canonical chain of blocks. +The following are the most popular implementations: + +* **[op-node](https://github.com/ethereum-optimism/optimism/tree/develop/op-node)** +* **[kona-node](https://github.com/ethereum-optimism/optimism/tree/develop/rust/kona/bin/node)** + +### Execution Layer + +The execution layer provides the evm execution environment. +The following are the most popular implementations: + +* **[op-geth](https://github.com/ethereum-optimism/op-geth)** +* **[op-reth](https://github.com/ethereum-optimism/optimism/tree/develop/rust/op-reth)** +* **[Nethermind](https://github.com/NethermindEth/nethermind)** + +## Node Types + +Different node types serve different purposes: + +* **Full node**: keeps a complete copy of the blockchain, validates all transactions and blocks, and participates on the P2P network. +* **Archive node**: additionally retains all historical state for every block. +* **Sequencer node**: can be a full or archive node, but it can create new L2 blocks. + +## Next steps + +* [**Run a Node from Docker**](/node-operators/tutorials/node-from-docker) diff --git a/docs/public-docs/node-operators/reference/architecture/architecture.mdx b/docs/public-docs/node-operators/reference/architecture/architecture.mdx new file mode 100644 index 0000000000000..2629ac09d77f5 --- /dev/null +++ b/docs/public-docs/node-operators/reference/architecture/architecture.mdx @@ -0,0 +1,49 @@ +--- +title: Node architecture +description: Learn about node architecture. +--- + +This page reviews node architecture for all nodes running on OP Stack networks. All OP Mainnet nodes are composed of two core software services, the Rollup Node and the Execution Client. +OP Mainnet also optionally supports a third component, Legacy Geth, that can serve stateful queries for blocks and transactions created before the [Bedrock Upgrade](https://web.archive.org/web/20230608050602/https://blog.oplabs.co/introducing-optimism-bedrock/). + +## Node flow diagram +The following diagram shows how the Rollup Node, Execution Client, and Legacy Geth components work together to form a complete node running on OP Stack networks. +This diagram uses the `op-node` implementation of the Rollup Node and shows the general architecture that applies to all execution client implementations. + +![OP Mainnet node architecture diagram.](/public/img/guides/node-operators/node-arch.svg) + +## Rollup node + +The Rollup Node is responsible for deriving L2 block payloads from L1 data and passing those payloads to the Execution Client. The Rollup Node can also optionally participate in a peer-to-peer network to receive blocks directly from the Sequencer before those blocks are submitted to L1. The Rollup Node is largely analogous to a [consensus client](https://ethereum.org/en/developers/docs/nodes-and-clients/#what-are-nodes-and-clients) in Ethereum. + +## Execution client + +The Execution Client is responsible for executing the block payloads it receives from the Rollup Node over JSON-RPC via the standard [Ethereum Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#engine-api----common-definitions). +The Execution Client exposes the standard JSON-RPC API that Ethereum developers are familiar with, and can be used to query blockchain data and submit transactions to the network. +The Execution Client is largely analogous to an [execution client](https://ethereum.org/en/developers/docs/nodes-and-clients/#what-are-nodes-and-clients) in Ethereum. + +The OP Stack supports multiple execution client implementations: + +### op-geth + +`op-geth` is the original execution client for the OP Stack, based on the Go Ethereum (Geth) implementation. It has been modified to support the specific requirements of OP Stack chains. + +### Nethermind + +Nethermind is a high-performance execution client implementation written in C#. The `Nethermind` client has been adapted to support OP Stack chains, providing an alternative execution client option for node operators. + +## Legacy Geth + +OP Mainnet underwent a large database migration as part of the [Bedrock Upgrade](https://web.archive.org/web/20230608050602/https://blog.oplabs.co/introducing-optimism-bedrock/) in 2023. +Blocks and transactions included in OP Mainnet prior to the Bedrock Upgrade are served by current execution engines but cannot be executed without the help of a special component called Legacy Geth. +This means that you will need to run Legacy Geth if you want to be able to run RPC calls such as `eth_call` on blocks before the Bedrock Upgrade. + +Legacy Geth is the software that was used to run OP Mainnet nodes prior to the Bedrock Upgrade. +If you run an instance of Legacy Geth alongside your OP Mainnet node, your node will be able to forward requests against historical transactions to the Legacy Geth instance. +Legacy Geth is **not** required and is typically only necessary if you want to maintain a complete archive node for OP Mainnet. + +## Next steps + +* To get your node up and running, start with the [run a node from docker](/node-operators/tutorials/node-from-docker) or [build a node from source](/node-operators/tutorials/node-from-source) tutorial. These guides include instructions for both `op-geth` and `Nethermind` execution clients. +* If you've already got your node up and running, check out the [Node Metrics and Monitoring Guide](/node-operators/guides/monitoring/metrics) to learn how to keep tabs on your node and make sure it keeps running smoothly. +* If you run into any problems, please visit the [Node Troubleshooting Guide](/node-operators/guides/troubleshooting) for help. diff --git a/docs/public-docs/node-operators/reference/architecture/rollup-node.mdx b/docs/public-docs/node-operators/reference/architecture/rollup-node.mdx new file mode 100644 index 0000000000000..e9f2750318c31 --- /dev/null +++ b/docs/public-docs/node-operators/reference/architecture/rollup-node.mdx @@ -0,0 +1,132 @@ +--- +title: How to run an OP Stack node +description: Learn how to run an OP Stack rollup node. +--- + +This guide provides an overview of how to run an OP Stack rollup node. It walks you through how to build, configure, run, and monitor your node on one of the OP Stack chains. To skip ahead to building and running a node, you can explore the [node operator tutorials](#node-operator-tutorials). + +## Build your node + +Before building your node, you will learn fundamental aspects of OP Stack rollup nodes. + + + +These are the two fundamental components of an OP Stack rollup node: + + * **Node Architecture**: OP Stack rollup nodes use the rollup node and execution client and can also support legacy geth for pre-bedrock historical execution requests. For more details, see the [Node Architecture](/node-operators/reference/architecture/architecture) guide. + * **Network Upgrades**: Network upgrades for OP Stack rollup nodes are generally [activated by timestamps](/operators/node-operators/network-upgrades#activations). Failing to upgrade your node before the timestamp causes a chain divergence, requiring you to resync your node to reconcile the chain. Follow the established [Node Upgrade Process](/operators/node-operators/network-upgrades#upgrade-process) to avoid chain divergence. + + + If you are building an archive node on OP Mainnet, then you'll need a [node snapshot](/node-operators/guides/management/snapshots). This is **not required** for nodes using [snap sync](/node-operators/guides/management/snap-sync). + + + + + +Now, you are ready to build your OP Stack rollup node. You have two options for this: + + * **Option 1:** Follow the [Running a Node with Docker](/node-operators/tutorials/node-from-docker) tutorial, which gets your OP Stack rollup node up and running without having to build it from source. + * **Option 2:** Follow the [Building a Node from Source](/node-operators/tutorials/node-from-source) tutorial, if you need to use a specific architecture or want to inspect the source code of your OP Stack rollup node. + + + + +## Configure your node + +OP Stack rollup nodes can be configured for individual needs. The following steps will get you started with a working base configuration for OP Stack rollup nodes, along with recommended flags. + + + +* Configure your execution client using the [Execution Client Configuration](/node-operators/guides/configuration/execution-clients) guide + * Configure your consensus client using the [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients) guide + + + + +* Enable [snap sync](/node-operators/guides/management/snap-sync) for your node to significantly improve the experience and speed of syncing an OP Stack node. + This is an **optional** feature but highly recommended for node providers. + + + Additional configuration options exist for [`op-geth`](/node-operators/reference/op-geth-config) and [`op-node`](/node-operators/reference/op-node-config), respectively. + + + + + +## Run your node + +Now, you will run your node and set your node debugging log level for more granular feedback. + + + +You will now run your node from source for your OP Stack network. Here are your options. + + + The tutorial [Building a Node from Source](/node-operators/tutorials/node-from-source) is a **pre-requisite** to running your node from source and must be completed first. + + + * **Option 1:** Follow the [Running an OP Sepolia Node from Source](/node-operators/tutorials/run-node-from-source) tutorial. + * **Option 2:** Follow the [Running an OP Mainnet Node from Source](/node-operators/tutorials/run-node-from-source) tutorial, if you plan to run a full node or archive node. + + + + +As part of running your rollup node, you may want to adjust the log level for more or less granular feedback when debugging. + + * Update node [log level](/node-operators/reference/op-node-config#loglevel) based on individual needs. For more details, see the guide on [Geth Logs](https://geth.ethereum.org/docs/fundamentals/logs). + + + + +## Monitor your node + +It is important to regularly monitor your node, and you can optionally configure Prometheus and Grafana dashboard to make this process easier for you. + + + +* Enable the [metrics port](/node-operators/guides/monitoring/metrics) for your node by passing the `--metrics.enabled` flag to `op-node`. + * Optionally, you can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively. + + + + + + The following steps are intended for `go-ethereum`, so it must be tweaked to work for rollup nodes running on OP Stack networks. + + + For `op-geth`: + * Set up [InfluxDB](https://geth.ethereum.org/docs/monitoring/dashboards#setting-up-influxdb) to hold metrics data + * Set up [Prometheus](https://geth.ethereum.org/docs/monitoring/dashboards#setting-up-prometheus) to read your endpoint + * Set up your [Grafana dashboard](https://geth.ethereum.org/docs/monitoring/dashboards#setting-up-grafana) to provide a UI for metrics + + For `Nethermind`: + * Set up [Grafana and Prometheus](https://docs.nethermind.io/monitoring/metrics/grafana-and-prometheus/#step-1-set-up-grafana-and-prometheus) + * Follow the [metrics documentation](https://docs.nethermind.io/monitoring/metrics/) for detailed monitoring setup + + + + +## Follow node updates + +* It's important to keep your node software up to date. Software updates can also include important bug fixes and patches that can help keep your node stable. +* Notifications are also posted to the Optimism Upgrade Announcement Channels on [**Discord**](https://discord.com/channels/667044843901681675/754090866435424270) and [**Telegram**](https://t.me/+LtAJL1Mt1PYyNjBh). +* You can also stay up to date in the [OP Stack Developer Discord](https://guild.xyz/superchain-devs). + +## Node operator tutorials + + + Got an idea for a new tutorial? + We'd love to hear it. + Head over to GitHub to [suggest a new tutorial](https://github.com/ethereum-optimism/docs/issues/new?assignees=\&labels=tutorial%2Cdocumentation%2Ccommunity-request\&projects=\&template=suggest_tutorial.yaml\&title=%5BTUTORIAL%5D+Add+PR+title). + + +| Tutorial Name | Description | Difficulty Level | +| ----------------------------------------------------------------------- | ------------------------------------------------------ | ---------------- | +| [Running a node with docker](/node-operators/tutorials/node-from-docker) | Learn how to run a node with Docker. | 🟢 Easy | +| [Building an OP Stack node from source](/node-operators/tutorials/node-from-source) | Learn how to compile node components from source code. | 🟢 Easy | +| [Running an OP Stack node from source](/node-operators/tutorials/run-node-from-source) | Learn how to run an OP Stack node from source code. | 🟡 Medium | + +## Next steps + +* If you've already got your node up and running, check out the [Node Metrics and Monitoring Guide](/node-operators/guides/monitoring/metrics) to learn how to keep tabs on your node and make sure it keeps running smoothly. +* If you run into any problems, please visit the [Node Troubleshooting Guide](/node-operators/guides/troubleshooting) for help. diff --git a/docs/public-docs/node-operators/reference/consensus-layer-sync.mdx b/docs/public-docs/node-operators/reference/consensus-layer-sync.mdx new file mode 100644 index 0000000000000..bf6ec55d782a6 --- /dev/null +++ b/docs/public-docs/node-operators/reference/consensus-layer-sync.mdx @@ -0,0 +1,101 @@ +--- +title: Consensus-layer sync +description: Learn about the consensus-layer sync mode. +--- + +This page documents the consensus-layer sync mode, which is the default sync method for op-node but not the recommended approach for most node operators. + +## Overview + +Consensus-layer sync is a sync approach where `op-node` reads transaction data from L1 and derives blocks, then inserts them into the execution client. Unlike execution-layer sync (snap sync), this method does not rely on P2P networking to download state or block data from other L2 nodes. + +While consensus-layer sync is still the default mode, **execution-layer sync (snap sync) is recommended** for faster synchronization and better performance. + +## When to use consensus-layer sync + +This sync mode might be preferred in the following scenarios: + +* **Independent verification**: Decentralized developer groups who need to independently verify the entire chain by deriving all data from L1 +* **No P2P connectivity**: Environments where P2P networking is restricted or unavailable +* **L1-only trust model**: Applications that prefer to trust only L1 data without relying on L2 peer nodes +* **Debugging and research**: Analyzing how blocks are derived from L1 data + + + Consensus-layer sync is significantly slower than execution-layer sync (snap sync). For most node operators, [snap sync](/node-operators/guides/management/snap-sync) is the recommended approach for faster synchronization. + + +## Configuration + +### Configuration for op-node + +Set the following flag on `op-node`: + +```shell +--syncmode=consensus-layer +``` + + + The `--syncmode=consensus-layer` is the default setting for `op-node`. You don't need to specify it explicitly unless you want to be explicit about the sync mode or are overriding a different configuration. + + +### Configuration for op-geth + +Set the following flag on `op-geth`: + +```shell +--syncmode=full +``` + + + The `--syncmode=full` flag is not the default setting and must be explicitly configured. This ensures blocks are inserted by `op-node` rather than synced via P2P. + + +### Configuration for Nethermind + +Set the following flags on `Nethermind`: + +```shell +--config op-mainnet +--Sync.SnapSync=false +--Sync.FastSync=false +``` + + + Replace `op-mainnet` with the appropriate configuration for your network (e.g., `op-sepolia` for OP Sepolia). + + +## How consensus-layer sync works + +The consensus-layer sync process: + +1. **L1 monitoring**: `op-node` continuously monitors the L1 chain for transaction batches +2. **Block derivation**: `op-node` derives L2 blocks from the L1 transaction data +3. **Block insertion**: Derived blocks are inserted into the execution client one by one +4. **State building**: The execution client builds state by executing each block sequentially + +This approach ensures that every block is derived from L1 and independently verified, but it's much slower than downloading state snapshots from peers. + +## Performance considerations + +* **Sync time**: Significantly slower than snap sync or archive sync with execution-layer mode +* **Network requirements**: Requires reliable L1 RPC access but minimal L2 P2P connectivity +* **Resource usage**: Lower P2P bandwidth usage but more L1 RPC calls +* **OP Mainnet**: For OP Mainnet, you'll still need the [bedrock datadir](/node-operators/guides/management/snapshots) for state before the Bedrock upgrade + +## Comparison with other sync modes + +| Feature | Consensus-Layer (Legacy) | Execution-Layer (Snap Sync) | Archive with Execution-Layer | +|---------|-------------------------|----------------------------|------------------------------| +| Data source | L1 only | L2 P2P + L1 | L2 P2P + L1 | +| Sync speed | Slowest | Fastest | Medium | +| State pruning | Yes (by default) | Yes | No | +| Historical state | Not available | Not available | Full history | +| P2P required | No | Yes | Yes | +| Verification | Full L1 derivation | Cryptographic verification | Full execution | + +## Next steps + +* See the [Snap Sync guide](/node-operators/guides/management/snap-sync) for the recommended fast sync method +* See the [Archive Node guide](/node-operators/guides/management/archive-node) for running an archive node +* See the [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients) and [Execution Client Configuration](/node-operators/guides/configuration/execution-clients) guides for additional explanation or customization +* If you experience difficulty at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions) diff --git a/docs/public-docs/node-operators/reference/features/snap-sync.mdx b/docs/public-docs/node-operators/reference/features/snap-sync.mdx new file mode 100644 index 0000000000000..a13a84872a9e8 --- /dev/null +++ b/docs/public-docs/node-operators/reference/features/snap-sync.mdx @@ -0,0 +1,66 @@ +--- +title: Snap Sync +description: Learn how snap sync works and its benefits for node operators. +--- + +Snap Sync is a native feature of execution clients that significantly improves the experience of syncing an OP Stack node. This section provides an overview of snap sync, its benefits, and links to detailed implementation guides. + +## Overview + +Snap Sync works by downloading a snapshot of the state from other nodes on the network. Instead of re-executing every single block from genesis, the node downloads a recent state snapshot and then starts executing blocks from that point forward. This means that performing a Snap Sync is significantly faster than performing a full sync. + +Both `op-geth` and `Nethermind` support Snap Sync, which can be optionally enabled on `op-node` when using either of these execution clients. + +## Benefits of snap sync + +Snap Sync provides several key benefits for node operators: + +* **No datadir required for OP Mainnet**: Node operators can sync without having to download the [bedrock datadir](/node-operators/guides/management/snapshots). +* **Post-Ecotone network joining**: Nodes can join the network after Ecotone activates without requiring a [blob archiver](/node-operators/guides/management/blobs). +* **Archive node support**: Archive nodes are fully supported with Snap Sync. +* **Faster sync times**: By downloading a state snapshot rather than re-executing all blocks, sync times are dramatically reduced. + +## How snap sync works + + + 1. **Initial state download**: + + * The node connects to peers on the network and requests a snapshot of the current state. + * State data is downloaded in parallel from multiple peers to optimize sync speed. + + 2. **State verification**: + + * The downloaded state is cryptographically verified against the state root in recent block headers. + * This ensures the integrity and correctness of the downloaded state. + + 3. **Block execution**: + + * Once the state snapshot is complete, the node switches to inserting blocks one by one. + * From this point forward, the node processes blocks normally like any other synced node. + + +## Supported execution clients + +Snap Sync is supported by the following execution clients: + +* **op-geth**: Native snap sync support with `--syncmode=snap` (enabled by default) +* **Nethermind**: Native snap sync support with `--Sync.SnapSync=true` +* **reth**: Execution-layer sync support (archive mode) +* **op-erigon**: Execution-layer sync support (archive mode) + +## Network requirements + +For snap sync to work properly, nodes need to be able to discover and connect to peers: + +* **Port requirements**: All `op-geth` nodes should expose port `30303` TCP and `30303` UDP +* **Custom ports**: If using custom ports via `--discovery.port` or `--port`, ensure those ports are properly opened +* **Exceptions**: Sequencers and transaction ingress nodes may have different requirements + +## Links to related pages + +For detailed instructions on enabling and configuring snap sync, refer to the following resources: + +* [Using Snap Sync for Node Operators](/node-operators/guides/management/snap-sync) +* [Using Snap Sync for Chain Operators](/chain-operators/guides/features/snap-sync) +* [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients) +* [Execution Client Configuration](/node-operators/guides/configuration/execution-clients) diff --git a/docs/public-docs/node-operators/reference/op-geth-config.mdx b/docs/public-docs/node-operators/reference/op-geth-config.mdx new file mode 100644 index 0000000000000..ce3f63a185c35 --- /dev/null +++ b/docs/public-docs/node-operators/reference/op-geth-config.mdx @@ -0,0 +1,1850 @@ +--- +title: op-geth configuration options +description: Complete reference for all op-geth command-line flags and environment variables. +--- + +This page provides detailed documentation for all available op-geth configuration options, organized by functionality. +The following options are from [v1.101603.3-rc.3](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101603.3-rc.3) + +Please note that the executable is still named `geth` to maintain a [minimal diff](https://op-geth.optimism.io/?utm_source=op-docs&utm_medium=docs). + +The following sections provide detailed documentation for all available op-geth configuration options, organized by functionality. + +### Account management + +Options for managing accounts, keystores, and external signers. + +#### keystore + +Directory for the keystore. The default is inside the data directory. + + + `--keystore ` + `--keystore=/path/to/keystore` + `GETH_KEYSTORE=/path/to/keystore` + + +#### lightkdf + +Reduce key-derivation RAM & CPU usage at some expense of KDF strength. The default value is `false`. + + + `--lightkdf` + `--lightkdf=false` + `GETH_LIGHTKDF=false` + + +#### password + +Password file to use for non-interactive password input. + + + `--password ` + `--password=/path/to/passwordfile` + `GETH_PASSWORD=/path/to/passwordfile` + + +#### pcscdpath + +Path to the smartcard daemon (pcscd) socket file. The default value is `"/run/pcscd/pcscd.comm"`. + + + `--pcscdpath ` + `--pcscdpath=/custom/path/to/pcscd.comm` + `GETH_PCSCDPATH=/custom/path/to/pcscd.comm` + + +#### signer + +External signer (url or path to ipc file). + + + `--signer ` + `--signer=/path/to/ipcfile` + `GETH_SIGNER=/path/to/ipcfile` + + +#### usb + +Enable monitoring and management of USB hardware wallets. The default value is `false`. + + + `--usb` + `--usb=false` + `GETH_USB=false` + + +### RPC and API configuration + +Configure HTTP, WebSocket, authenticated RPC endpoints, and console access. + +#### authrpc.addr + +Listening address for authenticated APIs. The default value is `"localhost"`. + + + `--authrpc.addr ` + `--authrpc.addr="localhost"` + `GETH_AUTHRPC_ADDR="localhost"` + + +#### authrpc.jwtsecret + +Path to a JWT secret to use for authenticated RPC endpoints. + + + `--authrpc.jwtsecret ` + `--authrpc.jwtsecret=/path/to/jwtsecret` + `GETH_AUTHRPC_JWTSECRET=/path/to/jwtsecret` + + +#### authrpc.port + +Listening port for authenticated APIs. The default value is `8551`. + + + `--authrpc.port ` + `--authrpc.port=8551` + `GETH_AUTHRPC_PORT=8551` + + +#### authrpc.vhosts + +Comma separated list of virtual hostnames from which to accept requests (server enforced). +The default value is `"localhost"`. Accepts '\*' wildcard. + + + `--authrpc.vhosts ` + `--authrpc.vhosts="localhost"` + `GETH_AUTHRPC_VHOSTS="localhost"` + + +#### exec + +Execute JavaScript statement. + + + `--exec ` + `--exec="console.log('Hello, World!')"` + `GETH_EXEC="console.log('Hello, World!')"` + + +#### graphql + +Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server +is started as well. The default value is `false`. + + + `--graphql` + `--graphql=false` + `GETH_GRAPHQL=false` + + +#### graphql.corsdomain + +Comma separated list of domains from which to accept cross origin requests (browser enforced). + + + `--graphql.corsdomain ` + `--graphql.corsdomain="http://example.com"` + `GETH_GRAPHQL_CORSDOMAIN="http://example.com"` + + +#### graphql.vhosts + +Comma separated list of virtual hostnames from which to accept requests (server enforced). The default value is `"localhost"`. Accepts '\*' wildcard. + + + `--graphql.vhosts ` + `--graphql.vhosts="localhost"` + `GETH_GRAPHQL_VHOSTS="localhost"` + + +#### header + +Pass custom headers to the RPC server when using `--remotedb` or the geth attach console. This flag can be given multiple times. + + + `--header `, `-H ` + `--header "X-Custom-Header: Value"` + Not applicable for environment variables + + +#### http + +Enable the HTTP-RPC server. The default value is `false`. + + + `--http` + `--http=false` + `GETH_HTTP=false` + + +#### http.addr + +HTTP-RPC server listening interface. The default value is `"localhost"`. + + + `--http.addr ` + `--http.addr="localhost"` + `GETH_HTTP_ADDR="localhost"` + + +#### http.api + +API's offered over the HTTP-RPC interface. + + + `--http.api ` + `--http.api="eth,web3"` + `GETH_HTTP_API="eth,web3"` + + +#### http.corsdomain + +Comma separated list of domains from which to accept cross origin requests (browser enforced). + + + `--http.corsdomain ` + `--http.corsdomain="http://example.com"` + `GETH_HTTP_CORSDOMAIN="http://example.com"` + + +#### http.port + +HTTP-RPC server listening port. The default value is `8545`. + + + `--http.port ` + `--http.port=8545` + `GETH_HTTP_PORT=8545` + + +#### http.rpcprefix`\ + +HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths. + + + `--http.rpcprefix ` + `--http.rpcprefix="/"` + `GETH_HTTP_RPCPREFIX="/"` + + +#### http.vhosts + +Comma separated list of virtual hostnames from which to accept requests (server enforced). The default value is `"localhost"`. Accepts '\*' wildcard. + + + `--http.vhosts ` + `--http.vhosts=localhost` + `GETH_HTTP_VHOSTS=localhost` + + +#### ipcdisable + +Disable the IPC-RPC server. The default value is `false`. + + + `--ipcdisable` + `--ipcdisable=false` + `GETH_IPCDISABLE=false` + + +#### ipcpath + +Filename for IPC socket/pipe within the datadir (explicit paths escape it). + + + `--ipcpath ` + `--ipcpath=/path/to/ipcfile` + `GETH_IPCPATH=/path/to/ipcfile` + + +#### jspath + +JavaScript root path for `loadScript`. The default value is `.` (current directory). + + + `--jspath ` + `--jspath=/path/to/scripts` + `GETH_JSPATH=/path/to/scripts` + + +#### preload + +Comma separated list of JavaScript files to preload into the console. + + + `--preload ` + `--preload="file1.js,file2.js"` + `GETH_PRELOAD="file1.js,file2.js"` + + +#### rpc.allow-unprotected-txs + +Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC. The default value is `false`. + + + `--rpc.allow-unprotected-txs` + `--rpc.allow-unprotected-txs=false` + `GETH_RPC_ALLOW_UNPROTECTED_TXS=false` + + +#### rpc.batch-request-limit + +Maximum number of requests in a batch. The default value is `1000`. + + + `--rpc.batch-request-limit=` + `--rpc.batch-request-limit=1000` + `GETH_RPC_BATCH_REQUEST_LIMIT=1000` + + +#### rpc.batch-response-max-size + +Maximum number of bytes returned from a batched call. The default value is `25000000`. + + + `--rpc.batch-response-max-size=` + `--rpc.batch-response-max-size=25000000` + `GETH_RPC_BATCH_RESPONSE_MAX_SIZE=25000000` + + +#### rpc.evmtimeout + +Sets a timeout used for eth\_call (0=infinite). The default value is `5s`. + + + `--rpc.evmtimeout ` + `--rpc.evmtimeout=5s` + `GETH_RPC_EVMTIMEOUT=5s` + + +#### rpc.gascap + +Sets a cap on gas that can be used in eth\_call/estimateGas (0=infinite). The default value is `50000000`. + + + `--rpc.gascap=` + `--rpc.gascap=50000000` + `GETH_RPC_GASCAP=50000000` + + +#### rpc.txfeecap + +Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap). The default value is `1`. + + + `--rpc.txfeecap=` + `--rpc.txfeecap=1` + Not directly applicable for environment variables + + +#### ws + +Enable the WS-RPC server. The default value is `false`. + + + `--ws` + `--ws=false` + `GETH_WS=false` + + +#### ws.addr + +WS-RPC server listening interface. The default value is `"localhost"`. + + + `--ws.addr=` + `--ws.addr=localhost` + `GETH_WS_ADDR=localhost` + + +#### ws.api + +API's offered over the WS-RPC interface. + + + `--ws.api=` + `--ws.api="eth,web3"` + `GETH_WS_API="eth,web3"` + + +#### ws.origins + +Origins from which to accept websockets requests. + + + `--ws.origins=` + `--ws.origins="http://example.com"` + `GETH_WS_ORIGINS="http://example.com"` + + +#### ws.port + +WS-RPC server listening port. The default value is `8546`. + + + `--ws.port=` + `--ws.port=8546` + `GETH_WS_PORT=8546` + + +#### ws.rpcprefix + +HTTP path prefix on which JSON-RPC is served over WS. Use '/' to serve on all paths. + + + `--ws.rpcprefix=` + `--ws.rpcprefix="/"` + `GETH_WS_RPCPREFIX="/"` + + +### Development Chain + +Options for running a local development chain with pre-funded accounts. + +#### dev + +Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled. +The default value is `false`. + + + `--dev` + `--dev=false` + `GETH_DEV=false` + + +#### dev.gaslimit + +Initial block gas limit. The default value is `11500000`. + + + `--dev.gaslimit=` + `--dev.gaslimit=11500000` + `GETH_DEV_GASLIMIT=11500000` + + +#### dev.period + +Block period to use in developer mode (0 = mine only if transaction pending). The default value is `0`. + + + `--dev.period=` + `--dev.period=0` + `GETH_DEV_PERIOD=0` + + +### Chain and network selection + +Configure which network to connect to and manage blockchain data storage. + +#### bloomfilter.size + +Megabytes of memory allocated to bloom-filter for pruning. The default value is `2048`. + + + `--bloomfilter.size=` + `--bloomfilter.size=2048` + `GETH_BLOOMFILTER_SIZE=2048` + + +#### config + +TOML configuration file. + + + `--config=` + `--config=/path/to/config.toml` + `GETH_CONFIG=/path/to/config.toml` + + +#### datadir + +Data directory for the databases and keystore. The default value is `/home//.ethereum`. + + + `--datadir=` + `--datadir=/home/soyboy/.ethereum` + `GETH_DATADIR=/home/soyboy/.ethereum` + + +#### datadir.ancient + +Root directory for ancient data (default = inside chaindata). + + + `--datadir.ancient=` + Default value inside=chaindata + `GETH_DATADIR_ANCIENT=` + + +#### datadir.era + +Root directory for era1 history (default = inside ancient/chain). + + + `--datadir.era=` + Default value inside ancient/chain + `GETH_DATADIR_ERA=` + + +#### datadir.minfreedisk + +Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled). + + + `--datadir.minfreedisk=` + `--datadir.minfreedisk=0` + `GETH_DATADIR_MINFREEDISK=0` + + +#### db.engine + +Backing database implementation to use ('pebble' or 'leveldb'). + + + `--db.engine=` + `--db.engine=leveldb` + `GETH_DB_ENGINE=leveldb` + + +#### eth.requiredblocks + +Comma separated block number-to-hash mappings to require for peering (`=`). + + + `--eth.requiredblocks=` + `--eth.requiredblocks="12345=0xabcde12345..."` + `GETH_ETH_REQUIREDBLOCKS="12345=0xabcde12345..."` + + +#### exitwhensynced + +Exits after block synchronization completes. The default value is `false`. + + + `--exitwhensynced` + `--exitwhensynced=false` + `GETH_EXITWHENSYNCED=false` + + +#### holesky + +Holesky network: pre-configured proof-of-stake test network. The default value is `false`. + + + `--holesky` + `--holesky=false` + `GETH_HOLESKY=false` + + +#### hoodi + +Hoodi network: pre-configured proof-of-stake test network. The default value is `false`. + + + `--hoodi` + `--hoodi=false` + `GETH_HOODI=false` + + +#### mainnet + +Ethereum mainnet. The default value is `false`. + + + `--mainnet` + `--mainnet=false` + `GETH_MAINNET=false` + + +#### networkid + +Explicitly set network id (integer). The default value is `0`. For testnets: use --sepolia, --holesky, --hoodi instead. + + + `--networkid ` + `--networkid=1` + `GETH_NETWORKID=1` + + +#### op-network, beta.op-network + +Select a pre-configured OP-Stack network (warning: op-mainnet and op-goerli +require special sync, datadir is recommended), options: +arena-z-mainnet, arena-z-sepolia, automata-mainnet, base-devnet-0-sepolia-dev-0, +base-mainnet, base-sepolia, bob-mainnet, camp-sepolia, creator-chain-testnet-sepolia, +cyber-mainnet, cyber-sepolia, ethernity-mainnet, ethernity-sepolia, fraxtal-mainnet, +funki-mainnet, funki-sepolia, hashkeychain-mainnet, ink-mainnet, ink-sepolia, +lisk-mainnet, lisk-sepolia, lyra-mainnet, metal-mainnet, metal-sepolia, mint-mainnet, +mode-mainnet, mode-sepolia, op-mainnet, op-sepolia, oplabs-devnet-0-sepolia-dev-0, +orderly-mainnet, ozean-sepolia, pivotal-sepolia, polynomial-mainnet, race-mainnet, +race-sepolia, radius_testnet-sepolia, redstone-mainnet, rehearsal-0-bn-0-rehearsal-0-bn, +rehearsal-0-bn-1-rehearsal-0-bn, settlus-mainnet-mainnet, settlus-sepolia-sepolia, +shape-mainnet, shape-sepolia, silent-data-mainnet-mainnet, snax-mainnet, +soneium-mainnet, soneium-minato-sepolia, sseed-mainnet, swan-mainnet, swell-mainnet, +tbn-mainnet, tbn-sepolia, unichain-mainnet, unichain-sepolia, worldchain-mainnet, +worldchain-sepolia, xterio-eth-mainnet, zora-mainnet, zora-sepolia + + + `--op-network=` + `--op-network=base-mainnet` + `GETH_OP_NETWORK=base-mainnet` + + +#### override.cancun + +Manually specify the Cancun fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.cancun=` + `--override.cancun=0` + `GETH_OVERRIDE_CANCUN=0` + + +#### override.canyon + +Manually specify the Optimism Canyon fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.canyon=` + `--override.canyon=0` + `GETH_OVERRIDE_CANYON=0` + + +#### override.ecotone + +Manually specify the Optimism Ecotone fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.ecotone=` + `--override.ecotone=0` + `GETH_OVERRIDE_ECOTONE=0` + + +#### override.fjord + +Manually specify the Optimism Fjord fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.fjord=` + `--override.fjord=0` + `GETH_OVERRIDE_FJORD=0` + + +#### override.granite + +Manually specify the Optimism Granite fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.granite=` + `--override.granite=0` + `GETH_OVERRIDE_GRANITE=0` + + +#### override.holocene + +Manually specify the Optimism Holocene fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.holocene=` + `--override.holocene=0` + `GETH_OVERRIDE_HOLOCENE=0` + + +#### `override.interop` + +Manually specify the Optimism Interop feature-set fork timestamp, overriding the bundled setting. +The default value is `0`. + + + `--override.interop=` + `--override.interop=0` + `GETH_OVERRIDE_INTEROP=0` + + +#### override.isthmus + +Manually specify the Optimism Isthmus fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.isthmus=` + `--override.isthmus=0` + `GETH_OVERRIDE_ISTHMUS=0` + + +#### override.jovian + +Manually specify the Optimism Jovian fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.jovian=` + `--override.jovian=0` + `GETH_OVERRIDE_JOVIAN=0` + + +#### override.osaka + +Manually specify the Osaka fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.osaka=` + `--override.osaka=0` + `GETH_OVERRIDE_OSAKA=0` + + +#### override.verkle + +Manually specify the Verkle fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.verkle=` + `--override.verkle=0` + `GETH_OVERRIDE_VERKLE=0` + + +#### sepolia + +Sepolia network: pre-configured proof-of-work test network. The default value is `false`. + + + `--sepolia` + `--sepolia=false` + `GETH_SEPOLIA=false` + + +#### `snapshot` + +Enables snapshot-database mode. The default value is `true`. + + + `--snapshot` + `--snapshot=true` + `GETH_SNAPSHOT=true` + + +### Gas price oracle + +Configure how gas prices are estimated and suggested to users. + +#### gpo.blocks + +Number of recent blocks to check for gas prices. The default value is `20`. + + + `--gpo.blocks=` + `--gpo.blocks=20` + `GETH_GPO_BLOCKS=20` + + +#### gpo.ignoreprice + +Gas price below which GPO will ignore transactions. The default value is `2`. + + + `--gpo.ignoreprice=` + `--gpo.ignoreprice=2` + `GETH_GPO_IGNOREPRICE=2` + + +#### gpo.maxprice + +Maximum transaction priority fee (or gasprice before London fork) to be recommended by GPO. +The default value is `500000000000`. + + + `--gpo.maxprice=` + `--gpo.maxprice=500000000000` + `GETH_GPO_MAXPRICE=500000000000` + + +#### gpo.minsuggestedpriorityfee + +Minimum transaction priority fee to suggest. Used on OP chains when blocks are not full. +The default value is `1000000`. + + + `--gpo.minsuggestedpriorityfee=` + `--gpo.minsuggestedpriorityfee=1000000` + `GETH_GPO_MINSUGGESTEDPRIORITYFEE=1000000` + + +#### gpo.percentile + +Suggested gas price is the given percentile of a set of recent transaction gas prices. The default value is `60`. + + + `--gpo.percentile=` + `--gpo.percentile=60` + `GETH_GPO_PERCENTILE=60` + + +### Logging and debugging + +Control log output, profiling, and debugging tools. + +#### go-execution-trace + +Write Go execution trace to the given file. + + + `--go-execution-trace ` + `--go-execution-trace=/path/to/trace.out` + `GETH_GO_EXECUTION_TRACE=/path/to/trace.out` + + +#### log.compress + +Compress the log files. The default value is `false`. + + + `--log.compress` + `--log.compress=false` + `GETH_LOG_COMPRESS=false` + + +#### log.file + +Write logs to a file. + + + `--log.file=` + `--log.file=/path/to/logfile` + `GETH_LOG_FILE=/path/to/logfile` + + +#### log.format + +Log format to use (json|logfmt|terminal). + + + `--log.format=` + `--log.format=logfmt` + `GETH_LOG_FORMAT=logfmt` + + +#### log.maxage + +Maximum number of days to retain a log file. The default value is `30`. + + + `--log.maxage=` + `--log.maxage=30` + `GETH_LOG_MAXAGE=30` + + +#### log.maxbackups + +Maximum number of log files to retain. The default value is `10`. + + + `--log.maxbackups=` + `--log.maxbackups=10` + `GETH_LOG_MAXBACKUPS=10` + + +#### log.maxsize + +Maximum size in MBs of a single log file. The default value is `100`. + + + `--log.maxsize=` + `--log.maxsize=100` + `GETH_LOG_MAXSIZE=100` + + +#### `log.rotate` + +Enables log file rotation. The default value is `false`. + + + `--log.rotate` + `--log.rotate=false` + `GETH_LOG_ROTATE=false` + + +#### log.vmodule + +Per-module verbosity: comma-separated list of `=` (e.g. eth/\*=5,p2p=4). + + + `--log.vmodule=` + `--log.vmodule="eth/*=5,p2p=4"` + `GETH_LOG_VMODULE="eth/*=5,p2p=4"` + + +#### nocompaction + +Disables database compaction after import. The default value is `false`. + + + `--nocompaction` + `--nocompaction=false` + `GETH_NOCOMPACTION=false` + + +#### pprof + +Enable the pprof HTTP server. The default value is `false`. + + + `--pprof` + `--pprof=false` + `GETH_PPROF=false` + + +#### pprof.addr + +pprof HTTP server listening interface. The default value is `"127.0.0.1"`. + + + `--pprof.addr=` + `--pprof.addr="127.0.0.1"` + `GETH_PPROF_ADDR="127.0.0.1"` + + +#### pprof.blockprofilerate + +Turn on block profiling with the given rate. The default value is `0`. + + + `--pprof.blockprofilerate=` + `--pprof.blockprofilerate=0` + `GETH_PPROF_BLOCKPROFILERATE=0` + + +#### pprof.cpuprofile + +Write CPU profile to the given file. + + + `--pprof.cpuprofile=` + `--pprof.cpuprofile=/path/to/cpu.profile` + + +#### pprof.memprofilerate + +Turn on memory profiling with the given rate. The default value is `524288`. + + + `--pprof.memprofilerate=` + `--pprof.memprofilerate=524288` + `GETH_PPROF_MEMPROFILERATE=524288` + + +#### pprof.port + +pprof HTTP server listening port. The default value is `6060`. + + + `--pprof.port=` + `--pprof.port=6060` + `GETH_PPROF_PORT=6060` + + +#### remotedb + +URL for remote database. + + + `--remotedb=` + `--remotedb=http://example.com/db` + `GETH_REMOTEDB=http://example.com/db` + + +#### verbosity + +Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail. The default value is `3`. + + + `--verbosity=` + `--verbosity=3` + `GETH_VERBOSITY=3` + + +### Metrics and monitoring + +Enable metrics collection and export to monitoring systems. + +#### ethstats + +Reporting URL of an ethstats service (nodename:secret\@host:port). + + + `--ethstats=` + `--ethstats="nodename:secret@host:port"` + `GETH_ETHSTATS="nodename:secret@host:port"` + + +#### metrics + +Enable metrics collection and reporting. The default value is `false`. + + + `--metrics` + `--metrics=false` + `GETH_METRICS=false` + + +#### metrics.addr + +Enable stand-alone metrics HTTP server listening interface. + + + `--metrics.addr=` + `--metrics.addr="127.0.0.1:9090"` + `GETH_METRICS_ADDR="127.0.0.1:9090"` + + +#### metrics.expensive + +Enable expensive metrics collection and reporting. The default value is `false`. + + + `--metrics.expensive` + `--metrics.expensive=false` + `GETH_METRICS_EXPENSIVE=false` + + +#### metrics.influxdb + +Enable metrics export/push to an external InfluxDB database. The default value is `false`. + + + `--metrics.influxdb` + `--metrics.influxdb=false` + `GETH_METRICS_INFLUXDB=false` + + +#### metrics.influxdb.bucket + +InfluxDB bucket name to push reported metrics to (v2 only). The default value is `"geth"`. + + + `--metrics.influxdb.bucket=` + `--metrics.influxdb.bucket="geth"` + `GETH_METRICS_INFLUXDB_BUCKET="geth"` + + +#### metrics.influxdb.database + +InfluxDB database name to push reported metrics to. The default value is `"geth"`. + + + `--metrics.influxdb.database=` + `--metrics.influxdb.database="geth"` + `GETH_METRICS_INFLUXDB_DATABASE="geth"` + + +#### metrics.influxdb.endpoint + +InfluxDB API endpoint to report metrics to. The default value is `"http://localhost:8086"`. + + + `--metrics.influxdb.endpoint=` + `--metrics.influxdb.endpoint="http://localhost:8086"` + `GETH_METRICS_INFLUXDB_ENDPOINT="http://localhost:8086"` + + +#### metrics.influxdb.organization + +InfluxDB organization name (v2 only). The default value is `"geth"`. + + + `--metrics.influxdb.organization=` + `--metrics.influxdb.organization="geth"` + `GETH_METRICS_INFLUXDB_ORGANIZATION="geth"` + + +#### metrics.influxdb.password + +Password to authorize access to the database. The default value is `"test"`. + + + `--metrics.influxdb.password=` + `--metrics.influxdb.password="test"` + `GETH_METRICS_INFLUXDB_PASSWORD="test"` + + +#### metrics.influxdb.tags + +Comma-separated InfluxDB tags (key/values) attached to all measurements. The default value is `"host=localhost"`. + + + `--metrics.influxdb.tags=` + `--metrics.influxdb.tags="host=localhost"` + `GETH_METRICS_INFLUXDB_TAGS="host=localhost"` + + +#### metrics.influxdb.token + +Token to authorize access to the database (v2 only). The default value is `"test"`. + + + `--metrics.influxdb.token=` + `--metrics.influxdb.token="test"` + `GETH_METRICS_INFLUXDB_TOKEN="test"` + + +#### metrics.influxdb.username + +Username to authorize access to the database. The default value is `"test"`. + + + `--metrics.influxdb.username=` + `--metrics.influxdb.username="test"` + `GETH_METRICS_INFLUXDB_USERNAME="test"` + + +#### metrics.influxdbv2 + +Enable metrics export/push to an external InfluxDB v2 database. The default value is `false`. + + + `--metrics.influxdbv2` + `--metrics.influxdbv2=false` + `GETH_METRICS_INFLUXDBV2=false` + + +#### metrics.port + +Metrics HTTP server listening port. The default value is `6060`. +Please note that `--metrics.addr` must be set to start the server. + + + `--metrics.port=` + `--metrics.port=6060` + `GETH_METRICS_PORT=6060` + + +### General options + +Help, version, and other general-purpose flags. + +#### help + +Show help. This is typically used to display command-line options and usage information. + + + `--help` or `-h` + `--help` + + +#### synctarget + +Hash of the block to full sync to (dev testing feature). + + + `--synctarget=` + `--synctarget="0x123...456"` + `GETH_SYNCTARGET="0x123...456"` + + +#### version + + + Nodes built from source do not output the correct version numbers that are reported on + the GitHub release page. + + +Print the version. This option is typically used to display the version of the software. + + + `--version` or `-v` + `--version` + + +### Peer-to-peer networking + +Configure P2P connections, peer discovery, and network settings. + +#### bootnodes + +Comma separated enode URLs for P2P discovery bootstrap. + + + `--bootnodes=` + `--bootnodes=` + `GETH_BOOTNODES=` + + +#### discovery.dns + +Sets DNS discovery entry points (use "" to disable DNS). + + + `--discovery.dns=` + `--discovery.dns=""` + `GETH_DISCOVERY_DNS=""` + + +#### discovery.port + +Use a custom UDP port for P2P discovery. The default value is `30303`. + + + `--discovery.port=` + `--discovery.port=30303` + `GETH_DISCOVERY_PORT=30303` + + +#### discovery.v4 + +Enables the V4 discovery mechanism. The default value is `false`. + + + `--discovery.v4` or `--discv4` + `--discovery.v4=false` + `GETH_DISCOVERY_V4=false` + + +#### discovery.v5 + +Enables the V5 discovery mechanism. The default value is `true`. + + + `--discovery.v5` or `--discv5` + `--discovery.v5=true` + `GETH_DISCOVERY_V5=true` + + +#### identity + +Custom node name. + + + `--identity=` + `--identity="MyNode"` + `GETH_IDENTITY="MyNode"` + + +#### maxpeers + +Maximum number of network peers (network disabled if set to 0). The default value is `50`. + + + `--maxpeers=` + `--maxpeers=50` + `GETH_MAXPEERS=50` + + +#### maxpendpeers + +Maximum number of pending connection attempts (defaults used if set to 0). The default value is `0`. + + + `--maxpendpeers=` + `--maxpendpeers=0` + `GETH_MAXPENDPEERS=0` + + +#### nat + +NAT port mapping mechanism (any|none|upnp|pmp|pmp:``|extip:``|stun:``). The default value is `"any"`. + + + `--nat=` + `--nat="any"` + `GETH_NAT="any"` + + +#### netrestrict + +Restricts network communication to the given IP networks (CIDR masks). + + + `--netrestrict=` + `--netrestrict="192.168.0.0/24"` + `GETH_NETRESTRICT="192.168.0.0/24"` + + +#### nodekey + +P2P node key file. + + + `--nodekey=` + `--nodekey=/path/to/nodekey` + `GETH_NODEKEY=/path/to/nodekey` + + +#### nodekeyhex + +P2P node key as hex (for testing). + + + `--nodekeyhex=` + `--nodekeyhex="abcdef..."` + `GETH_NODEKEYHEX="abcdef..."` + + +#### nodiscover + +Disables the peer discovery mechanism (manual peer addition). The default value is `false`. + + + `--nodiscover` + `--nodiscover=false` + `GETH_NODISCOVER=false` + + +#### port + +Network listening port. The default value is `30303`. + + + `--port=` + `--port=30303` + `GETH_PORT=30303` + + +### Performance and resource management + +Tune memory usage, caching, and other performance-related settings. + +#### `cache` + +Megabytes of memory allocated to internal caching. +The default is `4096` MB for mainnet full node and `128` MB for light mode. + + + `--cache=` + `--cache=1024` + `GETH_CACHE=1024` + + +#### cache.blocklogs + +Size (in number of blocks) of the log cache for filtering. The default value is `32`. + + + `--cache.blocklogs=` + `--cache.blocklogs=32` + `GETH_CACHE_BLOCKLOGS=32` + + +#### cache.database + +Percentage of cache memory allowance to use for database I/O. The default value is `50`. + + + `--cache.database=` + `--cache.database=50` + `GETH_CACHE_DATABASE=50` + + +#### cache.gc + +Percentage of cache memory allowance to use for trie pruning. The default is `25%` for full mode +and `0%` for archive mode. + + + `--cache.gc=` + `--cache.gc=25` + `GETH_CACHE_GC=25` + + +#### cache.noprefetch + +Disable heuristic state prefetch during block import (less CPU and disk IO, more +time waiting for data). The default value is `false`. + + + `--cache.noprefetch` + `--cache.noprefetch=false` + `GETH_CACHE_NOPREFETCH=false` + + +#### cache.preimages + +Enable recording the SHA3/keccak preimages of trie keys. The default value is `false`. + + + `--cache.preimages` + `--cache.preimages=false` + `GETH_CACHE_PREIMAGES=false` + + +#### cache.snapshot + +Percentage of cache memory allowance to use for snapshot caching. +The default is `10%` for full mode and `20%` for archive mode. + + + `--cache.snapshot=` + `--cache.snapshot=10` + `GETH_CACHE_SNAPSHOT=10` + + +#### cache.trie + +Percentage of cache memory allowance to use for trie caching. +The default is `15%` for full mode and `30%` for archive mode. + + + `--cache.trie=` + `--cache.trie=15%` + `GETH_CACHE_TRIE=15%` + + +#### crypto.kzg + +KZG library implementation to use; gokzg (recommended) or ckzg. +The default value is `"gokzg"`. + + + `--crypto.kzg=` + `--crypto.kzg=gokzg` + `GETH_CRYPTO_KZG=gokzg` + + +#### fdlimit + +Raise the open file descriptor resource limit. The default is the system fd limit. + + + `--fdlimit=` + `--fdlimit=0` (system default) + `GETH_FDLIMIT=0` + + +### OP Stack specific options + +Configuration options specific to OP Stack rollup functionality. + +#### rollup.computependingblock + +By default, the pending block equals the latest block to save resources and not leak +transactions from the tx-pool. This flag enables computing of the pending block from +the tx-pool instead. The default value is `false`. + + + `--rollup.computependingblock` + `--rollup.computependingblock=false` + `GETH_ROLLUP_COMPUTEPENDINGBLOCK=false` + + +#### rollup.disabletxpoolgossip + +Disable transaction pool gossip. The default value is `false`. + + + `--rollup.disabletxpoolgossip` + `--rollup.disabletxpoolgossip=false` + `GETH_ROLLUP_DISABLETXPOOLGOSSIP=false` + + +#### rollup.enabletxpooladmission + +Add RPC-submitted transactions to the txpool (on by default if --rollup.sequencerhttp is not set). +The default value is `false`. + + + `--rollup.enabletxpooladmission` + `--rollup.enabletxpooladmission=false` + `GETH_ROLLUP_ENABLETXPOOLADMISSION=false` + + +#### rollup.halt + +Opt-in option to halt on incompatible protocol version requirements of the +given level (major/minor/patch/none), as signaled through the Engine API +by the rollup node. + + + `--rollup.halt=` + `--rollup.halt="major"` + `GETH_ROLLUP_HALT="major"` + + +#### rollup.historicalrpc + +RPC endpoint for historical data. + + + `--rollup.historicalrpc ` + `--rollup.historicalrpc "http://example.com"` + `GETH_ROLLUP_HISTORICALRPC="http://example.com"` + + +#### rollup.historicalrpctimeout + +Timeout for historical RPC requests. The default value is `5s`. + + + `--rollup.historicalrpctimeout=` + `--rollup.historicalrpctimeout=5s` + `GETH_ROLLUP_HISTORICALRPCTIMEOUT=5s` + + +#### rollup.interopmempoolfiltering + +If using interop, transactions are checked for interop validity before being added to the mempool (experimental). +The default value is `false`. + + + `--rollup.interopmempoolfiltering` + `--rollup.interopmempoolfiltering=false` + `GETH_ROLLUP_INTEROPMEMPOOLFILTERING=false` + + +#### rollup.interoprpc + +RPC endpoint for interop message verification (experimental). + + + `--rollup.interoprpc ` + `--rollup.interoprpc="http://example.com"` + `GETH_ROLLUP_INTEROPRPC="http://example.com"` + + +#### rollup.sequencerhttp + +HTTP endpoint for the sequencer mempool. + + + `--rollup.sequencerhttp=` + `--rollup.sequencerhttp="http://example.com"` + `GETH_ROLLUP_SEQUENCERHTTP="http://example.com"` + + +#### rollup.sequencertxconditionalcostratelimit + +Maximum cost -- storage lookups -- allowed for conditional transactions in a given second. +The default value is `5000`. + + + `--rollup.sequencertxconditionalcostratelimit ` + `--rollup.sequencertxconditionalcostratelimit=5000` + `GETH_ROLLUP_SEQUENCERTXCONDITIONALCOSTRATELIMIT=5000` + + +#### rollup.sequencertxconditionalenabled + +Serve the eth_sendRawTransactionConditional endpoint and apply the conditional constraints on mempool inclusion & block building. +The default value is `false`. + + + `--rollup.sequencertxconditionalenabled` + `--rollup.sequencertxconditionalenabled=false` + `GETH_ROLLUP_SEQUENCERTXCONDITIONALENABLED=false` + + +#### rollup.superchain-upgrades + +Apply superchain-registry config changes to the local chain-configuration. +The default value is `true`. + + + `--rollup.superchain-upgrades` or `--beta.rollup.superchain-upgrades` + `--rollup.superchain-upgrades=true` + `GETH_ROLLUP_SUPERCHAIN_UPGRADES=true` + + +### State and history management + +Control how much historical state and transaction data to retain. + +#### gcmode + +Blockchain garbage collection mode, only relevant in `state.scheme=hash`. +Options are "full" and "archive". The default value is `"full"`. + + + `--gcmode=` + `--gcmode="full"` + `GETH_GCMODE="full"` + + +#### history.chain + +Blockchain history retention ("all" or "postmerge"). The default value is `"all"`. + + + `--history.chain=` + `--history.chain="all"` + `GETH_HISTORY_CHAIN="all"` + + +#### history.logs + +Number of recent blocks to maintain log search index for. The default is about one year (`2350000` blocks), with `0` representing the entire chain. + + + `--history.logs=` + `--history.logs=2350000` + `GETH_HISTORY_LOGS=2350000` + + +#### history.logs.disable + +Do not maintain log search index. The default value is `false`. + + + `--history.logs.disable` + `--history.logs.disable=false` + `GETH_HISTORY_LOGS_DISABLE=false` + + +#### history.logs.export + +Export checkpoints to file in go source file format. + + + `--history.logs.export ` + `--history.logs.export="/path/to/export.go"` + `GETH_HISTORY_LOGS_EXPORT="/path/to/export.go"` + + +#### history.state + +Number of recent blocks to retain state history for, only relevant in state.scheme=path. +The default is `90000` blocks, with `0` representing the entire chain. + + + `--history.state=` + `--history.state=90000` + `GETH_HISTORY_STATE=90000` + + +#### history.transactions + +Number of recent blocks to maintain transactions index for. +The default is about one year (`2350000` blocks), with `0` representing the entire chain. + + + `--history.transactions=` + `--history.transactions=2350000` + `GETH_HISTORY_TRANSACTIONS=2350000` + + +#### state.scheme + +Scheme to use for storing Ethereum state. Options are 'hash' or 'path'. + + + `--state.scheme=` + `--state.scheme="hash"` + `GETH_STATE_SCHEME="hash"` + + +#### syncmode + +Blockchain sync mode. Options are "snap", or "full". The default value is `"snap"`. + + + `--syncmode=` + `--syncmode="full"` + `GETH_SYNCMODE="full"` + + +### Transaction pool - Blob transactions + +Configure the blob transaction pool (EIP-4844). + +#### blobpool.datacap + +Disk space to allocate for pending blob transactions (soft limit). +The default value is `2684354560`. + + + `--blobpool.datacap=` + `--blobpool.datacap=2684354560` + `GETH_BLOBPOOL_DATACAP=2684354560` + + +#### blobpool.datadir + +Data directory to store blob transactions in. The default value is `"blobpool"`. + + + `--blobpool.datadir=` + `--blobpool.datadir="blobpool"` + `GETH_BLOBPOOL_DATADIR="blobpool"` + + +#### blobpool.pricebump + +Price bump percentage to replace an already existing blob transaction. +The default value is `100`. + + + `--blobpool.pricebump=` + `--blobpool.pricebump=100` + `GETH_BLOBPOOL_PRICEBUMP=100` + + +### Transaction pool - Standard transactions + +Configure the standard EVM transaction pool. + +#### txpool.accountqueue + +Maximum number of non-executable transaction slots permitted per account. +The default value is `64`. + + + `--txpool.accountqueue=` + `--txpool.accountqueue=64` + `GETH_TXPOOL_ACCOUNTQUEUE=64` + + +#### txpool.accountslots + +Minimum number of executable transaction slots guaranteed per account. +The default value is `16`. + + + `--txpool.accountslots=` + `--txpool.accountslots=16` + `GETH_TXPOOL_ACCOUNTSLOTS=16` + + +#### txpool.globalqueue + +Maximum number of non-executable transaction slots for all accounts. +The default value is `1024`. + + + `--txpool.globalqueue=` + `--txpool.globalqueue=1024` + `GETH_TXPOOL_GLOBALQUEUE=1024` + + +#### txpool.globalslots + +Maximum number of executable transaction slots for all accounts. +The default value is `5120`. + + + `--txpool.globalslots=` + `--txpool.globalslots=5120` + `GETH_TXPOOL_GLOBALSLOTS=5120` + + +#### txpool.journal + +Disk journal for local transactions to survive node restarts. +The default value is `"transactions.rlp"`. + + + `--txpool.journal=` + `--txpool.journal="transactions.rlp"` + `GETH_TXPOOL_JOURNAL="transactions.rlp"` + + +#### txpool.journalremotes + +Includes remote transactions in the journal. The default value is `false`. + + + `--txpool.journalremotes` + `--txpool.journalremotes=false` + `GETH_TXPOOL_JOURNALREMOTES=false` + + +#### txpool.lifetime + +Maximum amount of time non-executable transactions are queued. +The default value is `3h0m0s`. + + + `--txpool.lifetime=` + `--txpool.lifetime=3h0m0s` + `GETH_TXPOOL_LIFETIME=3h0m0s` + + +#### txpool.locals + +Comma-separated accounts to treat as locals (no flush, priority inclusion). + + + `--txpool.locals=` + `--txpool.locals="0x123...,0x456..."` + `GETH_TXPOOL_LOCALS="0x123...,0x456..."` + + +#### txpool.maxtxgas + +Maximum gas limit for individual transactions (0 = no limit). Transactions exceeding this limit will be rejected by the transaction pool. The default value is `0`. + + + `--txpool.maxtxgas=` + `--txpool.maxtxgas=0` + `GETH_TXPOOL_MAXTXGAS=0` + + +#### txpool.nolocals + +Disables price exemptions for locally submitted transactions. The default value is `false`. + + + `--txpool.nolocals` + `--txpool.nolocals=false` + `GETH_TXPOOL_NOLOCALS=false` + + +#### txpool.pricebump + +Price bump percentage to replace an already existing transaction. The default value is `10`. + + + `--txpool.pricebump=` + `--txpool.pricebump=10` + `GETH_TXPOOL_PRICEBUMP=10` + + +#### txpool.pricelimit + +Minimum gas price tip to enforce for acceptance into the pool. The default value is `1`. + + + `--txpool.pricelimit=` + `--txpool.pricelimit=1` + `GETH_TXPOOL_PRICELIMIT=1` + + +#### txpool.rejournal + +Time interval to regenerate the local transaction journal. The default value is `1h0m0s`. + + + `--txpool.rejournal=` + `--txpool.rejournal=1h0m0s` + `GETH_TXPOOL_REJOURNAL=1h0m0s` + + +### EVM configuration + +Options for EVM debugging and instrumentation. + +#### vmdebug + +Record information useful for VM and contract debugging. The default value is `false`. + + + `--vmdebug` + `--vmdebug=false` + `GETH_VMDEBUG=false` + + +#### vmtrace + +Name of tracer which should record internal VM operations (costly). + + + `--vmtrace ` + `--vmtrace="callTracer"` + `GETH_VMTRACE="callTracer"` + + +#### vmtrace.jsonconfig + +Tracer configuration (JSON). The default value is `"{}"`. + + + `--vmtrace.jsonconfig ` + `--vmtrace.jsonconfig="{}"` + `GETH_VMTRACE_JSONCONFIG="{}"` + diff --git a/docs/public-docs/node-operators/reference/op-geth-json-rpc.mdx b/docs/public-docs/node-operators/reference/op-geth-json-rpc.mdx new file mode 100644 index 0000000000000..7040afd31c054 --- /dev/null +++ b/docs/public-docs/node-operators/reference/op-geth-json-rpc.mdx @@ -0,0 +1,679 @@ +--- +title: op-geth JSON-RPC API +description: Complete reference for op-geth execution client RPC methods with OP Stack enhancements. +--- + +`op-geth` is the execution client for the OP Stack, based on Go Ethereum (Geth). It provides the execution layer with Ethereum-equivalent functionality plus OP Stack-specific enhancements for L2 gas accounting and receipts. + +## Overview + +The `op-geth` execution client implements the standard Ethereum JSON-RPC API with minimal modifications. This means you can use familiar Ethereum tools and libraries with OP Stack chains. + + + The execution engine's RPC interface is identical to [the upstream Geth RPC interface](https://geth.ethereum.org/docs/interacting-with-geth/rpc). The key difference is that transaction receipts include additional L1 gas usage and pricing information specific to L2 operations. + + +## Key Differences from Ethereum + +While `op-geth` maintains compatibility with Ethereum's JSON-RPC API, there are important differences: + +### Transaction Receipts + +Transaction receipts in `op-geth` include additional L1 data fee information: + +- **`l1GasUsed`**: Amount of L1 gas used for L1 data availability +- **`l1GasPrice`**: L1 gas price at the time of transaction execution +- **`l1Fee`**: Total L1 data fee charged (in wei) +- **`l1FeeScalar`**: Scalar value used in L1 fee calculation + +This additional information helps you understand the full cost of L2 transactions, which include both L2 execution costs and L1 data availability costs. + +### Gas Price Calculation + +For L2 gas prices, use the standard [`eth_gasPrice`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gasprice) method. + +For L1 gas prices and fee estimation, use the [`GasPriceOracle`](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F) predeployed contract or [the Optimism SDK](/app-developers/tutorials/transactions/sdk-estimate-costs). + +## Standard JSON-RPC Methods + +`op-geth` supports all standard Ethereum JSON-RPC methods. Below are commonly used methods with examples. + +### eth_blockNumber + +Returns the number of the most recent block. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast block-number --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x4b7" +} +``` + +### eth_getBalance + +Returns the balance of the account at the given address. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb","latest"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast balance 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x0234c8a3397aab58" +} +``` + +### eth_getTransactionByHash + +Returns information about a transaction by transaction hash. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast tx 0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "blockHash": "0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2", + "blockNumber": "0x5daf3b", + "from": "0xa7d9ddbe1f17865597fbd27ec712455208b6b76d", + "gas": "0xc350", + "gasPrice": "0x4a817c800", + "hash": "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b", + "input": "0x68656c6c6f21", + "nonce": "0x15", + "to": "0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb", + "transactionIndex": "0x41", + "value": "0xf3dbb76162000", + "type": "0x0", + "chainId": "0xa4b1", + "v": "0x1546d71", + "r": "0x8a8eafb3cf4c5c4c5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e", + "s": "0x8a8eafb3cf4c5c4c5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e5c5e" + } +} +``` + +### eth_getTransactionReceipt + +Returns the receipt of a transaction by transaction hash. This method includes OP Stack-specific L1 fee fields. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast receipt 0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "blockHash": "0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2", + "blockNumber": "0x5daf3b", + "contractAddress": null, + "cumulativeGasUsed": "0x33bc", + "effectiveGasPrice": "0x4a817c800", + "from": "0xa7d9ddbe1f17865597fbd27ec712455208b6b76d", + "gasUsed": "0x4dc", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb", + "transactionHash": "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b", + "transactionIndex": "0x41", + "type": "0x0", + "l1GasUsed": "0x5e0", + "l1GasPrice": "0x3b9aca00", + "l1Fee": "0x16a3f5b9c0", + "l1FeeScalar": "1.0" + } +} +``` + + + Notice the additional fields at the end of the receipt: `l1GasUsed`, `l1GasPrice`, `l1Fee`, and `l1FeeScalar`. These fields are specific to OP Stack chains and provide information about L1 data availability costs. + + +### eth_call + +Executes a new message call immediately without creating a transaction on the blockchain. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0x420000000000000000000000000000000000000F","data":"0x519b4bd3"},"latest"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast call 0x420000000000000000000000000000000000000F "l1BaseFee()" --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x00000000000000000000000000000000000000000000000000000003b9aca00" +} +``` + +### eth_estimateGas + +Generates and returns an estimate of gas needed for a transaction to complete. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_estimateGas","params":[{"from":"0x8D97689C9818892B700e27F316cc3E41e17fBeb9","to":"0xd3CdA913deB6f67967B99D67aCDFa1712C293601","value":"0xde0b6b3a7640000"}],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast estimate 0xd3CdA913deB6f67967B99D67aCDFa1712C293601 --value 1ether --from 0x8D97689C9818892B700e27F316cc3E41e17fBeb9 --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x5208" +} +``` + + + `eth_estimateGas` only estimates L2 execution gas. To calculate the total transaction cost including L1 data fees, use the [Optimism SDK](/app-developers/tutorials/transactions/sdk-estimate-costs) or query the [`GasPriceOracle` predeployed contract](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F). + + +### eth_sendRawTransaction + +Submits a signed transaction to the network. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast publish 0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675 --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" +} +``` + +### eth_getBlockByNumber + +Returns information about a block by block number. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x1b4",true],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast block 0x1b4 --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "hash": "0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x4200000000000000000000000000000000000011", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1b4", + "parentHash": "0x9b83c12c69edb74f6c8dd5d052765c1adf940e320bd1291696e6fa07829eee71", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x27f", + "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff", + "timestamp": "0x54e34e8e", + "totalDifficulty": "0x0", + "transactions": [], + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncles": [] + } +} +``` + +### eth_getLogs + +Returns an array of all logs matching a given filter object. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0x1","toBlock":"0x2","address":"0x8320fe7702b96808f7bbc0d4a888ed1468216cfd"}],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast logs --from-block 1 --to-block 2 --address 0x8320fe7702b96808f7bbc0d4a888ed1468216cfd --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [{ + "address": "0x8320fe7702b96808f7bbc0d4a888ed1468216cfd", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000000000000000000000000000000000000000000fe", + "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "blockNumber": "0x1", + "transactionHash": "0xab059a62e22e230fe0f56d8555340a29b2e9532360368f810595453f6fdd213b", + "transactionIndex": "0x0", + "blockHash": "0x8243343df08b9751f5ca0c5f8c9c0460d8a9b6351066fae0acbd4d3e776de8bb", + "logIndex": "0x0", + "removed": false + }] +} +``` + +### eth_chainId + +Returns the currently configured chain ID, used for signing replay-protected transactions. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast chain-id --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0xa" +} +``` + +## Gas Price Methods + +### eth_gasPrice + +Returns the current gas price in wei for L2 execution. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast gas-price --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x4a817c800" +} +``` + + + This only returns the L2 gas price. For comprehensive transaction cost estimation including L1 data fees, use the [`GasPriceOracle` predeployed contract](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F) or [the Optimism SDK](/app-developers/tutorials/transactions/sdk-estimate-costs). + + +### eth_maxPriorityFeePerGas + +Returns the current maximum priority fee per gas (EIP-1559). + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_maxPriorityFeePerGas","params":[],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast --priority-gas-price --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x3b9aca00" +} +``` + +## Account and State Methods + +### eth_getCode + +Returns code at a given address. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_getCode","params":["0x420000000000000000000000000000000000000F","latest"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast code 0x420000000000000000000000000000000000000F --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806354fd4d501461003b578063..." +} +``` + +### eth_getStorageAt + +Returns the value from a storage position at a given address. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["0x420000000000000000000000000000000000000F","0x0","latest"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast storage 0x420000000000000000000000000000000000000F 0x0 --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x0000000000000000000000000000000000000000000000000000000000000001" +} +``` + +### eth_getTransactionCount + +Returns the number of transactions sent from an address (the nonce). + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x8D97689C9818892B700e27F316cc3E41e17fBeb9","latest"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast nonce 0x8D97689C9818892B700e27F316cc3E41e17fBeb9 --rpc-url http://localhost:8545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": "0x1a" +} +``` + +## Debug and Trace Methods + +`op-geth` supports the same debug and trace methods as upstream Geth, allowing detailed transaction analysis and debugging. + + + Debug and trace methods can be resource-intensive. Most public RPC providers disable these methods. You'll need to run your own node with `--http.api debug,trace` to access them. + + +### debug_traceTransaction + +Returns the trace of a transaction, showing all internal calls and state changes. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"],"id":1}' \ + http://localhost:8545 + ``` + + + ```sh + cast run 0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b --rpc-url http://localhost:8545 --trace + ``` + + + +### debug_traceCall + +Traces a call without executing it on-chain. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"debug_traceCall","params":[{"to":"0x420000000000000000000000000000000000000F","data":"0x519b4bd3"},"latest"],"id":1}' \ + http://localhost:8545 + ``` + + + +## WebSocket Support + +`op-geth` supports WebSocket connections for real-time event subscriptions using the `eth_subscribe` method. + +### eth_subscribe + +Creates a subscription for specific events. + +```javascript +// Connect to WebSocket +const ws = new WebSocket('ws://localhost:8546'); + +// Subscribe to new block headers +ws.send(JSON.stringify({ + "jsonrpc": "2.0", + "method": "eth_subscribe", + "params": ["newHeads"], + "id": 1 +})); + +// Subscribe to logs +ws.send(JSON.stringify({ + "jsonrpc": "2.0", + "method": "eth_subscribe", + "params": [ + "logs", + { + "address": "0x8320fe7702b96808f7bbc0d4a888ed1468216cfd", + "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"] + } + ], + "id": 2 +})); +``` + +### eth_unsubscribe + +Cancels an active subscription. + +```javascript +ws.send(JSON.stringify({ + "jsonrpc": "2.0", + "method": "eth_unsubscribe", + "params": ["0x9cef478923ff08bf67fde6c64013158d"], + "id": 1 +})); +``` + +## Additional Resources + +For a complete list of all supported JSON-RPC methods, refer to: + +- [Geth JSON-RPC Documentation](https://geth.ethereum.org/docs/interacting-with-geth/rpc) +- [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/) +- [OP Stack Transaction Cost Estimation](/app-developers/tutorials/transactions/sdk-estimate-costs) + +## Running a Node with RPC Access + +To run your own `op-geth` node with full RPC access: + +```sh +op-geth \ + --http \ + --http.addr 0.0.0.0 \ + --http.port 8545 \ + --http.api eth,net,web3,debug,trace \ + --ws \ + --ws.addr 0.0.0.0 \ + --ws.port 8546 \ + --ws.api eth,net,web3 \ + ... # additional flags +``` + + + Be cautious when exposing RPC endpoints publicly. Use authentication, rate limiting, and firewall rules to prevent abuse. Debug and trace APIs should only be enabled for trusted clients. + diff --git a/docs/public-docs/node-operators/reference/op-node-config.mdx b/docs/public-docs/node-operators/reference/op-node-config.mdx new file mode 100644 index 0000000000000..4b4515eb058b7 --- /dev/null +++ b/docs/public-docs/node-operators/reference/op-node-config.mdx @@ -0,0 +1,1251 @@ +--- +title: op-node configuration options +description: Complete reference for all op-node command-line flags and environment variables. +--- + +This page provides detailed documentation for all available op-node configuration options, organized by functionality. +The following options are from the `--help` in [v1.10.2](https://github.com/ethereum-optimism/optimism/releases/tag/op-node/v1.10.2). + +## L1 and L2 connection settings + +Configure how op-node connects to L1 (Ethereum) and L2 (execution layer) endpoints. + +### l1 + +Address of L1 User JSON-RPC endpoint to use (eth namespace required). The default value is `"http://127.0.0.1:8545"`. + + + `--l1=` + `--l1="http://127.0.0.1:8545"` + `OP_NODE_L1_ETH_RPC="http://127.0.0.1:8545"` + + +### l1.beacon + +Address of L1 Beacon-node HTTP endpoint to use. + + + `--l1.beacon=` + `--l1.beacon="http://127.0.0.1:3500"` + `OP_NODE_L1_BEACON="http://127.0.0.1:3500"` + + +### l1.beacon-fallbacks + +Addresses of L1 Beacon-API compatible HTTP fallback endpoints. Used to fetch blob sidecars not available at the l1.beacon (e.g. expired blobs). + + + `--l1.beacon-fallbacks=` + `--l1.beacon-fallbacks="http://fallback1.example.com,http://fallback2.example.com"` + `OP_NODE_L1_BEACON_FALLBACKS="http://fallback1.example.com,http://fallback2.example.com"` + + +### l1.beacon-header + +Optional HTTP header to add to all requests to the L1 Beacon endpoint. Format: 'X-Key: Value' + + + `--l1.beacon-header=` + `--l1.beacon-header="Authorization: Bearer token"` + `OP_NODE_L1_BEACON_HEADER="Authorization: Bearer token"` + + +### l1.beacon.fetch-all-sidecars + +If true, all sidecars are fetched and filtered locally. Workaround for buggy Beacon nodes. The default value is `false`. + + + `--l1.beacon.fetch-all-sidecars=` + `--l1.beacon.fetch-all-sidecars=false` + `OP_NODE_L1_BEACON=false` + + +### l1.beacon.ignore + +When false, halts `op-node` startup if the healthcheck to the Beacon-node endpoint fails. The default value is `false`. + + + `--l1.beacon.ignore=` + `--l1.beacon.ignore=false` + `OP_NODE_L1_BEACON_IGNORE=false` + + +### l1.cache-size + +Cache size for blocks, receipts and transactions. If this flag is set to 0, 3/2 of the sequencing window size is used (usually 2400). The default value of 900 (~3h of L1 blocks) is good for (high-throughput) networks that see frequent safe head increments. On (low-throughput) networks with infrequent safe head increments, it is recommended to set this value to 0, or a value that well covers the typical span between safe head increments. Note that higher values will cause significantly increased memory usage. The default value is `900`. + + + `--l1.cache-size=` + `--l1.cache-size=900` + `OP_NODE_L1_CACHE_SIZE=900` + + +### l1.epoch-poll-interval + +Poll interval for retrieving new L1 epoch updates such as safe and finalized block changes. Disabled if 0 or negative. The default value is `6m24s`. + + + `--l1.epoch-poll-interval=` + `--l1.epoch-poll-interval=6m24s` + `OP_NODE_L1_EPOCH_POLL_INTERVAL=6m24s` + + +### l1.http-poll-interval + +Polling interval for latest-block subscription when using an HTTP RPC provider. Ignored for other types of RPC endpoints. The default value is `12s`. + + + `--l1.http-poll-interval=` + `--l1.http-poll-interval=12s` + `OP_NODE_L1_HTTP_POLL_INTERVAL=12s` + + +### l1.max-concurrency + +Maximum number of concurrent RPC requests to make to the L1 RPC provider. The default value is `10`. + + + `--l1.max-concurrency=` + `--l1.max-concurrency=10` + `OP_NODE_L1_MAX_CONCURRENCY=10` + + +### l1.rpc-max-batch-size + +Maximum number of RPC requests to bundle, e.g., during L1 blocks receipt fetching. The L1 RPC rate limit counts this as N items, but allows it to burst at once. The default value is `20`. + + + `--l1.rpc-max-batch-size=` + `--l1.rpc-max-batch-size=20` + `OP_NODE_L1_RPC_MAX_BATCH_SIZE=20` + + +### l1.rpc-rate-limit + +Optional self-imposed global rate-limit on L1 RPC requests, specified in requests / second. Disabled if set to 0. The default value is `0`. + + + `--l1.rpc-rate-limit=` + `--l1.rpc-rate-limit=0` + `OP_NODE_L1_RPC_RATE_LIMIT=0` + + +### l1.rpckind + +The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: alchemy, quicknode, infura, parity, `nethermind`, debug\_geth, erigon, basic, any, standard. The default value is `standard`. + + + `--l1.rpckind=` + `--l1.rpckind=standard` + `OP_NODE_L1_RPC_KIND=standard` + + + + For details on additional values, see [RPC Receipts](https://github.com/ethereum-optimism/optimism/blob/844cc20084a2e9716631b4092ce7eca4804a8e0a/op-service/sources/receipts_rpc.go#L239-L322). + + +### l1.runtime-config-reload-interval + +Poll interval for reloading the runtime config, useful when config events are not being picked up. Disabled if 0 or negative. The default value is `10m0s`. + + + `--l1.runtime-config-reload-interval=` + `--l1.runtime-config-reload-interval=10m0s` + `OP_NODE_L1_RUNTIME_CONFIG_RELOAD_INTERVAL=10m0s` + + +### l1.trustrpc + +Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data. The default value is `false`. + + + If you're running an Erigon Ethereum execution client for your L1 provider you will need to include `--l1.trustrpc`. At the time of writing, + Erigon doesn't support the `eth_getProof` that we prefer to use to load L1 data for some processing in `op-node`. The trustrpc flag makes it + use something else that erigon supports, but the `op-node` can't verify for correctness. + + + + `--l1.trustrpc=` + `--l1.trustrpc=false` + `OP_NODE_L1_TRUST_RPC=false` + + +### l2 + +Address of L2 Engine JSON-RPC endpoints to use (engine and eth namespace required). This is referred to as `authrpc` by Geth and Reth. + + + `--l2=` + `--l2=http://127.0.0.1:8751` + `OP_NODE_L2_ENGINE_RPC=http://127.0.0.1:8751` + + +### l2.engine-rpc-timeout + +L2 engine client rpc timeout. The default value is `10s`. + + + `--l2.engine-rpc-timeout=` + `--l2.engine-rpc-timeout=10s` + `OP_NODE_L2_ENGINE_RPC_TIMEOUT=10s` + + +### l2.jwt-secret + +Path to JWT secret key. Keys are 32 bytes, hex encoded in a file. A new key will be generated if left empty. + + + `--l2.jwt-secret=` + `--l2.jwt-secret=/path/to/jwt/secret` + `OP_NODE_L2_ENGINE_AUTH=/path/to/jwt/secret` + + +### l2.enginekind + +The kind of engine client, used to control the behavior of optimism in respect to different types of engine clients. Valid options: `geth`, `reth`, `erigon`. The default value is `geth`. + + + `--l2.enginekind=` + `--l2.enginekind=geth` + `OP_NODE_L2_ENGINE_KIND=geth` + + +## Network selection and rollup configuration + +Configure which OP Stack network to connect to and manage rollup-specific settings. + +### network + +Predefined network selection. Available networks: arena-z-mainnet, arena-z-sepolia, +automata-mainnet, base-devnet-0-sepolia-dev-0, base-mainnet, base-sepolia, bob-mainnet, +camp-sepolia, creator-chain-testnet-sepolia, cyber-mainnet, cyber-sepolia, +ethernity-mainnet, ethernity-sepolia, fraxtal-mainnet, funki-mainnet, funki-sepolia, +hashkeychain-mainnet, ink-mainnet, ink-sepolia, lisk-mainnet, lisk-sepolia, lyra-mainnet, +metal-mainnet, metal-sepolia, mint-mainnet, mode-mainnet, mode-sepolia, op-mainnet, +op-sepolia, oplabs-devnet-0-sepolia-dev-0, orderly-mainnet, ozean-sepolia, pivotal-sepolia, +polynomial-mainnet, race-mainnet, race-sepolia, radius_testnet-sepolia, redstone-mainnet, +rehearsal-0-bn-0-rehearsal-0-bn, rehearsal-0-bn-1-rehearsal-0-bn, settlus-mainnet-mainnet, +settlus-sepolia-sepolia, shape-mainnet, shape-sepolia, silent-data-mainnet-mainnet, +snax-mainnet, soneium-mainnet, soneium-minato-sepolia, sseed-mainnet, swan-mainnet, +swell-mainnet, tbn-mainnet, tbn-sepolia, unichain-mainnet, unichain-sepolia, +worldchain-mainnet, worldchain-sepolia, xterio-eth-mainnet, zora-mainnet, zora-sepolia. + + + `--network=` + `--network=op-mainnet` + `OP_NODE_NETWORK=op-mainnet` + + +### rollup.config + +Rollup chain parameters. + + + `--rollup.config=` + `--rollup.config=[ConfigValueHere]` + `OP_NODE_ROLLUP_CONFIG=[ConfigValueHere]` + + +### rollup.halt + +Opt-in option to halt on incompatible protocol version requirements of the given level (major/minor/patch/none), as signaled onchain in L1. + + + `--rollup.halt=` + `--rollup.halt=[HaltOptionHere]` + `OP_NODE_ROLLUP_HALT=[HaltOptionHere]` + + +### rollup.l1-chain-config + +Path to .json file with the chain configuration for the L1, either in the direct format or genesis.json format (i.e. embedded under the .config property). Not necessary / will be ignored if using Ethereum mainnet or Sepolia as an L1. + + + `--rollup.l1-chain-config=` + `--rollup.l1-chain-config=/path/to/l1-config.json` + `OP_NODE_ROLLUP_L1_CHAIN_CONFIG=/path/to/l1-config.json` + + +### rollup.load-protocol-versions + +Load protocol versions from the superchain L1 ProtocolVersions contract (if available), and report in logs and metrics. Default is `false`. + + + `--rollup.load-protocol-versions=[true|false]` + `--rollup.load-protocol-versions=false` + `OP_NODE_ROLLUP_LOAD_PROTOCOL_VERSIONS=false` + + +### syncmode + +Blockchain sync mode. Options are "consensus-layer" or "execution-layer". The default value is `consensus-layer`. + + + `--syncmode=` + `--syncmode=consensus-layer` + `OP_NODE_SYNCMODE=consensus-layer` + + +## Fork overrides + +Manually override fork activation timestamps for testing or custom deployments. + +### override.canyon + +Manually specify the Canyon fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.canyon=` + `--override.canyon=0` + `OP_NODE_OVERRIDE_CANYON=0` + + +### override.delta + +Manually specify the Delta fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.delta=` + `--override.delta=0` + `OP_NODE_OVERRIDE_DELTA=0` + + +### override.ecotone + +Manually specify the ecotone fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.ecotone=` + `--override.ecotone=0` + `OP_NODE_OVERRIDE_ECOTONE=0` + + +### override.fjord + +Manually specify the fjord fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.fjord=` + `--override.fjord=0` + `OP_NODE_OVERRIDE_FJORD=0` + + +### override.granite + +Manually specify the granite fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.granite=` + `--override.granite=0` + `OP_NODE_OVERRIDE_GRANITE=0` + + +### override.holocene + +Manually specify the holocene fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.holocene=` + `--override.holocene=0` + `OP_NODE_OVERRIDE_HOLOCENE=0` + + +### override.isthmus + +Manually specify the isthmus fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.isthmus=` + `--override.isthmus=0` + `OP_NODE_OVERRIDE_ISTHMUS=0` + + +### override.interop + +Manually specify the Interop fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.interop=` + `--override.interop=0` + `OP_NODE_OVERRIDE_INTEROP=0` + + +### override.jovian + +Manually specify the Jovian fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.jovian=` + `--override.jovian=0` + `OP_NODE_OVERRIDE_JOVIAN=0` + + +### override.pectrablobschedule + +Manually specify the PectraBlobSchedule fork timestamp, overriding the bundled setting. The default value is `0`. + + + `--override.pectrablobschedule=` + `--override.pectrablobschedule=0` + `OP_NODE_OVERRIDE_PECTRABLOBSCHEDULE=0` + + +## Logging configuration + +Control log output format, level, and color. + +### log.color + +Color the log output if in terminal mode. The default value is `false`. + + + `--log.color=` + `--log.color=false` + `OP_NODE_LOG_COLOR=false` + + +### log.format + +Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty'. The default value is `text`. + + + `--log.format=` + `--log.format=text` + `OP_NODE_LOG_FORMAT=text` + + +### log.level + +The lowest log level that will be output. The default value is `info`. + + + `--log.level=` + `--log.level=info` + `OP_NODE_LOG_LEVEL=info` + + +### log.pid + +Show pid in the log. The default value is `false`. + + + `--log.pid=` + `--log.pid=false` + `OP_NODE_LOG_PID=false` + + +## Logging and RPC + +Configure RPC API endpoints and admin functionality. + +### rpc.addr + +RPC listening address. Default is `"0.0.0.0"`. + + + `--rpc.addr=` + `--rpc.addr=0.0.0.0` + `OP_NODE_RPC_ADDR=0.0.0.0` + + +### rpc.admin-state + +File path used to persist state changes made via the admin API so they persist across restarts. Disabled if not set. + + + `--rpc.admin-state=` + `--rpc.admin-state=[FilePathHere]` + `OP_NODE_RPC_ADMIN_STATE=[FilePathHere]` + + +### rpc.enable-admin + +Enable the admin API (experimental). Default is `false`. + + + `--rpc.enable-admin=[true|false]` + `--rpc.enable-admin=false` + `OP_NODE_RPC_ENABLE_ADMIN=false` + + +### rpc.port + +RPC listening port. Default is `9545`. + + + `--rpc.port=` + `--rpc.port=9545` + `OP_NODE_RPC_PORT=9545` + + +## Metrics and profiling + +Enable observability through metrics and performance profiling. + +### metrics.addr + +Metrics listening address. The default value is `"0.0.0.0"`. + + + `--metrics.addr=` + `--metrics.addr="0.0.0.0"` + `OP_NODE_METRICS_ADDR="0.0.0.0"` + + +### metrics.enabled + +Enable the metrics server. The default value is `false`. + + + `--metrics.enabled=` + `--metrics.enabled=false` + `OP_NODE_METRICS_ENABLED=false` + + +### metrics.port + +Metrics listening port. The default value is `7300`. + + + `--metrics.port=` + `--metrics.port=7300` + `OP_NODE_METRICS_PORT=7300` + + +### pprof.addr + +pprof listening address. Default is `"0.0.0.0"`. + + + `--pprof.addr=` + `--pprof.addr=0.0.0.0` + `OP_NODE_PPROF_ADDR=0.0.0.0` + + +### pprof.enabled + +Enable the pprof server. Default is `false`. + + + `--pprof.enabled=[true|false]` + `--pprof.enabled=false` + `OP_NODE_PPROF_ENABLED=false` + + +### pprof.path + +pprof file path. If it is a directory, the path is \{dir}/\{profileType}.prof + + + `--pprof.path=` + `--pprof.path={dir}/{profileType}.prof` + `OP_NODE_PPROF_PATH={dir}/{profileType}.prof` + + +### pprof.port + +pprof listening port. Default is `6060`. + + + `--pprof.port=` + `--pprof.port=6060` + `OP_NODE_PPROF_PORT=6060` + + +### pprof.type + +pprof profile type. One of cpu, heap, goroutine, threadcreate, block, mutex, allocs + + + `--pprof.type=` + `--pprof.type=cpu` + `OP_NODE_PPROF_TYPE=cpu` + + +## P2P networking + +Configure peer-to-peer networking, discovery, and connection management. + +### p2p.advertise.ip + +The IP address to advertise in Discv5, put into the ENR of the node. This may also be a hostname/domain name to resolve to an IP. + + + `--p2p.advertise.ip=` + `--p2p.advertise.ip=YourIPAddressOrHostnameHere` + `OP_NODE_P2P_ADVERTISE_IP=YourIPAddressOrHostnameHere` + + +### p2p.advertise.tcp + +The TCP port to advertise in Discv5, put into the ENR of the node. Set to p2p.listen.tcp value if 0. The default value is `0`. + + + `--p2p.advertise.tcp=` + `--p2p.advertise.tcp=3456` + `OP_NODE_P2P_ADVERTISE_TCP=3456` + + +### p2p.advertise.udp + +The UDP port to advertise in Discv5 as a fallback if not determined by Discv5, put into the ENR of the node. Set to p2p.listen.udp value if 0. The default value is `0`. + + + `--p2p.advertise.udp=` + `--p2p.advertise.udp=3457` + `OP_NODE_P2P_ADVERTISE_UDP=3457` + + +### p2p.ban.duration + +The duration that peers are banned for. The default value is `1h0m0s`. + + + `--p2p.ban.duration=` + `--p2p.ban.duration=1h0m0s` + `OP_NODE_P2P_PEER_BANNING_DURATION=1h0m0s` + + +### p2p.ban.peers + +Enables peer banning. The default value is `true`. + + + `--p2p.ban.peers=` + `--p2p.ban.peers=true` + `OP_NODE_P2P_PEER_BANNING=true` + + +### p2p.ban.threshold + +The minimum score below which peers are disconnected and banned. The default value is `-100`. + + + `--p2p.ban.threshold=` + `--p2p.ban.threshold=-100` + `OP_NODE_P2P_PEER_BANNING_THRESHOLD=-100` + + +### p2p.bootnodes + +Comma-separated base64-format ENR list. Bootnodes to start discovering other node records from. + + + `--p2p.bootnodes=` + `--p2p.bootnodes=YourBootnodesListHere` + `OP_NODE_P2P_BOOTNODES=YourBootnodesListHere` + + +### p2p.disable + +Completely disable the P2P stack. The default value is `false`. + + + `--p2p.disable=` + `--p2p.disable=false` + `OP_NODE_P2P_DISABLE=false` + + +### p2p.discovery.path + +Enables persistent storage of discovered ENRs in a database to recover from a restart without bootstrapping the discovery process again. Set to 'memory' to never persist the peerstore. The default value is `opnode_discovery_db`. + + + `--p2p.discovery.path=` + `--p2p.discovery.path=opnode_discovery_db` + `OP_NODE_P2P_DISCOVERY_PATH=opnode_discovery_db` + + +### p2p.gossip.timestamp.threshold + +Threshold for rejecting gossip messages with payload timestamps older than this duration. The default value is `1m0s`. + + + `--p2p.gossip.timestamp.threshold=` + `--p2p.gossip.timestamp.threshold=1m0s` + `OP_NODE_P2P_GOSSIP_TIMESTAMP_THRESHOLD=1m0s` + + +### p2p.listen.ip + +Specifies the IP to bind LibP2P and Discv5 to. The default value is `0.0.0.0`. + + + `--p2p.listen.ip=` + `--p2p.listen.ip=0.0.0.0` + `OP_NODE_P2P_LISTEN_IP=0.0.0.0` + + +### p2p.listen.tcp + +Defines the TCP port to bind LibP2P to. Any available system port if set to 0. The default value is `9222`. + + + `--p2p.listen.tcp=` + `--p2p.listen.tcp=9222` + `OP_NODE_P2P_LISTEN_TCP_PORT=9222` + + +### p2p.listen.udp + +Sets the UDP port to bind Discv5 to. It will be the same as the TCP port if left at 0. The default value is `0`. + + + `--p2p.listen.udp=` + `--p2p.listen.udp=0` + `OP_NODE_P2P_LISTEN_UDP_PORT=0` + + +### p2p.nat + +Enables NAT traversal with PMP/UPNP devices to learn external IP. The default value is `false`. + + + `--p2p.nat=` + `--p2p.nat=false` + `OP_NODE_P2P_NAT=false` + + +### p2p.netrestrict + +Specifies a comma-separated list of CIDR masks. P2P will only try to connect on these networks. + + + `--p2p.netrestrict=` + `--p2p.netrestrict=` + `OP_NODE_P2P_NETRESTRICT=` + + +### p2p.no-discovery + +Disables Discv5 (node discovery). The default value is `false`. + + + `--p2p.no-discovery=` + `--p2p.no-discovery=false` + `OP_NODE_P2P_NO_DISCOVERY=false` + + +### p2p.peers.grace + +Determines the grace period to keep a newly connected peer around, if it is not misbehaving. The default value is `30s`. + + + `--p2p.peers.grace=` + `--p2p.peers.grace=30s` + `OP_NODE_P2P_PEERS_GRACE=30s` + + +### p2p.peers.hi + +Sets the high-tide peer count. The node starts pruning peer connections slowly after reaching this number. The default value is `30`. + + + `--p2p.peers.hi=` + `--p2p.peers.hi=30` + `OP_NODE_P2P_PEERS_HI=30` + + +### p2p.peers.lo + +Determines the low-tide peer count. The node actively searches for new peer connections if below this amount. The default value is `20`. + + + `--p2p.peers.lo=` + `--p2p.peers.lo=20` + `OP_NODE_P2P_PEERS_LO=20` + + +### p2p.peerstore.path + +Specifies the Peerstore database location. Persisted peerstores help recover peers after restarts. Set to 'memory' to never persist the peerstore. Warning: a copy of the priv network key of the local peer will be persisted here. The default value is `"opnode_peerstore_db"`. + + + `--p2p.peerstore.path=` + `--p2p.peerstore.path=opnode_peerstore_db` + `OP_NODE_P2P_PEERSTORE_PATH=opnode_peerstore_db` + + +### p2p.priv.path + +Defines the file path for reading the hex-encoded 32-byte private key for the peer ID. Created if not already exists. Important for maintaining the same network identity after restarting. The default value is `"opnode_p2p_priv.txt"`. + + + `--p2p.priv.path=` + `--p2p.priv.path=opnode_p2p_priv.txt` + `OP_NODE_P2P_PRIV_PATH=opnode_p2p_priv.txt` + + +### p2p.scoring + +Sets the peer scoring strategy for the P2P stack. Options include 'none' or 'light'. The default value is `"light"`. + + + `--p2p.scoring=` + `--p2p.scoring=light` + `OP_NODE_P2P_PEER_SCORING=light` + + +### p2p.sequencer.key + +Hex-encoded private key for signing off on p2p application messages as sequencer. + + + `--p2p.sequencer.key=` + `--p2p.sequencer.key=[YourKeyHere]` + `OP_NODE_P2P_SEQUENCER_KEY=[YourKeyHere]` + + +### p2p.static + +Comma-separated multiaddr-format(an unsigned address, containing: IP, TCP port, [PeerID](/node-operators/reference/op-node-json-rpc#opp2p_self)) peer list. Static connections to make and +maintain, these peers will be regarded as trusted. Addresses of the local peer +are ignored. Duplicate/Alternative addresses for the same peer all apply, but +only a single connection per peer is maintained. + + + `--p2p.static=` + `--p2p.static=/ip4/127.0.0.1/tcp/9222/p2p/16Uiu2HAm2y6DXp6THWHCyquczNUh8gVAm4spo6hjP3Ns1dGRiAdE` + `OP_NODE_P2P_STATIC=/ip4/127.0.0.1/tcp/9222/p2p/16Uiu2HAm2y6DXp6THWHCyquczNUh8gVAm4spo6hjP3Ns1dGRiAdE` + + +### p2p.sync.onlyreqtostatic + +Restricts `RequestL2Range` sync requests to static peers only. Useful for enforcing trusted peer sync. When enabled, non-static peers are skipped during sync. Default is `false`. + + + `--p2p.sync.onlyreqtostatic=[true|false]` + `--p2p.sync.onlyreqtostatic=true` + `OP_NODE_P2P_SYNC_ONLYREQTOSTATIC=true` + + +### p2p.sync.req-resp + +Enables P2P req-resp alternative sync method, on both server and client side. Default is `true`. + + + `--p2p.sync.req-resp=[true|false]` + `--p2p.sync.req-resp=true` + `OP_NODE_P2P_SYNC_REQ_RESP=true` + + +## Sequencer options + +Configuration for running op-node as a sequencer. + +### sequencer.enabled + +Enable sequencing of new L2 blocks. A separate batch submitter has to be deployed to publish the data for verifiers. Default is `false`. + + + `--sequencer.enabled=[true|false]` + `--sequencer.enabled=false` + `OP_NODE_SEQUENCER_ENABLED=false` + + +### sequencer.l1-confs + +Number of L1 blocks to keep distance from the L1 head as a sequencer for picking an L1 origin. Default is `4`. + + + `--sequencer.l1-confs=` + `--sequencer.l1-confs=4` + `OP_NODE_SEQUENCER_L1_CONFS=4` + + + +The maximum value for `sequencer.l1-confs` cannot exceed the sequencer drift, currently set to 30 minutes (1800 seconds or 150 blocks). Setting a value higher than this limit will prevent the sequencer from producing blocks within the sequence window. + + +### sequencer.max-safe-lag + +Maximum number of L2 blocks for restricting the distance between L2 safe and unsafe. Disabled if 0. Default is `0`. + + + `--sequencer.max-safe-lag=` + `--sequencer.max-safe-lag=0` + `OP_NODE_SEQUENCER_MAX_SAFE_LAG=0` + + +### sequencer.recover + +Forces the sequencer to strictly prepare the next L1 origin and create empty L2 blocks. Default is `false`. + + + `--sequencer.recover=[true|false]` + `--sequencer.recover=false` + `OP_NODE_SEQUENCER_RECOVER=false` + + +### sequencer.stopped + +Initialize the sequencer in a stopped state. The sequencer can be started using the admin\_startSequencer RPC. Default is `false`. + + + `--sequencer.stopped=[true|false]` + `--sequencer.stopped=false` + `OP_NODE_SEQUENCER_STOPPED=false` + + +## Verifier options + +Configuration for running op-node as a verifier (replica node). + +### verifier.l1-confs + +Number of L1 blocks to keep distance from the L1 head before deriving L2 data from. Reorgs are supported, but may be slow to perform. Default is `0`. + + + `--verifier.l1-confs=` + `--verifier.l1-confs=0` + `OP_NODE_VERIFIER_L1_CONFS=0` + + + +While `verifier.l1-confs` has no strict limit, it's recommended to keep this value within 12-13 minutes (typically 10-20 blocks) for optimal performance. Exceeding this range may impact the verifier's data processing efficiency. + + +## Conductor mode + +Options for running op-node in conductor mode (for conductor-enabled chains). + +### conductor.enabled + +Enable the conductor service. The default value is `false`. + + + `--conductor.enabled=` + `--conductor.enabled=false` + `OP_NODE_CONDUCTOR_ENABLED=false` + + +### conductor.rpc + +Conductor service rpc endpoint. The default value is `http://127.0.0.1:8547`. + + + `--conductor.rpc=` + `--conductor.rpc=http://127.0.0.1:8547` + `OP_NODE_CONDUCTOR_RPC=http://127.0.0.1:8547` + + +### conductor.rpc-timeout + +Conductor service rpc timeout. The default value is `1s`. + + + `--conductor.rpc-timeout=` + `--conductor.rpc-timeout=1s` + `OP_NODE_CONDUCTOR_RPC_TIMEOUT=1s` + + +## Database and state persistence + +Configure persistent storage for node state and snapshot data. + +### safedb.path + +File path used to persist safe head update data. Disabled if not set. + + + `--safedb.path=` + `--safedb.path=/db` + `OP_NODE_SAFEDB_PATH=/db` + + +### snapshotlog.file + +Path to the snapshot log file. + + + `--snapshotlog.file=` + `--snapshotlog.file=[FilePathHere]` + `OP_NODE_SNAPSHOT_LOG=[FilePathHere]` + + +## Signer configuration + +Options for configuring an external signer for P2P messages. + +### signer.address + +Address the signer is signing requests for. + + + `--signer.address=` + `--signer.address=0x1234...` + `OP_NODE_SIGNER_ADDRESS=0x1234...` + + +### signer.endpoint + +Signer endpoint the client will connect to. + + + `--signer.endpoint=` + `--signer.endpoint=https://signer.example.com` + `OP_NODE_SIGNER_ENDPOINT=https://signer.example.com` + + +### signer.header + +Headers to pass to the remote signer. Format `key=value`. Value can contain any character allowed in a HTTP header. When using env vars, split with commas. When using flags one key value pair per flag. + + + `--signer.header=` + `--signer.header="Authorization: Bearer token"` + `OP_NODE_SIGNER_HEADER="Authorization: Bearer token"` + + +### signer.tls.ca + +TLS ca cert path. The default value is `"tls/ca.crt"`. + + + `--signer.tls.ca=` + `--signer.tls.ca="tls/ca.crt"` + `OP_NODE_SIGNER_TLS_CA="tls/ca.crt"` + + +### signer.tls.cert + +TLS cert path. The default value is `"tls/tls.crt"`. + + + `--signer.tls.cert=` + `--signer.tls.cert="tls/tls.crt"` + `OP_NODE_SIGNER_TLS_CERT="tls/tls.crt"` + + +### signer.tls.enabled + +Enable or disable TLS client authentication for the signer. The default value is `true`. + + + `--signer.tls.enabled=` + `--signer.tls.enabled=true` + `OP_NODE_SIGNER_TLS_ENABLED=true` + + +### signer.tls.key + +TLS key path. The default value is `"tls/tls.key"`. + + + `--signer.tls.key=` + `--signer.tls.key="tls/tls.key"` + `OP_NODE_SIGNER_TLS_KEY="tls/tls.key"` + + +## Alt-DA (Experimental) + + +Alt-DA Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues. + + +Configuration options for Alternative Data Availability mode. + +### altda.enabled + +Enable Alt-DA mode. The default value is `false`. + + + `--altda.enabled=` + `--altda.enabled=false` + `OP_NODE_ALTDA_ENABLED=false` + + +### altda.da-server + +HTTP address of a DA Server. + + + `--altda.da-server=` + `--altda.da-server=http://da-server.example.com` + `OP_NODE_ALTDA_DA_SERVER=http://da-server.example.com` + + +### altda.da-service + +Use DA service type where commitments are generated by Alt-DA server. The default value is `false`. + + + `--altda.da-service=` + `--altda.da-service=false` + `OP_NODE_ALTDA_DA_SERVICE=false` + + +### altda.get-timeout + +Timeout for get requests. 0 means no timeout. The default value is `0s`. + + + `--altda.get-timeout=` + `--altda.get-timeout=0s` + `OP_NODE_ALTDA_GET_TIMEOUT=0s` + + +### altda.max-concurrent-da-requests + +Maximum number of concurrent requests to the DA server. The default value is `1`. + + + `--altda.max-concurrent-da-requests=` + `--altda.max-concurrent-da-requests=1` + `OP_NODE_ALTDA_MAX_CONCURRENT_DA_REQUESTS=1` + + +### altda.put-timeout + +Timeout for put requests. 0 means no timeout. The default value is `0s`. + + + `--altda.put-timeout=` + `--altda.put-timeout=0s` + `OP_NODE_ALTDA_PUT_TIMEOUT=0s` + + +### altda.verify-on-read + +Verify input data matches the commitments from the DA storage service. The default value is `true`. + + + `--altda.verify-on-read=` + `--altda.verify-on-read=true` + `OP_NODE_ALTDA_VERIFY_ON_READ=true` + + +## Interop (Super Experimental) + + +Interop is a highly experimental feature. Use with caution in production environments. + + +Configuration options for OP Stack interop networks. + +### interop.dependency-set + +Dependency-set configuration, point at JSON file. + + + `--interop.dependency-set=` + `--interop.dependency-set=/path/to/dependency-set.json` + `OP_NODE_INTEROP_DEPENDENCY_SET=/path/to/dependency-set.json` + + +### interop.jwt-secret + +Interop RPC server authentication. Path to JWT secret key. Keys are 32 bytes, hex encoded in a file. A new key will be generated if the file is empty. Applies only to Interop-enabled networks. + + + `--interop.jwt-secret=` + `--interop.jwt-secret=/path/to/interop-jwt-secret.txt` + `OP_NODE_INTEROP_JWT_SECRET=/path/to/interop-jwt-secret.txt` + + +### interop.rpc.addr + +Interop Websocket-only RPC listening address, for supervisor service to manage syncing of the op-node. Applies only to Interop-enabled networks. Optional, disabled if left empty. Do not enable if you do not run a supervisor service. + + + `--interop.rpc.addr=` + `--interop.rpc.addr=0.0.0.0` + `OP_NODE_INTEROP_RPC_ADDR=0.0.0.0` + + +### interop.rpc.port + +Interop RPC listening port, to serve supervisor syncing. Applies only to Interop-enabled networks. The default value is `9645`. + + + `--interop.rpc.port=` + `--interop.rpc.port=9645` + `OP_NODE_INTEROP_RPC_PORT=9645` + + +## Experimental and miscellaneous + +Additional experimental features and utility flags. + +### experimental.sequencer-api + +Enables experimental test sequencer RPC functionality. The default value is `false`. + + + `--experimental.sequencer-api=` + `--experimental.sequencer-api=false` + `OP_NODE_EXPERIMENTAL_SEQUENCER_API=false` + + +### fetch-withdrawal-root-from-state + +Read withdrawal_storage_root (aka message passer storage root) from state trie (via execution layer) instead of the block header. Restores pre-Isthmus behavior, requires an archive EL client. The default value is `false`. + + + `--fetch-withdrawal-root-from-state=` + `--fetch-withdrawal-root-from-state=false` + `OP_NODE_FETCH_WITHDRAWAL_ROOT_FROM_STATE=false` + + +## General options + +Help and version information. + +### --help, -h + +Show help. The default value is `false`. + + + `--help` OR `-h` + `--help` + + +### --version, -v + + + Nodes built from source do not output the correct version numbers that are reported on + the GitHub release page. + + +Print the version. The default value is `false`. + + + `--version` OR `-v` + `--version` + + +## Node log levels + +Node log levels determine the verbosity of log messages, allowing operators to filter messages based on importance and detail. The log levels for the `op-node` (used in Optimism) +are as follows: + +1. Silent (0): No log messages are displayed. This level is rarely used as it provides + no feedback on the node's status. + +2. Error (1): Only error messages are displayed. Use this level to focus on critical + issues that need immediate attention. + +3. Warn (2): Displays error messages and warnings. This level helps to identify + potential problems that might not be immediately critical but require attention. + +4. Info (3): Displays error messages, warnings, and normal activity logs. This is the + default level and provides a balanced view of the node's operations without being too + verbose. + +5. Debug (4): All info-level messages plus additional debugging information. Use this + level when troubleshooting issues or developing the node software. + +6. Detail (5): The most verbose level, including detailed debugging information and + low-level system operations. This level generates a large amount of log data and is + typically used only for in-depth troubleshooting. + +To set the log level, use the `--log.level` flag when running the `op-node` command. For +example, to set the log level to debug: + +```bash +op-node --log.level=debug +``` + +By adjusting the log level, operators can control the amount and type of information that +gets logged, helping to manage log data volume and focus on relevant details during +different operational scenarios. diff --git a/docs/public-docs/node-operators/reference/op-node-json-rpc.mdx b/docs/public-docs/node-operators/reference/op-node-json-rpc.mdx new file mode 100644 index 0000000000000..104c7f6080f4e --- /dev/null +++ b/docs/public-docs/node-operators/reference/op-node-json-rpc.mdx @@ -0,0 +1,985 @@ +--- +title: op-node JSON-RPC API +description: Complete reference for op-node RPC methods including rollup-specific functionality. +--- + +`op-node` implements rollup-specific functionality as the Consensus Layer, similar to an L1 beacon node. It provides RPC methods for querying rollup state, managing peers, and controlling sequencer operations. + + + Use [`eth_gasPrice`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gasprice) instead of `rollup_gasPrices` for the L2 gas price. + For the L1 gas price, you can call the [`GasPriceOracle`'s `l1BaseFee` function](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F#readProxyContract#F11). + If you want to estimate the cost of a transaction, you can [use the SDK](/app-developers/tutorials/transactions/sdk-estimate-costs). + + +## Making RPC Requests + +The following examples show you how to make requests with [`curl`](https://curl.se/) and [`cast`](https://book.getfoundry.sh/cast/). + + + Protip: piping these commands into [`jq`](https://jqlang.github.io/jq/) will give you nicely formatted JSON responses. + + `$ cast rpc optimism_syncStatus --rpc-url http://localhost:9545 | jq` + + +## optimism Namespace + +Optimism-specific rollup methods for querying chain state and configuration. + +### optimism_outputAtBlock + +Get the output root at a specific block. This method is documented in [the specifications](https://specs.optimism.io/protocol/rollup-node.html?utm_source=op-docs&utm_medium=docs#output-method-api). + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"optimism_outputAtBlock","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc optimism_outputAtBlock --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc":"2.0", + "id":1, + "result":[ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xabe711e34c1387c8c56d0def8ce77e454d6a0bfd26cef2396626202238442421" + ] +} +``` + +### optimism_syncStatus + +Get the synchronization status of the rollup node. This method provides detailed information about L1 and L2 block processing states. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc optimism_syncStatus --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "current_l1": { + "hash": "0xff3b3253058411b727ac662f4c9ae1698918179e02ecebd304beb1a1ae8fc4fd", + "number": 4427350, + "parentHash": "0xb26586390c3f04678706dde13abfb5c6e6bb545e59c22774e651db224b16cd48", + "timestamp": 1696478784 + }, + "current_l1_finalized": { + "hash": "0x7157f91b8ae21ef869c604e5b268e392de5aa69a9f44466b9b0f838d56426541", + "number": 4706784, + "parentHash": "0x1ac2612a500b9facd650950b8755d97cf2470818da2d88552dea7cd563e86a17", + "timestamp": 1700160084 + }, + "head_l1": { + "hash": "0x6110a8e6ed4c4aaab20477a3eac81bf99e505bf6370cd4d2e3c6d34aa5f4059a", + "number": 4706863, + "parentHash": "0xee8a9cba5d93481f11145c24890fd8f536384f3c3c043f40006650538fbdcb56", + "timestamp": 1700161272 + }, + "safe_l1": { + "hash": "0x8407c9968ce278ab435eeaced18ba8f2f94670ad9d3bdd170560932cf46e2804", + "number": 4706811, + "parentHash": "0x6593cccab3e772776418ff691f6e4e75597af18505373522480fdd97219c06ef", + "timestamp": 1700160480 + }, + "finalized_l1": { + "hash": "0x7157f91b8ae21ef869c604e5b268e392de5aa69a9f44466b9b0f838d56426541", + "number": 4706784, + "parentHash": "0x1ac2612a500b9facd650950b8755d97cf2470818da2d88552dea7cd563e86a17", + "timestamp": 1700160084 + }, + "unsafe_l2": { + "hash": "0x9a3b2edab72150de252d45cabe2f1ac57d48ddd52bb891831ffed00e89408fe4", + "number": 2338094, + "parentHash": "0x935b94ec0bac0e63c67a870b1a97d79e3fa84dda86d31996516cb2f940753f53", + "timestamp": 1696478728, + "l1origin": { + "hash": "0x38731e0a6eeb40091f0c4a00650e911c57d054aaeb5b158f55cd5705fa6a3ebf", + "number": 4427339 + }, + "sequenceNumber": 3 + }, + "safe_l2": { + "hash": "0x9a3b2edab72150de252d45cabe2f1ac57d48ddd52bb891831ffed00e89408fe4", + "number": 2338094, + "parentHash": "0x935b94ec0bac0e63c67a870b1a97d79e3fa84dda86d31996516cb2f940753f53", + "timestamp": 1696478728, + "l1origin": { + "hash": "0x38731e0a6eeb40091f0c4a00650e911c57d054aaeb5b158f55cd5705fa6a3ebf", + "number": 4427339 + }, + "sequenceNumber": 3 + }, + "finalized_l2": { + "hash": "0x285b03afb46faad747be1ca7ab6ef50ef0ff1fe04e4eeabafc54f129d180fad2", + "number": 2337942, + "parentHash": "0x7e7f36cba1fd1ccdcdaa81577a1732776a01c0108ab5f98986cf997724eb48ac", + "timestamp": 1696478424, + "l1origin": { + "hash": "0x983309dadf7e0ab8447f3050f2a85b179e9acde1cd884f883fb331908c356412", + "number": 4427314 + }, + "sequenceNumber": 7 + }, + "pending_safe_l2": { + "hash": "0x9a3b2edab72150de252d45cabe2f1ac57d48ddd52bb891831ffed00e89408fe4", + "number": 2338094, + "parentHash": "0x935b94ec0bac0e63c67a870b1a97d79e3fa84dda86d31996516cb2f940753f53", + "timestamp": 1696478728, + "l1origin": { + "hash": "0x38731e0a6eeb40091f0c4a00650e911c57d054aaeb5b158f55cd5705fa6a3ebf", + "number": 4427339 + }, + "sequenceNumber": 3 + }, + "queued_unsafe_l2": { + "hash": "0x3af253f5b993f58fffdd5e594b3f53f5b7b254cdc18f4bdb13ea7331149942db", + "number": 4054795, + "parentHash": "0x284b7dc92bac97be8ec3b2cf548e75208eb288704de381f2557938ecdf86539d", + "timestamp": 1699912130, + "l1origin": { + "hash": "0x1490a63c372090a0331e05e63ec6a7a6e84835f91776306531f28b4217394d76", + "number": 4688196 + }, + "sequenceNumber": 2 + }, + "engine_sync_target": { + "hash": "0x9a3b2edab72150de252d45cabe2f1ac57d48ddd52bb891831ffed00e89408fe4", + "number": 2338094, + "parentHash": "0x935b94ec0bac0e63c67a870b1a97d79e3fa84dda86d31996516cb2f940753f53", + "timestamp": 1696478728, + "l1origin": { + "hash": "0x38731e0a6eeb40091f0c4a00650e911c57d054aaeb5b158f55cd5705fa6a3ebf", + "number": 4427339 + }, + "sequenceNumber": 3 + } +} +``` + +### optimism_rollupConfig + +Get the rollup configuration parameters. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"optimism_rollupConfig","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc optimism_rollupConfig --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "genesis": { + "l1": { + "hash": "0x48f520cf4ddaf34c8336e6e490632ea3cf1e5e93b0b2bc6e917557e31845371b", + "number": 4071408 + }, + "l2": { + "hash": "0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d", + "number": 0 + }, + "l2_time": 1691802540, + "system_config": { + "batcherAddr": "0x8f23bb38f531600e5d8fddaaec41f13fab46e98c", + "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc", + "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0", + "gasLimit": 30000000 + } + }, + "block_time": 2, + "max_sequencer_drift": 600, + "seq_window_size": 3600, + "channel_timeout": 300, + "l1_chain_id": 11155111, + "l2_chain_id": 11155420, + "regolith_time": 0, + "canyon_time": 1699981200, + "batch_inbox_address": "0xff00000000000000000000000000000011155420", + "deposit_contract_address": "0x16fc5058f25648194471939df75cf27a2fdc48bc", + "l1_system_config_address": "0x034edd2a225f7f429a63e0f1d2084b9e0a93b538", + "protocol_versions_address": "0x79add5713b383daa0a138d3c4780c7a1804a8090" +} +``` + +### optimism_version + +Get the software version of the op-node. + + +At the moment, building from source will not give you the correct version, but our docker images will. + + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"optimism_version","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc optimism_version --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc":"2.0", + "id":1, + "result":"v0.0.0-" +} +``` + +## opp2p Namespace + +The `opp2p` namespace handles peer-to-peer networking interactions, allowing you to manage peer connections, discovery, and network policies. + +### opp2p_self + +Returns your node's peer information including peer ID, addresses, and network configuration. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_self","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_self --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "peerID": "16Uiu2HAm2y6DXp6THWHCyquczNUh8gVAm4spo6hjP3Ns1dGRiAdE", + "nodeID": "75a52a90fe5f972171fefce2399ca5a73191c654e7c7ddfdd71edf4fca6697f0", + "userAgent": "", + "protocolVersion": "", + "ENR": "enr:-J-4QFOtI_hDBa_kilrQcg4iTJt9VMAuDLCbgAAKMa--WfxoPml1xDYxypUG7IsWga83FOlvr78LG3oH8CfzRzUmsDyGAYvKqIZ2gmlkgnY0gmlwhGxAaceHb3BzdGFja4Xc76gFAIlzZWNwMjU2azGhAnAON-FvpiWY2iG_LXJDYosknGyikaajPDd1cQARsVnBg3RjcIIkBoN1ZHCC0Vs", + "addresses": [ + "/ip4/127.0.0.1/tcp/9222/p2p/16Uiu2HAm2y6DXp6THWHCyquczNUh8gVAm4spo6hjP3Ns1dGRiAdE", + "/ip4/192.168.1.71/tcp/9222/p2p/16Uiu2HAm2y6DXp6THWHCyquczNUh8gVAm4spo6hjP3Ns1dGRiAdE", + "/ip4/108.64.105.199/tcp/9222/p2p/16Uiu2HAm2y6DXp6THWHCyquczNUh8gVAm4spo6hjP3Ns1dGRiAdE" + ], + "protocols": null, + "connectedness": 0, + "direction": 0, + "protected": false, + "chainID": 0, + "latency": 0, + "gossipBlocks": true, + "scores": { + "gossip": { + "total": 0, + "blocks": { + "timeInMesh": 0, + "firstMessageDeliveries": 0, + "meshMessageDeliveries": 0, + "invalidMessageDeliveries": 0 + }, + "IPColocationFactor": 0, + "behavioralPenalty": 0 + }, + "reqResp": { + "validResponses": 0, + "errorResponses": 0, + "rejectedPayloads": 0 + } + } + } +} +``` + +### opp2p_peers + +Returns a list of your node's peers with detailed connection information. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_peers","params":[true],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_peers true --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "totalConnected": 20, + "peers": { + "16Uiu2HAkvNYscHu4V1uj6fVWkwrAMCRsqXDSq4mUbhpGq4LttYsC": { + "peerID": "16Uiu2HAkvNYscHu4V1uj6fVWkwrAMCRsqXDSq4mUbhpGq4LttYsC", + "nodeID": "d693c5b58424016c0c38ec5539c272c754cb6b8007b322e0ecf16a4ee13f96fb", + "userAgent": "optimism", + "protocolVersion": "", + "ENR": "", + "addresses": [ + "/ip4/20.249.62.215/tcp/9222/p2p/16Uiu2HAkvNYscHu4V1uj6fVWkwrAMCRsqXDSq4mUbhpGq4LttYsC" + ], + "protocols": [ + "/ipfs/ping/1.0.0", + "/meshsub/1.0.0", + "/meshsub/1.1.0", + "/opstack/req/payload_by_number/11155420/0", + "/floodsub/1.0.0", + "/ipfs/id/1.0.0", + "/ipfs/id/push/1.0.0" + ], + "connectedness": 1, + "direction": 1, + "protected": false, + "chainID": 0, + "latency": 0, + "gossipBlocks": true, + "scores": { + "gossip": { + "total": -5.04, + "blocks": { + "timeInMesh": 0, + "firstMessageDeliveries": 0, + "meshMessageDeliveries": 0, + "invalidMessageDeliveries": 0 + }, + "IPColocationFactor": 0, + "behavioralPenalty": 0 + }, + "reqResp": { + "validResponses": 0, + "errorResponses": 0, + "rejectedPayloads": 0 + } + } + } + }, + "bannedPeers": [], + "bannedIPS": [], + "bannedSubnets": [] + } +} +``` + +### opp2p_peerStats + +Returns aggregate statistics about your peer connections. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_peerStats","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_peerStats --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "connected": 20, + "table": 94, + "blocksTopic": 20, + "blocksTopicV2": 18, + "banned": 0, + "known": 71 + } +} +``` + +### opp2p_discoveryTable + +Returns your peer discovery table containing node records (ENRs) of discovered peers. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_discoveryTable","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_discoveryTable --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + "enr:-J24QGC_SzoGG4EqyvO_082paQOwhvECeWGT-kaenrHdE2_iLTLeGmH-IOVpqEjC0L-yWmkI-c7598VaCjHQRNWn1CyGAYsqDgrFgmlkgnY0gmlwhNgp3WeHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQPiq20PNZYzyvpEifcGVrOXHfM94JeWSgDL07I2hSl0d4N0Y3CCJAaDdWRwgiQG", + "enr:-J24QKvt2ThBM8-FPeHfAmpoaVLdfVD2cw1cRpNuwmvH_bQtQ1dqrrZw9FqiMbXbFRQf9IvjrlKSFLodbsRALIFATICGAYuQClJigmlkgnY0gmlwhKI3ZuaHb3BzdGFja4O6BACJc2VjcDI1NmsxoQLQRz2CH95qQd6vmF5saV-WOoTZobNfSt-FUdVa7R35nYN0Y3CCIyuDdWRwgiMr" + ] +} +``` + +### opp2p_blockPeer + +Blocks a peer by peer ID, preventing future connections. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_blockPeer","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_blockPeer --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_unblockPeer + +Unblocks a previously blocked peer. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_unblockPeer","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_unblockPeer --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_listBlockedPeers + +Returns a list of blocked peer IDs. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_listBlockedPeers","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_listBlockedPeers --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + "16Uiu2HAmV3PueiaHj7Rg2bs3mrRUo2RVhjXRMpH67k9iZquDGQ8v" + ] +} +``` + +### opp2p_blockAddr + +Blocks connections from a specific IP address. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_blockAddr","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_blockAddr --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_unblockAddr + +Unblocks a previously blocked IP address. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_unblockAddr","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_unblockAddr --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_listBlockedAddrs + +Returns a list of blocked IP addresses. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_listBlockedAddrs","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_listBlockedAddrs --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + "2607:f8b0:4002:c0c::65" + ] +} +``` + +### opp2p_blockSubnet + +Blocks connections from a specific subnet (CIDR notation). + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_blockSubnet","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_blockSubnet --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_unblockSubnet + +Unblocks a previously blocked subnet. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_unblockSubnet","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_unblockSubnet --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_listBlockedSubnets + +Returns a list of blocked subnets. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_listBlockedSubnets","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_listBlockedSubnets --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [] +} +``` + +### opp2p_protectPeer + +Protects a peer from being pruned from the connection pool. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_protectPeer","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_protectPeer --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_unprotectPeer + +Removes protection from a peer, allowing it to be pruned if necessary. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_unprotectPeer","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_unprotectPeer --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_connectPeer + +Initiates a connection to a peer using its multiaddress. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_connectPeer","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_connectPeer --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### opp2p_disconnectPeer + +Disconnects from a specific peer. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"opp2p_disconnectPeer","params":[""],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc opp2p_disconnectPeer --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +## admin Namespace + +Administrative methods for controlling sequencer operations and the derivation pipeline. + +### admin_resetDerivationPipeline + +Resets the derivation pipeline, forcing it to re-derive L2 blocks from L1 data. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"admin_resetDerivationPipeline","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc admin_resetDerivationPipeline --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### admin_startSequencer + +Starts the sequencer if it was previously stopped. This allows the node to begin producing new blocks. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"admin_startSequencer","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc admin_startSequencer --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### admin_stopSequencer + +Stops the sequencer, preventing it from producing new blocks. The node will continue to sync from other sequencers. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"admin_stopSequencer","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc admin_stopSequencer --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": null +} +``` + +### admin_sequencerActive + +Returns whether the sequencer is currently active and producing blocks. + + + + ```sh + curl -X POST -H "Content-Type: application/json" --data \ + '{"jsonrpc":"2.0","method":"admin_sequencerActive","params":[],"id":1}' \ + http://localhost:9545 + ``` + + + ```sh + cast rpc admin_sequencerActive --rpc-url http://localhost:9545 + ``` + + + +Sample success output: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +``` diff --git a/docs/public-docs/node-operators/reference/op-reth-config.mdx b/docs/public-docs/node-operators/reference/op-reth-config.mdx new file mode 100644 index 0000000000000..2e03e95d691e3 --- /dev/null +++ b/docs/public-docs/node-operators/reference/op-reth-config.mdx @@ -0,0 +1,2316 @@ +--- +title: op-reth configuration options +description: Complete reference for all op-reth command-line flags. +--- + +This page provides detailed documentation for all available op-reth configuration options, organized by functionality. +The following options are from [v1.10.1](https://github.com/paradigmxyz/reth/releases/tag/v1.10.1) + +## General Options + +options for configuring the node instance and chain. + +### config + +The path to the configuration file to use. + + + `--config ` + + +### chain + +The chain this node is running. Possible values are either a built-in chain or the path to a chain specification file. + +Built-in chains: `optimism`, `optimism_sepolia`, `base`, `base_sepolia`, `dev`, etc. + + + `--chain ` + `optimism` + + +### instance + +Add a new instance of a node. Configures the ports of the node to avoid conflicts with the defaults. + + + `--instance ` + + +### with-unused-ports + +Sets all ports to unused, allowing the OS to choose random unused ports when sockets are bound. Mutually exclusive with `--instance`. + + + `--with-unused-ports` + + +## Metrics + +Options for Prometheus metrics. + +### metrics + +Enable Prometheus metrics. The metrics will be served at the given interface and port. + + + `--metrics ` + + +### metrics.prometheus.push.url + +URL for pushing Prometheus metrics to a push gateway. + + + `--metrics.prometheus.push.url ` + + +### metrics.prometheus.push.interval + +Interval in seconds for pushing metrics to push gateway. + + + `--metrics.prometheus.push.interval ` + `5` + + +## Datadir + +Options for data storage. + +### datadir + +The path to the data dir for all reth files and subdirectories. + +Defaults to the OS-specific data directory: +- Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` +- Windows: `{FOLDERID_RoamingAppData}/reth/` +- macOS: `$HOME/Library/Application Support/reth/` + + + `--datadir ` + `default` + + +### datadir.static-files + +The absolute path to store static files in. + + + `--datadir.static-files ` + + +### datadir.rocksdb + +The absolute path to store `RocksDB` database in. + + + `--datadir.rocksdb ` + + +### datadir.pprof-dumps + +The absolute path to store pprof dumps in. + + + `--datadir.pprof-dumps ` + + +## Networking + +Options for P2P networking and discovery. + +### disable-discovery + +Disable the discovery service. + + + `--disable-discovery` + + +### disable-dns-discovery + +Disable the DNS discovery. + + + `--disable-dns-discovery` + + +### disable-discv4-discovery + +Disable Discv4 discovery. + + + `--disable-discv4-discovery` + + +### enable-discv5-discovery + +Enable Discv5 discovery. + + + `--enable-discv5-discovery` + + +### disable-nat + +Disable NAT discovery. + + + `--disable-nat` + + +### discovery.addr + +The UDP address to use for devp2p peer discovery version 4. + + + `--discovery.addr ` + `0.0.0.0` + + +### discovery.port + +The UDP port to use for devp2p peer discovery version 4. + + + `--discovery.port ` + `30303` + + +### discovery.v5.addr + +The UDP IPv4 address to use for devp2p peer discovery version 5. + + + `--discovery.v5.addr ` + + +### discovery.v5.addr.ipv6 + +The UDP IPv6 address to use for devp2p peer discovery version 5. + + + `--discovery.v5.addr.ipv6 ` + + +### discovery.v5.port + +The UDP IPv4 port to use for devp2p peer discovery version 5. + + + `--discovery.v5.port ` + `9200` + + +### discovery.v5.port.ipv6 + +The UDP IPv6 port to use for devp2p peer discovery version 5. + + + `--discovery.v5.port.ipv6 ` + `9200` + + +### discovery.v5.lookup-interval + +The interval in seconds at which to carry out periodic lookup queries, for the whole run of the program + + + `--discovery.v5.lookup-interval ` + `20` + + +### discovery.v5.bootstrap.lookup-interval + +The interval in seconds at which to carry out boost lookup queries, for a fixed number of times, at bootstrap + + + `--discovery.v5.bootstrap.lookup-interval ` + `5` + + +### discovery.v5.bootstrap.lookup-countdown + +The number of times to carry out boost lookup queries at bootstrap + + + `--discovery.v5.bootstrap.lookup-countdown ` + `200` + + +### trusted-peers + +Comma separated enode URLs of trusted peers for P2P connections. + + + `--trusted-peers ` + + +### trusted-only + +Connect to or accept from trusted peers only. + + + `--trusted-only` + + +### bootnodes + +Comma separated enode URLs for P2P discovery bootstrap. + + + `--bootnodes ` + + +### dns-retries + +Amount of DNS resolution requests retries to perform when peering + + + `--dns-retries ` + `0` + + +### peers-file + +The path to the known peers file. Connected peers are dumped to this file on nodes shutdown, and read on startup. Cannot be used with `--no-persist-peers`. + + + `--peers-file ` + + +### identity + +Custom node identity. + + + `--identity ` + `reth/v1.10.1...` + + +### p2p-secret-key + +Secret key to use for this node. + + + `--p2p-secret-key ` + + +### p2p-secret-key-hex + +Hex encoded secret key to use for this node. +This will also deterministically set the peer ID. Cannot be used together with `--p2p-secret-key`. + + + `--p2p-secret-key-hex ` + + +### no-persist-peers + +Do not persist peers. + + + `--no-persist-peers` + + +### nat + +NAT resolution method (any|none|upnp|publicip|extip:\) + + + `--nat ` + `any` + + +### addr + +Network listening address. + + + `--addr ` + `0.0.0.0` + + +### port + +Network listening port. + + + `--port ` + `30303` + + +### max-outbound-peers + +Maximum number of outbound peers. + + + `--max-outbound-peers ` + `100` + + +### max-inbound-peers + +Maximum number of inbound peers. default: 30 + + + `--max-inbound-peers ` + `30` + + +### max-peers + +Maximum number of total peers (inbound + outbound). + + + `--max-peers ` + + +### max-tx-reqs + +Max concurrent `GetPooledTransactions` requests. + + + `--max-tx-reqs ` + `130` + + +### max-tx-reqs-peer + +Max concurrent `GetPooledTransactions` requests per peer. + + + `--max-tx-reqs-peer ` + `1` + + +### max-seen-tx-history + +Max number of seen transactions to remember per peer. + + + `--max-seen-tx-history ` + `320` + + +### max-pending-imports + +Max number of transactions to import concurrently. + + + `--max-pending-imports ` + `4096` + + +### pooled-tx-response-soft-limit +Experimental, for usage in research. Sets the max accumulated byte size of transactions to pack in one response. + + + `--pooled-tx-response-soft-limit ` + `2097152` + + +### pooled-tx-pack-soft-limit + +Experimental, for usage in research. Sets the max accumulated byte size of transactions to request in one request. + + + `--pooled-tx-pack-soft-limit ` + `131072` + + +### max-tx-pending-fetch + +Max capacity of cache of hashes for transactions pending fetch. + + + `--max-tx-pending-fetch ` + `25600` + + + +### net-if.experimental + +Name of network interface used to communicate with peers. + + + `--net-if.experimental ` + `eth0` + + + +### tx-propagation-policy + +Transaction Propagation Policy +The policy determines which peers transactions are gossiped to. + + + `--tx-propagation-policy ` + `all` + + + +### tx-ingress-policy + +Transaction ingress policy +Determines which peers' transactions are accepted over P2P. + + + `--tx-ingress-policy ` + `all` + + + +### disable-tx-gossip + +Disable transaction pool gossip. + + + `--disable-tx-gossip` + + +### tx-propagation-mode + +Sets the transaction propagation mode by determining how new pending transactions are propagated to other peers in full. + + + `--tx-propagation-mode ` + `sqrt` + + +### required-block-hashes + +Comma separated list of required block hashes or block number=hash pairs. Peers that don't have these blocks will be filtered out. + + + `--required-block-hashes ` + + +### network-id + +Optional network ID to override the chain specification's network ID for P2P connections. + + + `--network-id ` + + +### netrestrict + +Restrict network communication to the given IP networks (CIDR masks). +Comma separated list of CIDR network specifications. Only peers with IP addresses within these ranges will be allowed to connect. + + + `--netrestrict ` + + +## RPC + +Options for HTTP, WebSocket, and IPC RPC servers. + +### http + +Enable the HTTP-RPC server. + + + `--http` + + +### http.addr + +Http server address to listen on. + + + `--http.addr ` + `127.0.0.1` + + +### http.port + +Http server port to listen on. + + + `--http.port ` + `8545` + + +### http.disable-compression + +Disable compression for HTTP responses + + + `--http.disable-compression` + + +### http.api + +Rpc Modules to be configured for the HTTP server. + + + `--http.api ` + `admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev, testing` + + +### http.corsdomain + +Http Corsdomain to allow request from. + + + `--http.corsdomain ` + + +### ws + +Enable the WS-RPC server. + + + `--ws` + + +### ws.addr + +Ws server address to listen on. + + + `--ws.addr ` + `127.0.0.1` + + +### ws.port + +Ws server port to listen on. + + + `--ws.port ` + `8546` + + +### ws.origins + +Origins from which to accept `WebSocket` requests + + + `--ws.origins ` + + +### ws.api + +Rpc Modules to be configured for the WS server. + + + `--ws.api ` + + +### ipcdisable + +Disable the IPC-RPC server + + + `--ipcdisable` + + +### ipcpath + +Filename for IPC socket/pipe within the datadir + + + `--ipcpath ` + `/tmp/reth.ipc` + + +### ipc.permissions + +Set the permissions for the IPC socket file, in octal format. +If not specified, the permissions will be set by the system's umask. + + + `--ipc.permissions ` + + +### authrpc.addr + +Auth server address to listen on. + + + `--authrpc.addr ` + `127.0.0.1` + + +### authrpc.port + +Auth server port to listen on. + + + `--authrpc.port ` + `8551` + + +### authrpc.jwtsecret + +Path to a JWT secret to use for the authenticated engine-API RPC server. + + + `--authrpc.jwtsecret ` + + +### auth-ipc + +Enable auth engine API over IPC. + + + --auth-ipc + + +### auth-ipc.path + +Filename for auth IPC socket/pipe within the datadir. + + + `--auth-ipc.path ` + `/tmp/reth_engine_api.ipc` + + +### disable-auth-server + +Disable the auth/engine API server. This will prevent the authenticated engine-API server from starting. Use this if you're running a node that doesn't need to serve engine API requests. + + + `--disable-auth-server` + + +### rpc.jwtsecret +Hex encoded JWT secret to authenticate the regular RPC server(s), see --http.api and --ws.api. +This is not used for the authenticated engine-API RPC server (see --authrpc.jwtsecret). + + + `--rpc.jwtsecret ` + + +### rpc.max-request-size + +Set the maximum RPC request payload size for both HTTP and WS in megabytes. + + + `--rpc.max-request-size ` + `15` + + +### rpc.max-response-size + +Set the maximum RPC response payload size for both HTTP and WS in megabytes. +Alias: `--rpc.returndata.limit` + + + `--rpc.max-response-size ` + `160` + + +### rpc.max-subscriptions-per-connection + +Set the maximum concurrent subscriptions per connection. + + + `--rpc.max-subscriptions-per-connection ` + `1024` + + +### rpc.max-connections + +Maximum number of RPC server connections. + + + `--rpc.max-connections ` + `500` + + +### rpc.max-tracing-requests + +Maximum number of concurrent tracing requests. By default this chooses a sensible value based on the number of available cores. Tracing requests are generally CPU bound. Choosing a value that is higher than the available CPU cores can have a negative impact on the performance of the node and affect the node's ability to maintain sync. + + + `--rpc.max-tracing-requests ` + `9` + + +### rpc.max-blocking-io-requests + +Maximum number of concurrent blocking IO requests. +Blocking IO requests include eth_call, eth_estimateGas, and similar methods that require EVM execution. These are spawned as blocking tasks to avoid blocking the async runtime. + + + `--rpc.max-blocking-io-requests ` + `256` + + +### rpc.max-trace-filter-blocks +Maximum number of blocks for trace_filter requests. + + + `--rpc.max-trace-filter-blocks ` + `100` + + +### rpc.max-blocks-per-filter + +Maximum number of blocks that could be scanned per filter request. (0 = entire chain) + + + `--rpc.max-blocks-per-filter ` + `100000` + + +### rpc.max-logs-per-response + +Maximum number of logs that can be returned in a single response. (0 = no limit) + + + `--rpc.max-logs-per-response ` + `20000` + + +### rpc.gascap + +Maximum gas limit for eth_call and call tracing RPC methods. + + + `--rpc.gascap ` + `50000000` + + +### rpc.evm-memory-limit + +Maximum memory the EVM can allocate per RPC request. + + + `--rpc.evm-memory-limit ` + `4294967295` + + +### rpc.txfeecap + +Maximum eth transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap). + + + `--rpc.txfeecap ` + `1.0` + + +### rpc.max-simulate-blocks + +Maximum number of blocks for eth_simulateV1 call. + + + `--rpc.max-simulate-blocks ` + `256` + + +### rpc.eth-proof-window + +The maximum proof window for historical proof generation. This value allows for generating historical proofs up to configured number of blocks from current tip (up to tip - window). + + + `--rpc.eth-proof-window ` + `0` + + +### rpc.proof-permits + +Maximum number of concurrent getproof requests. + + + `--rpc.proof-permits ` + `25` + + +### rpc.pending-block + +Configures the pending block behavior for RPC responses. Options: full (include all transactions), empty (header only), none (disable pending blocks). + + + `--rpc.pending-block ` + `full` + + +### rpc.forwarder + +Endpoint to forward transactions to. + + + `--rpc.forwarder ` + + +## Builder + +Options for the payload builder. + +### builder.disallow + +Path to file containing disallowed addresses, json-encoded list of strings. Block validation API will reject blocks containing transactions from these addresses. + + + `--builder.disallow ` + + +### builder.extradata + +Block extra data set by the payload builder. + + + `--builder.extradata ` + `reth/v1.10.1...` + + +### builder.gaslimit + +Target gas limit for built blocks. + + + `--builder.gaslimit ` + + +### builder.interval + +The interval at which the job should build a new payload after the last. +Interval is specified in seconds or in milliseconds if the value ends with `ms`. + + + `--builder.interval ` + `1` + + +### builder.deadline + +The deadline for when the payload builder job should resolve. + + + `--builder.deadline ` + `12` + + +### builder.max-tasks + +Maximum number of tasks to spawn for building a payload. + + + `--builder.max-tasks ` + `3` + + +### builder.max-blobs + +Maximum number of blobs to include per block. + + + `--builder.max-blobs ` + + +## RPC State Cache + +Options for the RPC state cache. + +### rpc-cache.max-blocks + +Max number of blocks in cache. + + + `--rpc-cache.max-blocks ` + `5000` + + +### rpc-cache.max-receipts + +Max number receipts in cache. + + + `--rpc-cache.max-receipts ` + `2000` + + +### rpc-cache.max-headers + +Max number of headers in cache. + + + `--rpc-cache.max-headers ` + `1000` + + +### rpc-cache.max-concurrent-db-requests + +Max number of concurrent database requests. + + + `--rpc-cache.max-concurrent-db-requests ` + `512` + + +## Gas Price Oracle + +Options for the Gas Price Oracle. + +### gpo.blocks + +Number of recent blocks to check for gas price. + + + `--gpo.blocks ` + `20` + + +### gpo.ignoreprice + +Gas Price below which gpo will ignore transactions. + + + `--gpo.ignoreprice ` + `2` + + +### gpo.maxprice + +Maximum transaction priority fee (or gasprice before London Fork) to be recommended by gpo. + + + `--gpo.maxprice ` + `500000000000` + + +### gpo.percentile + +The percentile of gas prices to use for the estimate. + + + `--gpo.percentile ` + `60` + + +### gpo.default-suggested-fee + +The default gas price to use if there are no blocks to use for estimation. + + + `--gpo.default-suggested-fee ` + + +### rpc.send-raw-transaction-sync-timeout + +Timeout for the send_raw_transaction_sync RPC method. + + + `--rpc.send-raw-transaction-sync-timeout ` + `30s` + + +### testing.skip-invalid-transactions + +Skip invalid transactions in `testing_buildBlockV1` instead of failing. +When enabled, transactions that fail execution will be skipped, and all subsequent transactions from the same sender will also be skipped. + + + `--testing.skip-invalid-transactions` + + +## TxPool + +Options for the Transaction Pool. + +### txpool.pending-max-count + +Max number of transaction in the pending sub-pool. + + + `--txpool.pending-max-count ` + `10000` + + +### txpool.pending-max-size + +Max size of the pending sub-pool in megabytes. + + + `--txpool.pending-max-size ` + `20` + + +### txpool.basefee-max-count + +Max number of transaction in the basefee sub-pool. + + + `--txpool.basefee-max-count ` + `10000` + + +### txpool.basefee-max-size + +Max size of the basefee sub-pool in megabytes. + + + `--txpool.basefee-max-size ` + `20` + + +Max number of transaction in the basefee sub-pool. + + + `--txpool.basefee-max-count ` + `10000` + + +### txpool.queued-max-count + +Max number of transaction in the queued sub-pool. + + + `--txpool.queued-max-count ` + `10000` + + +### txpool.queued-max-size + +Max size of the queued sub-pool in megabytes. + + + `--txpool.queued-max-size ` + `20` + + +### txpool.blobpool-max-count + +Max number of transactions in the blobpool. + + + `--txpool.blobpool-max-count ` + `10000` + + +### txpool.blobpool-max-size + +Max size of the blobpool in megabytes. + + + `--txpool.blobpool-max-size ` + `20` + + +### txpool.blob-cache-size + +Max number of entries for the in-memory cache of the blob store. + + + `--txpool.blob-cache-size ` + + +### txpool.disable-blobs-support + +Disable EIP-4844 blob transaction support. + + + --txpool.disable-blobs-support + + +### txpool.max-account-slots + +Max number of executable transaction slots guaranteed per account. + + + `--txpool.max-account-slots ` + `16` + + +### txpool.pricebump + +Price bump (in %) for the transaction pool underpriced check. + + + `--txpool.pricebump ` + `10` + + +### txpool.minimal-protocol-fee + +Minimum base fee required by the protocol. + + + `--txpool.minimal-protocol-fee ` + `7` + + +### txpool.minimum-priority-fee + +Minimum priority fee required for transaction acceptance into the pool. +Transactions with a priority fee below this value will be rejected. + + + `--txpool.minimum-priority-fee ` + + +### txpool.gas-limit + +The default enforced gas limit for transactions entering the pool. + + + `--txpool.gas-limit ` + `30000000` + + +### txpool.max-tx-gas + +Maximum gas limit for individual transactions. Transactions exceeding this limit will be rejected. + + + `--txpool.max-tx-gas ` + + +### blobpool.pricebump + +Price bump percentage to replace an already existing blob transaction. + + + `--blobpool.pricebump ' + `100` + + +### txpool.max-tx-input-bytes + +Max size in bytes of a single transaction allowed to enter the pool. + + + `--txpool.max-tx-input-bytes ` + `131072` + + +### txpool.max-cached-entries + +The maximum number of blobs to keep in the in-memory blob cache. + + + `--txpool.max-cached-entries ` + `100` + + +### txpool.nolocals + +Flag to disable local transaction exemptions. + + + `--txpool.nolocals` + + +### txpool.locals + +Flag to allow certain addresses as local. + + + `--txpool.locals ` + + +### txpool.no-local-transactions-propagation + +Flag to toggle local transaction propagation. + + + `--txpool.no-local-transactions-propagation` + + +### txpool.additional-validation-tasks + +Number of additional transaction validation tasks to spawn. + + + `--txpool.additional-validation-tasks ` + `1` + + +### txpool.max-pending-txns + +Maximum number of pending transactions from the network to buffer. + + + `--txpool.max-pending-txns ` + `2048` + + +### txpool.max-new-txns + +Maximum number of new transactions to buffer. + + + `--txpool.max-new-txns ` + `1024` + + +### txpool.max-new-pending-txs-notifications + +How many new pending transactions to buffer and send to in-progress pending transaction iterators. + + + `--txpool.max-new-pending-txs-notifications ` + `200` + + +### txpool.lifetime + +Maximum amount of time (in seconds) non-executable transactions are queued. + + + `--txpool.lifetime ` + `10800` + + +### txpool.transactions-backup + +Path to store the local transaction backup at, to survive node restarts. + + + `--txpool.transactions-backup ` + + +### txpool.disable-transactions-backup + +Disables transaction backup to disk on node shutdown. + + + `--txpool.disable-transactions-backup` + + +### txpool.max-batch-size + +Max batch size for transaction pool insertions. + + + `--txpool.max-batch-size ` + `1` + + +## Debug + +Options for debugging. + +### debug.terminate + +Flag indicating whether the node should be terminated after the pipeline sync. + + + `--debug.terminate` + + +### debug.tip + +Set the chain tip manually for testing purposes. +NOTE: This is a temporary flag. + + + `--debug.tip ` + + +### debug.max-block + +Runs the sync only up to the specified block. + + + `--debug.max-block ` + + +### debug.etherscan + +Runs a fake consensus client that advances the chain using recent block hashes on Etherscan. If specified, requires an `ETHERSCAN_API_KEY` environment variable. + + + `--debug.etherscan []` + + +### debug.rpc-consensus-url + +Runs a fake consensus client using blocks fetched from an RPC endpoint. Supports both HTTP and `WebSocket` endpoints. + + + `--debug.rpc-consensus-url ` + + +### debug.skip-fcu + +If provided, the engine will skip `n` consecutive FCUs. + + + `--debug.skip-fcu ` + + +### debug.skip-new-payload + +If provided, the engine will skip `n` consecutive new payloads. + + + `--debug.skip-new-payload ` + + +### debug.reorg-frequency + +If provided, the chain will be reorged at specified frequency. + + + `--debug.reorg-frequency ` + + +### debug.reorg-depth + +The reorg depth for chain reorgs. + + + `--debug.reorg-depth ` + + +### debug.engine-api-store + +The path to store engine API messages at. If specified, all of the intercepted engine API messages will be written to specified location. + + + `--debug.engine-api-store ` + + +### debug.invalid-block-hook + +Determines which type of invalid block hook to install. +Possible values: `witness`, `pre-state`, `opcode`. + + + `--debug.invalid-block-hook ` + `witness` + + +### debug.healthy-node-rpc-url + +The RPC URL of a healthy node to use for comparing invalid block hook results against. + + + `--debug.healthy-node-rpc-url ` + + +### ethstats + +The URL of the ethstats server to connect to. Example: `nodename:secret@host:port`. + + + `--ethstats ` + + +### debug.startup-sync-state-idle + +Set the node to idle state when the backfill is not running. +This makes the `eth_syncing` RPC return "Idle" when the node has just started or finished the backfill, but did not yet receive any new blocks. + + + `--debug.startup-sync-state-idle` + + +## Database + +Options for the database. + +### db.log-level + +Database logging level. Levels higher than "notice" require a debug build. +Possible values: `fatal`, `error`, `warn`, `notice`, `verbose`, `debug`, `trace`, `extra`. + + + `--db.log-level ` + + +### db.exclusive + +Open environment in exclusive/monopolistic mode. Makes it possible to open a database on an NFS volume. + + + `--db.exclusive ` + + +### db.max-size + +Maximum database size (e.g., 4TB, 8TB). +This sets the "map size" of the database. If the database grows beyond this limit, the node will stop with an "environment map size limit reached" error. + + + `--db.max-size ` + `8TB` + + +### db.page-size + +Database page size (e.g., 4KB, 8KB, 16KB). +Specifies the page size used by the MDBX database. +The page size determines the maximum database size. MDBX supports up to 2^31 pages, so with the default 4KB page size, the maximum database size is 8TB. To allow larger databases, increase this value to 8KB or higher. + +WARNING: This setting is only configurable at database creation; changing it later requires re-syncing. + + + `--db.page-size ` + + +### db.growth-step + +Database growth step (e.g., 4GB, 4KB). + + + `--db.growth-step ` + + +### db.read-transaction-timeout + +Read transaction timeout in seconds, 0 means no timeout. + + + `--db.read-transaction-timeout ` + + +### db.max-readers + +Maximum number of readers allowed to access the database concurrently. + + + `--db.max-readers ` + + +### db.sync-mode + +Controls how aggressively the database synchronizes data to disk. + + + `--db.sync-mode ` + + +## Dev testnet + +Options for running a local development testnet. + +### dev + +Start the node in dev mode. + +This mode uses a local proof-of-authority consensus engine with either fixed block times or automatically mined blocks. +Disables network discovery and enables local http server. +Prefunds 20 accounts derived by mnemonic "test test test test test test test test test test test junk" with 10 000 ETH each. + + + `--dev` + + +### dev.block-max-transactions + +How many transactions to mine per block. + + + `--dev.block-max-transactions ` + + +### dev.block-time + +Interval between blocks. +Parses strings using `humantime::parse_duration`. +Example: `--dev.block-time 12s` + + + `--dev.block-time ` + + +### dev.mnemonic + +Derive dev accounts from a fixed mnemonic instead of random ones. + + + `--dev.mnemonic ` + `test test test test test test test test test test test junk` + + +## Pruning + +Options for data pruning. + +### full + +Run full node. Only the most recent `MINIMUM_PRUNING_DISTANCE` block states are stored. + + + `--full` + + +### minimal + +Run minimal storage mode with maximum pruning and smaller static files. +This mode configures the node to use minimal disk space by: +- Fully pruning sender recovery, transaction lookup, receipts +- Leaving 10,064 blocks for account, storage history and block bodies +- Using 10,000 blocks per static file segment + + + `--minimal` + + +### prune.block-interval + +Minimum pruning interval measured in blocks. + + + `--prune.block-interval ` + + +### prune.sender-recovery.full + +Prunes all sender recovery data. + + + `--prune.sender-recovery.full` + + +### prune.sender-recovery.distance + +Prune sender recovery data before the head-N block number. In other words, keep the last $N + 1$ blocks. + + + `--prune.sender-recovery.distance ` + + +### prune.sender-recovery.before + +Prune sender recovery data before the specified block number. The specified block number is not pruned. + + + `--prune.sender-recovery.before ` + + +### prune.transaction-lookup.full + +Prunes all transaction lookup data. + + + `--prune.transaction-lookup.full` + + +### prune.transaction-lookup.distance + +Prune transaction lookup data before the head-N block number. In other words, keep the last $N + 1$ blocks. + + + `--prune.transaction-lookup.distance ` + + +### prune.transaction-lookup.before + +Prune transaction lookup data before the specified block number. The specified block number is not pruned. + + + `--prune.transaction-lookup.before ` + + +### prune.receipts.full + +Prunes all receipt data. + + + `--prune.receipts.full` + + +### prune.receipts.pre-merge + +Prune receipts before the merge block. + + + `--prune.receipts.pre-merge` + + +### prune.receipts.distance + +Prune receipts before the head-N block number. In other words, keep the last $N + 1$ blocks. + + + `--prune.receipts.distance ` + + +### prune.receipts.before + +Prune receipts before the specified block number. The specified block number is not pruned. + + + `--prune.receipts.before ` + + +### prune.receiptslogfilter + +Configure receipts log filter. +Format: `
:...` where `` can be `full`, `distance:`, or `before:`. + + + `--prune.receiptslogfilter ` + + +### prune.account-history.full + +Prunes all account history. + + + `--prune.account-history.full` + + +### prune.account-history.distance + +Prune account history before the head-N block number. In other words, keep the last $N + 1$ blocks. + + + `--prune.account-history.distance ` + + +### prune.account-history.before + +Prune account history before the specified block number. The specified block number is not pruned. + + + `--prune.account-history.before ` + + +### prune.storage-history.full + +Prunes all storage history data. + + + `--prune.storage-history.full` + + +### prune.storage-history.distance + +Prune storage history before the head-N block number. In other words, keep the last $N + 1$ blocks. + + + `--prune.storage-history.distance ` + + +### prune.storage-history.before + +Prune storage history before the specified block number. The specified block number is not pruned. + + + `--prune.storage-history.before ` + + +### prune.bodies.pre-merge + +Prune bodies before the merge block. + + + `--prune.bodies.pre-merge` + + +### prune.bodies.distance + +Prune bodies before the head-N block number. In other words, keep the last $N + 1$ blocks. + + + `--prune.bodies.distance ` + + +### prune.bodies.before + +Prune body history before the specified block number. The specified block number is not pruned. + + + `--prune.bodies.before ` + + +## Engine + +Engine API options. + +### engine.persistence-threshold + +Configure persistence threshold for the engine. This determines how many canonical blocks must be in-memory, ahead of the last persisted block, before flushing canonical blocks to disk again. + +To persist blocks as fast as the node receives them, set this value to zero. This will cause more frequent DB writes. + + + `--engine.persistence-threshold ` + `2` + + +### engine.memory-block-buffer-target + +Configure the target number of blocks to keep in memory. + + + `--engine.memory-block-buffer-target ` + `0` + + +### engine.legacy-state-root + +Enable legacy state root calculation. + + + `--engine.legacy-state-root` + + +### engine.disable-state-cache + +Disable the state cache. + + + `--engine.disable-state-cache` + + +### engine.disable-prewarming + +Disable parallel prewarming of the state. + + + `--engine.disable-prewarming` + + +### engine.disable-parallel-sparse-trie + +Disable the parallel sparse trie in the engine. + + + `--engine.disable-parallel-sparse-trie` + + +### engine.state-provider-metrics + +Enable state provider latency metrics. This allows the engine to collect and report stats about how long state provider calls took during execution, but introduces slight overhead. + + + `--engine.state-provider-metrics` + + +### engine.cross-block-cache-size + +Configure the size of the cross-block cache in megabytes. + + + `--engine.cross-block-cache-size ` + `4096` + + +### engine.state-root-task-compare-updates + +Enable comparing trie updates from the state root task to the trie updates from the regular state root calculation. + + + `--engine.state-root-task-compare-updates` + + +### engine.accept-execution-requests-hash + +Enables accepting a requests hash instead of an array of requests in engine_newPayloadV4. + + + `--engine.accept-execution-requests-hash` + + +### engine.multiproof-chunking + +Whether the multiproof task should chunk proof targets. + + + `--engine.multiproof-chunking` + + +### engine.multiproof-chunk-size + +Multiproof task chunk size for proof targets. + + + `--engine.multiproof-chunk-size ` + `60` + + +### engine.reserved-cpu-cores + +Configure the number of reserved CPU cores for non-reth processes. + + + `--engine.reserved-cpu-cores ` + `1` + + +### engine.disable-precompile-cache + +Disable the precompile cache. + + + `--engine.disable-precompile-cache` + + +### engine.state-root-fallback + +Enable state root fallback. Primarily used for testing purposes. + + + `--engine.state-root-fallback` + + +### engine.always-process-payload-attributes-on-canonical-head + +Always process payload attributes and begin a payload build process even if `forkchoiceState.headBlockHash` is already the canonical head or an ancestor. See `TreeConfig::always_process_payload_attributes_on_canonical_head` for more details. + +Note: This is a no-op on OP Stack. + + + `--engine.always-process-payload-attributes-on-canonical-head` + + +### engine.allow-unwind-canonical-header + +Allow unwinding canonical header to ancestor during forkchoice updates. See `TreeConfig::unwind_canonical_header` for more details. + + + `--engine.allow-unwind-canonical-header` + + +### engine.storage-worker-count + +Configure the number of storage proof workers in the Tokio blocking pool. If not specified, defaults to 2x available parallelism, clamped between 2 and 64. + + + `--engine.storage-worker-count ` + + +### engine.account-worker-count + +Configure the number of account proof workers in the Tokio blocking pool. If not specified, defaults to the same count as storage workers. + + + `--engine.account-worker-count ` + + +### engine.enable-proof-v2 + +Enable V2 storage proofs for state root calculations. + + + `--engine.enable-proof-v2` + + +## ERA + +Options for ERA1 file import. + +### era.enable + +Enable import from ERA1 files. + + + `--era.enable` + + +### era.path + +The path to a directory for import. +The ERA1 files are read from the local directory parsing headers and bodies. + + + `--era.path ` + + +### era.url + +The URL to a remote host where the ERA1 files are hosted. +The ERA1 files are read from the remote host using HTTP GET requests parsing headers and bodies. + + + `--era.url ` + + +## Static Files + +Options for static file storage configuration. + +### static-files.blocks-per-file.headers + +Number of blocks per file for the headers segment. + + + `--static-files.blocks-per-file.headers ` + + +### static-files.blocks-per-file.transactions + +Number of blocks per file for the transactions segment. + + + `--static-files.blocks-per-file.transactions ` + + +### static-files.blocks-per-file.receipts + +Number of blocks per file for the receipts segment. + + + `--static-files.blocks-per-file.receipts ` + + +### static-files.blocks-per-file.transaction-senders + +Number of blocks per file for the transaction senders segment. + + + `--static-files.blocks-per-file.transaction-senders ` + + +### static-files.blocks-per-file.account-change-sets + +Number of blocks per file for the account changesets segment. + + + `--static-files.blocks-per-file.account-change-sets ` + + +### static-files.receipts + +Store receipts in static files instead of the database. +When enabled, receipts will be written to static files on disk instead of the database. +Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + + + `--static-files.receipts` + + +### static-files.transaction-senders + +Store transaction senders in static files instead of the database. +When enabled, transaction senders will be written to static files on disk instead of the database. +Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + + + `--static-files.transaction-senders` + + +### static-files.account-change-sets + +Store account changesets in static files. +When enabled, account changesets will be written to static files on disk instead of the database. +Note: This setting can only be configured at genesis initialization. Once the node has been initialized, changing this flag requires re-syncing from scratch. + + + `--static-files.account-change-sets` + + +## Rollup + +Options for Rollup configuration. + +### rollup.sequencer + +Endpoint for the sequencer mempool (can be both HTTP and WS). +Aliases: `--rollup.sequencer-http`, `--rollup.sequencer-ws` + + + `--rollup.sequencer ` + + +### rollup.disable-tx-pool-gossip + +Disable transaction pool gossip. + + + `--rollup.disable-tx-pool-gossip` + + +### rollup.compute-pending-block + +By default the pending block equals the latest block to save resources and not leak txs from the tx-pool, this flag enables computing of the pending block from the tx-pool instead. + +If `compute_pending_block` is not enabled, the payload builder will use the payload attributes from the latest block. Note that this flag is not yet functional. + + + `--rollup.compute-pending-block` + + +### rollup.discovery.v4 + +Enables discovery v4 if provided. + + + `--rollup.discovery.v4` + + +### rollup.enable-tx-conditional + +Enable transaction conditional support on sequencer. + + + `--rollup.enable-tx-conditional` + + +### rollup.supervisor-http + +HTTP endpoint for the supervisor. + + + `--rollup.supervisor-http ` + `http://localhost:1337/` + + +### rollup.supervisor-safety-level + +Safety level for the supervisor. + + + `--rollup.supervisor-safety-level ` + `CrossUnsafe` + + +### rollup.sequencer-headers + +Optional headers to use when connecting to the sequencer. + + + `--rollup.sequencer-headers ` + + +### rollup.historicalrpc + +RPC endpoint for historical data. + + + `--rollup.historicalrpc ` + + +### min-suggested-priority-fee + +Minimum suggested priority fee (tip) in wei. + + + `--min-suggested-priority-fee ` + `1000000` + + +### flashblocks-url + +A URL pointing to a secure websocket subscription that streams out flashblocks. +If given, the flashblocks are received to build pending block. All request with "pending" block tag will use the pending state based on flashblocks. + + + `--flashblocks-url ` + + +### flashblock-consensus + +Enable flashblock consensus client to drive the chain forward. +When enabled, the flashblock consensus client will process flashblock sequences and submit them to the engine API to advance the chain. Requires `flashblocks_url` to be set. + + + `--flashblock-consensus` + + +## Logging + +Options for logging. + +### log.stdout.format + +The format to use for logs written to stdout. +Possible values: `json`, `log-fmt`, `terminal`. + + + `--log.stdout.format ` + `terminal` + + +### log.stdout.filter + +The filter to use for logs written to stdout. + + + `--log.stdout.filter ` + + +### log.file.format + +The format to use for logs written to the log file. +Possible values: `json`, `log-fmt`, `terminal`. + + + `--log.file.format ` + `terminal` + + +### log.file.filter + +The filter to use for logs written to the log file. + + + `--log.file.filter ` + `debug` + + +### log.file.directory + +The path to put log files in. + + + `--log.file.directory ` + `(OS-specific default)` + + +### log.file.name + +The prefix name of the log files. + + + `--log.file.name ` + `reth.log` + + +### log.file.max-size + +The maximum size (in MB) of one log file. + + + `--log.file.max-size ` + `200` + + +### log.file.max-files + +The maximum amount of log files that will be stored. If set to 0, background file logging is disabled. + + + `--log.file.max-files ` + `5` + + +### log.journald + +Write logs to journald. + + + `--log.journald` + + +### log.journald.filter + +The filter to use for logs written to journald. + + + `--log.journald.filter ` + `error` + + +### color + +Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting. +Possible values: `always`, `auto`, `never`. + + + `--color ` + always + + +### logs-otlp + +Enable `Opentelemetry` logs export to an OTLP endpoint. +If no value provided, defaults based on protocol: +- HTTP: `http://localhost:4318/v1/logs` +- gRPC: `http://localhost:4317` + + + `--logs-otlp[=]` + `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` + + +### logs-otlp.filter + +Set a filter directive for the OTLP logs exporter. This controls the verbosity of logs sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable. +Defaults to INFO if not specified. + + + `--logs-otlp.filter ` + `info` + + +## Display + +Options for display and verbosity. + +### verbosity + +Set the minimum log level. +- `-v`: Errors +- `-vv`: Warnings +- `-vvv`: Info +- `-vvvv`: Debug +- `-vvvvv`: Traces (warning: very verbose!) + + + `-v`, `--verbosity` + `3` (Info) + + +### quiet + +Silence all log output. + + + `-q`, `--quiet` + + +## Tracing + +Options for OpenTelemetry tracing. + +### tracing-otlp + +Enable `Opentelemetry` tracing export to an OTLP endpoint. + +If no value provided, defaults based on protocol: +- HTTP: `http://localhost:4318/v1/traces` +- gRPC: `http://localhost:4317` + + + `--tracing-otlp[=]` + `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` + + +### tracing-otlp-protocol + +OTLP transport protocol to use for exporting traces and logs. + +- `http`: expects endpoint path to end with `/v1/traces` or `/v1/logs` +- `grpc`: expects endpoint without a path +Defaults to HTTP if not specified. + +Possible values: +- `http`: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path +- `grpc`: gRPC transport, port 4317 + + + `--tracing-otlp-protocol ` + `http` + `OTEL_EXPORTER_OTLP_PROTOCOL` + + +### tracing-otlp.filter + +Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable. + +Example: `--tracing-otlp.filter=info,reth=debug,hyper_util=off` + +Defaults to TRACE if not specified. + + + `--tracing-otlp.filter ` + `debug` + + +### tracing-otlp.sample-ratio + +Trace sampling ratio to control the percentage of traces to export. + +Valid range: 0.0 to 1.0 (1.0 = Sample all, 0.0 = Disable). +- `1.0`, default: Sample all traces +- `0.01`: Sample 1% of traces +- `0.0`: Disable sampling + +Example: `--tracing-otlp.sample-ratio=0.0`. + + + `--tracing-otlp.sample-ratio ` + `OTEL_TRACES_SAMPLER_ARG` + \ No newline at end of file diff --git a/docs/public-docs/node-operators/reference/op-reth-historical-proof-config.mdx b/docs/public-docs/node-operators/reference/op-reth-historical-proof-config.mdx new file mode 100644 index 0000000000000..de547539a3e4a --- /dev/null +++ b/docs/public-docs/node-operators/reference/op-reth-historical-proof-config.mdx @@ -0,0 +1,102 @@ +--- +title: op-reth historical proof configuration +description: Configuration options for historical proofs using ExEx in op-reth (op-rs fork). +--- + +This page provides documentation for the configuration options available in [op-reth](https://github.com/ethereum-optimism/optimism/tree/develop/rust/op-reth) for supporting historical proofs via ExEx. + +By enabling this Execution Extension (ExEx), the node initializes a separate storage database specifically for historical trie nodes. This allows the node to **generate and serve historical proofs** efficiently, which is critical for applications requiring historical state access, such as fault proof systems. + +For a complete setup guide, see the tutorial on [Running op-reth with Historical Proofs](/node-operators/tutorials/reth-historical-proofs). + + +This fork inherits all standard op-reth configuration options. See the [op-reth configuration reference](/node-operators/reference/op-reth-config). + + +## Proof History + +Options for configuring the proof history ExEx. + +### proofs-history + +If true, initialize external-proofs exex to save and serve trie nodes to provide proofs faster. + + + `--proofs-history` + + +### proofs-history.storage-path + +The path to the storage DB for proofs history. + + + `--proofs-history.storage-path ` + + +### proofs-history.window + +The window to span blocks for proofs history. Value is the number of blocks. +Default is 1 month of blocks based on 2 seconds block time (`30 * 24 * 60 * 60 / 2 = 1,296,000`). + + + `--proofs-history.window ` + `1296000` + + +### proofs-history.prune-interval + +Interval between proof-storage prune runs. Accepts human-friendly durations like "100s", "5m", "1h". + +- Shorter intervals prune smaller batches more often. +- Longer intervals prune larger batches less often. + +A shorter interval is preferred so that prune runs stay small and don’t stall writes for too long. + + + `--proofs-history.prune-interval ` + `15s` + + +### proofs-history.verification-interval + +Verification interval: perform full block execution every N blocks for data integrity. + +- `0`: Disabled (Default). Always use fast path with pre-computed data. +- `1`: Always verify. Always execute blocks. +- `N`: Verify every Nth block (e.g., 100 = every 100 blocks). + +Periodic verification helps catch data corruption while maintaining good performance. + + + `--proofs-history.verification-interval ` + `0` + + + +## Management Commands + +The `op-reth proofs` command allows you to manage the storage of historical proofs. + +### init + +Initialize the proofs storage with the current state of the chain. + +```bash +op-reth proofs init --chain --datadir --proofs-history.storage-path +``` + +### prune + +Prune old proof history to reclaim space. + +```bash +op-reth proofs prune --chain --datadir --proofs-history.storage-path --proofs-history.window +``` + +### unwind + +Unwind the proofs storage to a specific block + +```bash +op-reth proofs unwind --datadir --proofs-history.storage-path --target +``` diff --git a/docs/public-docs/node-operators/tutorials/node-from-docker.mdx b/docs/public-docs/node-operators/tutorials/node-from-docker.mdx new file mode 100644 index 0000000000000..946af8dc3c100 --- /dev/null +++ b/docs/public-docs/node-operators/tutorials/node-from-docker.mdx @@ -0,0 +1,133 @@ +--- +title: Running a Node With Docker +description: Learn how to run a node using Docker. +--- + +Using [Docker](https://docs.docker.com/engine/install/) is an easy way to run an OP Mainnet node. +This tutorial will walk you through the process of using [`simple-optimism-node`](https://github.com/smartcontracts/simple-optimism-node) to run an OP Mainnet or OP Sepolia node using Docker. +`simple-optimism-node` also provides useful tools like a monitoring dashboard and health checking software. +Although less flexible than [running a node from source](/node-operators/tutorials/node-from-source) or building your own Docker setup, this is a great way to quickly get started with OP Mainnet. + + + By default, `simple-optimism-node` uses `op-geth` as the execution client. Support for `Nethermind` as an alternative execution client is also available. + + +## What's included + +`simple-optimism-node` includes all the basic components to run an OP Mainnet or OP Sepolia node. +It also includes several additional services to monitor the health of your node and the health of the network you're connected to. +See the [What's Included](https://github.com/smartcontracts/simple-optimism-node#whats-included) section of the `simple-optimism-node` README for more details. + +## Dependencies + +* [Docker](https://docs.docker.com/engine/install/) +* [Docker Compose](https://docs.docker.com/compose/install/) + +## Setup + +Clone the `simple-optimism-node` repository to get started. + +```bash +git clone https://github.com/smartcontracts/simple-optimism-node.git +cd simple-optimism-node +``` + +## Configuration + +Configuration for `simple-optimism-node` is handled through environment variables inside of an `.env` file. + + + +The repository includes a sample environment variable file located at `.env.example` that you can copy and modify to get started. Make a copy of this file and name it `.env`. + + ```bash + cp .env.example .env + ``` + + + + +Open the `.env` file in your favorite text editor. Set the variables inside of the `REQUIRED (BEDROCK)` section of the file according to the guide below: + + **NETWORK_NAME** - Choose which Optimism network layer you want to operate on: + + * `op-mainnet` - OP Mainnet + * `op-sepolia` - OP Sepolia (Testnet) + * `base-mainnet` - Base Mainnet + * `base-sepolia` - Base Sepolia (Testnet) + + **NODE_TYPE** - Choose the type of node you want to run: + + * `full` (Full node) - A Full node contains a few recent blocks without historical states. + * `archive` (Archive node) - An Archive node stores the complete history of the blockchain, including historical states. + + **EXECUTION_CLIENT** - Choose which execution client to use: + + * `op-geth` - The original execution client for OP Stack (default if not specified) + * `nethermind` - Alternative high-performance execution client written in C# + + **OP_NODE__RPC_ENDPOINT** - Specify the endpoint for the RPC of Layer 1 (e.g., Ethereum mainnet). For instance, you can use the free plan of Alchemy for the Ethereum mainnet. + + **OP_NODE__L1_BEACON** - Specify the beacon endpoint of Layer 1. You can use [QuickNode for the beacon endpoint](https://www.quicknode.com). For example: `https://xxx-xxx-xxx.quiknode.pro/db55a3908ba7e4e5756319ffd71ec270b09a7dce`. + + **OP_NODE__RPC_TYPE** - Specify the service provider for the RPC endpoint you've chosen in the previous step. The available options are: + + * `alchemy` - Alchemy + * `quicknode` - Quicknode (ETH only) + * `erigon` - Erigon + * `basic` - Other providers + + **HEALTHCHECK__REFERENCE_RPC_PROVIDER** - Specify the public RPC endpoint for Layer 2 network you want to operate on for healthcheck. For instance: + + * **OP Mainnet** - [https://mainnet.optimism.io](https://mainnet.optimism.io) + * **OP Sepolia** - [https://sepolia.optimism.io](https://sepolia.optimism.io) + * **Base Mainnet** - [https://mainnet.base.org](https://mainnet.base.org) + * **Base Sepolia** - [https://sepolia.base.org](https://sepolia.base.org) + + + + +## Run the node + +Once you've configured your `.env` file, you can run the node using Docker Compose. +The following command will start the node in the background. + +```bash +docker compose up -d --build +``` + +The node will start with the execution client you specified in your `.env` file. If you didn't specify an execution client, `op-geth` will be used by default. + +## Upgrade the node + +Pull the latest updates from GitHub, Docker Hub and rebuild the container. + +```bash +git pull +docker compose pull +docker compose up -d --build --force-recreate +``` + +## Operating the node + +You can use Docker Compose to interact with the node and manage the various containers that you started. +Refer to the [Operating the Node](https://github.com/smartcontracts/simple-optimism-node#operating-the-node) section of the `simple-optimism-node` README for more information. + +## Checking node status + +`simple-optimism-node` includes a monitoring dashboard that you can use to check the status of your node. +You can access the dashboard by visiting `http://localhost:3000` in your browser. +Refer to the [Grafana dashboard](https://github.com/smartcontracts/simple-optimism-node#grafana-dashboard) section of the `simple-optimism-node` README for more information. + +Another way to check the node syncing progress is to run `./progress.sh`, which will print output showing the number of blocks per second and the estimated time until synchronization is completed. + +``` +./progress.sh +``` + +``` +Chain ID: 10 +Please wait +Blocks per minute: ... +Hours until sync completed: ... +``` diff --git a/docs/public-docs/node-operators/tutorials/node-from-source.mdx b/docs/public-docs/node-operators/tutorials/node-from-source.mdx new file mode 100644 index 0000000000000..6d71acbdb8fac --- /dev/null +++ b/docs/public-docs/node-operators/tutorials/node-from-source.mdx @@ -0,0 +1,202 @@ +--- +title: Building an OP Stack node from source +description: Learn how to build your own node without relying on images from Optimism. +--- + +Docker images are the easiest way to run an OP Mainnet node, but you can always build your own node from source code. +You might want to do this if you want to run a node on a specific architecture or if you want to inspect the source code of the node you're running. +This guide will walk you through the full process of building a node from source. + +## What you're going to build + +### Rollup node + +The Rollup Node is responsible for deriving L2 block payloads from L1 data and passing those payloads to the Execution Client. +The Rollup Node can also optionally participate in a peer-to-peer network to receive blocks directly from the Sequencer before those blocks are submitted to L1. +The Rollup Node is largely analogous to a [consensus client](https://ethereum.org/en/developers/docs/nodes-and-clients/#what-are-nodes-and-clients) in Ethereum. + +In this tutorial you will build the `op-node` implementation of the Rollup Node as found in the [Optimism Monorepo](https://github.com/ethereum-optimism/optimism). + +### Execution client + +The Execution Client is responsible for executing the block payloads it receives from the Rollup Node over JSON-RPC via the standard [Ethereum Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#engine-api----common-definitions). +The Execution Client exposes the standard JSON-RPC API that Ethereum developers are familiar with, and can be used to query blockchain data and submit transactions to the network. +The Execution Client is largely analogous to an [execution client](https://ethereum.org/en/developers/docs/nodes-and-clients/#what-are-nodes-and-clients) in Ethereum. + + + + The `op-geth` implementation of the Execution Client can be found in the [`op-geth` repository](https://github.com/ethereum-optimism/op-geth). + + #### Prerequisites + + * Go 1.21 or later + * Make + + #### Building + + Follow the instructions in the `op-geth` repository to build from source. + + + + `Nethermind` is an alternative execution client implementation written in .NET. + + #### Prerequisites + + * [.NET SDK](https://aka.ms/dotnet/download) 9 or later + + #### Building + + + 1. Clone the repository: + The `Nethermind`'s source code can be obtained from [`nethermind` repository](https://github.com/NethermindEth/nethermind) + + ```bash + git clone --recursive https://github.com/nethermindeth/nethermind.git + ``` + + 2. Build the source: + + ```bash + dotnet build src/Nethermind/Nethermind.sln -c release + ``` + + 3. Run `Nethermind` (this will build it first if needed): + + ```bash + cd src/Nethermind/Nethermind.Runner + dotnet run -c release -- -c mainnet + ``` + + The build artifacts will be located in `src/Nethermind/artifacts/bin/Nethermind.Runner/release`. + + + For more details about running a `Nethermind` node, see the [`Nethermind` documentation](https://docs.nethermind.io/get-started/running-node/). + + + +### Legacy Geth (optional) + +Legacy Geth is an optional component for OP Mainnet archive nodes. +Legacy Geth allows you to execute stateful queries like `eth_call` against blocks and transactions that occurred before the OP Mainnet [Bedrock Upgrade](https://web.archive.org/web/20230608050602/https://blog.oplabs.co/introducing-optimism-bedrock/). +Legacy Geth is only relevant to OP Mainnet archive nodes and is not required for full nodes or OP Sepolia nodes. + +Currently, `l2Geth` is the only available implementation of Legacy Geth. +In this tutorial you will build the `l2geth` implementation of Legacy Geth as found in the [`optimism-legacy` repository](https://github.com/ethereum-optimism/optimism-legacy). + +## Software dependencies + +Our build environment is managed through a tool called [mise](https://mise.jdx.dev/). Mise provides a convenient way to install and manage all necessary dependencies for building and testing the packages in this repository. You can find the mise configuration in the [`mise.toml`](https://github.com/ethereum-optimism/optimism/blob/develop/mise.toml) in the root of the Optimism Monorepo. + +## Build the rollup node + +First you're going to build the `op-node` implementation of the Rollup Node as found in the [Optimism Monorepo](https://github.com/ethereum-optimism/optimism). + + + +The Optimism Monorepo contains the source code for the `op-node`. + + ```bash + git clone https://github.com/ethereum-optimism/optimism.git + cd optimism + ``` + + + + +Release branches are created when new versions of the `op-node` are created. + Read through the [Releases page](https://github.com/ethereum-optimism/optimism/releases) to determine the correct branch to check out. + + ```bash + git checkout + ``` + + + Make sure to read the Releases page carefully to determine the correct branch to check out. + Some releases may only be required for the OP Sepolia testnet. + + + + + + +Build the `op-node` implementation of the Rollup Node. + + ```bash + cd op-node + just + ``` + + + + +## Build the execution client + +Next you're going to build the `op-geth` implementation of the Execution Client as found in the [`op-geth` repository](https://github.com/ethereum-optimism/op-geth). + + + +The [`op-geth` repository](https://github.com/ethereum-optimism/op-geth) contains the source code for the `op-geth` implementation of the Execution Client. + + ```bash + git clone https://github.com/ethereum-optimism/op-geth.git + cd op-geth + ``` + + + + +Release branches are created when new versions of the `op-geth` are created. + Read through the [Releases page](https://github.com/ethereum-optimism/op-geth/releases) to determine the correct branch to check out. + + ```bash + git checkout + ``` + + + Make sure to read the Releases page carefully to determine the correct branch to check out. + Some releases may only be required for the OP Sepolia testnet. + + + + + + +Build the `op-geth` implementation of the Execution Client. + + ```bash + make geth + ``` + + + + +## Build legacy Geth (optional) + +Legacy Geth is an optional component for OP Mainnet archive nodes. +Legacy Geth allows you to execute stateful queries like `eth_call` against blocks and transactions that occurred before the OP Mainnet [Bedrock Upgrade](https://web.archive.org/web/20230608050602/https://blog.oplabs.co/introducing-optimism-bedrock/). +Legacy Geth is only relevant to OP Mainnet archive nodes and is not required for full nodes or OP Sepolia nodes. + + + +The OP Legacy repository contains the source code for the `l2geth` implementation of Legacy Geth. + + ```bash + git clone https://github.com/ethereum-optimism/optimism-legacy.git + cd optimism-legacy + ``` + + + + +```bash + cd l2geth + make + ``` + + + + +## Next steps + +* Click here to [Run an OP Stack node from source code](/node-operators/tutorials/run-node-from-source) +* If you run into any problems, please visit the [Node Troubleshooting Guide](/node-operators/guides/troubleshooting) for help. diff --git a/docs/public-docs/node-operators/tutorials/reth-historical-proofs.mdx b/docs/public-docs/node-operators/tutorials/reth-historical-proofs.mdx new file mode 100644 index 0000000000000..8fb66ffdf0cb6 --- /dev/null +++ b/docs/public-docs/node-operators/tutorials/reth-historical-proofs.mdx @@ -0,0 +1,145 @@ +--- +title: Running op-reth with Historical Proofs +description: Learn how to initialize and run op-reth with the historical proofs ExEx. +--- + +This guide covers the specific steps required to run `op-reth` with the historical proofs ExEx (Execution Extension), which allows for saving and serving trie nodes to provide proofs faster. + +## Prerequisites + +* [Rust toolchain](https://www.rust-lang.org/tools/install) installed. +* Basic understanding of [execution client configuration](/node-operators/guides/configuration/execution-clients). + +## Installation + +First, clone and build the `op-reth` binary from the `op-rs` fork. + +```bash +git clone --depth 1 --branch develop --single-branch https://github.com/ethereum-optimism/optimism.git +cd optimism/rust/op-reth +cargo build --release --bin op-reth +``` + +The binary will be located at `./target/release/op-reth`. + +## Initialization Steps + +Running `op-reth` with historical proofs requires a two-step initialization process: +1. Initialize the standard `op-reth` database. +2. Initialize the specific `proofs` storage. + +### 1. Initialize op-reth + +Initialize the core database with the genesis file for your chosen chain (e.g., `optimism`). + +```bash +./target/release/op-reth init \ + --datadir="/path/to/datadir" \ + --chain="optimism" +``` + +### Option: Start from a Snapshot + +If you prefer to start from a pre-synchronized database snapshot instead of syncing from genesis: + +1. Download and extract the `op-reth` snapshot into your `datadir`. +2. Skip the `op-reth init` command (step 1 above). +3. Proceed to **Initialize Proofs Storage** (step 2 below). The `proofs init` command will initialize the proofs storage based on the state present in the snapshot. + +### 2. Initialize Proofs Storage + +Initialize the separate storage used by the ExEx to store historical proofs. + +```bash +./target/release/op-reth proofs init \ + --datadir="/path/to/datadir" \ + --chain="optimism" \ + --proofs-history.storage-path="/path/to/proofs-db" +``` + +## Running op-reth + +Once initialized, you can start the execution client with the `--proofs-history` flag enabled. + +```bash +./target/release/op-reth node \ + --datadir="/path/to/datadir" \ + --chain="optimism" \ + --proofs-history \ + --proofs-history.storage-path="/path/to/proofs-db" \ + --proofs-history.window=956200 \ + --proofs-history.prune-interval=10s \ + --http \ + --http.port=8545 \ + --http.addr=0.0.0.0 \ + --http.corsdomain="*" \ + --http.api=admin,net,eth,web3,debug,trace,txpool \ + --ws \ + --ws.addr=0.0.0.0 \ + --ws.port=8546 \ + --ws.api=net,eth,web3,debug,txpool \ + --ws.origins="*" \ + --authrpc.port=8551 \ + --authrpc.jwtsecret="/path/to/jwt.hex" \ + --authrpc.addr=0.0.0.0 \ + --discovery.port=30303 \ + --port=30303 \ + --metrics=0.0.0.0:9001 \ + --rollup.sequencerhttp="https://mainnet-sequencer.optimism.io" +``` + + +Make sure to include all other standard flags required for your network (e.g., specific bootnodes). See the configuration reference for details. + + +## Running Consensus Node + +Start the consensus client to drive the execution client. You can use either `op-node` or `kona-node`. + + + + `op-node` is the reference implementation of the OP Stack consensus client, written in Go. + + ```bash + op-node \ + --l2=http://127.0.0.1:8551 \ + --l2.jwt-secret="/path/to/jwt.hex" \ + --verifier.l1-confs=1 \ + --network="optimism" \ + --rpc.addr=0.0.0.0 \ + --rpc.port=8547 \ + --l1= \ + --l1.beacon= \ + --p2p.advertise.ip= \ + --p2p.advertise.tcp=9003 \ + --p2p.advertise.udp=9003 \ + --p2p.listen.ip=0.0.0.0 \ + --p2p.listen.tcp=9003 \ + --p2p.listen.udp=9003 \ + --safedb.path="/path/to/op-node-db" + ``` + + + + `kona-node` is experimental Rust-based implementation of the OP Stack consensus client. + + ```bash + kona-node \ + --l1-eth-rpc= \ + --l1-beacon= \ + --l2-engine-rpc=http://localhost:8551 \ + --l2-engine-jwt-secret=/path/to/jwt-secret.txt \ + --chain=10 + ``` + + + +## Verification + +After starting both clients, verify that the historical proofs ExEx is active by checking the logs. + +1. **Check op-reth logs**: Look for valid initialization messages indicating the ExEx is running. + ```text + INFO [..] ExEx initialized ... + ``` +2. **Check syncing status**: Ensure `op-node` and `op-reth` are connected and syncing. `op-reth` should start importing blocks and the `proofs` storage should begin populating. diff --git a/docs/public-docs/node-operators/tutorials/run-node-from-source.mdx b/docs/public-docs/node-operators/tutorials/run-node-from-source.mdx new file mode 100644 index 0000000000000..b6b081e12f5ef --- /dev/null +++ b/docs/public-docs/node-operators/tutorials/run-node-from-source.mdx @@ -0,0 +1,515 @@ +--- +title: Running an OP Stack node from source +description: Learn how to run an OP Stack node from source code for full nodes and archive nodes. +--- + +This tutorial explains how to run an OP Stack node from source code for full nodes and archive nodes. +Running an OP Stack node from source code is a flexible alternative to using pre-built Docker images. + +## Building the source code + +You'll need to build `op-node` and execution client (`op-geth` or `Nethermind`) from their respective source repositories before you can run a node. +Make sure to follow the instructions on [Building a Node from Source](./node-from-source) before continuing. + +## Hardware requirements + +Hardware requirements for OP Mainnet nodes can vary depending on the type of node you plan to run. +Archive nodes generally require significantly more resources than full nodes. +Below are suggested minimum hardware requirements for each type of node. + +* 16GB RAM +* Reasonably modern CPU + +### SSD capacity requirements + +Given the growing size of the blockchain state, choosing the right SSD size is important. +Below are the storage needs as of June 2025: + +* **Full Node:** The snapshot size for a full node is approximately 700GB, with the data directory's capacity increasing by about 100GB every six months. +* **Archive Node:** The snapshot size for an archive node is approximately 14TB, with the data directory's capacity increasing by about 3.5TB every six months. A local SSD with a NVME interface is recommended for archive nodes + +Based on these trends, node operators should plan for future storage needs and choose SSDs that can handle these increasing requirements. + + + Geth supports a "freezer" feature to store older chain data on HDDs, saving SSD space. Configure this for OP Mainnet using the `--datadir.ancient` flag. See [Geth docs](https://geth.ethereum.org/docs/fundamentals/databases) and [OP docs](/node-operators/reference/op-geth-config#datadirancient) for details. + + +## OP Stack nodes + +All OP Stack nodes can be run from `op-node` and an execution client (`op-geth` or `Nethermind`) if they're included in the [Superchain Registry](/op-stack/protocol/superchain-registry). You can specify the type of node you want to run by configuring the `--network` flag on `op-node` and the corresponding network flag on your execution client (`--op-network` for `op-geth` or `-c` for `Nethermind`). + +### Assess blob archiver + +Assess if you need to configure a blob archiver service by reading the [Configure a Blob Archiver documentation](/node-operators/guides/management/blobs#configure-a-blob-archiver). + +### Create a JWT secret + +The execution client and `op-node` communicate over the engine API authrpc. +This communication is secured using a shared secret. +You will need to generate a shared secret and provide it to both your execution client and `op-node` when you start them. +In this case, the secret takes the form of a 32 byte hex string. + +Run the following command to generate a random 32 byte hex string: + +```bash +openssl rand -hex 32 > jwt.txt +``` + +### Start the execution client + +It's generally easier to start the execution client before starting `op-node`. +You can still start the execution client without yet running `op-node`, but it will simply not receive any blocks until `op-node` is started. + + + + + +Find the directory where you built the `op-geth` binary. + + + + +Copy the JWT secret you generated in a previous step into the `op-geth` directory. + + ```bash + cp /path/to/jwt.txt . + ``` + + + + + +Set the following environment variables: + + ```bash + export DATADIR_PATH=... # Path to the desired data directory for op-geth + ``` + + + + + + + If you want to run an archive node, you will need to set `--gcmode=archive`. If you want to run an OP Mainnet archive node, please refer to the [OP Mainnet archive nodes](#op-mainnet-archive-nodes) section. + + + Use the following command to start `op-geth` in a default configuration. + The JSON-RPC API will become available on port 8545. + Refer to the [execution clients configuration guide](/node-operators/guides/configuration/execution-clients) for more detailed information about available options. + + ```bash + ./build/bin/geth \ + --http \ + --http.port=8545 \ + --http.addr=localhost \ + --authrpc.addr=localhost \ + --authrpc.jwtsecret=./jwt.txt \ + --verbosity=3 \ + --rollup.sequencerhttp=https://mainnet-sequencer.optimism.io/ \ + --op-network=op-sepolia \ + --datadir=$DATADIR_PATH + ``` + + + + + + + + +Find the directory where you built the `Nethermind` binary. + + + + +Copy the JWT secret you generated in a previous step into the `Nethermind` directory. + + ```bash + cp /path/to/jwt.txt . + ``` + + + + + + + If you want to run an archive node, use the `op-sepolia_archive` configuration instead of `op-sepolia`. For OP Mainnet, use `op-mainnet` or `op-mainnet_archive` configurations respectively. + + + Use the following command to start `Nethermind` in a default configuration. + The JSON-RPC API will become available on port 8545. + Refer to the `Nethermind` [configuration documentation](/node-operators/guides/configuration/execution-clients) for more detailed information about available options. + + ```bash + ./Nethermind.Runner \ + -c op-sepolia \ + --data-dir path/to/data/dir \ + --JsonRpc.JwtSecretFile=./jwt.txt + ``` + + This command uses the built-in `op-sepolia` configuration which already includes the correct settings for: + + * JSON-RPC endpoints + * Network ports + * Sequencer URL + * Other OP Stack specific settings + + + + + + +### Start `op-node` + +Once you've started your execution client, you can start `op-node`. +`op-node` will connect to the execution client and begin synchronizing the OP Mainnet state. +`op-node` will begin sending block payloads to the execution client when it derives enough blocks from Ethereum. + + + +Find the directory where you built the `op-node` binary. + + + + +Both the execution client and `op-node` need to use the same JWT secret. + Copy the JWT secret you generated in a previous step into the `op-node` directory. + + ```bash + cp /path/to/jwt.txt . + ``` + + + + + +Set the following environment variables: + + ```bash + export L1_RPC_URL=... # URL for the L1 node to sync from. If your L1 RPC is a local node, the most common URL is http://127.0.0.1:8545 + export L1_RPC_KIND=... # RPC type (alchemy, quicknode, infura, parity, nethermind, debug_geth, erigon, basic, any) + export L1_BEACON_URL=... # URL address for the L1 Beacon-node HTTP endpoint to use. If your L1 Beacon is a local node, the most common URL is http://127.0.0.1:3500 + ``` + + + + + +Use the following command to start `op-node` in a default configuration. + Refer to the [consensus clients configuration guide](/node-operators/guides/configuration/consensus-clients) for more detailed information about available options. + + + The `op-node` RPC should not be exposed publicly. If left exposed, it could + accidentally expose admin controls to the public internet. + + + + Sync mode is set to `--syncmode=execution-layer` to enable [snap sync](/node-operators/guides/management/snap-sync) + for both `op-geth` and `Nethermind`, removing the need to initialize the node with a data directory. + + + ```bash + ./bin/op-node \ + --l1=$L1_RPC_URL \ + --l1.rpckind=$L1_RPC_KIND \ + --l1.beacon=$L1_BEACON_URL \ + --l2=ws://localhost:8551 \ + --l2.jwt-secret=./jwt.txt \ + --network=op-sepolia \ + --syncmode=execution-layer \ + --l2.enginekind=geth + ``` + + + Some L1 nodes, like Erigon, do not support the `eth_getProof` RPC method that the `op-node` uses to load L1 data for certain processing steps. + If you are using an L1 node that does not support `eth_getProof`, you will need to include the `--l1.trustrpc` flag when starting `op-node`. + Note that this flag will cause `op-node` to trust the L1 node to provide correct data as it will no longer be able to independently verify the data it receives. + + + + + +### Synchronization verification + +Once you've started your execution client and `op-node` you should see them begin to communicate with each other and synchronize the OP Mainnet chain. + +#### Snap sync (default) + + + + Initial synchronization can take several hours to complete. You will see these + `op-node` logs at the start of snap sync: + + ```text + INFO [03-06|10:56:55.602] Starting EL sync + INFO [03-06|10:56:55.615] Sync progress reason="unsafe payload from sequencer while in EL sync" l2_finalized=000000..000000:0 l2_safe=000000..000000:0 l2_pending_safe=000000..000000:0 l2_unsafe=4284ab..7e7e84:117076319 l2_time=1,709,751,415 l1_derived=000000..000000:0 + INFO [03-06|10:56:57.567] Optimistically inserting unsafe L2 execution payload to drive EL sync id=4ac160..df4d12:117076320 + ``` + + `Starting EL sync` is shown once and the **sync progress / inserting logs** should be repeated until done. + + `op-node` will log the following when done: + + ```text + lvl=info msg="Finished EL sync" sync_duration=23h25m0.370558429s finalized_block=0x4f69e83ff1407f2e2882f2526ee8a154ac326590799889cede3af04a7742f18d:116817417 + ``` + + There are two stages on `op-geth` for snap sync: + + + +`op-geth` will log something like this as it is downloading the headers: + + ```text + lvl=info msg="Syncing beacon headers" downloaded=116775778 left=1162878 eta=53.182s + ``` + + + + +For the second stage, `op-geth` will log the following: + + ```text + lvl=info msg="Syncing: state download in progress" synced=99.75% state="191.33 GiB" accounts=124,983,227@25.62GiB slots=806,829,266@165.16GiB codes=78965@566.74MiB eta=-2m7.602s + ``` + + ```text + msg="Syncing: chain download in progress" synced=100.00% chain="176.01 GiB" headers=116,817,399@45.82GiB bodies=116,817,286@52.87GiB receipts=116,817,286@77.32GiB eta=77.430ms + ``` + + All the while, `op-geth` will also log the forkchoice update: + + ```text + Forkchoice requested sync to new head number=117,076,468 hash=e3884c..bf4e2b + ``` + + + + + + + Initial synchronization can take several hours to complete. + + Snap sync in `Nethermind` works by: + + 1. Downloading only the leaf nodes of the state tree + 2. Generating intermediate nodes locally + 3. Verifying the state root matches + + This approach is up to 10 times faster than traditional full sync. + + + +#### Full sync + + +For OP Mainnet you will need access to the migrated database to run a full node with full sync. +You can [migrate your own data directory](https://web.archive.org/web/20240110231645/https://blog.oplabs.co/reproduce-bedrock-migration/) or follow the options available for [archive nodes](#get-the-migrated-data-directory). + + + + + Initial full synchronization can take several days or weeks to complete. During this time, you will initially observe `op-node` deriving blocks from Ethereum without sending these blocks to your execution client. + + + + To use full sync with `Nethermind`, set the following configuration: + + ```bash + ./Nethermind.Runner \ + -c op-sepolia \ + --Sync.SnapSync=false \ + --Sync.FastSync=false \ + --data-dir path/to/data/dir \ + --JsonRpc.JwtSecretFile=./jwt.txt + ``` + + Full sync will download and verify every block from genesis, which takes significantly longer than snap sync but provides the strongest security guarantees. + + + +Both execution clients will receive blocks from `op-node` as it requests them from Ethereum one-by-one and determines the corresponding OP Mainnet blocks that were published. +You should see logs like the following from `op-node`: + +```text +INFO [06-26|13:31:20.389] Advancing bq origin origin=17171d..1bc69b:8300332 originBehind=false +``` + +Once the initial sync is complete, you'll see sync progress logs: + +```text +INFO [06-26|14:00:59.460] Sync progress reason="processed safe block derived from L1" l2_finalized=ef93e6..e0f367:4067805 l2_safe=7fe3f6..900127:4068014 l2_unsafe=7fe3f6..900127:4068014 l2_time=1,673,564,096 l1_derived=6079cd..be4231:8301091 +INFO [06-26|14:00:59.460] Found next batch epoch=8e8a03..11a6de:8301087 batch_epoch=8301087 batch_timestamp=1,673,564,098 +INFO [06-26|14:00:59.461] generated attributes in payload queue txs=1 timestamp=1,673,564,098 +INFO [06-26|14:00:59.463] inserted block hash=e80dc4..72a759 number=4,068,015 state_root=660ced..043025 timestamp=1,673,564,098 parent=7fe3f6..900127 prev_randao=78e43d..36f07a fee_recipient=0x4200000000000000000000000000000000000011 txs=1 update_safe=true +``` + +You should then also begin to see logs from your execution client: + + + + ```text + INFO [06-26|14:02:12.974] Imported new potential chain segment number=4,068,194 hash=a334a0..609a83 blocks=1 txs=1 mgas=0.000 elapsed=1.482ms mgasps=0.000 age=5mo2w20h dirty=2.31MiB + INFO [06-26|14:02:12.976] Chain head was updated number=4,068,194 hash=a334a0..609a83 root=e80f5e..dd06f9 elapsed="188.373µs" age=5mo2w20h + INFO [06-26|14:02:12.982] Starting work on payload id=0x5542117d680dbd4e + ``` + + + + * **Received New Block**: Block received with number, hash and extra data `21288296 (0xb61f74...cbfbe7), Extra Data: Titan (titanbuilder.xyz)` + * **Processed**: Block or block range processed, e.g., or `x4 21288291 .. 21288295` or `21288296` + * **Received ForkChoice**: Updates on the blockchain's canonical chain; with safe and finalized block, e.g., `21288296 (0xb61f74...cbfbe7), Safe: 21288252 (0x46906d...7777b8), Finalized: 21288221 (0x22a7d2...ebeae9)` + * **Synced Chain Head**: Latest synced block number and hash on the chain, e.g., `21288296 (0xb61f74...cbfbe7)` + + + +## OP Mainnet archive nodes + + + You only need an archive node if you need the historical state. Most node operators should default to full nodes. + + +### Get the migrated data directory + +OP Mainnet underwent a large database migration as part of the [Bedrock Upgrade](https://web.archive.org/web/20230608050602/https://blog.oplabs.co/introducing-optimism-bedrock/) in 2023. +You will need access to the migrated OP Mainnet database to run an archive node. +You can [migrate your own data directory](https://web.archive.org/web/20240110231645/https://blog.oplabs.co/reproduce-bedrock-migration/) or simply download a database that has already been migrated. +In this section, you'll learn how to download and verify the pre-migrated database. + + + +Click the link below to find the latest publicly available database snapshots for OP Mainnet. + Snapshots are available for multiple dates and snapshots get larger as they get closer to the current date. + Snapshots are large files and may take some time to download. + [OP Mainnet Snapshots](/node-operators/guides/management/snapshots#op-mainnet) + + + + +You should always verify the integrity of your downloads to ensure that they have not been corrupted. + A corrupted database can include invalid data or may cause your node to fail. + Verify the integrity of the download by checking the SHA256 checksum of the downloaded file. + + ```bash + sha256sum + ``` + + For instance, if you've downloaded the very first database snapshot, you can verify the download by running the following command: + + ```bash + sha256sum mainnet-bedrock.tar.zst + ``` + + You should see the following output: + + ```bash + ec4baf47e309a14ffbd586dc85376833de640c0f2a8d7355cb8a9e64c38bfcd1 mainnet-bedrock.tar.zst + ``` + + Your exact output will depend on the snapshot you've downloaded. + Check the [OP Mainnet Snapshots](/node-operators/guides/management/snapshots#op-mainnet) page for the correct checksum for the snapshot you've downloaded. + + + + + +Once you've downloaded the database snapshot, you'll need to extract it to a directory on your machine. + This will take some time to complete. + + ```bash + tar xvf + ``` + + For instance, if you've downloaded the very first database snapshot, you can extract it by running the following command: + + ```bash + tar xvf mainnet-bedrock.tar.zst + ``` + + + + + +Set `--syncmode=full` and `--gcmode=archive` on `op-geth`. + + + + +### Get the legacy Geth directory (optional) + +Blocks and transactions included in OP Mainnet before the Bedrock Upgrade cannot be executed by modern OP Mainnet nodes. +OP Mainnet nodes will **serve** these blocks and transactions but cannot run certain queries against them (e.g. `eth_call`). +If you need to run stateful queries like `eth_call` against these older blocks and transactions, you will need to run a Legacy Geth node alongside your OP Mainnet node. + +Running a Legacy Geth node is entirely optional and typically only useful for operators who want to run complete archive nodes of the OP Mainnet state. +If you want to run a full node then you can safely skip this section. + + + +Click the link below to download the latest publicly available database snapshot for Legacy Geth. + This is a very large file (2.9TB), so expect the download to take some time to complete. + + [Legacy Geth Data Directory (2.9TB)](/node-operators/guides/management/snapshots#op-mainnet-legacy) + + + + +You should always verify the integrity of your downloads to ensure that they have not been corrupted. + A corrupted database can include invalid data or may cause your node to fail. + Verify the integrity of the download by checking the SHA256 checksum of the downloaded file. + + ```bash + sha256sum mainnet-legacy-archival.tar.zst + ``` + + You should see the following output: + + ```bash + 4adedb61125b81b55f9bdccc2e85092050c65ef2253c86e2b79569732b772829 mainnet-legacy-archival.tar.zst + ``` + + If you see a different output, then the download is corrupted and you should try downloading the file again. + + + + + +Once you've downloaded the database snapshot, you'll need to extract it to a directory on your machine. + This will take some time to complete. + + ```bash + tar xvf mainnet-legacy-archival.tar.zst + ``` + + + + +### Start legacy Geth (optional) + +If you've chosen to run a Legacy Geth node alongside your OP Mainnet node, you'll need to start it before you start your OP Mainnet node. + + + +Find the directory where you built the `l2geth` binary. + + + + +Run the following command to start `l2geth`: + + ```bash + USING_OVM=true \ + ETH1_SYNC_SERVICE_ENABLE=false \ + RPC_API=eth,rollup,net,web3,debug \ + RPC_ENABLE=true \ + RPC_PORT=8546 \ + ./build/bin/geth --datadir /path/to/l2geth-datadir + ``` + + + + +## Next steps + +* If you've already got your node up and running, check out the [Node Metrics and Monitoring Guide](/node-operators/guides/monitoring/metrics) to learn how to keep tabs on your node and make sure it keeps running smoothly. +* If you run into any problems, please visit the [Node Troubleshooting Guide](/node-operators/guides/troubleshooting) for help. diff --git a/docs/public-docs/notices/archive/blob-fee-bug.mdx b/docs/public-docs/notices/archive/blob-fee-bug.mdx new file mode 100644 index 0000000000000..5299d02769bfc --- /dev/null +++ b/docs/public-docs/notices/archive/blob-fee-bug.mdx @@ -0,0 +1,114 @@ +--- +title: Superchain testnets' blob fee bug +description: Learn about the blob fee bug that affects OP Stack testnets on the Superchain and how to fix it. +--- + +At the time of writing, all OP Stack testnets are currently sequencing a "wrong" chain. The L1 Block's blob base fee is calculated with pre-Prague blob schedule parameters. This is not a critical security issue on any OP Sepolia (or Holesky) chain. However, when L1 blob base fees rise to 100 Mwei or higher on the L1 testnet, there is a temporary liveness risk, because we'd overcharge L1 fees. + +This was fixed on the `develop` branch with this [PR](https://github.com/ethereum-optimism/optimism/pull/14500) and there will be no impact on any mainnet chains. Mainnet chains will just need to update their binaries to the latest release prior to the activation of Pectra on Ethereum, so the bug isn't triggered when Pectra activates. + +The following sections describe the impact of this bug and how to fix it on your testnet. + +## Impact + +### Testnet L1 cost overcharging + +All OP Stack testnet chains are currently sequencing an unintended chain. The L1 Block's blob base fee is calculated with pre-Prague blob schedule parameters. The L1 Block's blob base fee is calculated with pre-Prague schedule parameters. This has little impact as long as blob base fees on Sepolia are low, but as they rise, we exponentially overcharge. More concretely, our blob base fee calculation is off by an exponential of 1.5: + +``` +BBF_Cancun = BBF_Prague^1.5 +``` + +This has a huge impact as fees grow: + +Blob Fee Bug Graph + +| BBF Prague (actual) | BBF Cancun (we charge) | +| ------------------- | ---------------------- | +| 1 Mwei | 1 Gwei | +| 10 Mwei | 31.6 Gwei | +| 100 Mwei | 1 Twei | +| 1 Gwei | 31.6 Twei (31.6e12) | +| 10 Gwei | 1e15 Wei = 1 PetaWei | +| 100 Gwei | 31.6e15 PetaWei | +| 1 Twei | 1e18 Wei = 1 ExaWei | + +## For chain and node operators + +All chain operators will need to coordinate with their node operators to schedule a hardfork activation time to utilize the correct blob base fee calculation. The following is a list of chains who have opted into the Superchain [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md) are `OP Sepolia`, `Soneium Minato`, `Zora Sepolia`, `Unichain Sepolia`, `Base Sepolia`, `Mode Sepolia`, `Metal Sepolia`, `Creator Chain Sepolia`, `Ink Sepolia`, and `Ethernity Sepolia`. + +The node operators for these chains should use the network flags (`--network` and `op-geth` with the `--op-network`) to inherit the `Thu Mar 20 16:00:00 UTC 2025` (`1742486400`) hardfork activation time. + +The following chains are in the Superchain Registry and are not opted into the hardfork activation inheritance behavior: `Arena-Z Sepolia`, `Cyber Sepolia`, `Funki Sepolia`, `Lisk Sepolia`, `Pivotal Sepolia`, `Race Sepolia`, `Shape Sepolia`, `TBN Sepolia`, and `Worldchain Sepolia`. These chains and any other OP Stack testnet that is not included in the lists above will need to manually set the hardfork activation time for their network to activate on. These following steps are necessary for every node operator: + + + + + You must configure your op-node to utilize the activation timestamp outlined in step 2 at the same time as upgrading your node binary. This is to ensure that the hardfork is activated uniformly across the network. If the Pectra Blob Schedule flag is not set, your node will either not start or automatically apply the hardfork at startup causing the node to fork from the rest of the network. + + + The following `op-node/v1.12.2` adds a kill-switch to op-node to print an error at startup if the Pectra Blob Schedule Fix time is not set for a Sepolia or Holesky chain. The check only happens if the chain's genesis is before the Holesky/Sepolia Pectra activation time. The check can be disabled with a hidden flag. + + The purpose of the kill-switch is to make sure that node operators don't accidentally upgrade their nodes without scheduling the fix because most Holesky and Sepolia chains were running a sequencer that ran into the Pectra Blob Schedule bug. So it's better to not start op-node in such cases at all rather than accidentally forking the chain. + + The `op-node/v1.12.1` and `op-node/v1.12.0` binaries do not have this kill-switch and will automatically apply the hardfork at startup if there is no Pectra Blob Schedule Fix time set. + + * `op-node` at [`v1.12.2`](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.12.2) + * `op-geth` at [`v1.101503.1`](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101503.1) + + + + + If you are operating a node for an OP Chain that has opted into the [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md), the Pectra Blob Schedule Fix activation date is part of the op-node node. So, no action is needed for the sequencer after upgrading to the latest release, assuming you're using the network flags. + + That is: `OP Sepolia`, `Soneium Minato`, `Zora Sepolia`, `Unichain Sepolia`, `Base Sepolia`, `Mode Sepolia`, `Metal Sepolia`, `Creator Chain Sepolia`, `Ink Sepolia`, and `Ethernity Sepolia`. + + + For chains that are not opted into the hardfork activation inheritance behavior, you will need to manually set the hardfork activation time. This can be done one of two ways: + + * **Option 1:** Set the activation time in the `rollup.json` for `op-node`. You do not need to set any configurations in `op-geth`. + + * **Option 2:** Set the activation time via overrides (CLI) in the `op-node`. These will need to be set on `op-node` for the sequencer and all other nodes. The override flag looks like this: `--override.pectrablobschedule=1742486400`. + + + + If your Pectra Blob Schedule fix activation time is not in the Superchain Registry, decide on a hardfork activation time, and open a PR update the Superchain Registry with the activation time. See this [PR](https://github.com/ethereum-optimism/superchain-registry/pull/941) as an example. + + + +## Fixing diverged nodes + +If you upgraded your node binary to `op-node/v1.12.1` or `op-node/v1.12.0` and did not set the Pectra Blob Schedule Fix activation time your node will have forked from the rest of the network. This could have happened if you set the incorrect activation time e.g. set the wrong timestamp in your `rollup.json`, set the wrong timestamp in the `op-node` CLI, or did not set the timestamp at all. + +To fix this, you can either restore your node from a snapshot and configure the Pectra Blob Schedule Fix activation time. Or you can use [`op-wheel`](https://github.com/ethereum-optimism/optimism/tree/develop/op-wheel) to roll back your node to a block before you diverged from the network. + + + + * Stop op-node: This component is stateless, so you can simply terminate the process. + Stop op-geth: It's crucial to use `Ctrl+C` to stop op-geth gracefully to avoid database corruption. An unclean shutdown can lead to various problems when restarting. + + + + ``` + cast block --rpc-url=http://localhost:8545 6185373 + #hash: 0x8b39f479198a5e658bbb28b1ce80790ca863ac39206c36152ba5c38db68989b2 + #timestamp: 1735565082 + # 6185373 in hex is 0x5E619D + ``` + + + + ``` + cast rpc --rpc-url=http://localhost:8545 debug_setHead 0x5E619D + ``` + + + + For example: + + ``` + go run ./op-wheel/cmd engine set-forkchoice --engine=http://localhost:8551 --engine.jwt-secret=.jwt_secret.txt --finalized=6185373 --safe=6185373 --unsafe=6185373 + ``` + + + diff --git a/docs/public-docs/notices/archive/fusaka-notice.mdx b/docs/public-docs/notices/archive/fusaka-notice.mdx new file mode 100644 index 0000000000000..444165ca91bdd --- /dev/null +++ b/docs/public-docs/notices/archive/fusaka-notice.mdx @@ -0,0 +1,159 @@ +--- +title: Fusaka upgrade notice +description: Learn how to prepare for the Fusaka upgrade +lang: en-US +content_type: notice +topic: fusaka-upgrade +personas: + - chain-operator +categories: + - security + - protocol + - infrastructure +is_imported_content: 'false' +--- + + +# Preparing for L1 Fusaka upgrade + +This page provides external-facing information about the Fusaka readiness upgrade for OP Stack chains. It summarizes scope, timelines, required actions, and resources for chain operators and partners. This upgrade is necessary for all OP Stack chains deriving from an L1 chain which is due to activate Fusaka (including Ethereum Mainnet and Sepolia). + +This is NOT Fusaka adoption on L2. It's a readiness upgrade to make the OP Stack protocol compatible with L1 that activates the Fusaka fork. L2 adoption will happen in a future upgrade. + + + All of the tasks below must be performed before Fusaka activates on L1. + + +## What's included in Fusaka + +Fusaka contains various breaking changes. See [EIP-7607: Hardfork Meta - Fusaka](https://eips.ethereum.org/EIPS/eip-7607) for more info. + +**Important dates:** + +* Ethereum Sepolia Fusaka hard fork: Tuesday, October 14th, 2025 07:36:00 UTC (`1760427360`) +* Sepolia BPO 1: Tuesday, October 21st, 2025 03:26:24 UTC (`1761017184`) +* Sepolia BPO 2: Monday, October 27th, 2025 23:16:48 UTC (`1761607008`) +* Ethereum Mainnet Fusaka hard fork: Wednesday, December 3rd, 2025 21:49:11 UTC (`1764798551`) +* Ethereum Mainnet BPO 1: Tuesday, December 9th, 2025 14:21:11 UTC (`1765290071`) +* Ethereum Mainnet BPO 2: Wednesday, January 7th, 2026 01:01:11 UTC (`1767747671`) + +## For node operators + +Update your node software as soon as possible after release: + + * [op-node/v1.16.2](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.16.2) + * [op-geth/v1.101603.5](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101603.5) + * [op-reth/v1.9.2](https://github.com/paradigmxyz/reth/releases/tag/v1.9.2) + +### Ensure your L1 beacon node endpoint can serve all blobs + +**For operators using an external L1 Ethereum Beacon:** + +Before the Ethereum Fusaka hard fork, ensure your external L1 Beacon Chain RPC provider has configured their node to subscribe to all subnets. +If your external L1 Beacon Chain RPC provider doesn't subscribe to all subnets, migrate to one that does prior to these deadlines. + +**For operators running their own L1 Ethereum Beacon Chain Node:** + +**If you operate your own L1 nodes, ensure also both the L1 execution and beacon nodes are upgraded** and configured for the Fusaka fork. + +Outdated L1 nodes will cause derivation failures, blob unavailability, or chain divergence after the fork. + +Configure your Beacon Node with the new flag (detailed below) before the relevant Fusaka hard fork date. + +L1 Beacon Chain Node flags: + +* Lighthouse: `--supernode` +* Teku: `--p2p-subscribe-all-custody-subnets-enabled` +* Grandine: `--subscribe-all-data-column-subnets` +* Lodestar: `--supernode` +* Nimbus: `--debug-peerdas-supernode` + +## For chain operators + +Update the following components: + +| Component | Version | Notes | +| --------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `proxyd` | [v4.19.0](https://github.com/ethereum-optimism/infra/releases/tag/proxyd%2Fv4.19.0) or greater | You need to whitelist `eth_blobBaseFee` rpc if using proxyd for L1 load balancing. If you use `dugtrio` ensure this is also updated to the latest release. | +| `op-batcher` | [v1.16.2](https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.16.2) | The `op-batcher` must be restarted at least once after updating `op-node` to the latest release. The batcher loads the rollup config once at startup and this will need to happen to update the activation timestamps for Jovian. We've opened this [issue](https://github.com/ethereum-optimism/optimism/issues/18178) to improve this experience in the future. | +| `op-node` | [v1.16.2](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.16.2) | L1 chain config must be supplied via a new flag (see the release notes). Not necessary for chains deriving from Ethereum Mainnet, Sepolia, Holesky or Hoodi. | +| `op-geth` | [v1.101603.5](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101603.5) | | +| `op-challenger` | [v1.7.0](https://github.com/ethereum-optimism/optimism/releases/tag/op-challenger%2Fv1.7.0) | If deriving from a chain other than Ethereum Mainnet, Sepolia or Hoodi then L1 chain config must be supplied via a new flag. | +| `kona-node` | [v1.2.4](https://github.com/op-rs/kona/releases/tag/kona-node%2Fv1.2.4) | | +| `kona-host` | [v1.2.4](https://github.com/op-rs/kona/releases/tag/kona-host%2Fv1.2.4) | | +| `kona-client` | [v1.2.4](https://github.com/op-rs/kona/releases/tag/kona-client%2Fv1.2.4) | | +| `op-reth` | [v1.9.2](https://github.com/paradigmxyz/reth/releases/tag/v1.9.2) | | +| `op-rbuilder` | [v0.2.11](https://github.com/flashbots/op-rbuilder/releases/tag/v0.2.11) | | +| `rollup-boost` | [v0.7.10](https://github.com/flashbots/rollup-boost/releases/tag/rollup-boost%2Fv0.7.10) | | + + +**Note:** `op-node` uses the new `/eth/v1/beacon/blobs/` API for fetching blob data from L1 beacon nodes. If you run a proxy/load balancer in front of your beacon API, ensure it correctly forwards all query parameters. + +Misconfigured proxies may cause blob fetch failures or derivation stalls after the Fusaka fork. + + +### For permissionless fault proof enabled chains + + + The Superchain will be bundling Fusaka support and the Mainnet [Jovian hard fork](/notices/upgrade-17) support into a single absolute prestate. + + + + Optimism will complete the onchain prestate update (including Dispute Game deployment and implementation set) on OP, Ink, and Unichain Mainnet and Sepolia. + However, off chain components (op-challenger) must still be configured by operators to use the new prestate. + If you are a Permissionless FP enabled chain not included in the list above, you must perform all steps below yourself. + + +Chains running permissionless fault proofs will need to deploy new dispute game contracts with new absolute prestates. + + + + + The absolute prestate is generated with the [op-program/v1.8.0-rc.4](https://github.com/ethereum-optimism/optimism/tree/op-program/v1.8.0-rc.4). You can use this new absolute prestate `0x03caa1871bb9fe7f9b11217c245c16e4ded33367df5b3ccb2c6d0a847a217d1b` for the following chains: + + * Mainnet and Sepolia: `OP`, `Base`, `Ink`, and `Unichain` + + You can verify this absolute prestate by running the following command in the root of the monorepo on the `op-program/v1.8.0-rc.4` tag: + + ```shell + make reproducible-prestate + ``` + + This will output the calculated prestates, the tail end of the output should look like this: + + ```shell + -------------------- Production Prestates -------------------- + + Cannon64 Absolute prestate hash: + 0x03caa1871bb9fe7f9b11217c245c16e4ded33367df5b3ccb2c6d0a847a217d1b + + -------------------- Experimental Prestates -------------------- + + Cannon64Next Absolute prestate hash: + 0x03caa1871bb9fe7f9b11217c245c16e4ded33367df5b3ccb2c6d0a847a217d1b + + CannonInterop Absolute prestate hash: + 0x03455f7f2179327853a989648c3ac9f2d58be45137bae603271660519b4d5245 + + CannonInteropNext Absolute prestate hash: + 0x03455f7f2179327853a989648c3ac9f2d58be45137bae603271660519b4d5245 + ``` + + * The "Cannon64" hash is the 64-bit prestate. + + Verify that your target prestate was calculated as expected and matches the corresponding entry in + [standard-prestates.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). + + + + During the previous step, you also generated the preimage of the absolute prestate, which is the op-program serialized into a binary file. You'll find that new file at `optimism/op-program/bin/prestate-mt64.bin.gz`. Rename that file to have the absolute prestate hash as the filename so it looks like `PRESTATEHASH.bin.gz`. + + Upload that file to where you're storing your other absolute preimage files. This should be the location where you're pointing your `--cannon-prestates-url` at. The `op-challenger` will grab this file and use it when it needs to challenge games. + + + + Once your `op-challenger` is ready with the new preimage, you can execute the upgrade transaction. This should be done by making a delegatecall to the `upgrade()` function of the OP Contract Manager at the address listed in [the registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-mainnet.toml). + + Please simulate and validate the expected output prior to executing the transaction. + + diff --git a/docs/public-docs/notices/archive/holocene-changes.mdx b/docs/public-docs/notices/archive/holocene-changes.mdx new file mode 100644 index 0000000000000..055cfcfa11601 --- /dev/null +++ b/docs/public-docs/notices/archive/holocene-changes.mdx @@ -0,0 +1,87 @@ +--- +title: Preparing for Holocene breaking changes +description: Learn how to prepare for Holocene upgrade breaking changes. +--- + +This page outlines breaking changes related to the Holocene network upgrade for chain operators, and node operators. +If you experience difficulty at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions). + + + The Holocene upgrade for the Sepolia Superchain will be activated at **Tue Nov 26 at 15:00:00 UTC** (`1732633200`). + + The Holocene upgrade for the Unichain Sepolia will be activated at **Wed Dec 18 at 22:00:00 UTC** (`1734559200`). + + The Holocene upgrade for the Soneium Minato (Sepolia) will be activated at **Fri Dec 20 at 09:00:00 UTC** (`1734685200`). + + The Holocene upgrade for the Mainnet Superchain is scheduled for **Thu 9 Jan 2025 18:00:01 UTC**, [governance approval](https://vote.optimism.io/proposals/20127877429053636874064552098716749508236019236440427814457915785398876262515). + + The Holocene upgrade for the Soneium Mainnet will be activated at **Mon Feb 03 at 09:00:00 UTC** (`1738573200`). + + +## What's included in Holocene + +Holocene contains three changes: + +* **Holocene block derivation**: a set of changes that render the derivation pipeline stricter and simpler, improving worst-case scenarios for the Fault Proof System and Interoperability. +* **EIP-1559 configurability**: The elasticity and denominator EIP-1559 parameters become configurable via the `SystemConfig` L1 contract, allowing the gas target and gas limit to be configured independently. +* **MIPS contract upgrade**: Updates to support additional calls made by the new `op-program` version. + +For more information on the Holocene implementation details, please review [Holocene specification](https://specs.optimism.io/protocol/holocene/overview.html?utm_source=op-docs&utm_medium=docs). + +## For chain operators + +Chain operators should upgrade their nodes ahead of the activation times to a release that contains the Holocene changes and has the activation times for their chains baked in, or set the activation times manually via overrides. + +Besides this, several L1 contract updates must be performed, the fault proof contracts should be updated before the Holocene activation. The `SystemConfig` should be upgraded after the Holocene activation. We have prepared an [upgrade script](https://github.com/ethereum-optimism/optimism/tree/op-contracts/v1.8.0-rc.4/packages/contracts-bedrock/scripts/upgrades/holocene) to automate most of the deployments and superchain-ops task generation or Safe multi-sig input bundle generation. + +Chain operators must upgrade their chain's `SystemConfig` to the latest OP Contracts [v1.8.0-rc.3 release](https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.3) to utilize the EIP-1559 configurability. The updated `SystemConfig` implementations are deployed at addresses: + +* Sepolia: `0x33b83E4C305c908B2Fc181dDa36e230213058d7d` - [Superchain Registry validation file](https://github.com/ethereum-optimism/superchain-registry/blob/ca80c8f88003d599428b79fefc741ceca049da6f/validation/standard/standard-versions-sepolia.toml#L9) +* Mainnet: `0xAB9d6cB7A427c0765163A7f45BB91cAfe5f2D375` - [Superchain Registry validation file](https://github.com/ethereum-optimism/superchain-registry/blob/ca80c8f88003d599428b79fefc741ceca049da6f/validation/standard/standard-versions-mainnet.toml#L9) + +Chain operators need to update their proxy contracts to point to these new implementations. The upgrade script in the monorepo can be used to facilitate the upgrade, please follow the instructions in this [README](https://github.com/ethereum-optimism/optimism/tree/op-contracts/v1.8.0-rc.4/packages/contracts-bedrock/scripts/upgrades/holocene/README.md). Note that it is recommended to upgrade the `SystemConfig` after the Holocene activation. You need to upgrade if you want to reconfigure your EIP-1559 parameters. + +### For fault proof enabled chains + +Since the Holocene upgrade changes the execution and derivation rules, the version of `op-program` used in the fault proof system has to be upgraded to a version that includes the Holocene activation date for the chain. The `op-program` version used is specified via the `faultGameAbsolutePrestate` setting, deployed as part of `FaultDisputeGame` and `PermissionedDisputeGame` contracts. Additionally, the `MIPS` contract must be upgraded to support additional calls made by the new `op-program`. + +The `FaultDisputeGame` and `PermissionedDisputeGame` contracts must be deployed separately for each chain. The `MIPS` contract implementation can be shared by all chains and is deployed at: + +* Sepolia: `0x69470D6970Cd2A006b84B1d4d70179c892cFCE01` - [Superchain Registry validation file](https://github.com/ethereum-optimism/superchain-registry/blob/ca80c8f88003d599428b79fefc741ceca049da6f/validation/standard/standard-versions-mainnet.toml#L12) +* Mainnet: `0x5fE03a12C1236F9C22Cb6479778DDAa4bce6299C` - [Superchain Registry validation file](https://github.com/ethereum-optimism/superchain-registry/blob/ca80c8f88003d599428b79fefc741ceca049da6f/validation/standard/standard-versions-mainnet.toml#L12) + +Chain operators need to update the `DisputeGameFactory` to use the new `FaultDisputeGame` and `PermissionedDisputeGame` contracts by calling `DisputeGameFactory.setImplementation`. The same upgrade script in the monorepo can be used to facilitate the upgrade, please follow the instructions in this [README](https://github.com/ethereum-optimism/optimism/tree/op-contracts/v1.8.0-rc.4/packages/contracts-bedrock/scripts/upgrades/holocene/README.md). + +## For node operators + +Node operators will need to upgrade to the respective Holocene releases before the activation dates. + +These following steps are necessary for every node operator: + + + + * [`op-node` at `v1.10.2`](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.10.2) + * [`op-geth` at `v1.101411.4`](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101411.4) + + + + + If you are operating a node for an OP Chain that has opted into the [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md), the Holocene activation date is part of the `op-node` and `op-geth` nodes. So, no action is needed for the sequencer after upgrading to the latest release. Please skip to [Step 3: Verify Your Configuration](#verify-your-configuration). + + For Sepolia that is: `OP Sepolia`, `Base Sepolia`, `Mode Sepolia`, `Zora Sepolia`, and `Metal Sepolia`. + + + For node operators of not included in the [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md), you will need to manually configure the activation. This can be done one of two ways: + + * **Option 1:** Set the activation time in the `rollup.json` for `op-node`. You will still need to set the `override.holocene` flag in `op-geth` if you use this option. + * **Option 2:** Set the activation time via overrides (CLI) in both `op-node` and `op-geth`. These will need to be set on `op-node` and `op-geth` for the sequencer and all other nodes. + + + + Make the following checks to verify that your node is properly configured. + + * `op-node` and `op-geth` will log their configurations at startup + * Check that the Holocene time is set to `activation-timestamp` in the op-node startup logs + * Check that the Holocene time is set to `activation-timestamp` in the op-geth startup logs + + diff --git a/docs/public-docs/notices/archive/pectra-changes.mdx b/docs/public-docs/notices/archive/pectra-changes.mdx new file mode 100644 index 0000000000000..e1f9694750bd1 --- /dev/null +++ b/docs/public-docs/notices/archive/pectra-changes.mdx @@ -0,0 +1,153 @@ +--- +title: Preparing for Pectra breaking changes +description: Learn how to prepare for Pectra upgrade breaking changes. +--- + + + Please note that this notice page was updated March 11th, 2025. There was a bug found in previous op-node releases where the L1 Block's blob base fee (for the sake of computing the L1 fees) is calculated with pre-Prague=Cancun blob schedule parameters, instead of using the Prague parameters. This bug has been fixed in the latest release of op-node. + + This is not a critical issue on any OP Sepolia chain. However, when L1 blob base fees rise to 100 Mwei or higher on Sepolia, there is a temporary liveness risk, because we'd be overcharge L1 fees. Essentially, our L1 cost computation now overcharges by an exponential of 1.5, BBF\_Cancun = `BBF_Prague^1.5` (where BBF=blob base fee). + + You must update your Sepolia nodes to the latest release of op-node and schedule a hardfork activation time to avoid this issue on your network. There are new instructions in the node operator section to help you mitigate this issue. + + See this notice page for more information: [Superchain testnets' blob fee bug](/notices/archive/blob-fee-bug). + + +This page outlines breaking changes related to the Ethereum Pectra (Prague-Electra) hard fork for chain operators and node operators on OP Stack chains. The OP Stack is dividing the Pectra upgrade into two parts: + +1. **Make the necessary upgrades to make sure OP Stack chains do not break when the L1 Pectra upgrade activates.** We will release new versions of the OP Stack to ensure that OP Stack chains remain functional after the L1 Pectra upgrade. If you're running a fault proof enabled chain, you will need to follow additional steps outlined below. +2. **Upgrade OP Stack chains to support Pectra's new features that apply to L2s.** We will implement all the features of Pectra that apply to L2s and will release a new version of the OP Stack that includes these features. We will provide more information as engineering work wraps up. We are tracking the spec work in this [issue](https://github.com/ethereum-optimism/specs/issues/499). The upcoming Isthmus hardfork will contain all Prague features, you can track our progress in this [project board](https://github.com/orgs/ethereum-optimism/projects/117). + +If you experience difficulty at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions). + + + This page will be updated continuously with information on upgrade runbooks and timelines as they come. Here's the tentative L1 Pectra hard fork times per the ACDC that happened on Feb 6th: + + L1 Client testnet releases out by Feb 13 (ACDE): + + * Holesky slot: `1740434112` (Mon, Feb 24 at 21:55:12 UTC) + * Sepolia slot: `1741159776` (Wed, Mar 5 at 07:29:36 UTC) + * +30 day mainnet slot: `1744154711` (Tue, Apr 8 at 23:25:11 UTC) + + +## What's included in Pectra? + +Pectra contains a variety of EIPs, some of which apply to the OP Stack; others do not. The following EIPs are included in Pectra as outlined in the [Pectra Devnet 5 notes](https://notes.ethereum.org/@ethpandaops/pectra-devnet-5): + +* [EIP-2537: Precompile for BLS12-381 curve operations](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-2537.md) +* [EIP-2935: Save historical block hashes in state](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-2935.md) +* [EIP-6110: Supply validator deposits on chain](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-6110.md) +* [EIP-7002: Execution layer triggerable withdrawals](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-7002.md) +* [EIP-7251: Increase the MAX\_EFFECTIVE\_BALANCE](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-7251.md) +* [EIP-7549: Move committee index outside Attestation](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-7549.md) +* [EIP-7623: Increase calldata cost](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-7623.md) +* [EIP-7685: General purpose execution layer requests](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-7685.md) +* [EIP-7691: Blob throughput increase](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-7691.md) +* [EIP-7702: Set EOA account code](https://github.com/ethereum/EIPs/blob/d96625a4dcbbe2572fa006f062bd02b4582eefd5/EIPS/eip-7702.md) +* [EIP-7840: Add blob schedule to EL config files](https://github.com/ethereum/EIPs/pull/9129) + +## For node operators + +Node operators will need to upgrade to the respective releases before the activation dates. These following steps are necessary for every node operator: + +### Update to the latest release + + + Full node operators, meaning those who are running op-geth with `gc-mode=full`, will need to reference the [`op-geth v1.101411.8`release notes](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101411.8) to handle an intermediate upgrade step before upgrading to the latest release. Archive node operators, `gc-mode=archive`, can skip this step and upgrade directly to the latest release. + + +* `op-node` at [`v1.12.0`](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.12.0) +* `op-geth` at [`v1.101503.0`](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101503.0) +* `op-reth` at [`v1.2.0`](https://github.com/paradigmxyz/reth/releases/tag/v1.2.0) also includes L1 Pectra support. + + + Schedule your hardfork activation time when upgrading your op-node binaries to ensure your network uses the correct fee calculation. Please review the Superchain Registry [configurations](https://github.com/ethereum-optimism/superchain-registry/tree/main/superchain/configs/sepolia) to determine if your network needs to coordinate this independently from the Superchain activation time. + + +For node operators of not included in the [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md), you will need to manually configure the activation. This can be done one of two ways: + +* **Option 1:** Set the `pectrablobschedule` activation time in the `rollup.json` for `op-node`. +* **Option 2:** Set the activation time via overrides (CLI) in `op-node`. These will need to be set on `op-node` for the sequencer and all other nodes. + +You do not need to specify an override for `op-geth`, but you will need to update it to the latest release. + +## For chain operators + +The following sections are how chain operators can prepare the first part of the OP Stack's Pectra L1 support. This ensures OP Stack chains do not break when the L1 Pectra upgrade activates. Every chain operator will need to: + + + + Follow the guidance outlined in the [Node Operator section](#update-to-the-latest-release) to update your nodes to the latest releases. + + + + Update your binaries to: + + * [`op-batcher/v1.11.5`](https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.11.5). + * [`op-proposer/v1.10.0`](https://github.com/ethereum-optimism/optimism/releases/tag/op-proposer%2Fv1.10.0). + + + + See [this notice about modifying fee scalars after the Pectra upgrade on L1](/notices/archive/pectra-changes). + + + +## For fault proof enabled chains + + + The following instructions assume your chain is on the latest contract release `op-contracts/v1.8.0` and has Holocene activated. + + +All fault proof enabled chains (both permisionless and permissioned fault proof systems) need to update their `op-challenger` binary to [`op-challenger/v1.3.2`](https://github.com/ethereum-optimism/optimism/releases/tag/op-challenger%2Fv1.3.2). + +The following steps are to update your absolute prestate with new dispute game contracts. **This is absolutely necessary for chains running permissionless fault proofs.** For chains running the Fault Proof System with permissioned games you can skip this section because games will not be played out and the absolute prestate is not used. + +The Pectra upgrade changes the derivation rules, permissionless fault proof chains need to upgrade the `op-program` version used in the fault proof system to support these changes. The `op-program` version used is specified via the `faultGameAbsolutePrestate` setting, deployed as part of `FaultDisputeGame` and `PermissionedDisputeGame` contracts. + + + + The absolute prestate is generated with the [op-program/v1.5.0-rc.4](https://github.com/ethereum-optimism/optimism/tree/op-program/v1.5.0-rc.4). You can use this new absolute prestate (`0x0354eee87a1775d96afee8977ef6d5d6bd3612b256170952a01bf1051610ee01`) for the following chains: + + * Sepolia: Base, OP, Metal, Mode, Zora, Ethernity, Unichain, Ink, Minato (Soneium) + * Mainnet: Base, OP, Orderly, Lyra, Metal, Mode, Zora, Lisk, Ethernity, Binary, Ink, Unichain, Soneium + + You can verify this absolute prestate by running the following [command](https://github.com/ethereum-optimism/optimism/blob/0026006e6a7a482332a7833876f915acb9dff4c6/Makefile#L129-L131) in the root of the monorepo on the `op-program/v1.5.0-rc.4` tag: + + ```shell + make reproducible-prestate + ``` + + You should expect the following output at the end of the command: + + ```shell + Cannon Absolute prestate hash: + 0x0354eee87a1775d96afee8977ef6d5d6bd3612b256170952a01bf1051610ee01 + Cannon64 Absolute prestate hash: + 0x03ee2917da962ec266b091f4b62121dc9682bb0db534633707325339f99ee405 + CannonInterop Absolute prestate hash: + 0x03673e05a48799e6613325a3f194114c0427d5889cefc8f423eed02dfb881f23 + ``` + + + + During the previous step, you also generated the preimage of the absolute prestate, which is basically the op-program serialized into a binary file. You'll find that new file at `optimism/op-program/bin/prestate.bin.gz`. Rename that file to have the absolute prestate hash as the filename so it looks like `0x0354eee87a1775d96afee8977ef6d5d6bd3612b256170952a01bf1051610ee01.bin.gz`. + + Upload that file to where you're storing your other absolute preimage files. This should be the location where you're pointing your `--cannon-prestates-url` at. The `op-challenger` will grab this file and use it when it needs to challenge games. + + + + You will then take the absolute prestate and deploy new `FaultDisputeGame` and `PermissionedDisputeGame` contracts with that value. You can reuse the [Holocene script](https://github.com/ethereum-optimism/optimism/tree/op-contracts/v1.8.0-rc.4/packages/contracts-bedrock/scripts/upgrades/holocene) to deploy the new contracts. The only change you will need to make is to update the `absolutePrestate` value in your [deploy-config](https://github.com/ethereum-optimism/optimism/blob/2073f4059bd806af3e8b76b820aa3fa0b42016d0/packages/contracts-bedrock/scripts/upgrades/holocene/README.md?plain=1#L53-L54). Alternatively we will be releasing an `OPPrestateUpdater` that can be used to deploy the new contracts. + + + + You will then need to update the `DisputeGameFactory` to point to the new `FaultDisputeGame` and `PermissionedDisputeGame` contracts by calling `DisputeGameFactory.setImplementation`. You can utilize this [template](https://github.com/ethereum-optimism/superchain-ops/tree/main/tasks/sep/fp-recovery/005-set-game-implementation) to generate the transaction and validation script for this step. Before executing, you will need to update your op-challenger. + + + + Once your `op-challenger` is ready with the new preimage, you can execute the "Set Dispute Game Implementation" transaction. Please simulate and validate that the expected output prior to executing the transaction. + + + +## For OP Stack forks + +We are working on a PR that will include the ability to sync L1 after the Pectra upgrade. The commits from the new node releases will need to be applied to your chain before Pectra goes live on L1. We will provide a link to the PR after it is complete. diff --git a/docs/public-docs/notices/archive/pectra-fees.mdx b/docs/public-docs/notices/archive/pectra-fees.mdx new file mode 100644 index 0000000000000..0b3bf6c23ece1 --- /dev/null +++ b/docs/public-docs/notices/archive/pectra-fees.mdx @@ -0,0 +1,51 @@ +--- +title: L1 Pectra user fees and chain profitability +description: L1 Pectra affect on user fees and chain profitability analysis +--- + +The Ethereum L1 Pectra upgrade has introduced changes to calldata gas costs via [EIP-7623](https://eips.ethereum.org/EIPS/eip-7623) that may affect OP Stack chain profitability in specific configurations and market conditions. This notice outlines the potential impact on your chain and recommends specific actions. + +[EIP-7623](https://eips.ethereum.org/EIPS/eip-7623) increases the amount of gas payable for calldata-heavy transactions. These include such transactions as those used by OPStack chains *before the [Ecotone upgrade](https://specs.optimism.io/protocol/ecotone/overview.html)* when blob data availability (blob DA) was introduced. + +Blob DA has now been the default and recommended option for the OPStack for some time, so almost all chains make use of it. In order to continue to optimize for chains operating with blob DA, the Optimism protocol's current DA fee pricing formula has remained unchanged despite Pectra activating on Ethereum. This has allowed chains using blob DA to continue without updating their fee config. + +Chains configured for blob data availability remain entirely unaffected by the Pectra upgrade. However, chains configured for calldata (i.e. operating in a legacy DA mode) may experience a reduction in profitability. It is possible for such chains to be making a loss on data availability costs under certain market conditions. Therefore all chains should check their fee scalar config. + +## Actions required + +Since the Ecotone upgrade, the Optimism protocol prices L2 transactions using a function that incorporates the L1 base fee as well as the L1 blob base fee. The other inputs are the so-called "Ecotone scalars": operator-controlled parameters stored in the SystemConfig contract which can be used to tune the chain's (approximate) target profit margin for DA. + +Please review your [Ecotone scalar chain configuration](/chain-operators/guides/features/blobs). + + + If your chain uses a zero blob base fee scalar, meaning it is configured to price for calldata only, you may need to update the base fee scalar and/or the blob base fee scalar. Otherwise, no action is necessary. + + +The impact and recommended actions for each of the possible configurations are summarized in the following table, and explained in more detail below: + +| `baseFeeScalar` | `blobBaseFeeScalar` | batcher DA | Impact | Action | +| --------------- | ------------------- | ----------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------ | +| any | >\~10^4 | `calldata`, `blob`, or `auto` | | No action required | +| `x` | 0 | `calldata` | Undercharging L2 Users | Check scalar config and consider setting `baseFeeScalar` to `x * 10/4` to preserve profit margin | +| any | 0 | `blob` or `auto` | Overcharging L2 Users most of the time | Check scalar config and consider setting a positive `blobBaseFeeScalar` | + +### Chains charging for calldata DA and spending on calldata DA + +If your chain uses a zero blob base fee scalar and your batcher is configured to submit using calldata *only*, then you should take the opportunity to check your chain's profit margin according to [this guide](/chain-operators/guides/features/blobs) and make any adjustments to your Ecotone scalars as necessary. This will ensure that, since you are paying exclusively for calldata DA, you are charging users appropriately such that your target profit margin is as desired. + +If your profit margin was perfectly tuned before Pectra, then you should scale your base fee scalar by 10/4. + +### Chains charging for calldata DA and spending on blob DA + +If your chain uses a zero blob base fee scalar and your batcher is configured to submit using blob DA, or configured to automatically choose the cheaper of the two DA modes, then you should adjust your Ecotone scalars for blob DA pricing (meaning a nonzero blob base fee scalar). You are referred back to [this guide](/chain-operators/guides/features/blobs). Doing so will ensure that you are charging accurately for using blob DA. Without such a change it is likely that you are overcharging users most of the time, and undercharging them in the rare occasions where blob DA is more expensive than calldata DA on Ethereum mainnet. + +### General remarks + +Continue tweaking the Ecotone scalars as desired to adjust your chain's profitability. +Chain operators should monitor profitability continuously so that scalars can be adjusted as necessary. + +### Technical references + +* [Optimism protocol specification - Ecotone L1 cost fee changes](https://specs.optimism.io/protocol/exec-engine.html#ecotone-l1-cost-fee-changes-eip-4844-da) +* [Optimism protocol specification - Fjord execution engine fees](https://specs.optimism.io/protocol/fjord/exec-engine.html#fees) +* [Implementation source code](https://github.com/ethereum-optimism/op-geth/blob/3d7afdc2701b74c5987e31521e2c336c4511afdf/core/types/rollup_cost.go#L527) diff --git a/docs/public-docs/notices/archive/superchain-withdrawal-pause-test.mdx b/docs/public-docs/notices/archive/superchain-withdrawal-pause-test.mdx new file mode 100644 index 0000000000000..e2858b6b698ee --- /dev/null +++ b/docs/public-docs/notices/archive/superchain-withdrawal-pause-test.mdx @@ -0,0 +1,21 @@ +--- +title: Superchain withdrawal pause test +description: Notice about the Superchain withdrawal pause test. +--- + + + * Sepolia Superchain withdrawal pause test was successfully completed on **April 14th 2025**. + * Mainnet Superchain withdrawal pause test will happen **May 14th 2025**. + + +The Optimism Collective will be testing improved incident response features on the Sepolia Superchain. + +## What's happening + +1. During this exercise, the privileged [`GUARDIAN`](/op-stack/protocol/privileged-roles#guardian) address will call the `pause` function on the `SuperchainConfig`. +2. Members of the Optimism Collective's security team will ensure that the pause is executed correctly and the incident response improvements worked as intended. +3. Then the `unpause` function will be called to resume normal operations. + +To learn more about this functionality, please refer to this [documentation](/op-stack/security/pause). + +This functionality is important for the security of the Superchain and should be understood by Chain Operators, users, and especially for centralized exchanges and third-party bridge operators. **Please note that this will not affect any L1-to-L2 deposit transactions or L2 transactions. No action is required from users or operators.** diff --git a/docs/public-docs/notices/archive/upgrade-13.mdx b/docs/public-docs/notices/archive/upgrade-13.mdx new file mode 100644 index 0000000000000..61897c2d1c3e3 --- /dev/null +++ b/docs/public-docs/notices/archive/upgrade-13.mdx @@ -0,0 +1,124 @@ +--- +title: Upgrade 13 OPCM and incident response improvements +description: Learn how to prepare for Isthmus upgrade breaking changes. +--- + + + Upgrade execution timelines are being publicly tracked in this [release management project board](https://github.com/orgs/ethereum-optimism/projects/117/views/12). + + * OP Sepolia, Ink Sepolia, and Minato Sepolia are executing their upgrades **March 21st** + * OP Mainnet, Ink Mainnet, and Soneium Mainnet are expected to have Upgrade 13 executed on **April 2nd**. + * Unichain Sepolia is executing its upgrade on **April 1st**. + * Unichain Mainnet is executing its upgrade on **April 8th**. + * Other Optimism governed chains' upgrades will be tracked in the release management project board. + + +This page outlines changes related to the Upgrade 13 network upgrade for chain operators, node operators, and users of OP Stack chains. The upgrade proposal is available [here](https://gov.optimism.io/t/upgrade-proposal-13-opcm-and-incident-response-improvements/9739) and the governance vote is available [here](https://vote.optimism.io/proposals/84511922734478887667300419900648701566511387783615524992018614345859900443455). + +If you experience difficulty at any stage of this process, please reach out to developer support. + +## What's included in Upgrade 13 + +Upgrade 13 contains three main components: + +* **OP Contracts Manager**: A new system for upgrading L1 contracts across the Superchain, making contract upgrades more scalable, less prone to error, and easier to test. +* **Fault Proofs incident response improvements**: Technical improvements to several key contracts to enable more flexible and less disruptive ways to respond to potential incidents in the OP Stack fault proof system. +* **`DeputyPauseModule` (Superchain pause improvements)**: A new Safe Module to be installed into the Optimism Foundation Safe to simplify the process of quickly responding to security incidents via the Superchain-wide pause mechanism. + +For more information on the Upgrade 13 implementation details, please review the specifications linked in the Technical Details section below. + +## Technical details + +### OP Contracts Manager + +The OP Contracts Manager ([OPCM](/chain-operators/reference/opcm)) is a new system for upgrading L1 contracts across the Superchain. Each release will have its own OPCM that can deploy new proxies and upgrade existing OP Chains. + +Key changes: + +* OPCM is not considered part of the protocol and has no special role +* In-protocol contract modifications include: + * Stack too deep fixes to enable code coverage measurements + * Updated contracts to call interfaces for external interactions rather than implementations + * Removal of CustomGasToken logic + * Changes to deposit transaction aliasing to ensure compatibility with the L1 Pectra upgrade's introduction of EIP-7702 + +### Fault Proofs incident response improvements + +Several components have been updated to improve incident response capabilities: + +**DelayedWETH** + +* `DelayedWETH.hold(...)` now executes an approval and transfer from the target account to the owner account +* Added a version of `DelayedWETH.hold(...)` that does not require the owner to specify the target's balance + +**OptimismPortal** + +* `OptimismPortal.setRespectedGameType(...)` no longer sets the respected game type and retirement timestamp simultaneously +* A special reserved input value can be used to set the retirement timestamp +* `OptimismPortal.checkWithdrawal(...)` now asserts that a FaultDisputeGame was the respected game type at the time of creation + +**AnchorStateRegistry** + +* `AnchorStateRegistry.tryUpdateAnchorState(...)` is removed +* `AnchorStateRegistry.setAnchorState(...)` is repurposed as the primary way for FaultDisputeGame contracts to update the anchor state +* Internal anchor state is now unified across all game types +* Updates enable all OP Stack chains to share a common AnchorStateRegistry implementation + +**FaultDisputeGame** + +* Added support for "bond refunding" to automatically distribute bonds back to their original depositors if the game is invalidated + + + If adopted and deployed, this proposal will cause a one-time invalidation of all pending withdrawal proofs created on L1. Users should complete any pending withdrawals before the upgrade is executed and avoid creating new withdrawal proofs that would not become executable in time. + + +### DeputyPauseModule (Superchain pause improvements) + +The `DeputyPauseModule` is a new Safe Module to be installed into the Optimism Foundation Safe that allows: + +* The Optimism Foundation to assign a "Pause Deputy" private key +* The Pause Deputy to create signatures that authorize the use of the Superchain-wide pause +* The Pause Deputy private key to cause the Optimism Foundation Safe to execute a call to the `DeputyGuardianModule` account ONLY for the purpose of executing the pause function + +The Optimism Foundation Safe is expected to rotate the Pause Deputy private key approximately every 3 months. + +## For chain operators + +If this proposal is accepted, multisig ceremonies will be coordinated to execute upgrade transactions. The following transactions will be executed on the respective chains: `OP Mainnet`, `Soneium Mainnet`, `Ink Mainnet`, `Base Mainnet`, and `Unichain Mainnet`. If your Optimism governed chain is not in this list, please reach out to OP Labs Solutions Engineering to coordinate your upgrade. If your chain is not Optimism governed, we'll be working on providing documentation to upgrade your chain. + +As this is an L1 contracts-only upgrade, no action is required to upgrade your node binaries. However, there is a required upgrade to [`op-dispute-mon/v1.4.0`](https://github.com/ethereum-optimism/optimism/releases/tag/op-dispute-mon%2Fv1.4.0) to remain compatible with the new contract changes. + +## For bridges and users + + + All withdrawals that are not finalized before the Fault Proofs upgrade executes will need to be reproven after the upgrade is complete. You may want to consider waiting until after the upgrade is complete to begin a withdrawal during this 7-day window. + + +Users should be aware of the following impacts: + +### Withdrawal flow changes + +1. There will be a one-time invalidation of all pending withdrawal proofs created on L1. + +2. Complete any pending withdrawals before the upgrade is executed + +3. Avoid creating new withdrawal proofs that would not become executable in time + +4. If a withdrawal was invalidated, submit a second withdrawal proof transaction on L1 + +This invalidation does not place any ETH or ERC-20 tokens at risk. + +## Audit reports + +The code for Upgrade 13 has undergone multiple audits: + +* **Offbeat Labs**: Completed an audit and found 1 Low Severity issue which has been addressed. +* **Spearbit**: Completed an audit and found 1 Medium Severity and 2 Low Severity issues. The Medium Severity issue was a design decision that conflicted with updated L2Beat Stage 1 requirements published in January 2025. The design has been modified to satisfy these requirements. +* **Radiant Labs**: Completed an audit of the DeputyPauseModule with only Low/informational findings, all of which have been addressed. +* **MiloTruck (independent)**: Completed an audit of the DeputyPauseModule with only Low/informational findings, all of which have been addressed. + +## Emergency cancellation + +If a critical security issue is discovered before upgrading, OP Labs will collaborate with the community to extensively communicate that the upgrade will no longer occur. The Optimism Foundation and Security Council will work to coordinate an emergency cancellation. + +For more detailed information, please refer to the full upgrade proposal or reach out to developer support. diff --git a/docs/public-docs/notices/archive/upgrade-14.mdx b/docs/public-docs/notices/archive/upgrade-14.mdx new file mode 100644 index 0000000000000..e30ef9e9f69f6 --- /dev/null +++ b/docs/public-docs/notices/archive/upgrade-14.mdx @@ -0,0 +1,93 @@ +--- +title: Upgrade 14 MT-Cannon and Isthmus L1 Contracts +description: Learn how to prepare for upgrade 14 breaking changes. +--- + + + Upgrade execution timelines are being publicly tracked in this [release management project board](https://github.com/orgs/ethereum-optimism/projects/117/views/12) and are subject to change. Here are the following expected timelines: + + * OP Sepolia, Ink Sepolia, and Minato Sepolia upgrades are targeting **April 9th, 2025**. + * OP Mainnet, Soneium Mainnet, Ink Mainnet are expected to execute Upgrade 14 on **April 25th, 2025**. + * Other Optimism governed chains' upgrades will be tracked in the release management project board. + + +This page outlines changes related to the Upgrade 14 network upgrade for chain operators, node operators, and users of OP Stack chains. The upgrade proposal is available [here](https://gov.optimism.io/t/upgrade-proposal-14-isthmus-l1-contracts-mt-cannon/9796) and the governance vote is available [here](https://vote.optimism.io/?utm_source=op-docs&utm_medium=docs). + +If you experience difficulty at any stage of this process, please reach out to developer support. + +## What's included in Upgrade 14 + +Upgrade 14 contains two main components: + +* **MT-Cannon**: An upgrade to the fault proof VM that supports the MIPS-64 instruction set and multi-threaded programs, removing memory constraints for the fault proof program. +* **Operator Fee**: Introduction of the first phase of a mechanism addressing challenges in accurately pricing user fees when chains employ ZK proving, alt-DA, and custom gas tokens. + +## Technical details + +### MT-Cannon + +MT-Cannon is an upgrade to [Cannon](/op-stack/fault-proofs/cannon), the [fault proof VM](/op-stack/fault-proofs/fp-components#fault-proof-virtual-machine) that supports the MIPS-64 instruction set and multi-threaded programs, removing memory constraints for the fault proof program. + +Key changes: + +* The emulated CPU architecture is now MIPS64 instead of MIPS32 + * Registers now hold 64-bit values + * Memory address space is dramatically expanded + * New 64-bit specific instructions added for operations on 64-bit values +* Supports reading 8 bytes of data at a time from the pre-image oracle instead of 4 +* Multithreading support + * Concurrency via multitasking + * VM now tracks ThreadState objects for each thread + * Thread-safe memory access enabled by Load Linked Word (ll) and Store Conditional Word (sc) instructions + * Extended syscall support for multi-threading +* Improved exception handling for unrecognized syscalls + +After this upgrade, the on-chain implementation of the fault proof VM will be [DeployMIPS.s.sol](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/cannon/MIPS64.sol) instead of [MIPS.s.sol](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/scripts/deploy/DeployMIPS.s.sol). +### Operator Fee + +This introduces two new rollup operator configured scalars: + +* `operatorFeeScalar` +* `operatorFeeConstant` + +These parameters are factored into the fee calculation as follows: + +``` +operatorFee = operatorFeeConstant + operatorFeeScalar * gasUsed / 1e6 + +totalFee = operatorFee + gasUsed * (baseFee + priorityFee) + l1Fee +``` + +The scalars will be updated via the SystemConfig L1 contract. A new fee vault, the OperatorFeeVault, will store the operator fee. While this feature is included in the upgrade, it is disabled by default and will be enabled in a future hardfork. + +### Absolute Prestate + +Beginning with this upgrade, op-program absolute prestates will now use the "cannon64" variant. This upgrade includes [the absolute prestate for op-program 1.5.1-rc.1, which is `0x03ee2917da962ec266b091f4b62121dc9682bb0db534633707325339f99ee405`](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). + +## For chain operators + +If this proposal is accepted, multisig ceremonies will be coordinated to execute upgrade transactions. The following transactions will be executed on the respective chains: `OP Mainnet`, `Soneium Mainnet`, `Ink Mainnet`, `Base Mainnet`, `Unichain Mainnet`, `Mode Mainnet`, `Zora Mainnet`, `Arena Z Mainnet`, `Swell Mainnet`, and `Metal Mainnet`. Depending on the current state of the chain the execution times will vary. If your Optimism governed chain is not in this list, please reach out to OP Labs Solutions Engineering to coordinate your upgrade. + +For chain operators running fault-proof infrastructure, ensure you are running up-to-date versions of the following: + +* op-challenger: `op-challenger/v1.3.3` (preferred) or at least `op-challenger/v1.3.1` +* op-dispute-mon: `op-dispute-mon/v1.4.0` + +## For node operators + +Node operators should ensure they are running an up-to-date version of op-node that can handle new events emitted from the `SystemConfig` contract: + +* op-node: `op-node/v1.12.2` + +## Audit reports + +The code for Upgrade 14 has undergone multiple audits: + +* **Spearbit**: Completed an audit of MT-Cannon. Details available [here](https://github.com/ethereum-optimism/optimism/blob/develop/docs/security-reviews/2025_01-MT-Cannon-Spearbit.pdf). +* **Coinbase Protocol Security**: Completed an audit of MT-Cannon. Details available [here](https://github.com/ethereum-optimism/optimism/blob/develop/docs/security-reviews/2025_01-MT-Cannon-Base.pdf). + +## Emergency cancellation + +If a critical security issue is discovered before upgrading, OP Labs will collaborate with the community to extensively communicate that the upgrade will no longer occur. The Optimism Foundation and Security Council will work to coordinate an emergency cancellation. + +For more detailed information, please refer to the full upgrade proposal or reach out to developer support. diff --git a/docs/public-docs/notices/archive/upgrade-15.mdx b/docs/public-docs/notices/archive/upgrade-15.mdx new file mode 100644 index 0000000000000..95e5a1f52f50e --- /dev/null +++ b/docs/public-docs/notices/archive/upgrade-15.mdx @@ -0,0 +1,122 @@ +--- +title: Upgrade 15 - Isthmus Hard Fork +description: Learn how to prepare for Isthmus hard fork breaking changes. +--- + +This page outlines breaking changes related to the Isthmus network upgrade for chain operators and node operators. The upgrade proposal is available [here](https://gov.optimism.io/t/upgrade-proposal-15-isthmus-hard-fork/9804) and the governance vote is available [here](https://gov.optimism.io/t/proposal-preview-implement-prague-features-on-the-op-stack/9703). + +If you experience difficulty at any stage of this process, please reach out to developer support. + + + The Isthmus hard fork for the Sepolia Superchain will be activated at **Thu Apr 17 16:00:00 UTC 2025** (`1744905600`) and the Mainnet Superchain will be activated at **Fri May 9 2025 16:00:01 UTC** (`1746806401`). + This is for all chains who have opted into the [hard fork inheritance behavior](/superchain/superchain-registry#hard-fork-activation-inheritance-behavior). + + +## What's included in Isthmus + +Isthmus contains these main changes: + +* **Implement Prague features on the OP Stack**: This includes the EIPs that are relevant to the L2 that are being added to Ethereum with its Pectra activation. Learn more about this [here](https://gov.optimism.io/t/proposal-preview-implement-prague-features-on-the-op-stack/9703). +* **L2 Withdrawals Root in Block Header**: This lowers the lift for chain operators by allowing them to run a full node to operate `op-dispute-mon` making it easier to guarantee the security of the fault proofs for the chains in the Superchain as the number of chains scales. Learn more about this [here](https://gov.optimism.io/t/proposal-preview-l2-withdrawals-root-in-block-header/9730). +* **Operator Fee**: A new component to the fee formula for the OP Stack which is a first step towards better resource pricing. This improves the OP Stack ability to support chains using Alt-DA or using ZK proving. Learn more about this [here](https://gov.optimism.io/t/proposal-preview-operator-fee/9715). + +For more information on the Isthmus implementation details, please review [Isthmus specification](https://specs.optimism.io/protocol/isthmus/overview.html). + +## For chain operators + +Chain operators must upgrade their nodes ahead of the activation times to a release that contains the Isthmus changes and has the activation times for their chains baked in, or set the activation times manually via overrides. The details are outlined in the node operator section below. Additionally, chain operators must update `op-batcher` to [`v1.12.0`](https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.12.0) and must update `op-challenger` to [`v1.4.0`](https://github.com/ethereum-optimism/optimism/releases/tag/op-challenger%2Fv1.4.0). + +In addition to this, the L1 smart contract upgrades in [upgrade 14](/notices/archive/upgrade-14) are required to utilize this new functionality. + +### For permissionless fault proof enabled chains + +Chains running permissionless fault proofs will need to deploy new dispute game contracts with new absolute prestates. The new 64 bit version of cannon will be utilized moving forward. The Sepolia Superchain, will utilize [op-program/v1.6.0-2](https://github.com/ethereum-optimism/optimism/tree/op-program/v1.6.0-rc.2). The following permissionless fault proof Sepolia chains are: `Base Sepolia`, `Ink Sepolia`, and `OP Sepolia`. + + + + + As of upgrade 14, the 64 bit multi-threaded version of cannon is utilized. + + + The absolute prestate is generated with the [op-program/v1.6.0-rc.2](https://github.com/ethereum-optimism/optimism/tree/op-program/v1.6.0-rc.2). You can use this new absolute prestate (`0x03682932cec7ce0a3874b19675a6bbc923054a7b321efc7d3835187b172494b6`) for the following chains: + + * Sepolia: Base, Creator Chain, OP, Metal, Mode, Zora, Ethernity, Unichain, Ink, and Minato (Soneium) + + You can verify this absolute prestate by running the following [command](https://github.com/ethereum-optimism/optimism/blob/d6fb90dd489e39efa206b55200766ccc075c1d9b/Makefile#L130-L132) in the root of the monorepo on the `op-program/v1.6.0-rc.2` tag: + + ```shell + make reproducible-prestate + ``` + + You should expect the following output at the end of the command: + + ```shell + Cannon Absolute prestate hash: + 0x03513e996556589f633fe1d38d694f63bc93cca5df559af37631b30875a829e9 + Cannon64 Absolute prestate hash: + 0x03682932cec7ce0a3874b19675a6bbc923054a7b321efc7d3835187b172494b6 + CannonInterop Absolute prestate hash: + 0x0399cfb018011977a027d1e7a85eb7ff101aec9bfc7d6500abac944c47a4581f + ``` + + + + During the previous step, you also generated the preimage of the absolute prestate, which is basically the op-program serialized into a binary file. You'll find that new file at `optimism/op-program/bin/prestate-mt64.bin.gz`. Rename that file to have the absolute prestate hash as the filename so it looks like `0x03682932cec7ce0a3874b19675a6bbc923054a7b321efc7d3835187b172494b6.bin.gz`. + + Upload that file to where you're storing your other absolute preimage files. This should be the location where you're pointing your `--cannon-prestates-url` at. The `op-challenger` will grab this file and use it when it needs to challenge games. + + + + You will then take the absolute prestate and deploy new `FaultDisputeGame` and `PermissionedDisputeGame` contracts with that value. + + + + You will then need to update the `DisputeGameFactory` to point to the new `FaultDisputeGame` and `PermissionedDisputeGame` contracts by calling `DisputeGameFactory.setImplementation`. + + + + Once your `op-challenger` is ready with the new preimage, you can execute the "Set Dispute Game Implementation" transaction. Please simulate and validate that the expected output prior to executing the transaction. + + + +The new op-program release that contains the Mainnet activation timestamps will be available soon. + +## For node operators + +Node operators will need to upgrade to the respective Isthmus releases before the activation dates. + +These following steps are necessary for every node operator: + + + + The releases contain both the Isthmus Mainnet and Sepolia Superchain activation timestamps. + + * [`op-node` at `v1.13.2`](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.13.2) + * [`op-geth` at `v1.101503.4`](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101503.4) + + + + + If you are operating a node for an OP Chain that has opted into the [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md) and are utilizing the network flags, the Isthmus activation date is part of the `op-node` and `op-geth` nodes. So, no action is needed for the sequencer after upgrading to the latest release. Please skip to [Step 3: Verify Your Configuration](#verify-your-configuration). + + The following chains are included but are subject to change: `Base Sepolia`, `Creator Chain Testnet`, `Ethernity Sepolia`, `Ink Sepolia`, `Lisk Sepolia`, `Metal Sepolia`, `Mode Sepolia`, `Minato (Soneium) Sepolia`, `OP Sepolia`, `Unichain Sepolia`, and `Zora Sepolia`. + + + For node operators of not using the [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md), you will need to manually configure the activation. This can be done one of two ways: + + + If you use custom genesis chain configuration, you need to set the `pragueTime` to the same value as the `isthmusTime`. It is not automatically set, this happens by default for chains using the network flags and also when using the override flags. + + + * **Option 1:** Set the activation time in the `rollup.json` for `op-node`. You will still need to set the `override.isthmus` flag in `op-geth` if you use this option. Please note that the chain configuration file is subject to a stricter format and needs to contain the `chain_op_config` outlined in the `op-node/v1.11.0` [release notes](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.11.0). + * **Option 2:** Set the activation time via overrides (CLI) in both `op-node` and `op-geth`. These will need to be set on `op-node` and `op-geth` for the sequencer and all other nodes. + + + + Make the following checks to verify that your node is properly configured. + + * `op-node` and `op-geth` will log their configurations at startup + * Check that the Isthmus time is set to `activation-timestamp` in the op-node startup logs + * Check that the Isthmus time is set to `activation-timestamp` in the op-geth startup logs + + diff --git a/docs/public-docs/notices/archive/upgrade-16.mdx b/docs/public-docs/notices/archive/upgrade-16.mdx new file mode 100644 index 0000000000000..aff710ba42d71 --- /dev/null +++ b/docs/public-docs/notices/archive/upgrade-16.mdx @@ -0,0 +1,146 @@ +--- +title: Upgrade 16 Preparing for Interop protocol upgrade +description: Learn how to prepare for the Upgrade 16 protocol changes. +--- + +This page outlines important changes related to Upgrade 16 for chain operators and users. The upgrade proposal includes modifications to the OP Stack that will set the Superchain up for continued success through the remainder of 2025. + + + The Upgrade 16 protocol upgrade on the **Sepolia** Superchain will be executed on **Wed, Jul 09, 2025**, and the **Mainnet** Superchain will be activated on **Thu, Jul 24, 2025 at 5:30 PM UTC (`1753675800`)**.\ + The upgrade will be executed on the following chains: `OP`, `Soneium`, `Ink` and `Unichain`.\ + Execution times may vary depending on the current state of each chain. + + +## What's included in Upgrade 16 + +Upgrade 16 contains these main changes: + +* **Interop-Ready smart contracts**: Interoperability is critical to realizing the Superchain as a unified network of OP Chains. This upgrade begins the rollout of foundational interoperability features by updating the `OptimismPortal` to handle future cross-chain messaging safely and extensibly. This upgrade does not turn on interop yet. +* **Stage 1 updates**: Modifications to meet [L2Beat's updated Stage 1 requirements](https://forum.l2beat.com/t/stages-update-a-high-level-guiding-principle-for-stage-1/338) from January 2025, including removal of `DeputyGuardianModule` and updates to `DeputyPauseModule`. +* **Go 1.23 Support in Cannon**: Updates to Cannon to support Go 1.23, allowing OP Stack to benefit from upstream go-ethereum changes. +* **Max gas limit increase**: Update to `MAX_GAS_LIMIT` from 200m to 500m gas after improvements to OP Stack infrastructure and the Cannon proof system. +* **Additional safety improvements**: Authentication for critical contract functions and simplification of `DelayedWETH` contract control. + +## For chain operators + +Upgrade 16 is an L1 smart contracts upgrade for the OP Stack. We do not expect any downtime or changes in performance. + +Chain operators must complete the following tasks: + +* Update `op-challenger` to [op-challenger/v1.5.1](https://github.com/ethereum-optimism/optimism/releases/tag/op-challenger%2Fv1.5.1) + +Chain operators should be aware that Upgrade 16 involves a one-time invalidation of all existing withdrawal proofs. Users who have proven withdrawals can either finalize withdrawals prior to the activation of Upgrade 16 or will be required to re-prove these withdrawals after the upgrade activates. + +If this proposal is accepted, multisig ceremonies will be coordinated to execute upgrade transactions. The following transactions will be executed on the respective chains: `OP`, `Soneium`, `Ink` and `Unichain` on both Mainnet and Sepolia. Depending on the current state of the chain the execution times will vary. If your Optimism governed chain is not in this list, please reach out to OP Labs Solutions Engineering to coordinate your upgrade. These upgrade tasks will be prepared in the [superchain-ops repo](https://github.com/ethereum-optimism/superchain-ops/tree/main/src/improvements/tasks). + +The tentative execution dates of these upgrades tasks can be tracked in our [release board](https://github.com/orgs/ethereum-optimism/projects/117/views/12). + +### For permissionless fault proof enabled chains + +Chains running permissionless fault proofs will need to deploy new dispute game contracts with new absolute prestates. + + + + + As of upgrade 14, the 64 bit multi-threaded version of cannon is utilized. + + + The absolute prestate is generated with the [op-program/v1.6.1-rc.1](https://github.com/ethereum-optimism/optimism/tree/op-program/v1.6.1-rc.1). You can use this new absolute prestate `0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8` for the following chains: + + * Mainnet and Sepolia: `OP`, `Soneium`, `Ink` and `Unichain` + + You can verify this absolute prestate by running the following [command](https://github.com/ethereum-optimism/optimism/blob/d6fb90dd489e39efa206b55200766ccc075c1d9b/Makefile#L130-L132) in the root of the monorepo on the `op-program/v1.6.1-rc.1` tag: + + ```shell + make reproducible-prestate + ``` + + This will output the calculated prestates, which will look something like: + + ```shell + -------------------- Production Prestates -------------------- + + + Cannon64 Absolute prestate hash: + 0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8 + + -------------------- Experimental Prestates -------------------- + + CannonInterop Absolute prestate hash: + 0x03fc3b4d091527d53f1ff369ea8ed65e5e17cc7fc98ebf75380238151cdc949c + + Cannon64Next Absolute prestate hash: + 0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8 + ``` + + * The "Cannon64" hash is the 64-bit prestate. + + Verify that your target prestate was calculated as expected and matches the corresponding entry in + [standard-prestates.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). + + + + During the previous step, you also generated the preimage of the absolute prestate, which is the op-program serialized into a binary file. You'll find that new file at `optimism/op-program/bin/prestate-mt64.bin.gz`. Rename that file to have the absolute prestate hash as the filename so it looks like `PRESTATEHASH.bin.gz`. + + Upload that file to where you're storing your other absolute preimage files. This should be the location where you're pointing your `--cannon-prestates-url` at. The `op-challenger` will grab this file and use it when it needs to challenge games. + + + + Once your `op-challenger` is ready with the new preimage, you can execute the upgrade transaction. This should be done by making a delegatecall to the `upgrade()` function of the OP Contract Manager (at the address listed in [the registry](https://github.com/ethereum-optimism/superchain-registry/blob/6621a0f13ce523fe1bb8deea739fe37abe20f90d/validation/standard/standard-versions-mainnet.toml#L22). + + Please simulate and validate the expected output prior to executing the transaction. + + + +## For bridges and users + + + All withdrawals that are not finalized before the Fault Proofs upgrade executes will need to be reproven after the upgrade is complete. You may want to consider waiting until after the upgrade is complete to begin a withdrawal during this 7-day window. + + +Users should be aware of the following impacts: + +### Withdrawal flow changes + +1. There will be a one-time invalidation of all pending withdrawal proofs created on L1. + +2. Complete any pending withdrawals before the upgrade is executed + +3. Avoid creating new withdrawal proofs that would not become executable in time + +4. If a withdrawal was invalidated, submit a second withdrawal proof transaction on L1 + +This invalidation does not place any ETH or ERC-20 tokens at risk. + +## Technical details + +### Interop-ready smart contracts + +Upgrade 16 updates the core bridge contracts of the OP Stack to support native interoperability. Key modifications include: + +* The `OptimismPortal` now relies on the `AnchorStateRegistry` as the source of truth for the validity of dispute games +* The `OptimismPortal` now stores ETH in a dedicated `ETHLockbox` contract +* The `OptimismPortal` includes a version of the `proveWithdrawalTransaction` function that supports the updated `SuperFaultDisputeGame` implementation (disabled by default) + +### Stage 1 updates + +* The `DeputyGuardianModule` has been removed +* The `DeputyPauseModule` has been updated to be installed into the Security Council's guardian safe +* The pause action now expires automatically after 3 months +* The pause action can now be applied on a per-chain basis as well as a Superchain-wide basis + +### Go 1.23 support in Cannon + +Cannon has been updated to support Go 1.23, allowing the OP Stack to benefit from upstream changes in go-ethereum. + +### `MAX_GAS_LIMIT` increases + +The `MAX_GAS_LIMIT` variable in the `SystemConfig` contract is being updated from 200m gas to 500m gas. + +### Security reviews + +* Changes to the bridge contracts were audited via a Cantina contest with no Medium+ severity issues found +* Upgrade 16 as a whole was audited by Spearbit with no Medium+ severity issues found + + + diff --git a/docs/public-docs/notices/archive/upgrade-16a.mdx b/docs/public-docs/notices/archive/upgrade-16a.mdx new file mode 100644 index 0000000000000..af5ffb0f7872d --- /dev/null +++ b/docs/public-docs/notices/archive/upgrade-16a.mdx @@ -0,0 +1,123 @@ +--- +title: Upgrade 16a Protocol upgrade +description: Learn how to prepare for the Upgrade 16a protocol changes. +--- + + + +Upgrade 16a is a **maintenance release that supersedes Upgrade 16**. If you are on upgrade 15 **please skip** upgrade 16 and go straight to this upgrade. It temporarily removes unused interop withdrawal-proving code introduced in Upgrade 16 and adds system-level feature toggles to improve flexibility and rollout safety. These changes reduce risk while maintaining momentum toward the Superchain roadmap. + + + The Upgrade 16a protocol upgrade on the **Sepolia** Superchain will be executed on **Mon, Sept 22, 2025**, and the **Mainnet** Superchain will be activated on **Thur, Oct 2, 2025**. + The upgrade will be executed on the following chains: `OP`, `Base`, `Soneium`, `Ink`, and `Unichain`. + Execution times may vary depending on the current state of each chain. + + +## What's included in Upgrade 16a + +Upgrade 16a introduces the following changes: + +**Removal of interop withdrawal-proving code**: Code paths for interop withdrawals added in Upgrade 16 have been removed following partner feedback. ETHLockbox remains supported on chains already running U16. Chains moving directly from U15 → U16a will not adopt ETHLockbox until a future upgrade. + +**System-level feature toggles** +Added via the `SystemConfig` contract. ETHLockbox is the first feature placed behind a toggle. This mechanism enables chain-specific configuration and safer iterative upgrades. + +**Development-only feature flags** +Introduced to allow testing of interop functionality on non-production environments. These flags cannot be enabled on production chains. + +**OP Contracts Manager (OPCM) updates** +Updated to support both upgrade paths: `U15 → U16a` and `U16 → U16a`. + +## Impact summary + +* No chain downtime is expected, however if upgrading from U15, withdrawals will be invalidated (see below). +* Withdrawal proofs created under U16 remain valid. +* Chains still on U15 are affected in the same way described in the Upgrade 16 notice. +* Chains upgrading directly from U15 → U16a will not adopt ETHLockbox until a future release. +* System-level toggles improve operator control and make future upgrades easier to roll out. + +## For chain operators + +Withdrawal invalidation impacts only chains still on U15. +Any pending L1 withdrawals on those chains that are not finalized before the upgrade executes will need to be reproven after the upgrade completes. Users must re-submit their proof transactions after the upgrade. +Chains already on U16 are not affected. No funds are at risk. + + + +### Chain operators must complete the following tasks: + +* Update `op-challenger` to [op-challenger/v1.5.1](https://github.com/ethereum-optimism/optimism/releases/tag/op-challenger%2Fv1.5.1) +* If you're building your own `op-challenger` software, then ensure that the `--cannon-bin` flag is set to `cannon/v1.6.0 build`. There's no action needed if you're using the official Optimism docker image. + + +### For permissionless fault proof enabled chains + +Chains running permissionless fault proofs will need to deploy new dispute game contracts with new absolute prestates. + + + ### Verify the new absolute prestate + + + As of upgrade 14, the 64 bit multi-threaded version of cannon is utilized. + + + The absolute prestate is generated with the [op-program/v1.6.1-rc.1](https://github.com/ethereum-optimism/optimism/tree/op-program/v1.6.1-rc.1). You can use this new absolute prestate `0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8` for the following chains: + + * Mainnet and Sepolia: `OP`, `Base`, `Soneium`, `Ink` and `Unichain` + + You can verify this absolute prestate by running the following [command](https://github.com/ethereum-optimism/optimism/blob/d6fb90dd489e39efa206b55200766ccc075c1d9b/Makefile#L130-L132) in the root of the monorepo on the `op-program/v1.6.1-rc.1` tag: + + ```shell + make reproducible-prestate + ``` + + This will output the calculated prestates, which will look something like: + + ```shell + -------------------- Production Prestates -------------------- + + + Cannon64 Absolute prestate hash: + 0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8 + + -------------------- Experimental Prestates -------------------- + + CannonInterop Absolute prestate hash: + 0x03fc3b4d091527d53f1ff369ea8ed65e5e17cc7fc98ebf75380238151cdc949c + + Cannon64Next Absolute prestate hash: + 0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8 + ``` + + * The "Cannon64" hash is the 64-bit prestate. + + Verify that your target prestate was calculated as expected and matches the corresponding entry in + [standard-prestates.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). + + ### Upload your new preimage file + + During the previous step, you also generated the preimage of the absolute prestate, which is the op-program serialized into a binary file. You'll find that new file at `optimism/op-program/bin/prestate-mt64.bin.gz`. Rename that file to have the absolute prestate hash as the filename so it looks like `PRESTATEHASH.bin.gz`. + + Upload that file to where you're storing your other absolute preimage files. This should be the location where you're pointing your `--cannon-prestates-url` at. The `op-challenger` will grab this file and use it when it needs to challenge games. + + ### Execute the upgrade + + Once your `op-challenger` is ready with the new preimage, you can execute the upgrade transaction. This should be done by making a delegatecall to the `upgrade()` function of the OP Contract Manager (at the address listed in [the registry](https://github.com/ethereum-optimism/superchain-registry/blob/6621a0f13ce523fe1bb8deea739fe37abe20f90d/validation/standard/standard-versions-mainnet.toml#L22). + + Please simulate and validate the expected output prior to executing the transaction. + + +### Withdrawal flow changes + +1. There will be a one-time invalidation of all pending withdrawal proofs created on L1. + +2. Complete any pending withdrawals before the upgrade is executed + +3. Avoid creating new withdrawal proofs that would not become executable in time + +4. If a withdrawal was invalidated, submit a second withdrawal proof transaction on L1 + +This invalidation does not place any ETH or ERC-20 tokens at risk. + + + diff --git a/docs/public-docs/notices/archive/upgrade-17.mdx b/docs/public-docs/notices/archive/upgrade-17.mdx new file mode 100644 index 0000000000000..d8c33559df8c1 --- /dev/null +++ b/docs/public-docs/notices/archive/upgrade-17.mdx @@ -0,0 +1,168 @@ +--- +title: Upgrade 17 - Jovian Hard Fork +description: Learn how to prepare for the Jovian hard fork +--- + +The Jovian hardfork is a proposed network upgrade for OP Stack chains, which brings several improvements to the way rollup fees are calculated as well as performing a maintenance update to the fault proof virtual machine. + + + The Jovian hard fork for the Sepolia Superchain will be activated at Wed 19 Nov 2025 16:00:01 UTC (`1763568001`) and the Mainnet Superchain will be optimistically activated at Tue 2 Dec 2025 16:00:01 UTC (`1764691201`) pending governance approval. + + The smart contract upgrade will be prepared by OP Labs for the following chains: `OP`, `Base`, `Soneium`, `Ink`, `Unichain`, `Metal`, `Mode`, `Zora`, `Arena-Z`, and `Swell` (Swell Mainnet only). The Optimism Security Council will review and sign the upgrades. + + Chains that inherit activations from the Superchain Registry and are NOT listed above must perform the upgrade themselves before the activation time. To check if you are inheriting the activation time automatically, see if your chain's [Superchain Registry toml file](https://github.com/ethereum-optimism/superchain-registry/tree/main/superchain/configs) includes the Jovian activation and the `superchain_time` field. + + Additionally, Swell Mainnet will activate the Isthmus hard fork at Tue 2 Dec 2025 04:00:01 UTC (`1764648001`). + + +## What's included in Upgrade 17 + +Upgrade 17 introduces the following changes: + +* **Cannon Go 1.24 support:** upgrading the on-chain fault proof virtual machine implementation to support Go 1.24. +* **Configurable Minimum Base Fee:** allows chain operators to specify a minimum base fee to shorten the length of priority fee auctions (disabled by default). +* **Data Availability Footprint Block Limit:** adds an in-protocol limit to estimated DA usage of transactions to prevent DA spam and priority fee auctions. (enabled by default; can be disabled via zero scalar on-chain). + +For more information on the Jovian implementation details, please review the [Jovian specifications](https://specs.optimism.io/protocol/jovian/overview.html). + +## Breaking Changes + +### Block header changes + +* `extraData` field is extended (for the [minBaseFee](https://specs.optimism.io/protocol/jovian/exec-engine.html#minimum-base-fee-in-block-header)) +* `blobGasUsed` may become nonzero when the DA footprint feature is enabled + +The `blobGasUsed` property of each block header is set to that block's `daFootprint`. Note that since Ecotone it was set to `0`, as OP Stack chains don't support blobs. It is now repurposed to store the DA footprint. If you want to disable it, set a scalar of 1. Setting 0 (or never setting the scalar value) implies the default value of the scalar, which can be updated in future forks. Please ensure your nodes and tooling can handle these updated header semantics. + +For more information, see the [specs](https://specs.optimism.io/protocol/jovian/exec-engine.html#da-footprint-block-limit). + +### Operator Fee +The offchain formula for operator fee was changed to support more flexible fee configurations for rollup operators and lays groundwork for future custom gas token support. + +If you are running operator fee with nonzero scalars, make sure to adjust them before activating the fork to avoid overcharging users. +We recommend setting the scalars to zero before the fork activates, and then reevaluating appropriate new scalars given the formula after the fork. +This will ensure that the operator fee is calculated correctly and that users are not overcharged. + + +For more information, see the [specs](https://specs.optimism.io/protocol/jovian/exec-engine.html#operator-fee). + +## For node operators + +These following steps are necessary for every node operator: + + + + The releases contain both the Jovian Mainnet and Sepolia Superchain activation timestamps. + + * op-node: [op-node/v1.16.2](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.16.2) + * op-geth: [v1.101603.5](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101603.5) + * op-reth: [op-reth/v1.9.2](https://github.com/paradigmxyz/reth/releases/tag/v1.9.2) or [op-reth/v1.9.3](https://github.com/paradigmxyz/reth/releases/tag/v1.9.3) for chains running flashblocks + + + + + If you are operating a node for an OP Chain that has opted into the [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md) and are utilizing the network flags, the Jovian activation date is part of the `op-node` and `op-geth` nodes. So, no action is needed for the sequencer after upgrading to the latest release. Please skip to [Step 3: Verify Your Configuration](#verify-your-configuration). + + The following chains are included but are subject to change: `OP`, `Base`, `Soneium`, `Ink`, `Unichain`, `Metal`, `Mode`, `Zora`, `Arena-Z`, and `Swell` (Swell Mainnet only). + + + For node operators of not using the [hardfork activation inheritance behavior](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md), you will need to manually configure the activation. This can be done one of two ways: + + * **Option 1:** Set the activation time via overrides (CLI) in both `op-node` and `op-geth`. These will need to be set on `op-node` and `op-geth` for the sequencer and all other nodes. + * **Option 2:** Set the activation time in the `rollup.json` for `op-node`. You will still need to set the `override.isthmus` flag in `op-geth` or set the time in the EL clients genesis file if you use this option. + + + + + Make the following checks to verify that your node is properly configured. + + * `op-node` and `op-geth` will log their configurations at startup + * Check that the Jovian time is set to its correct activation timestamp in the op-node startup logs + * Check that the Jovian time is set to its correct activation timestamp in the op-geth startup logs + + + +## For chain operators + +Update the following components: + +| Component | Version | Notes | +| --------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `proxyd` | [v4.19.0](https://github.com/ethereum-optimism/infra/releases/tag/proxyd%2Fv4.19.0) or greater | You need to whitelist `eth_blobBaseFee` rpc if using proxyd for L1 load balancing. If you use `dugtrio` ensure this is also updated to the latest release. | +| `op-batcher` | [v1.16.2](https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.16.2) | The `op-batcher` must be restarted at least once after updating `op-node` to the latest release. The batcher loads the rollup config once at startup and this will need to happen to update the activation timestamps for Jovian. We've opened this [issue](https://github.com/ethereum-optimism/optimism/issues/18178) to improve this experience in the future. | +| `op-node` | [v1.16.2](https://github.com/ethereum-optimism/optimism/releases/tag/op-node%2Fv1.16.2) | L1 chain config must be supplied via a new flag (see the release notes). Not necessary for chains deriving from Ethereum Mainnet, Sepolia, Holesky or Hoodi. | +| `op-geth` | [v1.101603.5](https://github.com/ethereum-optimism/op-geth/releases/tag/v1.101603.5) | | +| `op-challenger` | [v1.7.0](https://github.com/ethereum-optimism/optimism/releases/tag/op-challenger%2Fv1.7.0) | If deriving from a chain other than Ethereum Mainnet, Sepolia or Hoodi then L1 chain config must be supplied via a new flag. | +| `kona-node` | [v1.2.4](https://github.com/op-rs/kona/releases/tag/kona-node%2Fv1.2.4) | | +| `kona-host` | [v1.2.4](https://github.com/op-rs/kona/releases/tag/kona-host%2Fv1.2.4) | | +| `kona-client` | [v1.2.4](https://github.com/op-rs/kona/releases/tag/kona-client%2Fv1.2.4) | | +| `op-reth` | [v1.9.2](https://github.com/paradigmxyz/reth/releases/tag/v1.9.2) or [v1.9.3](https://github.com/paradigmxyz/reth/releases/tag/v1.9.3) for chains running flashblocks | | +| `op-rbuilder` | [v0.2.13](https://github.com/flashbots/op-rbuilder/releases/tag/op-rbuilder%2Fv0.2.13) | | +| `rollup-boost` | [v0.7.11](https://github.com/flashbots/rollup-boost/releases/tag/rollup-boost%2Fv0.7.11) | | + +### For permissionless fault proof enabled chains + +Chains running permissionless fault proofs will need to deploy new dispute game contracts with new absolute prestates. +These prestates also contain support for the L1 Fusaka activation. + + + The Superchain will be bundling [Fusaka support](/notices/fusaka-notice) and the Mainnet Jovian hard fork support into a single absolute prestate. + + + + Optimism will complete the onchain prestate update (including Dispute Game deployment and implementation set) on OP, Ink, and Unichain Mainnet and Sepolia. + However, off chain components (op-challenger) must still be configured by operators to use the new prestate. + If you are a Permissionless FP enabled chain not included in the list above, you must perform all steps below yourself. + + + + + + The absolute prestate is generated with the [op-program/v1.8.0-rc.4](https://github.com/ethereum-optimism/optimism/tree/op-program/v1.8.0-rc.4). You can use this new absolute prestate `0x03caa1871bb9fe7f9b11217c245c16e4ded33367df5b3ccb2c6d0a847a217d1b` for the following chains: + + * Mainnet and Sepolia: `OP`, `Base`, `Ink`, and `Unichain` + + You can verify this absolute prestate by running the following command in the root of the monorepo on the `op-program/v1.8.0-rc.4` tag: + + ```shell + make reproducible-prestate + ``` + + This will output the calculated prestates, the tail end of the output should look like this: + + ```shell + -------------------- Production Prestates -------------------- + + Cannon64 Absolute prestate hash: + 0x03caa1871bb9fe7f9b11217c245c16e4ded33367df5b3ccb2c6d0a847a217d1b + + -------------------- Experimental Prestates -------------------- + + Cannon64Next Absolute prestate hash: + 0x03caa1871bb9fe7f9b11217c245c16e4ded33367df5b3ccb2c6d0a847a217d1b + + CannonInterop Absolute prestate hash: + 0x03455f7f2179327853a989648c3ac9f2d58be45137bae603271660519b4d5245 + + CannonInteropNext Absolute prestate hash: + 0x03455f7f2179327853a989648c3ac9f2d58be45137bae603271660519b4d5245 + ``` + + * The "Cannon64" hash is the 64-bit prestate. + + Verify that your target prestate was calculated as expected and matches the corresponding entry in + [standard-prestates.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). + + + + During the previous step, you also generated the preimage of the absolute prestate, which is the op-program serialized into a binary file. You'll find that new file at `optimism/op-program/bin/prestate-mt64.bin.gz`. Rename that file to have the absolute prestate hash as the filename so it looks like `PRESTATEHASH.bin.gz`. + + Upload that file to where you're storing your other absolute preimage files. This should be the location where you're pointing your `--cannon-prestates-url` at. The `op-challenger` will grab this file and use it when it needs to challenge games. + + + +## Execute the L1 Contract Upgrade + +Once your `op-challenger` is ready with the new preimage, you can execute the upgrade transaction. This should be done by making a delegatecall to the `upgrade()` function of the OP Contract Manager at the address listed in [the registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-mainnet.toml). + +Please simulate and validate the expected output prior to executing the transaction. diff --git a/docs/public-docs/notices/archive/upgrade-18.mdx b/docs/public-docs/notices/archive/upgrade-18.mdx new file mode 100644 index 0000000000000..0c7fe6137c02c --- /dev/null +++ b/docs/public-docs/notices/archive/upgrade-18.mdx @@ -0,0 +1,173 @@ +--- +title: Upgrade 18 - Cannon + Kona and Custom Gas Token v2 +description: Learn how to prepare for Upgrade 18. +--- + +Upgrade 18 is a proposed network upgrade for OP Stack chains, which **introduces support for kona-proof as an alternate fault proof program**, introduces **Custom Gas Token v2 (CGT v2)** for new OP Stack chains that want to use a non-ETH native fee currency, and a creator pattern dispute game refactor. + + + Upgrade 18 is purely a smart contract upgrade. There are no hardforks activating during this upgrade. + + +## What's included in Upgrade 18 + +Upgrade 18 introduces the following changes: +* **Cannon + Kona game type support:** adds a new fault dispute game type `CANNON_KONA` (8) alongside `CANNON` (0) increasing the redundancy and diversity in the fault proof layer. This is a Rust-based fault proof implementation where `kona-client` (`kona-node` + `op-reth`) can operate alongside `op-program` (`op-node` + `op-geth`). Both game types use Cannon as the FPVM and the same dispute game implementation (`FaultDisputeGame.sol`). This upgrade does not change the respected game type. +* **Custom Gas Token v2 (CGT v2):** introduces an `isCustomGasToken()` flag and two new predeploys (`NativeAssetLiquidity` and `LiquidityController`) to support non-ETH native fee currencies configured at genesis (available in `op-contracts/v6.0.0`). +* **Creator Pattern Dispute Game Refactor:** simplifies the chain deployment by introducing clone-with-immutable-arguments (CWIA) for dispute game contracts. This change removes the need for chain-specific deployments, reducing operational overhead while preserving safety and upgradeability across chains, and reducing gas costs to ensure all operations stay below the EIP-7825 transaction gas limit cap. + +## Breaking Changes + +### `OPCM`: new `OpChainConfig` parameters for the Cannon + Kona game type + +Upgrade 18 updates the upgrade parameters so `OpChainConfig` include: +* `cannonKonaPrestate` (must be a valid `cannon64-kona` prestate hash) and +* `cannonPrestate` (must be a valid `cannon64` prestate hash) + +Both prestates must be canonical hashes from the Superchain Registry `standard-prestates.toml` and must embed an up-to-date chain config for your chain. + +After upgrade, `DisputeGameFactory` will have implementations set for: + +* `CANNON` (0) +* `PERMISSIONED` (1) +* `CANNON_KONA` (8) + +Games of any type can be created after upgrade, but withdrawals continue to use the respected game type. + + + For chains running only permissioned proofs: cannon+kona will not be deployed and `cannonKonaPrestate` is ignored. + + +### Custom Gas Token v2: ETH value is rejected when enabled + +When CGTv2 is enabled at genesis, `isCustomGasToken()` is set on L1 and L2: + +* **On L1** (`SystemConfig`): `OptimismPortal`, `L1CrossDomainMessenger`, and `L1StandardBridge` reject transactions containing ETH value (`msg.value`). +* **On L2** (`L1Block`): ETH-related operations are blocked in `L2ToL1MessagePasser`, `L2CrossDomainMessenger`, `L2StandardBridge`, and fee vaults. + + + ETH bridging routes through L1-WETH as an ERC-20 wrapper when CGT is enabled. + + + + There is currently no migration path from legacy CGT to CGT v2. + + + +## For chain operators + +Update the following components: + +| Component | Version | Notes | +| -------------- | ------- | ----- | +| `op-challenger` | [`op-challenger/v1.9.0`](https://github.com/ethereum-optimism/optimism/releases/tag/op-challenger%2Fv1.9.0) | Must be updated **prior** to the upgrade for permissionless fault proof chains. | +| `op-dispute-mon` | [`op-dispute-mon/v1.5.0`](https://github.com/ethereum-optimism/optimism/releases/tag/op-dispute-mon%2Fv1.5.0) | Must be updated **prior** to the upgrade for permissionless fault proof chains. | +| `op-contracts` | `op-contracts/v6.0.0` | [op-contracts/v6.0.0-rc.1](https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv6.0.0-rc.1) will be finalized if it gets governance approval. | + + +### For permissionless fault proof enabled chains + +Chains running permissionless fault proofs must ensure challengers are Kona-ready and that both `CANNON` and `CANNON_KONA` are configured correctly. + + + Optimism will complete the onchain cannon+kona deployment for chains managed by the Optimism Security Council. + However, off-chain components must still be configured by operators to support cannon-kona games. + If you are a permissionless FP enabled chain not included in the prepared set, you must perform all steps below yourself. + + + + + + The absolute prestate is generated with the [op-program/v1.9.0-rc.1](https://github.com/ethereum-optimism/optimism/tree/op-program/v1.9.0-rc.1). You can use this new absolute prestate `0x033c000916b4a88cfffeceddd6cf0f4be3897a89195941e5a7c3f8209b4dbb6e` for the following chains: + + * Mainnet and Sepolia: `OP`, `Base`, `Ink`, `Soneium`, and `Unichain` + + You can verify this absolute prestate by running the following command in the root of the monorepo on the `op-program/v1.9.0-rc.1` tag: + + ```shell + make reproducible-prestate + ``` + + This will output the calculated prestates, the tail end of the output should look like this: + + ```shell + -------------------- Production Prestates -------------------- + + Cannon64 Absolute prestate hash: + 0x033c000916b4a88cfffeceddd6cf0f4be3897a89195941e5a7c3f8209b4dbb6e + ... + ``` + + * The "Cannon64" hash is the 64-bit prestate. + + Verify that your target prestate was calculated as expected and matches the corresponding entry in + [standard-prestates.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). + + + + The absolute prestate is generated with the [kona-client/v1.2.7](https://github.com/ethereum-optimism/kona/tree/kona-client/v1.2.7) (Note that Kona is currently being moved into the [ethereum-optimism/optimism repo](https://github.com/ethereum-optimism/optimism), but for building this release use the [op-rs/kona repo](https://github.com/op-rs/kona)). You can use this new absolute prestate `0x0323914d3050e80c3d09da528be54794fde60cd26849cd3410dde0da7cd7d4fa` for the following chains: + + * Mainnet and Sepolia: `OP`, `Base`, `Ink`, `Soneium`, and `Unichain` + + You can verify this absolute prestate by running the following commands in the root of the kona monorepo on the `kona-client/v1.2.7` tag: + + ```shell + cd docker/fpvm-prestates + just cannon kona-client kona-client/v1.2.7 $(cat ../../.config/cannon_tag) + jq -r .pre ../../prestate-artifacts-cannon/prestate-proof.json + ``` + + Verify that your target prestate was calculated as expected and matches the corresponding entry in + [standard-prestates.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). + + + + During the previous step, you also generated the preimage of the absolute prestate, which is the op-program serialized into a binary file. You'll find the new cannon file at `optimism/op-program/bin/prestate-mt64.bin.gz`. Rename that file to have the absolute prestate hash as the filename so it looks like `0x033c000916b4a88cfffeceddd6cf0f4be3897a89195941e5a7c3f8209b4dbb6e.bin.gz`. + + You'll find the new kona cannon file at `prestate-artifacts-cannon/prestate.bin.gz`. Rename that file to have the absolute prestate hash as the filename so it looks like `0x0323914d3050e80c3d09da528be54794fde60cd26849cd3410dde0da7cd7d4fa.bin.gz`. + + Upload those file to where you're storing your other absolute preimage files. This should be the location where you're pointing your `--prestates-url` at. The `op-challenger` will grab this file and use it when it needs to challenge games. + + + + Ensure `op-challenger` is updated and configured to support Kona games. + Include `cannon-kona` in trace types: + + ```bash + OP_CHALLENGER_TRACE_TYPE="cannon,cannon-kona,permissioned" + # or + --trace-type=cannon,cannon-kona,permissioned + ``` + + Upload new preimage files and ensure they're available for the challenger. + If both preimage files are uploaded at the same location: + + ```bash + --prestates-url= + ``` + + If stored separately: + + ```bash + --cannon-prestates-url= + --cannon-kona-prestates-url= + ``` + + If not using the standard OP Labs `op-challenger` Docker image, set the `kona-host` binary path: + + ```bash + OP_CHALLENGER_CANNON_KONA_SERVER=/path/to/kona-host + # or + --cannon-kona-server=/path/to/kona-host + ``` + + Build `kona-host` from the same release as the configured cannon-kona prestate. + + + +## Execute the L1 Contract Upgrade + +Once your `op-challenger` is ready (and Kona-ready if permissionless), you can execute the upgrade transaction. This should be done by making a delegatecall to the `upgrade()` function of the OP Contract Manager at the address listed in [the registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-mainnet.toml). + +Please simulate and validate the expected output prior to executing the transaction. + diff --git a/docs/public-docs/notices/op-geth-deprecation.mdx b/docs/public-docs/notices/op-geth-deprecation.mdx new file mode 100644 index 0000000000000..3d5273372e176 --- /dev/null +++ b/docs/public-docs/notices/op-geth-deprecation.mdx @@ -0,0 +1,62 @@ +--- +title: End of Support for op-geth and op-program +description: op-geth and op-program will be supported through May 31st, 2026. Start migrating to op-reth and cannon-kona now. +lang: en-US +content_type: notice +topic: op-geth-deprecation +personas: + - node-operator + - chain-operator +categories: + - infrastructure + - protocol +is_imported_content: 'false' +--- + +As the ecosystem matures, we are transitioning full execution client support to **op-reth** and moving from **op-program** to **kona-client**. + +## What this means + +- **op-geth is supported through May 31st, 2026.** Security patches and critical bug fixes will continue to be issued during this window, after which support ends. +- **New feature development, including the next Karst hardfork, will happen on op-reth only.** +- **op-program is also reaching end-of-support.** The fault proof program is transitioning from **op-program** to **kona-client**. +- **Current op-program deployments are expected to remain usable until the next hardfork, Karst.** Chain operators will need to migrate to **kona-client** at Karst. +- **kona-node is not required for cannon-kona.** You can run kona proofs with your existing **op-node** setup. kona-node is a separate, optional component and is not part of the fault proof program migration. +- **op-node** is not being deprecated. + +## Action required + +**op-geth will not support the L1 Glamsterdam hardfork.** Chains still running op-geth at activation will not be able to follow the canonical chain. + +### Node Operators + +All node operators should migrate to op-reth as soon as possible. For more details, see **op-reth configuration** in the resources section below. + + + op-geth reaches end-of-support in **May 31st, 2026**. Glamsterdam will break compatibility with op-geth. + + + + Syncing op-reth takes time. Start early to allow adequate validation before the hardfork window. + + +Start syncing an op-reth node alongside your existing op-geth node and take a snapshot. Validate sync correctness over a meaningful window—compare block hashes, state roots, and RPC outputs. Once confident, migrate production traffic to op-reth. + +For OP Mainnet snapshots, you can find [snapshots here](https://datadirs.optimism.io/). + +### Chain Operators + +As soon as op-reth is fully synced, take a snapshot. Refresh it regularly as Glamsterdam approaches. +Syncing an op-reth node as soon as possible helps ensure a snapshot is available for other validators on your chain so they do not need to sync from scratch. + +### Permissionless Chain Operators + +In parallel, the fault proof program is moving from **op-program** to **kona client**. Current op-program deployments are expected to remain usable until the next hardfork, Karst, when chain operators will need to migrate to **kona-client**. Optimism will handle the onchain prestate updates for managed chains. If you operate a permissionless fault proof chain, watch for a separate notice with specific steps. + +## Resources + +- [op-reth repository](https://github.com/ethereum-optimism/optimism/tree/develop/rust) +- [op-reth configuration](https://docs.optimism.io/node-operators/guides/configuration/execution-clients#op-reth-configuration) +- [op-reth OP Mainnet Snapshots](https://datadirs.optimism.io/) +- [How to use Snapshots](https://docs.optimism.io/op-mainnet/network-information/snapshots) +- [kona repository](https://github.com/ethereum-optimism/optimism/tree/develop/rust/kona) diff --git a/docs/public-docs/op-mainnet/network-information/connecting-to-op.mdx b/docs/public-docs/op-mainnet/network-information/connecting-to-op.mdx new file mode 100644 index 0000000000000..6eb97c1020da9 --- /dev/null +++ b/docs/public-docs/op-mainnet/network-information/connecting-to-op.mdx @@ -0,0 +1,47 @@ +--- +title: Connecting to OP Mainnet +description: Documentation for OP Mainnet and OP Sepolia. This page covers network information including network names, chain IDs, RPC endpoints, currency symbols, block explorers, and contract addresses. +--- + +This page provides network information for OP Mainnet and OP Sepolia, including RPC endpoints, chain IDs, and block explorers. + + + The public RPC URLs provided below are rate limited and do not support websocket connections. + If you are experiencing rate limiting issues or need websocket functionality, consider [running your own node](/node-operators/reference/architecture/rollup-node) or signing up for a [third-party RPC provider](/app-developers/reference/rpc-providers). + + +## OP Mainnet + +| Parameter | Value | +| ---------------------------------------- | ---------------------------------------------------------------------------------------- | +| Network Name | `OP Mainnet` | +| Chain ID | `10` | +| Currency Symbol1 | ETH | +| Explorer | [https://explorer.optimism.io](https://explorer.optimism.io) | +| Public RPC URL | [https://mainnet.optimism.io](https://mainnet.optimism.io) | +| Sequencer URL2 | [https://mainnet-sequencer.optimism.io](https://mainnet-sequencer.optimism.io) | +| Flashblocks websocket URL 3 | [wss://op-mainnet-fb-ws-pub.optimism.io/ws](wss://op-mainnet-fb-ws-pub.optimism.io/ws) | +| Contract Addresses | Refer to the [Contract Addresses page](/op-mainnet/network-information/op-addresses#op-mainnet) | +| Connect Wallet | [Click here to connect your wallet to OP Mainnet](https://chainid.link?network=optimism) | + +1. The "currency symbol" is required by some wallets like MetaMask. +2. The sequencer URL is write only. +3. Strictly rate-limited public URL. Please rely on Ethereum JSON RPC or reach out to the Optimism team for a more relaxed private endpoint. + +## OP Sepolia + +| Parameter | Value | +| --------------------------- | ------------------------------------------------------------------------------------------ | +| Network Name | `OP Sepolia` | +| Chain ID | `11155420` | +| Currency Symbol1 | ETH | +| Explorer | [https://testnet-explorer.optimism.io](https://testnet-explorer.optimism.io) | +| Public RPC URL | [https://sepolia.optimism.io](https://sepolia.optimism.io) | +| Flashblocks websocket URL 3 | [wss://op-sepolia-fb-ws.optimism.io/ws](wss://op-sepolia-fb-ws.optimism.io/ws) | +| Sequencer URL2 | [https://sepolia-sequencer.optimism.io](https://sepolia-sequencer.optimism.io) | +| Contract Addresses | Refer to the [Contract Addresses page](/op-mainnet/network-information/op-addresses#op-sepolia) | +| Connect Wallet | [Click here to connect your wallet to OP Sepolia](https://chainid.link?network=op-sepolia) | + +1. The "currency symbol" is required by some wallets like MetaMask. +2. The sequencer URL is write only. +3. Strictly rate-limited public URL. Please rely on Ethereum JSON RPC or reach out to the Optimism team for a more relaxed private endpoint. diff --git a/docs/public-docs/op-mainnet/network-information/op-addresses.mdx b/docs/public-docs/op-mainnet/network-information/op-addresses.mdx new file mode 100644 index 0000000000000..d0da49d908858 --- /dev/null +++ b/docs/public-docs/op-mainnet/network-information/op-addresses.mdx @@ -0,0 +1,58 @@ +--- +title: OP Mainnet Contract Addresses +description: A comprehensive list of L1 and L2 contract addresses for OP Mainnet and OP Sepolia. +--- + +import { SuperchainContractTable } from "/snippets/superchain-contract-table.jsx" +import { L1ContractTable } from "/snippets/l1-contract-table.jsx" +import { L2ContractTable } from "/snippets/l2-contract-table.jsx" + +This page lists all contract addresses for OP Mainnet and OP Sepolia. For high-level details and source code, see the [Smart Contracts Overview](/op-stack/protocol/smart-contracts). + + +Contract addresses are automatically synced from the [superchain-registry](https://github.com/ethereum-optimism/superchain-registry/tree/main). + + +## L2 Contract Addresses + +### OP Mainnet + + + +### OP Sepolia + + + +## L1 Contract Addresses + +### Ethereum Mainnet + + + +### Ethereum Testnet (Sepolia) + + + +## Shared Contracts + +### Ethereum Mainnet + + + +### Ethereum Testnet (Sepolia) + + + +## Legacy Contracts + + +Legacy contracts are from previous versions of the OP Stack and are maintained for backwards compatibility. + + +### OP Mainnet Legacy (L2) + + + +### Ethereum Mainnet Legacy (L1) + + diff --git a/docs/public-docs/op-mainnet/network-information/snapshots.mdx b/docs/public-docs/op-mainnet/network-information/snapshots.mdx new file mode 100644 index 0000000000000..593c49dba075d --- /dev/null +++ b/docs/public-docs/op-mainnet/network-information/snapshots.mdx @@ -0,0 +1,54 @@ +--- +title: Snapshots +description: Find download links for data directories and database snapshots for running your own node. +--- + + + +# Node snapshots + +This page contains download links for data directories and node snapshots. +State snapshots are pre-synced node data that let you start an OP Stack execution client from a recent chain state instead of replaying from genesis. You download the snapshot and run your node from it, which dramatically cuts initial sync time. + + + Data directories and node snapshots are **not required** in the following cases: + + * When using [snap sync](/node-operators/guides/management/snap-sync) with op-geth + * When using [Nethermind](https://docs.nethermind.io/get-started/running-node/l2-networks#op-stack) (automatically handles snapshots) + + They are still required for archive nodes and in instances when you need to trace the entire chain with `op-reth` or `op-geth`. + + + +OP Mainnet underwent a large [database migration](https://web.archive.org/web/20240110231645/https://blog.oplabs.co/reproduce-bedrock-migration/) as part of the [Bedrock Upgrade](https://web.archive.org/web/20230608050602/https://blog.oplabs.co/introducing-optimism-bedrock/) in 2023. +Node operators using `op-reth` or `op-geth` must have a migrated OP Mainnet database to run an archival node. +Migrated OP Mainnet databases can be generated manually or pre-migrated databases can be downloaded from the links below. + + +## Available OP Mainnet Snapshots + + + Using [aria2](https://aria2.github.io/) to download snapshots can significantly speed up the download process. + + +All snapshots for OP Mainnet can be found at the OP Labs managed [Data Directories](https://datadirs.optimism.io/) website. +All geth snapshots are configured for pebbleDB and the hash state scheme. + + +### Nethermind + +[Nethermind](https://docs.nethermind.io/get-started/running-node/l2-networks#op-stack) automatically handles downloading and applying the necessary snapshots when you start the node. No manual snapshot download is required. The node will: + +1. Start with an empty database +2. Automatically download the required ancient data +3. Apply the data and continue syncing + +This process is fully automated and requires no additional configuration. +When you run `Nethermind` with the `-c op-mainnet` flag, it uses this configuration automatically. + +## 3rd Party Snapshots + +[Allnodes](https://www.allnodes.com) provides full node snapshots for OP Mainnet and Testnet. You can find them [here](https://www.publicnode.com/snapshots#optimism). +**Please note:** Allnodes is a 3rd party provider, and the Optimism Foundation hasn't verified the snapshots. + + diff --git a/docs/public-docs/op-stack/bridging/cross-domain.mdx b/docs/public-docs/op-stack/bridging/cross-domain.mdx new file mode 100644 index 0000000000000..c3b89c22cbe69 --- /dev/null +++ b/docs/public-docs/op-stack/bridging/cross-domain.mdx @@ -0,0 +1,60 @@ +--- +title: Cross-Domain Overview +description: An overview of the lifecycle of an OP Stack cross-chain transaction, detailing the flow of transactions between Layer 2 and Layer 1. +--- + + + This page is out of date. + +This overview provides a detailed walkthrough of the lifecycle of cross-chain transactions, covering deposits, withdrawals, and transaction flows between L1 and L2. The diagram below illustrates the main components and steps involved. + +![Lifecycle of an OP Stack Crosschain Transaction.](/public/img/op-stack/protocol/op-cross-chain-txn.jpeg) +_Figure 1: The Lifecycle of an OP Stack Crosschain Transaction_ + +Cross-domain communication in the OP Stack involves moving assets and messages between L1 and L2. Key components, such as bridges, messengers, and portals, ensure these transactions are executed securely and transparently. This page breaks down the lifecycle of a cross-chain transaction into three main flows: Deposit Flow, Transaction Flow (Tx Flow), and Withdrawal Flow. + +## Deposit Flow + +Depositing assets from L1 to L2 follows a structured process that ensures funds are securely transferred and credited to the user's L2 account. The steps are as follows: + +1. **User Initiation (L1 Standard Bridge):** + The user initiates a deposit by sending assets (e.g., ETH or tokens) to the L1 Standard Bridge contract. This contract receives the assets and prepares a message to relay to L2. + +2. **Message Relaying (L1 CrossDomain Messenger):** + The L1 Standard Bridge sends a message to the L1 CrossDomain Messenger, which is responsible for handling inter-layer communication. The messenger emits a `TransactionDeposited` event to signal the start of the deposit process. + +3. **Processing on L2 (OptimismPortal):** + The message is received by the OptimismPortal on L2. Here, the deposited assets are held securely until the transaction is finalized. The portal initiates the deposit transaction, updating the user's balance on L2. + +4. **Finalization (L2 CrossDomain Messenger):** + The L2 CrossDomain Messenger processes the deposit by updating the user's account balance and making the assets available for use on L2. + +## Transaction (Tx) Flow + +The transaction flow covers the steps involved in cross-domain message passing and state updates between L1 and L2: + +1. **Message Queuing (L2ToL1MessagePasser):** +During cross-layer communication, certain messages are queued for processing. The `L2ToL1MessagePasser` prepares these messages for state updates or withdrawals, ensuring they are available for proving and relaying. + +2. **State Reading and Proving (L2OutputOracle):** +The `L2OutputOracle` plays a critical role in validating state changes between L1 and L2. It reads the current state and submits a proposal using `proposeL2Output()`. This proposal includes information about queued messages or state changes that need to be relayed to L1. + +3. **Message Relay (CrossDomain Messengers):** +Messages are relayed between the L1 and L2 CrossDomain Messengers as part of transaction execution. This includes updating state or finalizing transactions on the target layer. + + +## Withdrawal Flow + +Withdrawing assets from L2 back to L1 involves a multi-step process to ensure the transaction is validated and executed correctly: + +1. **User Initiation (L2 Standard Bridge):** +The withdrawal process starts when the user calls the `withdraw()` function on the L2 Standard Bridge, specifying the amount and asset to be withdrawn. + +2. **Message Relay (L2 CrossDomain Messenger):** +The L2 CrossDomain Messenger receives the withdrawal request and relays the message for processing. It may involve queuing the message in the `L2ToL1MessagePasser` for further steps. + +3. **Proving and Finalization (L2OutputOracle):** +The withdrawal message is proven using the `L2OutputOracle`, which submits a state output proving that the withdrawal is legitimate. This step involves reading the state and generating the required proofs during the proving time. + +4. **Finalization (L1 CrossDomain Messenger and L1 Standard Bridge):** +Once the withdrawal is proven, the message is finalized by the L1 CrossDomain Messenger. The L1 Standard Bridge completes the process by releasing the withdrawn assets to the user's L1 account. diff --git a/docs/public-docs/op-stack/bridging/deposit-flow.mdx b/docs/public-docs/op-stack/bridging/deposit-flow.mdx new file mode 100644 index 0000000000000..0624ce93e2312 --- /dev/null +++ b/docs/public-docs/op-stack/bridging/deposit-flow.mdx @@ -0,0 +1,228 @@ +--- +title: Deposit flow +description: Learn the deposit flow process for L2 deposit transactions, triggered by events on L1. +--- +This guide explains the deposit flow process for L2 deposit transactions, triggered by transactions or events on L1. In Optimism terminology, "*deposit transaction*" refers to any L2 transaction that is triggered by a transaction or event on L1. + +The process is somewhat similar to the way [most networking stacks work](https://en.wikipedia.org/wiki/Encapsulation_\(networking\)). +Information is encapsulated in lower layer packets on the sending side and then retrieved and used by those layers on the receiving side while going up the stack to the receiving application. + +![Deposit Flow Diagram.](/public/img/op-stack/protocol/deposit-flow-dark-mode.svg) + +## L1 processing + +1. An L1 entity, either a smart contract or an externally owned account (EOA), sends a deposit transaction to [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/L1CrossDomainMessenger.sol), using [`sendMessage`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol#L249-L289). + This function accepts three parameters: + + * `_target`, target address on L2. + * `_message`, the L2 transaction's calldata, formatted as per the [ABI](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html) of the target account. + * `_minGasLimit`, the minimum gas limit allowed for the transaction on L2. Note that this is a *minimum* and the actual amount provided on L2 may be higher (but never lower) than the specified gas limit. The actual amount provided on L2 is often higher because the portal contract on L2 performs some processing before submitting the call to `_target`. + +2. The L1 cross domain messenger calls [its own `_sendMessage` function](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/L1CrossDomainMessenger.sol#L42-L52). + It uses these parameters: + + * `_to`, the destination address, is the messenger on the other side. + In the case of deposits, this is always [`0x4200000000000000000000000000000000000007`](https://testnet-explorer.optimism.io/address/0x4200000000000000000000000000000000000007). + * `_gasLimit`, the gas limit. + This value is calculated using [the `baseGas` function](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol#L442-L471). + * `_value`, the ETH that is sent with the message. + This amount is taken from the transaction value. + * `_data`, the calldata for the call on L2 that is needed to relay the message. + This is an [ABI encoded](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html) call to [`relayMessage`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol#L291-L413). + +3. [`_sendMessage`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/L1CrossDomainMessenger.sol#L42-L52) calls the portal's [`depositTransaction` function](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L422-L483). + + Note that other contracts can also call [`depositTransaction`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L422-L483) directly. + However, doing so bypasses certain safeguards, so in most cases it's a bad idea. + +4. [The `depositTransaction` function](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L422-L483) runs a few sanity checks, and then emits a [`TransactionDeposited`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L85-L99) event. + +## L2 processing + +1. The `op-node` component [looks for `TransactionDeposited` events on L1](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/op-node/rollup/derive/deposits.go#L13-L33). + If it sees any such events, it [parses](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/op-node/rollup/derive/deposit_log.go) them. + +2. Next, `op-node` [converts](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/op-node/rollup/derive/deposits.go#L35-L51) those `TransactionDeposited` events into [deposit transactions](https://specs.optimism.io/protocol/deposits.html?utm_source=op-docs\&utm_medium=docs#user-deposited-transactions). + +3. In most cases, user deposit transactions call the [`relayMessage`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol#L291-L413) function of [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol). + +4. `relayMessage` runs a few sanity checks and then, if everything is good, [calls the real target contract with the relayed calldata](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol#L394). + +## Denial of service (DoS) prevention + +As with all other L1 transactions, the L1 costs of a deposit are borne by the transaction's originator. +However, the L2 processing of the transaction is performed by the Optimism nodes. +If there were no cost attached, an attacker could submit a transaction that had high execution costs on L2, and that way perform a denial of service attack. + +To avoid this DoS vector, [`depositTransaction`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L422-L483), and the functions that call it, require a gas limit parameter. +[This gas limit is encoded into the `TransactionDeposited` event](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L469-L477), and used as the gas limit for the user deposit transaction on L2. + +This L2 gas is paid for by burning L1 gas [here](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/ResourceMetering.sol#L162). + +## Replaying messages + +Deposit transactions can fail due to several reasons: + +* Not enough gas provided. +* The state on L2 does not allow the transaction to be successful. + +It is possible to replay a failed deposit, possibly with more gas. + +### Replays in action + + + **L1 vs L2 network clarification** + + This tutorial involves **two different networks**: + + * **L1**: Ethereum Sepolia testnet (`https://sepolia.infura.io/v3/YOUR_KEY`) + * **L2**: OP Sepolia testnet (`https://sepolia.optimism.io`) + + You'll send transactions on L1 that trigger actions on L2. Make sure you're using the correct RPC URLs for each step. + + +To see how replays work, you can use [this contract on OP Sepolia](https://testnet-explorer.optimism.io/address/0xEF60cF6C6D0C1c755be104843bb72CDa3D778630#code). + +1. Call `stopChanges`, using this Foundry command: + + ```sh + PRIV_KEY= + export ETH_RPC_URL=https://sepolia.optimism.io + GREETER=0xEF60cF6C6D0C1c755be104843bb72CDa3D778630 + cast send --private-key $PRIV_KEY $GREETER "stopChanges()" + ``` + +2. Verify that `getStatus()` returns false, meaning changes are not allowed, and see the value of `greet()` using Foundry. + Note that Foundry returns false as zero. + + ```sh + cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()" + ``` + +3. Get the calldata. + You can use this Foundry command: + + ```sh + cast calldata "setGreeting(string)" "testing" + ``` + + Or just use this value: + + ``` + 0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000774657374696e6700000000000000000000000000000000000000000000000000 + ``` + +4. Send a greeting change as a deposit from L1 (Ethereum Sepolia) to L2 (OP Sepolia). + Use these commands: + + ```sh + # L1 = Ethereum Sepolia + # Get a free Infura key at https://infura.io or use the public RPC below + L1_RPC=https://sepolia.infura.io/v3/YOUR_INFURA_KEY + L1XDM_ADDRESS=0x5086d1eef304eb5284a0f6720f79403b4e9be294 + FUNC="sendMessage(address,bytes,uint32)" + CALLDATA=`cast calldata "setGreeting(string)" "testing"` + cast send --rpc-url $L1_RPC --private-key $PRIV_KEY $L1XDM_ADDRESS $FUNC $GREETER $CALLDATA 10000000 + ``` + + The transaction will be successful on **L1 (Ethereum Sepolia)**, but then emit a fail event on **L2 (OP Sepolia)**. + +5. The next step is to find the hash of the failed relay. There are several ways to do this: + + **Method A: Using Etherscan Internal Transactions** + + Look in [the internal transactions of the destination contract](https://testnet-explorer.optimism.io/address/0xEF60cF6C6D0C1c755be104843bb72CDa3D778630#internaltx), and select the latest one that appears as a failure. It should be a call to `L2CrossDomainMessenger` at address `0x420...007`. + + **Method B: Using Contract Events (if internal transactions aren't visible)** + + If you can't see internal transactions on Etherscan, check the [L2CrossDomainMessenger contract events](https://testnet-explorer.optimism.io/address/0x4200000000000000000000000000000000000007#events) and look for `FailedRelayedMessage` events with your contract address. + + **Method C: Using cast to query failed messages** + + ```sh + # First, you need the message hash. You can derive it from the L1 transaction, or check events + L2XDM_ADDRESS=0x4200000000000000000000000000000000000007 + # Replace MSG_HASH with the actual message hash from the FailedRelayedMessage event + cast call $L2XDM_ADDRESS "failedMessages(bytes32)" $MSG_HASH + ``` + + If the latest internal transaction is a success, it probably means your transaction hasn't relayed yet. Wait until it is, that may take a few minutes. + +6. Get the transaction information using Foundry. + + + **Wait for the failed relay transaction** + + Make sure you wait for the deposit to be processed on L2 and fail before proceeding. This can take 2-5 minutes. You should see a failed transaction in one of the methods from step 5. + + + ```sh + TX_HASH= + L2XDM_ADDRESS=0x4200000000000000000000000000000000000007 + REPLAY_DATA=`cast tx $TX_HASH input` + ``` + +7. Call `startChanges()` to allow changes using this Foundry command: + + ```sh + cast send --private-key $PRIV_KEY $GREETER "startChanges()" + ``` + + + Don't do this prematurely + + If you call `startChanges()` too early, it will happen when the message is relayed to L2, and then the initial deposit will be successful and there will be no need to replay it. + + +8. Verify that `getStatus()` returns true, meaning changes are not allowed, and see the value of `greet()`. + Foundry returns true as one. + + ```sh + cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()" + ``` + +9. Now send the replay transaction. + + ```sh + cast send --private-key $PRIV_KEY --gas-limit 10000000 $L2XDM_ADDRESS $REPLAY_DATA + ``` + + + Why do we need to specify the gas limit? + + The gas estimation mechanism tries to find the minimum gas limit at which the transaction would be successful. + However, `L2CrossDomainMessenger` does not revert when a replay fails due to low gas limit, it just emits a failure message. + The gas estimation mechanism considers that a success. + + To get a gas estimate, you can use this command: + + ```sh + cast estimate --from 0x0000000000000000000000000000000000000001 $L2XDM_ADDRESS $REPLAY_DATA + ``` + + That address is a special case in which the contract does revert. + + +10. Verify the greeting has changed: + + ```sh + cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()" + ``` + +## Debugging + +To debug deposit transactions, you can ask the L2 cross domain messenger for the state of the transaction. + +1. Look on Etherscan to see the `FailedRelayedMessage` event. Set `MSG_HASH` to that value. + +2. To check if the message is listed as failed, run this: + + ```sh + cast call $L2XDM_ADDRESS "failedMessages(bytes32)" $MSG_HASH + ``` + + To check if it is listed as successful, run this: + + ```sh + cast call $L2XDM_ADDRESS "successfulMessages(bytes32)" $MSG_HASH + ``` diff --git a/docs/public-docs/op-stack/bridging/withdrawal-flow.mdx b/docs/public-docs/op-stack/bridging/withdrawal-flow.mdx new file mode 100644 index 0000000000000..9bd105a90ef2c --- /dev/null +++ b/docs/public-docs/op-stack/bridging/withdrawal-flow.mdx @@ -0,0 +1,111 @@ +--- +title: Withdrawal flow +description: Learn the withdrawal flow process for transactions sent from L2 to L1. +--- + +In Optimism terminology, a *withdrawal* is a transaction sent from L2 (OP Mainnet, OP Sepolia etc.) to L1 (Ethereum mainnet, Sepolia, etc.). + +Withdrawals require the user to submit three transactions: + +1. **Withdrawal initiating transaction**, which the user submits on L2. +2. **Withdrawal proving transaction**, which the user submits on L1 to prove that the withdrawal is legitimate (based on a Merkle-Patricia trie root that commits to the state of the `L2ToL1MessagePasser`'s storage on L2) +3. **Withdrawal finalizing transaction**, which the user submits on L1 after the fault challenge period has passed, to actually run the transaction on L1. + + + You can see an example of how to implement this process [in the bridging tutorials](/app-developers/tutorials/bridging/cross-dom-bridge-erc20). + + +## Withdrawal initiating transaction + +1. On L2, a user, either an externally owned account (EOA) directly or a contract acting on behalf of an EOA, calls the [`sendMessage`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol#L191) function of the [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol#L21) contract. + + This function accepts three parameters: + + * `_target`, target address on L1. + * `_message`, the L1 transaction's calldata, formatted as per the [ABI](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html) of the target address. + * `_minGasLimit`, The minimum amount of gas that the withdrawal finalizing transaction can provide to the withdrawal transaction. This is enforced by the `SafeCall` library, and if the minimum amount of gas cannot be met at the time of the external call from the `OptimismPortal` -> `L1CrossDomainMessenger`, the finalization transaction will revert to allow for re-attempting with a higher gas limit. In order to account for the gas consumed in the `L1CrossDomainMessenger.relayMessage` function's execution, extra gas will be added on top of the `_minGasLimit` value by the `CrossDomainMessenger.baseGas` function when `sendMessage` is called on L2. + +2. `sendMessage` is a generic function that is used in both cross domain messengers. It calls [`_sendMessage`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol#L45), which is specific to [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol#L21). + +3. `_sendMessage` calls [`initiateWithdrawal`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol#L71) on [`L2ToL1MessagePasser`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol#L19). This function calculates the hash of the raw withdrawal fields. It then marks that hash as a sent message in [`sentMessages`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol#L30) and emits the fields with the hash in a [`MessagePassed`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol#L40) event. + + The raw withdrawal fields are: + + * `nonce` - A single use value to prevent two otherwise identical withdrawals from hashing to the same value + * `sender` - The L2 address that initiated the transfer, typically [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/1a8fe18c4989bfd0852a8873f30422542ad4f44d/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol#L21) + * `target` - The L1 target address + * `value` - The amount of WEI transferred by this transaction + * `gasLimit` - Gas limit for the transaction, the system guarantees that at least this amount of gas will be available to the transaction on L1. Note that if the gas limit is not enough, or if the L1 finalizing transaction does not have enough gas to provide that gas limit, the finalizing transaction returns a failure, it does not revert. + * `data` - The calldata for the withdrawal transaction + +4. When `op-proposer`proposes a new `output`, the output proposal includes the [output root](https://specs.optimism.io/glossary.html?utm_source=op-docs\&utm_medium=docs#l2-output-root), provided as part of the block by `op-node`. + This new output root commits to the state of the `sentMessages` mapping in the `L2ToL1MessagePasser` contract's storage on L2, and it can be used to prove the presence of a pending withdrawal within it. + +## Withdrawal proving transaction + +Once an output root that includes the `MessagePassed` event is published to L1, the next step is to prove that the message hash really is in L2. Typically this is done by viem. + +### Offchain processing + +1. A user calls viem's `proveWithdrawal()` function with the withdrawal transaction receipt. This function internally handles the preparation of the proving transaction parameters. + +2. To get the withdrawal details from the L2 transaction, viem uses the `getWithdrawals()` function which extracts the raw withdrawal fields from the `MessagePassed` event in the transaction receipt. + +3. To get the proof, viem uses the withdrawal proving functionality to generate the necessary Merkle proof. + +4. Finally, viem calls [`OptimismPortal.proveWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L242) on L1. + +### Onchain processing + +[`OptimismPortal.proveWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L242) runs a few sanity checks. Then it verifies that in `L2ToL1MessagePasser.sentMessages` on L2 the hash for the withdrawal is turned on, and that this proof has not been submitted before. If everything checks out, it writes the output root, the timestamp, and the L2 output index to which it applies in `provenWithdrawals` and emits an event. + +The next step is to wait the fault challenge period, to ensure that the L2 output root used in the proof is legitimate, and that the proof itself is legitimate and not a hack. + +## Withdrawal finalizing transaction + +Finally, once the fault challenge period passes, the withdrawal can be finalized and executed on L1. + +To do so, a user, either an externally owned account (EOA) directly or a contract acting on behalf of an EOA, calls the [`finalizeWithdrawalTransaction`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L320) function of the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L19) contract. + +## Expected internal reverts in withdrawal transactions + +During the withdrawal process, users may observe internal reverts when viewing the transaction on **Etherscan**. This is a common point of confusion but is expected behavior. + +These internal reverts often show up in yellow on the Etherscan UI and may cause concern that something went wrong with the transaction. However, these reverts occur due to the non-standard proxy used in Optimism, specifically the **Chugsplash Proxy**. The Chugsplash Proxy sometimes triggers internal calls that revert as part of the designed flow of the withdrawal process. + +### Why do these reverts happen? + +The Chugsplash Proxy operates differently than standard proxies. During a withdrawal transaction, it may trigger internal contract calls that result in reverts, but these reverts do not indicate that the withdrawal has failed. Instead, they are part of the internal logic of the system and are expected in certain scenarios. + +### Key takeaways: + +* **Internal Reverts Are Expected**: These reverts are part of the normal operation of the Chugsplash Proxy during withdrawal transactions and do not represent an error. +* **No Cause for Concern**: Although Etherscan highlights these reverts, they do not affect the final success of the transaction. +* **User Assurance**: If you encounter these reverts during a withdrawal transaction, rest assured that the withdrawal will still finalize as expected. + +### Offchain processing + +1. A user calls viem's `finalizeWithdrawal()` function with the withdrawal transaction receipt. + This function internally handles the preparation of the finalization transaction parameters. + +2. To get the withdrawal details from the L2 transaction, viem uses the `getWithdrawals()` function which extracts the raw withdrawal fields from the `MessagePassed` event in the transaction receipt. + +3. Finally, viem calls [`OptimismPortal.finalizeWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L320-L420) on L1. + +### Onchain processing + +1. [`OptimismPortal.finalizeWithdrawalTransaction()`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L320-L420) runs several checks. The interesting ones are: + + * [Verify the proof has already been submitted](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L341-L347). + * [Verify the proof has been submitted long enough ago that the fault challenge period has already passed](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L357-L364). + * [Verify that the proof applies to the current output root for that block (the output root for a block can be changed by the fault challenge process)](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L366-L378). + * [Verify that the current output root for that block was proposed long enough ago that the fault challenge period has passed](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L380-L384). + * [Verify that the transaction has not been finalized before to prevent replay attacks](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L386-L390). + + If any of these checks fail, the transaction reverts. + +2. Mark the withdrawal as finalized in `finalizedWithdrawals`. + +3. Run the actual withdrawal transaction (call the `target` contract with the calldata in `data`). + +4. Emit a [`WithdrawalFinalized`](https://github.com/ethereum-optimism/optimism/blob/62c7f3b05a70027b30054d4c8974f44000606fb7/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol#L118) event. diff --git a/docs/public-docs/op-stack/fault-proofs/cannon.mdx b/docs/public-docs/op-stack/fault-proofs/cannon.mdx new file mode 100644 index 0000000000000..ac03c8edcd20c --- /dev/null +++ b/docs/public-docs/op-stack/fault-proofs/cannon.mdx @@ -0,0 +1,218 @@ +--- +title: 'Fault proof VM: Cannon' +description: Learn about Cannon and its default operation as part of Optimism's Fault Proof + Virtual Machine. +--- +Cannon is an instance of a Fault Proof Virtual Machine (FPVM) that can be used as part of the Dispute Game for any OP Stack Blockchain. +The Dispute Game itself is modular, allowing for any FPVM to be used in a dispute. +However, Cannon is Optimism's default Fault Proof Virtual Machine (FPVM). Cannon has two main components: + +* Onchain `MIPS.sol`: EVM implementation to verify execution of a single MIPS instruction. +* Offchain `mipsevm`: Go implementation to produce a proof for any MIPS instruction to verify onchain. + +Note that Cannon is just one example of an FPVM that can be used to resolve disputes. + +This documentation will go into detail about the subcomponents that make up the offchain Cannon implementation as a whole. +Additionally, we will explore the differences between Cannon and the onchain `MIPS.sol`. For more information about the onchain +implementation, please refer to [MIPS reference](/op-stack/fault-proofs/mips). +Now for simplicity, when referring to Cannon in this documentation, we are referring to the offchain implementation. + +## Control flow + +![Fault Proof Control Flow.](/public/img/op-stack/protocol/fault-proof-control-flow.svg) + +In the above diagram, recreated from the [OP Fault Proof System video](https://www.youtube.com/watch?v=nIN5sNc6nQM) by Clabby, we can see +that Cannon interacts with OP-Challenger and OP-Program. However, this diagram is a simplification of the relationship between +OP-Challenger \<> Cannon, and OP-Program \<> Cannon. In general, Cannon will not be run until an active fault dispute reaches the +execution trace portion of the bisection game. This does not occur until the participants in the active fault dispute game reach a +single L2 block state transition that they disagree on. + +### OP-Challenger \<> Cannon + +Once an active fault dispute game reaches a depth below attacking / defending L2 block state transitions, [OP-Challenger](/op-stack/fault-proofs/challenger) will run +Cannon to begin processing MIPS instructions within the FPVM. As part of processing MIPS instructions, Cannon will generate state +witness hashes, which are the commitment to the results of the MIPS instructions' computation within the FPVM. Now, in the bisection game, OP-Challenger will provide the generated hashes +until a single MIPS instruction is identified as the root disagreement between participants in the active dispute. Cannon will then +generate the witness proof, which contains all the information required to run the MIPS instruction onchain. Running this single MIPS +instruction onchain in `MIPS.sol` will be used to definitively prove the correct post state, which will then be used to resolve the fault +dispute game. + +### OP-Program \<> Cannon + +Once the execution trace bisection begins and Cannon is run, an Executable and Linkable Format (ELF) binary will be loaded and run within Cannon. +Within Cannon is the `mipsevm` that is built to handle the MIPS R3000, 32-bit Instruction Set Architecture (ISA). The ELF file contains MIPS instructions, +where the code that has been compiled into MIPS instructions is OP-Program. + +OP-Program is golang code that will be compiled into MIPS instructions and run within the Cannon FPVM. OP-Program, whether run as standalone +golang code or in Cannon, fetches all necessary data used for deriving the state of the L2. It is built such that the +same inputs will produce not only the same outputs, but the same execution trace. This allows all participants in a fault dispute game to run +OP-Program such that, given the same L2 output root state transition, they can generate the same execution traces. This in turn generates the same +witness proof for the exact same MIPS instruction that will be run onchain. + +## Overview of offchain Cannon components + +Now, we will go over each major component that makes up Cannon. Components are grouped by what functionality is being performed for +Cannon, and may be correlated to one or more Go files. For brevity, each Go file will be explained at a high level, with the +most important features / considerations highlighted. + +![Cannon Components Overview.](/public/img/op-stack/protocol/cannon-internal-overview.svg) + +### `mipsevm` state and memory + +As mentioned previously, the `mipsevm` is 32-bit, which means the full addressable address range is `[0, 2^32-1]`. The memory layout +uses the typical monolithic memory structure, and the VM operates as though it were interacting directly with physical memory. + +For the `mipsevm`, how memory is stored isn't important, as it can hold the entire monolithic memory within the Go runtime. +In this way, how memory is represented is abstracted away from the VM itself. However, it is important for memory to be represented +such that only small portions are needed in order to run a MIPS instruction onchain. This is because it is infeasible to represent the +entire 32-bit memory space onchain due to cost. Therefore, memory is stored in a binary Merkle tree data structure, with the implementation +spread across [`memory.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/mipsevm/memory/memory.go) and +[`page.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/mipsevm/memory/page.go). +The tree has a fixed-depth of 27 levels, with leaf values of 32 bytes each. This spans the full 32-bit address space: `2**27 * 32 = 2**32`. +Each leaf contains the memory for that part of the tree. + +`memory.go` defines the data structure, `Memory`, which holds pointers to `nodes` and `pages`. A memory `node` holds the calculated Merkle +root of its parent node within the memory binary Merkle tree, where the 'location' of the node is determined by its generalized index. +The index calculated for a Merkle root in the nodes mapping follows the +[generalized Merkle tree index specification](https://github.com/ethereum/consensus-specs/blob/dev/ssz/merkle-proofs.md#generalized-merkle-tree-index). + +`page.go`, as the name implies, defines memory pages. Each `Page` is 4096 bytes, which is also specified as the minimum page allocation +size for the Go runtime. A `Page` represents the lowest depths of the memory binary Merkle tree, and `page.go` performs a similar role +to `memory.go`, calculating Merkle roots for each level of the tree. + +Nodes in this memory tree are combined as: `out = keccak256(left ++ right)`, where `++` is concatenation, +and `left` and `right` are the 32-byte outputs of the respective subtrees or the leaf values themselves. +Individual leaf nodes are not hashed. + +In both `memory.go` and `page.go`, there are a few optimizations designed to reduce the computationally expensive Merkle root calculations. +One such optimization is that the Merkle root of zeroed-out regions of memory are calculated for each depth. This means the tree is efficiently allocated, +since the root of fully zeroed subtrees can be computed without actually creating the full-subtree: +`zero_hash[d] = hash(zero_hash[d-1], zero_hash[d-1])`, until the base-case of `zero_hash[0] == bytes32(0)`. +So, pre-calculating zeroed Merkle roots initially allows unused memory regions to be cached. Additionally, non-zero Merkle roots +are cached in both `memory.go` and `page.go`, and used so long as the memory region the Merkle root covers has not been written to. +Otherwise, the cache is invalidated, which requires the Merkle root to be calculated over its entire subtree. Another optimization +specifically in `memory.go` is caching the last two pages that have been used. Two pages are cached because the `mipsevm` uses up +to two memory addresses per instruction: one address is the `PC`, which contains the instruction to run, and the other address may be the +target of a load or store instruction. + +Another important implementation detail is that the endianness of the `mipsevm`, which is the ordering of bytes in a word, is Big-Endian. +The assumption of Cannon is that it is operating on a Little-Endian machine, and as such all reads / writes have the endianness swapped +so that the internal `mipsevm` always handles Big-Endian words and the machine running Cannon always handles Little-Endian words. +Endianness is also an important factor for the onchain `MIPS.sol`, where the EVM itself is Big-Endian. Therefore, `MIPS.sol` does not have to +do any endianness swapping and can assume all data uses Big-Endian ordering. This reduces complexity within the smart contract itself. + +The last major component is located in [`state.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/mipsevm/state.go). +The `State` struct in `state.go` holds all the execution state that is required for the `mipsevm`. +The information stored is largely identical to the [VM execution state](/concepts/architecture/fault-proofs/mips#packed-vm-execution-state) for `MIPS.sol`. The key differences are: + +* Instead of storing just the memory Merkle root, there is a `Memory` Struct pointer for the binary Merkle tree representation of the entire 32-bit memory space. +* There is an optional `LastHint` bytes variable, which can be used to communicate a Pre-image hint to avoid having to load in multiple prior Pre-images. + +#### Generating the witness proof + +Cannon handles two major components in the dispute game: generating state witness hashes for OP-Challenger to post during the execution trace +bisection game, and generating the witness proof once a single MIPS instruction is reached as the root of disagreement in the fault dispute game. +The witness proof, as mentioned previously, contains all the necessary information for `MIPS.sol` to be able to run the same instruction onchain, +and derive the post state that will be used to resolve the fault dispute game. The post state of the instruction run by `MIPS.sol` should be +exactly the same as the post state generated by the `mipsevm`. + +The top-level [`witness.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/cmd/witness.go) +in `cannon/cmd` initiates the witness proof generation. The internal +[`witness.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/mipsevm/witness.go) in `cannon/mipsevm` defines the +struct that holds all the relevant information for the particular MIPS instruction. The information that is encoded for the `MIPS.sol` +calldata can be seen in the [`MIPS.sol` documentation](/concepts/architecture/fault-proofs/mips#contract-state). + +Additionally, if a Pre-image is required for the MIPS +instruction, `witness.go` will communicate the relevant Pre-image key and offset to OP-Challenger so that it can be posted onchain +to `PreimageOracle.sol`. + +An important note about generating the witness proof: it is imperative that all relevant information about the instruction to be run +onchain is generated **before** the `mipsevm` execution state changes as a result of processing the MIPS instruction. Otherwise, if the +witness proof is generated after running the instruction offchain, the state that will be encoded will be the post state. + +### Loading the ELF file + +Once the execution trace portion of the bisection game begins, the ELF file containing OP-Program compiled into MIPS instructions will +be run within Cannon. However, getting OP-Program into Cannon so that it can be run requires +a binary loader. The binary loader is composed of [`load_elf.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/cmd/load_elf.go) +and [`patch.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/mipsevm/program/patch.go). `load_elf.go` parses +the top-level arguments and reads, loads, and patches the ELF binary such that it can be run by Cannon. + +`patch.go` is responsible for actually parsing the headers of the ELF file, which among other information specifies what programs +exist within the file and where they are located in memory. The loader uses this information to instantiate the execution state +for the `mipsevm` and load each program into memory at the expected location. Additionally, as part of instantiating the execution +state, `patch.go` sets the values of `PC`, `NextPC`, the initial Heap location of `0x20000000`, the initial stack location of `0x7fffd000`, +and also sets the arguments required for the Go runtime above the stack pointer location. + +While loading the ELF file into Cannon, [`metadata.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/mipsevm/program/metadata.go) +is used to parse all the symbols stored in the ELF file. Understanding which ELF symbols exist and at which regions of memory +they are located is important for other functionality, such as understanding if the current `PC` is running within a specific function. + +Another step that occurs while loading the ELF file is patching the binary of any incompatible functions. This step is crucial, as a key design decision +in both the onchain and offchain `mipsevm` implementations is that neither implementation +has access to a kernel. This is primarily due to the limitations within the EVM itself, and since the offchain Cannon +implementation must match functionality exactly with its onchain counterpart, kernel access is also not available within Cannon. This means that the VMs +cannot replicate behavior that would otherwise be performed by a kernel 1:1, which primarily impacts system calls (syscalls). +So features like concurrency, memory management, I/O, etc. are either partially implemented or unimplemented. For the +unimplemented functionality, any function within the ELF file that would require this functionality is patched out. +Patching the binary of these functions involves identifying problematic functions, searching for their corresponding symbols +within the ELF file, and effectively stubbing out the function by returning immediately, and adding a `nop` to the delay slot. + +### Instruction stepping + +Once the MIPS binary is loaded into Cannon, we can then begin to run MIPS instructions one at a time. +[`run.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/cmd/run.go) contains the top-level +code responsible for stepping through MIPS instructions. Additionally, before each MIPS instruction, `run.go` will determine whether +separate actions need to be performed. The actions to be performed are configured by the user, and can include logging information, +stopping at a certain instruction, taking a snapshot at a certain instruction, or signaling to the VM to generate the witness proof. +The action(s) to be performed are instantiated and checked by [`matcher.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/cmd/matcher.go), +which generates a match function that triggers when the configured regular expression is true. + +Within `run.go`, the `StepFn` is the wrapper that initiates the MIPS instruction to be run. +[`instrumented.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/mipsevm/singlethreaded/instrumented.go) implements `Step` as the +interface to be initiated for each MIPS instruction. Additionally, `instrumented.go` handles encoding information for the witness proof +and Pre-image information (if required for the MIPS instruction). + +### `mips.go` + +[`mips.go`](https://github.com/ethereum-optimism/optimism/blob/develop/cannon/mipsevm/multithreaded/mips.go) implements all the required MIPS +instructions, and also tracks additional memory access for the instruction currently being run. +This is important to make sure that the second memory proof is encoded correctly for instructions that use it, such as loads, stores, and +certain syscalls. The full list of instructions supported can be found [here](/concepts/architecture/fault-proofs/mips#table-of-supported-mips-instructions). + +#### `mips.go` vs. `MIPS.sol` + +The offchain `mips.go` and the onchain Cannon `MIPS.sol` behave similarly when it comes to executing MIPS32 R1 instructions. +In fact, they must produce **exactly the same results** given the same instruction, memory, and register state. +Consequently, the witness data is essential to reproduce the same instruction onchain and offchain. +However, there are differences between the two: + +1. A single instruction will be run onchain in `MIPS.sol`, whereas the offchain `mips.go` will run all MIPS instructions for all state transitions in the disputed L2 state. +2. The `mipsevm` contains the entire 32-bit monolithic memory space, is responsible for maintaining the memory state based on the results of MIPS instructions, and generates the memory binary Merkle tree, Merkle root, and memory Merkle proofs. `MIPS.sol` is mostly stateless, and does not maintain the full memory space. Instead, it only requires the memory Merkle root, and up to two memory Merkle proofs: 1 for the instruction and 1 for potential load, store, or certain syscall instructions. +3. Unlike `MIPS.sol`, `mips.go` is responsible for writing Pre-images to the PreimageOracle Server, and optionally writing hints to the Server. + +### PreimageOracle interaction + +As mentioned previously, Cannon is responsible for setting up all state that may be required to run an instruction in `MIPS.sol`. +Cannon is also responsible for interacting with the PreimageOracle Server, and directing OP-Challenger to provide Pre-images to +the onchain `PreimageOracle.sol` if necessary for the instruction that will be run in `MIPS.sol`. + +The PreimageOracle Server connection is instantiated in `run.go`, where the server itself is run locally with its own local Key-Value store. +`mips.go` communicates with the PreimageOracle Server when reading and writing Pre-images as part of MIPS syscall instructions, +as well as hinting to the PreimageOracle Server. + +The OP-stack fault-proof [Pre-image Oracle specs](https://specs.optimism.io/experimental/fault-proof/index.html?utm_source=op-docs&utm_medium=docs#pre-image-oracle) +define the ABI for communicating pre-images. + +This ABI is implemented by the VM by intercepting the `read`/`write` syscalls to specific file descriptors. See [Cannon VM Specs](https://specs.optimism.io/experimental/fault-proof/cannon-fault-proof-vm.html?utm_source=op-docs&utm_medium=docs#io) for more details. + +Note that although the oracle provides up to 32 bytes of the pre-image, +Cannon only supports reading at most 4 bytes at a time, to unify the memory operations with 32-bit load/stores. + +## Further Reading + +* [Cannon FPVM Specification](https://specs.optimism.io/experimental/fault-proof/cannon-fault-proof-vm.html?utm_source=op-docs&utm_medium=docs) +* [Merkle Proofs Specification](https://github.com/ethereum/consensus-specs/blob/dev/ssz/merkle-proofs.md) +* [Executable and Linkable Format (ELF)](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) +* [Keys in Mordor Summit: Cannon & Fault Proofs](https://www.youtube.com/watch?v=ASLMj70V0Ao) +* [MIPS IV ISA Specification](https://www.cs.cmu.edu/afs/cs/academic/class/15740-f97/public/doc/mips-isa.pdf) diff --git a/docs/public-docs/op-stack/fault-proofs/challenger.mdx b/docs/public-docs/op-stack/fault-proofs/challenger.mdx new file mode 100644 index 0000000000000..44e7907c5f660 --- /dev/null +++ b/docs/public-docs/op-stack/fault-proofs/challenger.mdx @@ -0,0 +1,118 @@ +--- +title: OP-Challenger explainer +description: Learn about OP-Challenger and how it operates within the OP Stack's Fault + Proof System. +--- +The `op-challenger` operates as the *honest actor* in the fault dispute system and defends the chain by securing the `OptimismPortal` and ensuring the game always resolves to the correct state of the chain. For verifying the legitimacy of claims, `op-challenger` relies on a synced, trusted rollup node as well as a trace provider (e.g., [Cannon](/op-stack/fault-proofs/cannon)). + +Specifically, `op-challenger` performs the following actions: + +* monitors and interacts with dispute games +* defends valid output root proposals +* challenges invalid output root proposals +* assists `op-proposer` by resolving claims and games once chess clocks expire +* claims paid out bonds for both the challenger and proposer + +## Architecture +This diagram illustrates how `op-challenger` monitors dispute games, defends valid proposals, and challenges invalid ones. It also shows its interaction with the `op-proposer` in resolving claims. + +```mermaid +graph TD; + OP[OP-Challenger] -->|Monitors| DG[Dispute Games] + OP -->|Defends| ORP[Valid Output Root Proposals] + OP -->|Challenges| IORP[Invalid Output Root Proposals] + OP -->|Assists| OPP[OP-Proposer] + OPP -->|Resolves Claims| DG + DG -->|Claims Paid| Bonds[Bonds for Challenger and Proposer] + + classDef default fill:#00,stroke:#FF0420,stroke-width:2px; +``` + + + The `cannon`, `op-program host`, and `kona-host` executables are run in the `op-challenger` docker container as sub-processes when required to generate game trace data. + + +## Fault detection responses + +`op-challenger` assesses each claim's validity, countering only those deemed invalid, following this logic: + +1. If the trusted node agrees with the output, `op-challenger` takes no action. An honest challenger does nothing because there are no claims it disagrees with. It continues to monitor the game in case someone posts a counter-claim to the valid root claim, in which case the challenger will participate in the game to defend the proposal. +2. If the trusted node disagrees, `op-challenger` posts a counter-claim to challenge the proposed output. In contrast to the above scenario, an honest challenger aims to delete any output roots that its trusted node disagrees with in order to claim the bond attached to it. The honest challenger assumes that their rollup node is synced to the canonical state and that the fault proof program is correct, so it is willing to put its money on the line to counter any faults. + +## Fault dispute game responses + +`op-challenger` iterates through claims as stored in the contract, ensuring ancestors are processed before their descendants. For each claim, the honest challenger determines and tracks the set of honest responses to all claims, regardless of whether that response already exists in the full game state. + +### Root claim + +The root claim is considered to be an honest claim if and only if it has a [state witness Hash](https://specs.optimism.io/fault-proof/stage-one/fault-dispute-game.html?utm_source=op-docs&utm_medium=docs#claims) that agrees with the honest challenger's state witness hash for the root claim. + +### Counter claims + +When a new claim is made in a dispute game, the honest challenger processes it and performs a response. The honest challenger should counter a claim if and only if: + +1. The claim is a child of a claim in the set of honest responses +2. The set of honest responses contains a sibling to the claim with a trace index greater than or equal to the claim's trace index + + + This implies the honest challenger never counters its own claim, since there is at most one honest counter to each claim, so an honest claim never has an honest sibling. + + +### Possible moves + +The challenger monitors each game as new claims are added and reacts according to the [honest actor algorithm](https://specs.optimism.io/fault-proof/stage-one/honest-challenger-fdg.html?utm_source=op-docs&utm_medium=docs) to defend valid proposals and invalidate invalid proposals. A **move** is a challenge against an existing claim and must include an alternate claim asserting a different trace (e.g., attack, defend, or step). + +
+ +Challenger Moves flow + + + See the [specs](https://specs.optimism.io/fault-proof/stage-one/honest-challenger-fdg.html) for the full scope of the honest actor algorithm. + + +## Resolution + +When one side of a `FaultDisputeGame`'s chess clock runs out, the honest challenger's responsibility is to resolve each claim in the game by calling the `resolveClaim` function on the `FaultDisputeGame` contract. Once the root claim's subgame is resolved, the challenger then finally calls the `resolve` function to resolve the entire game. + +The `FaultDisputeGame` does not put a time cap on resolution - because of the liveness assumption on honest challengers and the bonds attached to the claims they've countered, challengers are economically motivated to resolve the game quickly, thereby liquidating their funds and securing rewards. + +## Next steps + +* Ready to get started? Read our guide on how to [configure `op-challenger` on your OP Stack chain](/chain-operators/guides/configuration/op-challenger-config-guide). +* For more info about how `op-challenger` works under the hood, [check out the specs](https://specs.optimism.io/fault-proof/stage-one/honest-challenger-fdg.html?utm_source=op-docs&utm_medium=docs). + +## FAQs + +### If I don't have a blob archiver with access to historical data, can I lose the game? + +Most likely, yes. If nobody has access to the historical data. All the honest actors work together without needing to coordinate offense because they're all trying to play the same moves. So, if there's only one honest actor that lacks a blob archiver, and the actor gets pushed down to the bottom half of the game (\~ 32 claims deep in the game), then challenger would log errors and wouldn't be able to respond without the blob archiver. The actor would have 3.5 days on their side of the game to address the problem by switching to a beacon node that does have blobs, and could then proceed as usual. + +Note: An actor would only be pushed down to the bottom half of the game if the block being disputed is older than the blob retention period (\~18 days). If valid proposals are resolving regularly, this is not possible because each valid proposal becomes the starting point for newly created dispute games. So if there are regular valid proposals, then only \~3.5 days worth of blocks are normally being disputed, which is well within the retention period. + +### How many CPUs should I run for challenger to work efficiently? + +The default `--max-concurrency` setting suits most operators. Increase it if the challenger lags, or decrease it if it overloads with requests. + +### How much ETH do you need to challenge in a game? + +The honest challengers need to have more combined ETH than the attacker, or they may run out of funds and be unable to respond to games (requiring the security overrides to be used to protect funds). So, there's no strict amount challengers need to have, but here are some general guidelines chain operators can use to estimate: + +* Generally speaking, a minimum to play 1 game = bond amount \* game max depth + gas costs for each move +* To play one game all the way down to the final step call in a single "chain" costs just over 631.2 ETH in total, so about 315.6 ETH per "side". + +### What is the bond? What is the bond's role in the FP system? + +Each *claim* pays a bond, including the root claim created when the game is created (thus the bond is paid when the game is created). Every time a new claim posts to the game, an additional bond must be paid based on the depth of the claim being posted. It is not necessary to prepay bonds (i.e., its not like staking). Instead, the bond amount is sent as the value of the transaction when calling `attack` or `defend` on `FaultDisputeGame` or calling `create` on `DisputeGameFactory`. + +Claims that are found to be correct have their bonds refunded. Claims that are found to be incorrect have their bonds paid to the account that posted the left-most uncountered child claim of the incorrect claim. More importantly, the bond for invalid claims is paid to whoever successfully counters the claim, but its setup so that the bond is only ever paid to a single person and never shared. + +* There is a delay on claiming bonds of 7 days after the claim is resolved. +* The 7-day period restarts each time a new bond from that game is paid to the same user. Typically this means that bonds are claimable 7 days after the game is resolved. + +The calculation for the bond amounts are hard-coded in the [`FaultDisputeGame` contract](https://github.com/ethereum-optimism/optimism/blob/547ea72d9849e13ce169fd31df0f9197651b3f86/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol#L99-L100), and there's a `getRequiredBond` method on the contract that suggests what bond to use. + +### How much ETH is required for the challenger bond? + +The dispute game factory has the bond value set. To ensure correct game outcomes, the combined funding of all honest actors must be more than the funding available to an attacker. Otherwise the attacker can just post so many claims that the honest actors run out of funds and can no longer counter them. There isn't a fixed amount that guarantees this, so chain operators should have significant funds available at short notice to respond to claims. + +Given the guardian can intervene and reallocate bond payments if needed, attackers who try to outspend the honest actors are guaranteed to lose their funds because the guardian will just intervene and pay all their bonds to the honest actors which is a very strong disincentive against trying to win games by outspending people. diff --git a/docs/public-docs/op-stack/fault-proofs/explainer.mdx b/docs/public-docs/op-stack/fault-proofs/explainer.mdx new file mode 100644 index 0000000000000..4fe80f4d4ab8d --- /dev/null +++ b/docs/public-docs/op-stack/fault-proofs/explainer.mdx @@ -0,0 +1,102 @@ +--- +title: Fault proofs explainer +description: Learn about the OP Stack's Fault Proof System. +--- + +Fault Proofs are an important part of an Optimistic Rollup system. +Users withdraw ETH and tokens from OP Stack chains like OP Mainnet by submitting a withdrawal proof that shows the withdrawal was actually included in the OP Stack chain. +Fault Proofs allow users to permissionlessly submit and challenge the proposals about the state of an OP Stack chain that are used to prove withdrawals. + +On June 10, 2024, Fault Proofs were officially added to the OP Stack and were activated on OP Mainnet. +This Fault Proofs upgrade moves the OP Stack closer to technical decentralization by: +* allowing anyone to make proposals about the state of the L2 +* allowing anyone to challenge proposals made by other users +* allowing users to send messages from L2 to L1 without the need for a trusted third party +* allowing users to trigger withdrawals from L2 to L1 without the need for a trusted third party +* introducing a modular fault proof design that can easily integrate additional proving mechanisms + +Although the fault proof game is permissionless, the Optimism Security Council acting as the Guardian role provides a backstop in case of a failure in the fault proof game. +Each proposal must wait for a delay period during which the Guardian can prevent invalid proposals from being used to withdraw ETH or tokens through a number of safety hatches. +The Guardian can also choose to shift the system to use a PermissionedDisputeGame, in which only specific `PROPOSER` and `CHALLENGER` roles can submit and challenge proposals. + +## Permissionless proposals + +"Proposals" or "State Proposals" are claims about the state of an OP Stack chain that are submitted to Ethereum through the `DisputeGameFactory` contract. +Proposals can be used for many things but are most commonly used by end-users to prove that they created a withdrawal on an OP Stack chain. +With the Fault Proofs upgrade to the OP Stack, proposals become permissionless and can be submitted by anyone. + +See the permissionless fault proofs diagram below for more details: + +Permissionless Fault Proofs flow + +## Permissionless challenges + +Because anyone can submit a proposal, it's important that invalid proposals can be challenged. +In [Optimistic Rollups like OP Stack Chains](/op-stack/protocol/overview) there is a \~1 week challenge period during which users can challenge a proposal if they believe it to be incorrect. +With the Fault Proofs upgrade to the OP Stack, challenges become permissionless and can be submitted by anyone. +Any user can run a node for the OP Stack chain in question and use the `op-challenger` tool to participate in the dispute process. + +## Modular design and multi-layer security + +The OP Stack Fault Proof System is [modular in design](/op-stack/fault-proofs/fp-components#system-design--modularity) and lays the groundwork for achieving a "multi-proof" system. This allows the OP Stack to support multiple proof systems alongside the initial [Cannon](/op-stack/fault-proofs/cannon) proof system. +With multiple proof systems in place, the OP Stack can be more resilient to potential attacks and bugs in any one proof system. + +Additionally, the following [security safeguards](/op-stack/fault-proofs/fp-security) have been built around the game, as follows: + +* An off chain monitoring system has been set up to monitor all proposed roots and ensure they align with the correct state. See [`op-dispute-mon`](https://github.com/ethereum-optimism/optimism/blob/develop/op-dispute-mon/README.md?plain=1) for more details. +* After a root is finalized through a game, an additional delay called the "airgap window" has been added before withdrawals can occur. During this period, the `GUARDIAN` role can reject the root. +* A contract called `DelayedWETH` has been set up to hold the bonds and only allow payouts after a delay, so that bonds can be redirected towards the correct recipient in the case that a game resolves incorrectly. + +## Next steps + +* Ready to get started? Review the [FP Components](/op-stack/fault-proofs/fp-components) to learn how the different components work together to enhance decentralization in the Optimism ecosystem. +* See the [Fault Proof Security](/op-stack/fault-proofs/fp-security) to understand changes to `OptimismPortal` and `FaultDisputeGame` contracts. +* For more info about how the FP system works under the hood, [check out the specs](https://specs.optimism.io/fault-proof/index.html?utm_source=op-docs&utm_medium=docs). + +## FAQs + +### How many steps/transactions are required to settle a dispute (worst-case scenario)? + +The maximum depth of a game is 73, but there can be any number of claims and counter-claims within a dispute game. +Due to the permissionless structure where many different actors can participate in the same game, a single claim may be countered by any number of different counter-claims, effectively combining multiple disputes into a single game. + +### Are there any dependencies to consider when proposing a new state root (in the event of sequencer and proposer failure)? + +Users can complete the full withdrawal cycle without depending on any privileged action. +The Guardian role can override the system by pausing withdrawals, blacklisting games, or reverting to a permissioned system. +As a result, the trust assumption is reduced to requiring only that the Guardian role does not act to intervene, inline with the stage 1 requirements. + +### Since the roles of proposer and challenger will be open to everyone, are guides available outlining the best practices for running them? + +It's not expected that normal users run `op-proposer` to regularly propose output roots. +Users would generally just propose a single output root if they need to withdraw and the chain operator isn't proposing outputs for them via direct calls to the `DisputeGameFactory` via Etherscan. + + +The [`create-game`](https://github.com/ethereum-optimism/optimism/tree/develop/op-challenger#create-game) subcommand of `op-challenger` is **for testing purposes only** and should not be used in production environments. It is not intended as a replacement for proper `op-proposer` infrastructure. + + +For detailed guidance on running `op-challenger`, see the [OP-Challenger explainer](/op-stack/fault-proofs/challenger) and [how to configure challenger for your chain](/chain-operators/guides/configuration/op-challenger-config-guide). + +### How much ETH should a chain operator put aside to operate the Fault Proof System? + +The nominal operating cost of running FPs (i.e., assuming no invalid proposals or malicious actors) will depend on the initial bond set for the `FaultDisputeGame` and the frequency of posting proposals. +Assuming OP Mainnet parameters, where proposals will be posted hourly, that's 0.08 ETH per hour. +Assuming a 7 day dispute window, you'll need roughly 14 ETH (including gas costs) to make proposals. +If chains are using the similar FP deploy configs as OP Mainnet, it's recommended to stick to a 0.08 ETH initial bond. + +However, the capital requirements for operating a FP chain in itself are much larger than 14 ETH. +An operator that secures their chain using FPs must be willing to stake a lot of ETH to secure the chain. +One may decide the capital requirements aren't worth it, and use only a Permissioned FP system. +The capital requirements will be improved in the later stages of Fault Proofs to make it more feasible for smaller chains. + +### How large are the bonds expected to be needed to sustain and win a dispute? + +The bonds are sized based on the anticipated cost to post a counter-claim as well as to deter spamming invalid claims. +As an example, on OP Sepolia, the game [`0xcf8f181497DAD07277781517A76cb131C54A1BEE`](https://sepolia.etherscan.io/address/0xcf8f181497DAD07277781517A76cb131C54A1BEE) shows the escalating bond sizes. +The list-claims subcommand of op-challenger can also provide a good view of the claims in the game: + +``` +./op-challenger/bin/op-challenger list-claims --l1-eth-rpc --game-address 0xcf8f181497DAD07277781517A76cb131C54A1BEE +``` + +See the [specs](https://specs.optimism.io/experimental/fault-proof/stage-one/bond-incentives.html?utm_source=op-docs&utm_medium=docs) for more details. diff --git a/docs/public-docs/op-stack/fault-proofs/fp-components.mdx b/docs/public-docs/op-stack/fault-proofs/fp-components.mdx new file mode 100644 index 0000000000000..0661c55b5a2ae --- /dev/null +++ b/docs/public-docs/op-stack/fault-proofs/fp-components.mdx @@ -0,0 +1,78 @@ +--- +title: FP system components +description: Learn about Fault Proof System components and how they work together to + enhance decentralization in the Optimism ecosystem. +--- +This page explains the fault proof system components and how they work together to enhance decentralization in the Optimism ecosystem. + +The Fault Proof System is comprised of three main components: a Fault Proof Program (FPP), a Fault Proof Virtual Machine (FPVM), and a dispute game protocol. +The system is designed to eventually enable secure bridging without central fallback. +The modular design of the Fault Proof System lays the foundation for a multi-proof future, inclusive of ZK proofs, and significantly increases the opportunities for ecosystem contributors to build alternative fault proof components to secure the system. + + + Visit the [Immunefi bug bounty page](https://immunefi.com/bounty/optimism/) for details on testing and helping to build a robust fault proof system. + + +## System design & modularity + +The Fault Proof System is comprised of three main components: a Fault Proof Program (FPP), a Fault Proof Virtual Machine (FPVM), and a dispute game protocol. +These components will work together to challenge malicious or faulty activity on the network to preserve trust and consistency within the system. +See the video below for a full technical walkthrough of the OP Stack's first fault proof system. + +