diff --git a/.circleci/Untitled b/.circleci/Untitled new file mode 100644 index 00000000000..30eb64ea43c --- /dev/null +++ b/.circleci/Untitled @@ -0,0 +1 @@ +c-contracts_changed \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index b3accd39cfe..63fbf044458 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2733,10 +2733,25 @@ workflows: .* c-devnet-metrics-collect << pipeline.parameters.devnet-metrics-collect >> .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 - - rust/.* rust_e2e_dispatch << pipeline.parameters.rust_e2e_dispatch >> .circleci/continue/rust-e2e.yml - rust/.* default_docker_image << pipeline.parameters.default_docker_image >> .circleci/continue/rust-e2e.yml - rust/.* go-cache-version << pipeline.parameters.go-cache-version >> .circleci/continue/rust-e2e.yml + (packages/contracts-bedrock|\.circleci|\.github|ops/check-changed)/.* c-contracts_changed true .circleci/continue/main.yml + ^(package\.json|mise\.toml)$ c-contracts_changed true .circleci/continue/main.yml + + # Docs CI — trigger on docs/public-docs/ changes + .* c-default_docker_image << pipeline.parameters.default_docker_image >> .circleci/continue/docs-ci.yml + docs/public-docs/.* c-docs_changes_detected true .circleci/continue/docs-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 + + # 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 setup-tag: when: diff --git a/.circleci/continue/docs-ci.yml b/.circleci/continue/docs-ci.yml new file mode 100644 index 00000000000..d31d8d80709 --- /dev/null +++ b/.circleci/continue/docs-ci.yml @@ -0,0 +1,124 @@ +version: 2.1 + +# Docs CI Continuation Configuration +# This file validates the Mintlify documentation build when docs/public-docs/ changes. + +parameters: + c-default_docker_image: + type: string + default: cimg/base:2026.03 + c-docs_changes_detected: + 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 + publish_contract_artifacts_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 + l2_fork_test_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__" + go-cache-version: + type: string + default: "v0.0" + +# ============================================================================ +# JOBS +# ============================================================================ +jobs: + mintlify-docs-build: + docker: + - image: <> + resource_class: xlarge + steps: + - checkout + - run: + name: Install Node.js and pnpm + command: | + curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - + sudo apt-get install -y nodejs + sudo npm install -g pnpm + - run: + name: Install dependencies + working_directory: docs/public-docs + command: | + pnpm install + - run: + name: Validate Mintlify documentation + working_directory: docs/public-docs + no_output_timeout: 30m + command: | + npx mintlify validate + echo "Mintlify Validation Complete" + +# ============================================================================ +# WORKFLOWS +# ============================================================================ +workflows: + docs-ci: + when: + and: + - equal: ["", << pipeline.git.tag >>] + - equal: ["webhook", << pipeline.trigger_source >>] + - << pipeline.parameters.c-docs_changes_detected >> + jobs: + - mintlify-docs-build diff --git a/.circleci/continue/main.yml b/.circleci/continue/main.yml index 848080a40b8..c26b4a33e9f 100644 --- a/.circleci/continue/main.yml +++ b/.circleci/continue/main.yml @@ -65,11 +65,6 @@ parameters: 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 @@ -442,7 +437,6 @@ 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 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" @@ -757,7 +751,8 @@ jobs: - "<< parameters.directory >>/target/<< parameters.profile >>/rollup-boost" # Build a single Rust binary from a submodule. - rust-build-submodule: + # Build a single Rust binary from a vendored (non-submodule) directory. + rust-build-vendored: docker: - image: <> resource_class: xlarge @@ -779,20 +774,11 @@ jobs: - utils/checkout-with-mise: checkout-method: blobless - run: - name: Get << parameters.directory >> submodule HASH + name: Get << parameters.directory >> content HASH command: | mkdir -p .circleci-cache - # Verify the path is a git submodule (gitlink). For submodules, ls-tree prints: - # 160000 commit - MODE_AND_TYPE="$(git ls-tree HEAD << parameters.directory >> | awk '{print $1 " " $2}')" - if [ "$MODE_AND_TYPE" != "160000 commit" ]; then - echo "ERROR: << parameters.directory >> is not a submodule path (expected '160000 commit', got '$MODE_AND_TYPE')" >&2 - git ls-tree HEAD << parameters.directory >> >&2 || true - exit 1 - fi - - git ls-tree HEAD << parameters.directory >> | awk '{print $3}' > .circleci-cache/expected-submod-sha.txt - echo "Expected submodule HASH: $(cat .circleci-cache/expected-submod-sha.txt)" + git ls-tree -r HEAD << parameters.directory >> | sha256sum | awk '{print $1}' > .circleci-cache/expected-submod-sha.txt + echo "Content HASH: $(cat .circleci-cache/expected-submod-sha.txt)" echo "<< parameters.binaries >>" | tr ' ' '\n' | awk 'NF' | sort -u > .circleci-cache/expected-binaries.txt echo "Expected binaries: $(tr '\n' ' ' < .circleci-cache/expected-binaries.txt)" - restore_cache: @@ -800,7 +786,7 @@ jobs: keys: - rust-<< parameters.directory >>-v8-{{ checksum ".circleci-cache/expected-submod-sha.txt" }}-{{ checksum ".circleci-cache/expected-binaries.txt" }} - run: - name: Build << parameters.directory >> submodule (if needed) + name: Build << parameters.directory >> (if needed) command: | ROOT_DIR="$(pwd)" BIN_CACHE_DIR="$ROOT_DIR/.circleci-cache/rust-binaries" @@ -818,9 +804,6 @@ jobs: fi echo "Cache miss - will build" - # Only run on cache miss. We intentionally do not cache into the submodule directory so that cache - # restore cannot make the submodule path non-empty (which breaks `git submodule update --init`). - git submodule update --init --recursive --depth 1 -j 8 --single-branch << parameters.directory >> if [ "<< parameters.needs_clang >>" = "true" ]; then sudo apt-get update @@ -851,6 +834,99 @@ jobs: paths: - ".circleci-cache/rust-binaries" + # Build a single Rust binary from a vendored (non-submodule) directory. + rust-build-vendored: + docker: + - image: <> + resource_class: xlarge + parameters: + directory: + description: "Directory containing the Cargo workspace" + type: string + binaries: + description: "Space-separated list of binary names to copy from target/release into the cache" + type: string + build_command: + description: "Cargo build command to run" + type: string + needs_clang: + description: "Whether to install clang (needed by bindgen for reth-mdbx-sys)" + type: boolean + default: false + steps: + - utils/checkout-with-mise: + checkout-method: blobless + - run: + name: Get << parameters.directory >> content HASH + command: | + mkdir -p .circleci-cache + git ls-tree -r HEAD << parameters.directory >> | sha256sum | awk '{print $1}' > .circleci-cache/expected-submod-sha.txt + echo "Content HASH: $(cat .circleci-cache/expected-submod-sha.txt)" + echo "<< parameters.binaries >>" | tr ' ' '\n' | awk 'NF' | sort -u > .circleci-cache/expected-binaries.txt + echo "Expected binaries: $(tr '\n' ' ' < .circleci-cache/expected-binaries.txt)" + - restore_cache: + name: Restore << parameters.directory >> cache + keys: + - rust-<< parameters.directory >>-v8-{{ checksum ".circleci-cache/expected-submod-sha.txt" }}-{{ checksum ".circleci-cache/expected-binaries.txt" }} + - run: + name: Build << parameters.directory >> (if needed) + command: | + ROOT_DIR="$(pwd)" + BIN_CACHE_DIR="$ROOT_DIR/.circleci-cache/rust-binaries" + + HIT=true + for bin in << parameters.binaries >>; do + if [ ! -f "$BIN_CACHE_DIR/$bin" ]; then + HIT=false + break + fi + done + if [ "$HIT" = "true" ]; then + echo "Cache hit - binaries exist" + exit 0 + fi + + echo "Cache miss - will build" + + if [ "<< parameters.needs_clang >>" = "true" ] && ! command -v clang >/dev/null; then + for i in 1 2 3; do + if sudo apt-get -o Acquire::Retries=5 update; then + break + fi + if [ "$i" = "3" ]; then + echo "apt-get update failed after 3 attempts" >&2 + exit 1 + fi + echo "apt-get update failed (attempt $i), retrying in 10s..." >&2 + sleep 10 + done + sudo apt-get install -y --no-install-recommends clang + fi + + cd << parameters.directory >> && << parameters.build_command >> + + mkdir -p "$BIN_CACHE_DIR" + for bin in << parameters.binaries >>; do + SRC="$ROOT_DIR/<< parameters.directory >>/target/release/$bin" + DST="$BIN_CACHE_DIR/$bin" + if [ ! -f "$SRC" ]; then + echo "ERROR: expected built binary not found at $SRC" >&2 + exit 1 + fi + cp "$SRC" "$DST" + chmod +x "$DST" || true + done + no_output_timeout: 30m + - save_cache: + key: rust-<< parameters.directory >>-v8-{{ checksum ".circleci-cache/expected-submod-sha.txt" }}-{{ checksum ".circleci-cache/expected-binaries.txt" }} + name: Save << parameters.directory >> cache + paths: + - ".circleci-cache/rust-binaries" + - persist_to_workspace: + root: . + paths: + - ".circleci-cache/rust-binaries" + initialize: docker: - image: <> @@ -907,12 +983,8 @@ jobs: TIMEOUT="45m" fi ../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 ./... + -- -timeout=$TIMEOUT -parallel=$(nproc) ./... working_directory: cannon - - codecov/upload: - disable_search: true - files: ./cannon/coverage-64.out - flags: cannon-go-tests-64 - store_test_results: path: ./tmp/test-results - store_artifacts: @@ -992,7 +1064,6 @@ jobs: mentions: "@security-oncall" contracts-bedrock-tests: - circleci_ip_ranges: true docker: - image: <> resource_class: 2xlarge @@ -1081,7 +1152,6 @@ jobs: mentions: "@security-oncall" contracts-bedrock-heavy-fuzz-nightly: - circleci_ip_ranges: true docker: - image: <> resource_class: 2xlarge @@ -1139,7 +1209,6 @@ jobs: # AI Contracts Test Maintenance System # Runbook: https://github.com/ethereum-optimism/optimism/blob/develop/ops/ai-eng/contracts-test-maintenance/docs/runbook.md ai-contracts-test: - circleci_ip_ranges: true docker: - image: <> resource_class: medium @@ -1171,7 +1240,6 @@ jobs: mentions: "@security-oncall" contracts-bedrock-coverage: - circleci_ip_ranges: true docker: - image: <> resource_class: 2xlarge @@ -1184,6 +1252,10 @@ jobs: description: Profile to use for testing type: string default: ci + l1_archive_rpc_url_env_var: + description: Mainnet L1 archive RPC URL environment variable + type: env_var_name + default: OP_CI_MAINNET_L1_ARCHIVE_RPC_URL features: description: Comma-separated list of features to enable (e.g., "OPTIMISM_PORTAL_INTEROP", "CUSTOM_GAS_TOKEN") type: string @@ -1209,13 +1281,15 @@ jobs: command: | sudo apt-get update sudo apt-get install -y lcov + - run: + name: Resolve L1 archive RPC URL + command: | + printf 'export ETH_RPC_URL="${%s:?%s must be set}"\n' "<>" "<>" >> "$BASH_ENV" - run: name: Write pinned block number for cache key command: | just print-pinned-block-number > ./pinnedBlockNumber.txt cat pinnedBlockNumber.txt - environment: - ETH_RPC_URL: https://ci-mainnet-l1-archive.optimism.io working_directory: packages/contracts-bedrock - restore_cache: name: Restore forked state @@ -1235,7 +1309,6 @@ jobs: command: just coverage-lcov-all environment: FOUNDRY_PROFILE: <> - ETH_RPC_URL: https://ci-mainnet-l1-archive.optimism.io working_directory: packages/contracts-bedrock no_output_timeout: <> - run: @@ -1243,7 +1316,6 @@ jobs: command: just test-rerun | tee failed-test-traces.log environment: FOUNDRY_PROFILE: <> - ETH_RPC_URL: https://ci-mainnet-l1-archive.optimism.io working_directory: packages/contracts-bedrock when: on_fail - codecov/upload: @@ -1263,7 +1335,6 @@ jobs: mentions: "@security-oncall" contracts-bedrock-tests-upgrade: - circleci_ip_ranges: true parameters: fork_op_chain: description: Fork OP Chain @@ -1273,9 +1344,10 @@ jobs: description: Fork Base Chain type: string default: "mainnet" - fork_base_rpc: - description: Fork Base RPC - type: string + fork_base_archive_rpc_env_var: + description: Fork base archive RPC URL environment variable + type: env_var_name + default: OP_CI_MAINNET_L1_ARCHIVE_RPC_URL test_profile: description: Profile to use for testing type: string @@ -1302,6 +1374,10 @@ jobs: name: Print forge version command: forge --version working_directory: packages/contracts-bedrock + - run: + name: Resolve fork base archive RPC URL + command: | + printf 'export ETH_RPC_URL="${%s:?%s must be set}"\n' "<>" "<>" >> "$BASH_ENV" - run: name: Write pinned block number for cache key command: | @@ -1309,7 +1385,6 @@ jobs: cat pinnedBlockNumber.txt environment: FORK_BASE_CHAIN: <> - ETH_RPC_URL: <> working_directory: packages/contracts-bedrock - restore_cache: name: Restore forked state @@ -1326,20 +1401,17 @@ jobs: FOUNDRY_FUZZ_SEED: 42424242 FOUNDRY_FUZZ_RUNS: 1 FOUNDRY_PROFILE: <> - ETH_RPC_URL: <> FORK_OP_CHAIN: <> FORK_BASE_CHAIN: <> working_directory: packages/contracts-bedrock no_output_timeout: 15m - run: name: Print failed test traces - command: | - just test-upgrade-rerun | tee failed-test-traces.log + command: just test-upgrade-rerun | tee failed-test-traces.log environment: FOUNDRY_FUZZ_SEED: 42424242 FOUNDRY_FUZZ_RUNS: 1 FOUNDRY_PROFILE: <> - ETH_RPC_URL: <> FORK_OP_CHAIN: <> FORK_BASE_CHAIN: <> working_directory: packages/contracts-bedrock @@ -1369,6 +1441,137 @@ jobs: when: always - notify-failures-on-develop: mentions: "@security-oncall" + contracts-bedrock-tests-l2-fork: + parameters: + fork_op_chain: + description: L2 chain to fork for testing + type: string + default: "op-mainnet" + l2_fork_rpc: + description: RPC URL for L2 fork. If empty, derived from fork_op_chain. + type: string + default: "" + op_mainnet_l2_rpc_url_env_var: + description: OP Mainnet L2 RPC URL environment variable + type: env_var_name + default: OP_CI_MAINNET_L2_RPC_URL + op_sepolia_l2_rpc_url_env_var: + description: OP Sepolia L2 RPC URL environment variable + type: env_var_name + default: OP_CI_SEPOLIA_L2_RPC_URL + l2_fork_block_number: + description: Block number to fork from + type: string + default: "latest" + test_profile: + description: Profile to use for testing + type: string + default: ci + features: + description: Comma-separated list of features to enable (e.g., "L2CM") + type: string + default: "L2CM" + docker: + - image: <> + resource_class: 2xlarge + steps: + - utils/checkout-with-mise: + enable-mise-cache: true + - install-contracts-dependencies + - check-changed: + patterns: contracts-bedrock + - install-solc-compilers + - run: + name: Print dependencies + command: just dep-status + working_directory: packages/contracts-bedrock + - run: + name: Print forge version + command: forge --version + working_directory: packages/contracts-bedrock + - run: + name: Resolve RPC URL + command: | + RPC="<>" + RPC_ENV_VAR="" + if [ -z "$RPC" ]; then + case "<>" in + op-mainnet) + RPC_ENV_VAR="<>" + ;; + op-sepolia) + RPC_ENV_VAR="<>" + ;; + esac + fi + if [ -n "$RPC_ENV_VAR" ]; then + printf 'export L2_FORK_RPC_URL="${%s:?%s must be set}"\n' "$RPC_ENV_VAR" "$RPC_ENV_VAR" >> "$BASH_ENV" + elif [ -z "$RPC" ]; then + RPC=$(jq -r '.["<>"] // empty' .circleci/l2-rpcs.json) + [ -z "$RPC" ] && echo "Unknown chain '<>' — add it to .circleci/l2-rpcs.json" && exit 1 + printf 'export L2_FORK_RPC_URL=%q\n' "$RPC" >> "$BASH_ENV" + else + printf 'export L2_FORK_RPC_URL=%q\n' "$RPC" >> "$BASH_ENV" + fi + - run: + name: Get latest block number if not specified + command: | + if [ "<>" = "latest" ]; then + BLOCK_NUMBER=$(cast block-number --rpc-url "$L2_FORK_RPC_URL") + echo "export L2_FORK_BLOCK_NUMBER=$BLOCK_NUMBER" >> $BASH_ENV + echo "Using latest block number: $BLOCK_NUMBER" + echo "$BLOCK_NUMBER" > ./l2ForkBlockNumber.txt + else + echo "export L2_FORK_BLOCK_NUMBER=<>" >> $BASH_ENV + echo "Using specified block number: <>" + echo "<>" > ./l2ForkBlockNumber.txt + fi + working_directory: packages/contracts-bedrock + - restore_cache: + name: Restore forked L2 state + key: forked-state-l2-<>-{{ checksum "packages/contracts-bedrock/l2ForkBlockNumber.txt" }} + - setup-features: + features: <> + - run: + name: Check NUT bundle is up-to-date + command: just nut-bundle-check-no-build + working_directory: packages/contracts-bedrock + - run: + name: Run L2 fork tests + command: | + mkdir -p results + JUNIT_TEST_PATH=results/results.xml just test-l2-fork-upgrade + environment: + FOUNDRY_PROFILE: <> + working_directory: packages/contracts-bedrock + no_output_timeout: 20m + - run: + name: Print failed test traces + command: | + just test-l2-fork-upgrade-rerun | tee failed-test-traces-l2-fork.log + environment: + FOUNDRY_PROFILE: <> + working_directory: packages/contracts-bedrock + when: on_fail + - save_cache: + name: Save Go build cache + key: golang-build-cache-contracts-bedrock-tests-{{ checksum "go.sum" }} + paths: + - "~/.cache/go-build" + - save_cache: + name: Save forked L2 state + key: forked-state-l2-<>-{{ checksum "packages/contracts-bedrock/l2ForkBlockNumber.txt" }} + when: always + paths: + - "~/.foundry/cache" + - store_artifacts: + path: packages/contracts-bedrock/failed-test-traces-l2-fork.log + when: on_fail + - store_test_results: + path: packages/contracts-bedrock/results + when: always + - notify-failures-on-develop: + mentions: "@security-oncall" contracts-bedrock-upload: machine: true @@ -1569,7 +1772,6 @@ jobs: docker: - image: <> resource_class: 2xlarge - circleci_ip_ranges: true parallelism: <> steps: - utils/checkout-with-mise: @@ -1666,9 +1868,6 @@ jobs: <> 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: @@ -1686,12 +1885,8 @@ jobs: op-acceptance-tests: parameters: - gate: - description: The gate to run. Reads package list from op-acceptance-tests/gates/.txt. If empty, runs all tests. - type: string - default: "" l2_cl_kind: - description: "L2 consensus layer client (op-node or kona)" + description: "L2 consensus layer client (op-node or kona-node)" type: string default: "op-node" l2_el_kind: @@ -1704,7 +1899,6 @@ jobs: default: 30m docker: - image: <> - circleci_ip_ranges: true resource_class: 2xlarge+ steps: - utils/checkout-with-mise: @@ -1719,7 +1913,6 @@ jobs: 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 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: @@ -1743,7 +1936,7 @@ jobs: working_directory: op-acceptance-tests no_output_timeout: <> command: | - LOG_LEVEL=info just acceptance-test "<>" + LOG_LEVEL=info just acceptance-test - run: name: Print results (summary) working_directory: op-acceptance-tests @@ -1820,23 +2013,18 @@ jobs: - utils/checkout-with-mise: checkout-method: blobless enable-mise-cache: true + - restore_cache: + name: Restore kona cache + key: kona-prestate-{{ checksum "./rust/justfile" }}-{{ checksum "./rust/kona/docker/fpvm-prestates/cannon-repro.dockerfile" }} - run: - name: Build prestates - 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 + name: Build kona prestates + command: just build-kona-prestates + working_directory: rust + - save_cache: + key: kona-prestate-{{ checksum "./rust/justfile" }}-{{ checksum "./rust/kona/docker/fpvm-prestates/cannon-repro.dockerfile" }} + name: Save Kona to cache + paths: + - "rust/kona/prestate-artifacts-*/" - persist_to_workspace: root: . paths: @@ -2299,7 +2487,6 @@ workflows: - or: - and: - equal: ["webhook", << pipeline.trigger_source >>] - - << pipeline.parameters.c-non_docs_changes >> - and: - equal: [true, <>] - equal: ["api", << pipeline.trigger_source >>] @@ -2531,6 +2718,7 @@ workflows: - go-binaries-for-sysgo context: - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets filters: branches: ignore: develop # Run on all branches EXCEPT develop (PR branches only) @@ -2550,6 +2738,7 @@ workflows: - go-binaries-for-sysgo context: - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets - slack - analyze-op-program-client: context: @@ -2603,6 +2792,8 @@ workflows: ignore-dirs: | ./packages/contracts-bedrock/lib ./docs/public-docs + ./rust/op-rbuilder + ./rust/rollup-boost context: - circleci-repo-readonly-authenticated-github-token # Acceptance test jobs (formerly in separate acceptance-tests workflow) @@ -2615,21 +2806,23 @@ workflows: rust_files_changed: << pipeline.parameters.c-rust_files_changed >> context: - circleci-repo-readonly-authenticated-github-token - - rust-build-submodule: &rust-build-op-rbuilder + - rust-build-vendored: &rust-build-op-rbuilder name: rust-build-op-rbuilder - directory: op-rbuilder + directory: rust/op-rbuilder binaries: "op-rbuilder" build_command: cargo build --release -p op-rbuilder --bin op-rbuilder needs_clang: true context: - circleci-repo-readonly-authenticated-github-token - - rust-build-submodule: &rust-build-rollup-boost + - rust-build-vendored: &rust-build-rollup-boost name: rust-build-rollup-boost - directory: rollup-boost + directory: rust/rollup-boost binaries: "rollup-boost" build_command: cargo build --release -p rollup-boost --bin rollup-boost context: - circleci-repo-readonly-authenticated-github-token + # Lint runs in `rust-ci.yml` as `op-rbuilder-checks` / `rollup-boost-checks` + # via each crate's `make lint`. - rust-binaries-for-sysgo: requires: - rust-workspace-binaries @@ -2651,10 +2844,9 @@ workflows: - cannon-prestate - rust-binaries-for-sysgo - go-binaries-for-sysgo - # IN-MEMORY (base gate) - op-node/op-reth + # IN-MEMORY - op-node/op-reth - op-acceptance-tests: name: memory-all-opn-op-reth - gate: "base" l2_el_kind: op-reth no_output_timeout: 120m context: @@ -2666,11 +2858,10 @@ workflows: - cannon-prestate - rust-binaries-for-sysgo - go-binaries-for-sysgo - # IN-MEMORY (base gate) - kona/op-reth + # IN-MEMORY - kona/op-reth - op-acceptance-tests: name: memory-all-kona-op-reth - gate: "base" - l2_cl_kind: kona + l2_cl_kind: kona-node l2_el_kind: op-reth no_output_timeout: 120m context: @@ -2834,6 +3025,7 @@ workflows: context: - slack - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets requires: - contracts-bedrock-build - cannon-prestate @@ -2957,4 +3149,252 @@ workflows: - devin-api - slack + l2-fork-test-workflow: + when: + and: + - equal: [true, << pipeline.parameters.c-l2_fork_test_dispatch >>] + - equal: ["api", << pipeline.trigger_source >>] + jobs: + - contracts-bedrock-tests-l2-fork: + name: contracts-bedrock-tests-l2-fork-dispatch op-mainnet + fork_op_chain: op-mainnet + l2_fork_block_number: latest + test_profile: ci + context: + - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets + - slack + + scheduled-weekly-tests: + when: + or: + - equal: [build_weekly, <>] + - equal: [true, << pipeline.parameters.c-l2_fork_weekly_dispatch >>] + jobs: + - contracts-bedrock-tests-l2-fork: + matrix: + parameters: + fork_op_chain: + - op-mainnet + - zora + - ink + - mode + - metal + - arena-z + - swell + - unichain + - soneium + - world-chain + - base + - xlayer + test_profile: ci + context: + - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets + - slack + + contracts-feature-tests: + when: + or: + - and: + - equal: ["webhook", << pipeline.trigger_source >>] + - << pipeline.parameters.c-contracts_changed >> + # Always run on develop (mirrors previous check-changed whitelist behavior) + - and: + - equal: ["webhook", << pipeline.trigger_source >>] + - equal: ["develop", << pipeline.git.branch >>] + # Merge queue (merge gate). Match checkout branch. + - matches: + pattern: "^gh-readonly-queue/.*" + value: << pipeline.git.branch >> + - and: + - equal: [true, <>] + - equal: ["api", << pipeline.trigger_source >>] + jobs: + - contracts-bedrock-tests: + # Heavily fuzz any fuzz tests within added or modified test files. + name: contracts-bedrock-tests-heavy-fuzz-modified <> + test_list: git diff origin/develop...HEAD --name-only --diff-filter=AM -- './test/**/*.t.sol' | sed 's|packages/contracts-bedrock/||' + test_timeout: 1h + test_profile: ciheavy + features: <> + matrix: + parameters: + features: &features_matrix + - main + - CUSTOM_GAS_TOKEN + - OPTIMISM_PORTAL_INTEROP + - ZK_DISPUTE_GAME + - CANNON_KONA + - SUPER_ROOT_GAMES_MIGRATION + - L2CM + - L2CM,CUSTOM_GAS_TOKEN + - L2CM,OPTIMISM_PORTAL_INTEROP + 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 + 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: + features: *features_matrix + context: + - circleci-repo-readonly-authenticated-github-token + - slack + filters: + branches: + only: develop + - contracts-bedrock-coverage: + # Generate coverage reports. + name: contracts-bedrock-coverage <> + test_timeout: 1h + test_profile: cicoverage + features: <> + matrix: + parameters: + features: *features_matrix + context: + - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets + - 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 + test_profile: liteci + features: <> + matrix: + parameters: + features: *features_matrix + context: + - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets + - 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 + test_profile: ci + features: <> + matrix: + parameters: + features: *features_matrix + context: + - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets + - 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 + test_profile: liteci + matrix: + parameters: + fork_op_chain: ["op", "ink", "unichain"] + context: + - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets + - slack + 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 + test_profile: ci + matrix: + parameters: + fork_op_chain: ["op", "ink", "unichain"] + context: + - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets + - slack + filters: + branches: + only: develop + # Always run L2 fork tests with ci profile + - contracts-bedrock-tests-l2-fork: + name: contracts-bedrock-tests-l2-fork op-mainnet + fork_op_chain: op-mainnet + l2_fork_block_number: latest + test_profile: ci + context: + - circleci-repo-readonly-authenticated-github-token + - circleci-repo-rpc-secrets + - slack + - contracts-bedrock-checks-fast: + name: contracts-bedrock-checks-fast-feature-tests + context: + - circleci-repo-readonly-authenticated-github-token + - slack + # ----------------------------------------------------------------------- + # required-contracts-ci: GitHub required status for this workflow. Terminal requires + # keep this job scheduled even when upstream jobs fail; ci-gate then checks they passed. + # ----------------------------------------------------------------------- + - required-contracts-ci: + requires: + - contracts-bedrock-tests main: terminal + - contracts-bedrock-tests-heavy-fuzz-modified main: terminal + - contracts-bedrock-coverage main: terminal + - contracts-bedrock-tests-upgrade op-mainnet main: terminal + - contracts-bedrock-checks-fast-feature-tests: terminal + context: + - circleci-api-token + # ============================================================================ + # Required contracts CI gate (skip) — runs when no contract changes + # Produces the required-contracts-ci status so the merge queue doesn't hang. + # ============================================================================ + contracts-feature-tests-short: + when: + not: # these conditions should be kept in sync with the ones in contracts-feature-tests + or: + - and: + - equal: ["webhook", << pipeline.trigger_source >>] + - << pipeline.parameters.c-contracts_changed >> + # Always run on develop (mirrors previous check-changed whitelist behavior) + - and: + - equal: ["webhook", << pipeline.trigger_source >>] + - equal: ["develop", << pipeline.git.branch >>] + # Merge queue (merge gate). Match checkout branch. + - matches: + pattern: "^gh-readonly-queue/.*" + value: << pipeline.git.branch >> + - and: + - equal: [true, <>] + - equal: ["api", << pipeline.trigger_source >>] + jobs: + - required-contracts-ci: + always-succeed: true + context: + - circleci-api-token diff --git a/.circleci/continue/rust-ci.yml b/.circleci/continue/rust-ci.yml index 3a04042072d..bc5f4a6ad71 100644 --- a/.circleci/continue/rust-ci.yml +++ b/.circleci/continue/rust-ci.yml @@ -125,7 +125,7 @@ commands: toolchain_version: description: The specific toolchain version for stable channel type: string - default: "1.92.0" + default: "1.94.0" target: description: A custom target architecture to add to the toolchain type: string @@ -410,10 +410,6 @@ jobs: - rust-prepare-and-restore-cache: &fmt-cache-args directory: <> prefix: <>-fmt - - run: - name: Install nightly toolchain - working_directory: <> - command: just install-nightly - run: name: Check formatting working_directory: <> @@ -601,10 +597,6 @@ jobs: directory: <> prefix: <>-docs features: "all" - - run: - name: Install nightly toolchain - working_directory: <> - command: just install-nightly - run: name: Build documentation working_directory: <> @@ -621,10 +613,10 @@ jobs: command: description: "Doc test command to run" type: string - default: "cargo test --workspace --doc" + default: "just test-docs" docker: - - image: <> - resource_class: xlarge + - image: <> + resource_class: 2xlarge steps: - utils/checkout-with-mise: checkout-method: blobless @@ -704,10 +696,6 @@ jobs: directory: <> prefix: <>-udeps profile: "release" - - run: - name: Install nightly toolchain - working_directory: <> - command: just install-nightly - install-cargo-binstall - run: name: Install cargo-udeps @@ -720,6 +708,75 @@ jobs: command: <> - rust-save-build-cache: *udeps-cache-args + # Lint + test job for vendored Rust workspaces that live as subdirectories + # of `rust/` (op-rbuilder, rollup-boost). Mirrors what upstream gates on in + # GitHub Actions: lint (fmt + clippy) and test against the vendored Cargo + # workspace. Optional pre-step + apt packages cover upstream-specific needs + # like building a test helper binary or installing native deps. + rust-vendored-checks: + parameters: + directory: + description: "Directory containing the vendored Cargo workspace" + type: string + apt_packages: + description: "Space-separated apt packages to install before building (e.g. native deps for sys crates)." + type: string + default: "" + pre_command: + description: "Optional command to run before lint/test (e.g. build a tester helper binary). Runs in ." + type: string + default: "" + lint_command: + description: "Lint command to run (in )." + type: string + default: "make lint" + test_command: + description: "Test command to run (in )." + type: string + default: "make test" + resource_class: + description: "CircleCI resource class for the job runner." + type: string + default: "xlarge" + docker: + - image: <> + resource_class: <> + steps: + - utils/checkout-with-mise: + checkout-method: blobless + - when: + condition: <> + steps: + - apt-install: + packages: <> + extra_flags: "--no-install-recommends" + - rust-prepare-and-restore-cache: &vendored-checks-cache-args + directory: <> + prefix: <>-checks + - when: + condition: <> + steps: + - run: + name: Pre-check setup + working_directory: <> + no_output_timeout: 30m + command: <> + - run: + name: Lint + working_directory: <> + no_output_timeout: 20m + command: <> + - run: + name: Test + working_directory: <> + no_output_timeout: 40m + environment: + TESTS_TEMP_DIR: /tmp/rust-vendored-tests + command: | + mkdir -p "$TESTS_TEMP_DIR" + <> + - rust-save-build-cache: *vendored-checks-cache-args + # Shared cargo hack build job (cross-compile targets like WASM) rust-ci-cargo-hack-build: parameters: @@ -797,6 +854,7 @@ jobs: op-reth-compact-codec: docker: - image: <> + # This job frequently gets killed for smaller resource_class resource_class: xlarge steps: - utils/checkout-with-mise: @@ -857,18 +915,12 @@ jobs: - run: name: Set run environment command: | - echo 'export BLOCK_NUMBER=26215604' >> $BASH_ENV - echo 'export L2_CLAIM=0x7415d942f80a34f77d344e4bccb7050f14e593f5ea33669d27ea01dce273d72d' >> $BASH_ENV - echo 'export L2_OUTPUT_ROOT=0xaa34b62993bd888d7a2ad8541935374e39948576fce12aa8179a0aa5b5bc787b' >> $BASH_ENV - echo 'export L2_HEAD=0xf4adf5790bad1ffc9eee315dc163df9102473c5726a2743da27a8a10dc16b473' >> $BASH_ENV - echo 'export L1_HEAD=0x010cfdb22eaa13e8cdfbf66403f8de2a026475e96a6635d53c31f853a0e3ae25' >> $BASH_ENV + echo 'export BLOCK_NUMBER=42427365' >> $BASH_ENV + echo 'export L2_CLAIM=0xaaf7cb1725cb9766899cbd78866270d8561e79b91c00be5c53adb7699e0e7715' >> $BASH_ENV + echo 'export L2_OUTPUT_ROOT=0xac90a2e8cfee33ecb47b73f3e83f9aa18ff7ecf28bc8d34b2de857919e50b6c6' >> $BASH_ENV + echo 'export L2_HEAD=0xc5fa07d52e83f7a2e68de356e7a7bfc9306a9b28666ec6c9cc6e4f8e1b268278' >> $BASH_ENV + echo 'export L1_HEAD=0xab33322985684f2c6fbcf375bdcb69cc0f10547e58cb5a7b889172ee9ea77aef' >> $BASH_ENV echo 'export L2_CHAIN_ID=11155420' >> $BASH_ENV - echo 'export WITNESS_TAR_NAME=holocene-op-sepolia-26215604-witness.tar.zst' >> $BASH_ENV - - run: - name: Decompress witness data - working_directory: rust/kona - command: | - tar --zstd -xvf ./bin/client/testdata/$WITNESS_TAR_NAME -C . - run: name: Run host + client offline working_directory: rust/kona/bin/client @@ -968,66 +1020,6 @@ jobs: flags: unit - rust-save-build-cache: *kona-coverage-cache - # Unified Rust Docs Build - rust-docs-build: - docker: - - image: <> - resource_class: xlarge - steps: - - utils/checkout-with-mise: - checkout-method: blobless - - run: - name: Install Node.js and Bun - command: | - curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - - sudo apt-get install -y nodejs - curl -fsSL https://bun.sh/install | bash - echo 'export BUN_INSTALL="$HOME/.bun"' >> $BASH_ENV - echo 'export PATH="$BUN_INSTALL/bin:$PATH"' >> $BASH_ENV - - run: - name: Install dependencies and Playwright browsers - working_directory: rust/docs - command: | - bun i - npx playwright install --with-deps chromium - - run: - name: Build Vocs documentation - working_directory: rust/docs - no_output_timeout: 60m - command: | - bun run build - echo "Vocs Build Complete" - - store_artifacts: - path: rust/docs/docs/docs/dist - destination: rust-docs - - # OP-Reth docs build - op-reth-docs-build: - docker: - - image: <> - resource_class: xlarge - steps: - - utils/checkout-with-mise: - checkout-method: blobless - - run: - name: Install Node.js and Bun - command: | - curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - - sudo apt-get install -y nodejs - curl -fsSL https://bun.sh/install | bash - echo 'export BUN_INSTALL="$HOME/.bun"' >> $BASH_ENV - echo 'export PATH="$BUN_INSTALL/bin:$PATH"' >> $BASH_ENV - - run: - name: Build op-reth documentation - working_directory: rust/op-reth - no_output_timeout: 30m - command: | - just docs-build - - store_artifacts: - path: op-reth/docs/vocs/docs/dist - destination: op-reth-docs - - # Kona Link Checker kona-link-checker: docker: @@ -1075,11 +1067,11 @@ jobs: oidc_token_file_path: /tmp/oidc_token.json - run: name: Generate prestate artifacts - working_directory: rust/kona + working_directory: rust no_output_timeout: 60m command: | - cd docker/fpvm-prestates - just "<>" "<>" "../../prestate-artifacts-<>" + just build-kona-reproducible-prestate-variant "<>" "prestate-artifacts-<>" + - run: name: Upload prestates to GCS working_directory: rust/kona @@ -1164,7 +1156,6 @@ workflows: - rust-ci-doctest: name: rust-doctest directory: rust - command: "cargo test --doc --workspace --all-features" context: *rust-ci-context - rust-ci-docs: @@ -1172,11 +1163,6 @@ workflows: directory: rust context: *rust-ci-context - # Docs build - - rust-docs-build: - context: - - circleci-repo-readonly-authenticated-github-token - - rust-ci-udeps: name: rust-udeps directory: rust @@ -1201,7 +1187,7 @@ workflows: - rust-build-binary: name: rust-msrv directory: rust - toolchain: "1.92.0" + toolchain: "1.94.0" context: *rust-ci-context # ----------------------------------------------------------------------- @@ -1212,7 +1198,7 @@ workflows: directory: rust command: | just check-no-std - toolchain: "1.92.0" + toolchain: "1.94.0" context: *rust-ci-context - rust-ci-cargo-hack-build: @@ -1242,25 +1228,21 @@ workflows: 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 crate-specific jobs (lint, FPVM builds, benches, coverage) # ----------------------------------------------------------------------- - kona-cargo-lint: - name: kona-lint-cannon - target: "cannon" + name: kona-lint-<> + matrix: + parameters: + target: ["cannon"] context: *rust-ci-context - kona-build-fpvm: - name: kona-build-fpvm-cannon-client - target: "cannon-client" + name: kona-build-fpvm-<> + matrix: + parameters: + target: ["cannon-client"] context: *rust-ci-context - kona-coverage: @@ -1272,14 +1254,69 @@ workflows: name: kona-host-client-offline-cannon context: *rust-ci-context + # Vendored Rust workspaces (op-rbuilder, rollup-boost). + # Mirrors upstream's lint + test gates. + - rust-vendored-checks: + name: op-rbuilder-checks + directory: rust/op-rbuilder + # libsqlite3 (db), libtss2 (tdx-quote-provider); upstream installs both. + apt_packages: "libsqlite3-dev libtss2-dev" + # Match upstream's pre-compile of the tester helper + main binary. + pre_command: | + make tester + cargo build -p op-rbuilder --bin op-rbuilder + lint_command: "make lint" + # nextest's process-per-test isolation keeps RSS bounded; the + # single-process `cargo test --lib` form OOMs on 2xlarge. + # `--no-default-features --features jemalloc` drops `docker-tests` + # (testcontainers needs /var/run/docker.sock, not exposed here). + test_command: | + cargo nextest run --workspace --test-threads=4 --retries 2 \ + --no-default-features --features jemalloc + resource_class: "2xlarge" + context: *rust-ci-context + + - rust-vendored-checks: + name: rollup-boost-checks + directory: rust/rollup-boost + lint_command: "make lint" + # `--no-default-features` drops `docker-tests` (no docker socket here). + test_command: | + cargo build --bin rollup-boost + cargo test --workspace --verbose --no-default-features + context: *rust-ci-context + # ----------------------------------------------------------------------- # Required gate — fans in on required Rust CI jobs # ----------------------------------------------------------------------- - required-rust-ci: requires: - - rust-tests - - rust-clippy - - rust-docs + - rust-fmt: terminal + - rust-clippy: terminal + - rust-deny: terminal + - rust-typos: terminal + - rust-zepter: terminal + - rust-tests: terminal + - rust-doctest: terminal + - rust-docs: terminal + - rust-udeps: terminal + - rust-cargo-hack: terminal + - rust-build: terminal + - rust-build-release: terminal + - rust-check-no-std: terminal + - rust-wasm-unknown: terminal + - rust-wasm-wasi: terminal + - op-reth-compact-codec: terminal + - op-reth-integration-tests: terminal + - kona-lint-cannon: terminal + - kona-build-fpvm-cannon-client: terminal + - kona-coverage: terminal + - kona-host-client-offline-cannon: terminal + - op-rbuilder-checks: terminal + - rollup-boost-checks: terminal + context: + - circleci-api-token + # ===================================== # ========================================================================== # Required Rust CI gate (skip) — runs when no rust changes and no dispatch @@ -1290,6 +1327,7 @@ workflows: and: - equal: ["webhook", << pipeline.trigger_source >>] - not: << pipeline.parameters.c-rust_changes_detected >> + - << pipeline.parameters.c-non_docs_changes >> jobs: - rust-ci-cargo-tests: name: rust-tests @@ -1303,14 +1341,6 @@ workflows: 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" @@ -1320,7 +1350,6 @@ workflows: requires: - rust-tests - op-reth-integration-tests - - op-reth-tests-edge - kona-build-fpvm-cannon-client diff --git a/.circleci/continue/rust-e2e.yml b/.circleci/continue/rust-e2e.yml index 2b36c6dad4c..955889bfcb6 100644 --- a/.circleci/continue/rust-e2e.yml +++ b/.circleci/continue/rust-e2e.yml @@ -19,9 +19,6 @@ parameters: 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)". @@ -152,10 +149,6 @@ jobs: at: . - go-restore-cache: namespace: kona-ci - - rust-build: &kona-rust-build-release - directory: rust - profile: release - binary: "kona-node" - run: name: Run common tests for node with sysgo orchestrator no_output_timeout: 60m @@ -198,8 +191,6 @@ jobs: at: . - go-restore-cache: namespace: kona-ci - - rust-build: - <<: *kona-rust-build-release - run: name: Run restart tests for node with sysgo orchestrator no_output_timeout: 60m @@ -228,10 +219,6 @@ jobs: at: . - go-restore-cache: namespace: op-reth-e2e - - rust-build: - directory: rust - profile: release - binary: "op-reth" - run: name: Run op-reth E2E tests with sysgo orchestrator working_directory: rust/op-reth/tests @@ -264,9 +251,6 @@ jobs: at: . - go-restore-cache: namespace: kona-ci - - rust-build: - <<: *kona-rust-build-release - binary: "kona-host" - run: name: Build kona and run action tests working_directory: rust/kona @@ -301,7 +285,6 @@ workflows: - 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 >>] @@ -313,58 +296,46 @@ workflows: - cannon-prestate: &rust-e2e-job-base context: - circleci-repo-readonly-authenticated-github-token - - rust-build-binary: &cannon-kona-host - name: cannon-kona-host - directory: rust - profile: release - binary: "kona-host" - context: - - circleci-repo-readonly-authenticated-github-token - - rust-build-binary: &kona-build-release - name: kona-build-release - directory: rust - profile: release - context: - - circleci-repo-readonly-authenticated-github-token - - rust-build-binary: - name: op-reth-build + # rust-workspace-release builds the whole rust workspace (no binary filter), + # so it produces kona-host, kona-node, op-reth, and the rest. Downstream + # e2e jobs attach the workspace and consume the binaries directly. + - rust-build-binary: &rust-workspace-release + name: rust-workspace-release directory: rust profile: release - binary: "op-reth" + persist_to_workspace: true context: - circleci-repo-readonly-authenticated-github-token - rust-e2e-sysgo-tests: name: rust-e2e-<> matrix: parameters: - devnet_config: ["simple-kona", "simple-kona-geth", "simple-kona-sequencer"] + devnet_config: + ["simple-kona", "simple-kona-geth", "simple-kona-sequencer"] context: - circleci-repo-readonly-authenticated-github-token requires: - contracts-bedrock-build - cannon-prestate - - cannon-kona-host - - kona-build-release - - op-reth-build + - rust-workspace-release - op-reth-e2e-sysgo-tests: <<: *rust-e2e-job-base requires: - contracts-bedrock-build - - op-reth-build + - rust-workspace-release - rust-restart-sysgo-tests: name: rust-e2e-restart <<: *rust-e2e-job-base requires: - contracts-bedrock-build - cannon-prestate - - cannon-kona-host - - kona-build-release + - rust-workspace-release # 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 + - rust-workspace-release - contracts-bedrock-build context: - circleci-repo-readonly-authenticated-github-token @@ -392,9 +363,10 @@ workflows: context: - circleci-repo-readonly-authenticated-github-token - rust-build-binary: - name: kona-build-release + name: rust-workspace-release directory: rust profile: release + persist_to_workspace: true context: - circleci-repo-readonly-authenticated-github-token # Proof tests - single kind only, interop excluded per original config @@ -402,7 +374,7 @@ workflows: name: kona-proof-action-single kind: single requires: - - kona-build-release + - rust-workspace-release - contracts-bedrock-build context: - circleci-repo-readonly-authenticated-github-token diff --git a/.circleci/l2-rpcs.json b/.circleci/l2-rpcs.json new file mode 100644 index 00000000000..d53e98ba5ab --- /dev/null +++ b/.circleci/l2-rpcs.json @@ -0,0 +1,14 @@ +{ + "op-mainnet": "", + "zora": "https://rpc.zora.energy", + "ink": "https://rpc-gel.inkonchain.com", + "mode": "https://mainnet.mode.network", + "metal": "https://rpc.metall2.com", + "arena-z": "https://rpc.arena-z.gg", + "swell": "https://swell-mainnet.alt.technology", + "unichain": "https://mainnet.unichain.org", + "soneium": "https://rpc.soneium.org", + "world-chain": "https://worldchain-mainnet.g.alchemy.com/public", + "base": "https://mainnet.base.org", + "xlayer": "https://xlayerrpc.okx.com" +} diff --git a/.claude/skills/fix-rust-fmt/SKILL.md b/.claude/skills/fix-rust-fmt/SKILL.md index d87b23bfa92..433a1045006 100644 --- a/.claude/skills/fix-rust-fmt/SKILL.md +++ b/.claude/skills/fix-rust-fmt/SKILL.md @@ -28,16 +28,7 @@ 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 +### Step 2: Run the formatter ```bash cd /rust && mise exec -- just fmt-fix diff --git a/.dockerignore b/.dockerignore index 379f38bafba..50651fddf41 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,15 @@ -/bin/ -/dist/ -/pkg/ -/.gocache/ -*.sw? +.github +.vscode + +.env +.envrc +**/.env + +test +**/*_test.go +build/_workspace +build/bin +build/_bin +tests/testdata + +rust/target diff --git a/.gitignore b/.gitignore index 4031cb1d276..0e8baeaaf5f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ coverage.json **/lcov-upgrade.info **/lcov-all.info +# Rust files +**/target + yarn-error.log .yarn/* !.yarn/releases @@ -39,6 +42,8 @@ packages/contracts-bedrock/deployments/anvil .devnet* +.claude/worktrees + # Ignore local fuzzing results **/testdata/fuzz/ @@ -50,4 +55,7 @@ __pycache__ crytic-export # ignore local asdf config -.tool-versions \ No newline at end of file +.tool-versions + +# Rust files +**/target diff --git a/.gitmodules b/.gitmodules index db59b70ff2c..427aecc6db8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -38,13 +38,3 @@ [submodule "packages/contracts-bedrock/lib/superchain-registry"] path = packages/contracts-bedrock/lib/superchain-registry url = https://github.com/ethereum-optimism/superchain-registry -[submodule "rust/kona/crates/protocol/registry/superchain-registry"] - path = rust/kona/crates/protocol/registry/superchain-registry - url = https://github.com/ethereum-optimism/superchain-registry -[submodule "op-rbuilder"] - path = op-rbuilder - url = https://github.com/flashbots/op-rbuilder -[submodule "rollup-boost"] - path = rollup-boost - url = https://github.com/flashbots/rollup-boost - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e851085a56e..8613f245d52 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -141,13 +141,11 @@ See [this document](./op-e2e/README.md) #### Running contract static analysis We perform static analysis with [`slither`](https://github.com/crytic/slither). -You must have Python 3.x installed to run `slither`. -To run `slither` locally, do: +Slither is pinned in `mise.toml`. To run it locally, do: ```bash cd packages/contracts-bedrock -pip3 install slither-analyzer -just slither +mise exec -- slither . --config-file test/slither/slither.config.json ``` ## Labels @@ -186,7 +184,6 @@ Also, all labels can be seen by visiting the [labels page][labels] When altering label names or deleting labels there are a few things you must be aware of. -- If the https://github.com/ethereum-optimism/optimism/labels/S-stale label is altered, the [close-stale](.github/workflows/close-stale.yml) workflow should be updated. - If the https://github.com/ethereum-optimism/optimism/labels/M-dependabot label is altered, the [dependabot config](.github/dependabot.yml) file should be adjusted. - Saved label filters for project boards will not automatically update. These should be updated if label names change. diff --git a/Makefile b/Makefile index 67302c5f382..1bf92f386d2 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ -# 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-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 diff --git a/README.md b/README.md index 3383e822dd8..80d7cfb44d1 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ For example if geth is at `v1.12.0`, the corresponding op-geth version would be Note that we pad out to three characters for the geth minor version and two characters for the geth patch version. Since we cannot left-pad with zeroes, the geth major version is not padded. -See the [Node Software Releases](https://docs.optimism.io/builders/node-operators/releases) page of the documentation for more information about releases for the latest node components. +See the [GitHub releases](https://github.com/ethereum-optimism/optimism/releases) page for more information about releases for the latest node components. The full set of components that have releases are: diff --git a/docker-bake.hcl b/docker-bake.hcl index eb2f509053f..c7695c1b5c2 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -351,6 +351,9 @@ target "op-rbuilder" { target "kona-node" { dockerfile = "kona/docker/apps/kona_app_generic.dockerfile" context = "rust" + contexts = { + nuts-bundles = "op-core/nuts/bundles" + } args = { REPO_LOCATION = "local" BIN_TARGET = "kona-node" @@ -363,6 +366,9 @@ target "kona-node" { target "kona-host" { dockerfile = "kona/docker/apps/kona_app_generic.dockerfile" context = "rust" + contexts = { + nuts-bundles = "op-core/nuts/bundles" + } args = { REPO_LOCATION = "local" BIN_TARGET = "kona-host" @@ -375,6 +381,9 @@ target "kona-host" { target "kona-client" { dockerfile = "kona/docker/apps/kona_app_generic.dockerfile" context = "rust" + contexts = { + nuts-bundles = "op-core/nuts/bundles" + } args = { REPO_LOCATION = "local" BIN_TARGET = "kona-client" diff --git a/docs/ai/rust-dev.md b/docs/ai/rust-dev.md index 51d621d8f51..dc000d57c44 100644 --- a/docs/ai/rust-dev.md +++ b/docs/ai/rust-dev.md @@ -22,6 +22,9 @@ cd rust # Build the workspace just build +# Build the workspace excluding example crates with the fast-build profile +just build-no-examples + # Build in release mode just build-release @@ -47,6 +50,30 @@ just test-unit just test-docs ``` +### Running op-reth E2E Tests + +The op-reth E2E tests (`rust/op-reth/tests/proofs/`) run a full devnet with op-geth (sequencer) and op-reth (validator). They require two build prerequisites: + +1. **Forge artifacts** — the devnet deploys contracts from compiled artifacts: + ```bash + cd packages/contracts-bedrock + mise exec -- just build-no-tests + ``` + +2. **op-reth release binary** — the test harness (`op-devstack/sysgo/rust_binary.go`) only searches `target/release/`, not `target/debug/`. Options: + ```bash + # Option A: let the test build it (slow first run, cached after) + RUST_JIT_BUILD=1 go test -v -run TestName ./rust/op-reth/tests/proofs/core/ + + # Option B: pre-build the binary + cd rust && just build-op-reth + ``` + +Run from the monorepo root: +```bash +mise exec -- go test -v -run TestExecutePayloadSuccess -count=1 ./rust/op-reth/tests/proofs/core/ +``` + ### Generating Prestates Kona prestates are built via Docker: @@ -74,12 +101,7 @@ Lint configuration lives in `rust/Cargo.toml` (workspace lints section), `rust/c ### 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 -``` +Formatting uses a pinned nightly toolchain (defined as `NIGHTLY` in `rust/justfile`). It is installed via mise. Then use `just fmt-fix` to auto-format, or `just fmt-check` to verify. @@ -126,6 +148,18 @@ Run these checks from `rust/`. Fix all issues — CI enforces zero warnings. Op-reth requires `clang` / `libclang-dev` for reth-mdbx-sys bindgen. CI installs this automatically — if you see bindgen errors locally, install clang. +## Updating the reth dependency + +The full guide lives at [`rust/UPDATING-RETH.md`](../../rust/UPDATING-RETH.md). Read it before bumping the reth rev in `rust/Cargo.toml`. + +Agent-specific tips beyond what's in the guide: + +- The bump is iterative — run `cargo check --workspace --tests`, fix the first batch of errors, re-run, repeat. Don't try to enumerate every API change up front and don't ask the user to confirm every line of adaptation; just iterate to a green compile and report the diff at the end. +- If you have a local checkout of `paradigmxyz/reth`, use it to look up upstream trait signatures and run `git log ..` to find the commit that changed any given symbol — much faster and more reliable than hand-fetching raw GitHub URLs. If you don't know whether one is available, ask the user. Don't assume a path. +- For trait methods that gained an ignored parameter, prefix the new param with `_` (e.g. `_block_access_list_hash: Option`) so it doesn't generate an unused-variable warning. Don't invent a meaningful name unless you're actually plumbing the value through. +- If upstream removed a trait or re-export that op-reth still uses, vendor it locally with a comment pointing at the upstream removal PR — don't try to refactor op-reth to do without it without first confirming the consumer is actually unused. The "stale" label upstream doesn't mean unused downstream. +- When the new rev pulls in new transitive deps (visible as `Adding ` lines from `cargo update`), check whether they're from upstream reth's own deps or from a misconfiguration on our side. `cargo tree -i ` traces the path. + ## 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/components/HomeCard.tsx b/docs/public-docs/components/HomeCard.tsx new file mode 100644 index 00000000000..e7ddd6ec645 --- /dev/null +++ b/docs/public-docs/components/HomeCard.tsx @@ -0,0 +1,134 @@ +import type { ReactNode } 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: ReactNode; +} + +export function CardList({ children }: CardListProps) { + return ( +
+ {children} +
+ ); +} + +interface HomeCardProps { + title: string; + content?: ReactNode + className?: string; + footerLink?: { + text: string; + href: string; + }; +} + +export function HomeCard({ + title, + content, + className = '', + footerLink +}: HomeCardProps) { + return ( +
+ +
+

{title}

+ {content} +
+ + {footerLink && ( + + )} +
+ ); +} + +interface HomeCardsProps { + children: 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/docs.json b/docs/public-docs/docs.json new file mode 100644 index 00000000000..e586b26013f --- /dev/null +++ b/docs/public-docs/docs.json @@ -0,0 +1,2764 @@ +{ + "$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" + ] + } + ] + } + ] + }, + { + "tab": "Rust", + "groups": [ + { + "group": "Overview", + "pages": [ + "rust/index" + ] + }, + { + "group": "Kona - Introduction", + "pages": [ + "rust/kona/intro/overview", + "rust/kona/intro/why", + "rust/kona/intro/contributing", + "rust/kona/intro/lore" + ] + }, + { + "group": "Kona - Kona for Node Operators", + "pages": [ + "rust/kona/node/requirements", + { + "group": "Installation", + "pages": [ + "rust/kona/node/install/overview", + "rust/kona/node/install/binaries", + "rust/kona/node/install/docker", + "rust/kona/node/install/source" + ] + }, + { + "group": "Run a Node", + "pages": [ + "rust/kona/node/run/overview", + "rust/kona/node/run/binary", + "rust/kona/node/run/docker", + "rust/kona/node/run/mechanics" + ] + }, + { + "group": "JSON-RPC Reference", + "pages": [ + "rust/kona/node/rpc/overview", + "rust/kona/node/rpc/p2p", + "rust/kona/node/rpc/rollup", + "rust/kona/node/rpc/admin" + ] + }, + "rust/kona/node/configuration", + "rust/kona/node/monitoring", + "rust/kona/node/subcommands", + { + "group": "FAQ", + "pages": [ + "rust/kona/node/faq/overview", + "rust/kona/node/faq/ports", + "rust/kona/node/faq/profiling" + ] + } + ] + }, + { + "group": "Kona - Kona as a Library", + "pages": [ + "rust/kona/sdk/overview", + { + "group": "Node SDK", + "pages": [ + "rust/kona/node/design/intro", + "rust/kona/node/design/derivation", + "rust/kona/node/design/engine", + "rust/kona/node/design/p2p", + "rust/kona/node/design/sequencer" + ] + }, + { + "group": "Proof SDK", + "pages": [ + "rust/kona/sdk/proof/intro", + "rust/kona/sdk/proof/fpvm-backend", + "rust/kona/sdk/proof/custom-backend", + "rust/kona/sdk/proof/exec-ext" + ] + }, + { + "group": "Fault Proof Program Development", + "pages": [ + "rust/kona/sdk/fpp-dev/intro", + "rust/kona/sdk/fpp-dev/env", + "rust/kona/sdk/fpp-dev/targets", + "rust/kona/sdk/fpp-dev/prologue", + "rust/kona/sdk/fpp-dev/execution", + "rust/kona/sdk/fpp-dev/epilogue" + ] + }, + { + "group": "Protocol Libraries", + "pages": [ + "rust/kona/sdk/protocol/intro", + "rust/kona/sdk/protocol/registry", + "rust/kona/sdk/protocol/interop", + "rust/kona/sdk/protocol/hardforks", + { + "group": "Derivation", + "pages": [ + "rust/kona/sdk/protocol/derive/intro", + "rust/kona/sdk/protocol/derive/providers", + "rust/kona/sdk/protocol/derive/stages", + "rust/kona/sdk/protocol/derive/signaling" + ] + }, + { + "group": "Genesis", + "pages": [ + "rust/kona/sdk/protocol/genesis/intro", + "rust/kona/sdk/protocol/genesis/rollup-config", + "rust/kona/sdk/protocol/genesis/system-config" + ] + }, + { + "group": "Protocol", + "pages": [ + "rust/kona/sdk/protocol/protocol/intro", + "rust/kona/sdk/protocol/protocol/block-info", + "rust/kona/sdk/protocol/protocol/l2-block-info", + "rust/kona/sdk/protocol/protocol/frames", + "rust/kona/sdk/protocol/protocol/channels", + "rust/kona/sdk/protocol/protocol/batches" + ] + } + ] + }, + { + "group": "Examples", + "pages": [ + "rust/kona/sdk/examples/intro", + "rust/kona/sdk/examples/load-a-rollup-config", + "rust/kona/sdk/examples/frames-to-batch", + "rust/kona/sdk/examples/new-l1-block-info-tx-hardfork", + "rust/kona/sdk/examples/executor-test-fixtures", + "rust/kona/sdk/examples/p2p-peer-scoring", + "rust/kona/sdk/examples/custom-derivation-pipeline" + ] + } + ] + }, + { + "group": "Kona - RFC", + "pages": [ + "rust/kona/rfc/active/intro", + { + "group": "Archived RFCs", + "pages": [ + "rust/kona/rfc/archived/umbrellas", + "rust/kona/rfc/archived/monorepo" + ] + } + ] + }, + { + "group": "op-reth - Introduction", + "pages": [ + "rust/op-reth/index" + ] + }, + { + "group": "op-reth - Running op-reth", + "pages": [ + "rust/op-reth/run/opstack", + { + "group": "FAQ", + "pages": [ + "rust/op-reth/run/faq/sync-op-mainnet" + ] + } + ] + }, + { + "group": "op-reth - CLI Reference", + "pages": [ + "rust/op-reth/cli/op-reth", + { + "group": "op-reth", + "pages": [ + "rust/op-reth/cli/op-reth", + "rust/op-reth/cli/op-reth/node", + "rust/op-reth/cli/op-reth/init", + "rust/op-reth/cli/op-reth/init-state", + "rust/op-reth/cli/op-reth/import-op", + "rust/op-reth/cli/op-reth/import-receipts-op", + "rust/op-reth/cli/op-reth/dump-genesis", + { + "group": "op-reth db", + "pages": [ + "rust/op-reth/cli/op-reth/db", + "rust/op-reth/cli/op-reth/db/stats", + "rust/op-reth/cli/op-reth/db/list", + { + "group": "op-reth db checksum", + "pages": [ + "rust/op-reth/cli/op-reth/db/checksum", + "rust/op-reth/cli/op-reth/db/checksum/mdbx", + "rust/op-reth/cli/op-reth/db/checksum/static-file" + ] + }, + "rust/op-reth/cli/op-reth/db/diff", + { + "group": "op-reth db get", + "pages": [ + "rust/op-reth/cli/op-reth/db/get", + "rust/op-reth/cli/op-reth/db/get/mdbx", + "rust/op-reth/cli/op-reth/db/get/static-file" + ] + }, + "rust/op-reth/cli/op-reth/db/drop", + { + "group": "op-reth db clear", + "pages": [ + "rust/op-reth/cli/op-reth/db/clear", + "rust/op-reth/cli/op-reth/db/clear/mdbx", + "rust/op-reth/cli/op-reth/db/clear/static-file" + ] + }, + "rust/op-reth/cli/op-reth/db/repair-trie", + { + "group": "op-reth db static-file-header", + "pages": [ + "rust/op-reth/cli/op-reth/db/static-file-header", + "rust/op-reth/cli/op-reth/db/static-file-header/block", + "rust/op-reth/cli/op-reth/db/static-file-header/path" + ] + }, + "rust/op-reth/cli/op-reth/db/version", + "rust/op-reth/cli/op-reth/db/path", + { + "group": "op-reth db settings", + "pages": [ + "rust/op-reth/cli/op-reth/db/settings", + "rust/op-reth/cli/op-reth/db/settings/get", + { + "group": "op-reth db settings set", + "pages": [ + "rust/op-reth/cli/op-reth/db/settings/set", + "rust/op-reth/cli/op-reth/db/settings/set/receipts", + "rust/op-reth/cli/op-reth/db/settings/set/transaction_senders", + "rust/op-reth/cli/op-reth/db/settings/set/account_changesets", + "rust/op-reth/cli/op-reth/db/settings/set/storages_history", + "rust/op-reth/cli/op-reth/db/settings/set/transaction_hash_numbers", + "rust/op-reth/cli/op-reth/db/settings/set/account_history", + "rust/op-reth/cli/op-reth/db/settings/set/storage_changesets" + ] + } + ] + }, + "rust/op-reth/cli/op-reth/db/account-storage" + ] + }, + { + "group": "op-reth stage", + "pages": [ + "rust/op-reth/cli/op-reth/stage", + "rust/op-reth/cli/op-reth/stage/run", + "rust/op-reth/cli/op-reth/stage/drop", + { + "group": "op-reth stage dump", + "pages": [ + "rust/op-reth/cli/op-reth/stage/dump", + "rust/op-reth/cli/op-reth/stage/dump/execution", + "rust/op-reth/cli/op-reth/stage/dump/storage-hashing", + "rust/op-reth/cli/op-reth/stage/dump/account-hashing", + "rust/op-reth/cli/op-reth/stage/dump/merkle" + ] + }, + { + "group": "op-reth stage unwind", + "pages": [ + "rust/op-reth/cli/op-reth/stage/unwind", + "rust/op-reth/cli/op-reth/stage/unwind/to-block", + "rust/op-reth/cli/op-reth/stage/unwind/num-blocks" + ] + } + ] + }, + { + "group": "op-reth p2p", + "pages": [ + "rust/op-reth/cli/op-reth/p2p", + "rust/op-reth/cli/op-reth/p2p/header", + "rust/op-reth/cli/op-reth/p2p/body", + { + "group": "op-reth p2p rlpx", + "pages": [ + "rust/op-reth/cli/op-reth/p2p/rlpx", + "rust/op-reth/cli/op-reth/p2p/rlpx/ping" + ] + }, + "rust/op-reth/cli/op-reth/p2p/bootnode" + ] + }, + "rust/op-reth/cli/op-reth/config", + "rust/op-reth/cli/op-reth/prune", + "rust/op-reth/cli/op-reth/re-execute" + ] + } + ] + }, + { + "group": "op-alloy - Introduction", + "pages": [ + "rust/op-alloy/intro", + "rust/op-alloy/starting" + ] + }, + { + "group": "op-alloy - Building", + "pages": [ + "rust/op-alloy/building/index", + "rust/op-alloy/building/consensus", + "rust/op-alloy/building/engine" + ] + }, + { + "group": "op-alloy - Reference", + "pages": [ + "rust/op-alloy/glossary" + ] + }, + { + "group": "op-revm", + "pages": [ + "rust/op-revm/index" + ] + } + ] + } + ], + "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/rust/docs/docs/public/banner.png b/docs/public-docs/public/rust/banner.png similarity index 100% rename from rust/docs/docs/public/banner.png rename to docs/public-docs/public/rust/banner.png diff --git a/rust/docs/docs/public/favicon.ico b/docs/public-docs/public/rust/favicon.ico similarity index 100% rename from rust/docs/docs/public/favicon.ico rename to docs/public-docs/public/rust/favicon.ico diff --git a/rust/docs/docs/public/logo.png b/docs/public-docs/public/rust/logo.png similarity index 100% rename from rust/docs/docs/public/logo.png rename to docs/public-docs/public/rust/logo.png diff --git a/rust/docs/docs/public/op-program-fpp.svg b/docs/public-docs/public/rust/op-program-fpp.svg similarity index 100% rename from rust/docs/docs/public/op-program-fpp.svg rename to docs/public-docs/public/rust/op-program-fpp.svg diff --git a/docs/public-docs/rust/index.mdx b/docs/public-docs/rust/index.mdx new file mode 100644 index 00000000000..9f6d4c46f35 --- /dev/null +++ b/docs/public-docs/rust/index.mdx @@ -0,0 +1,67 @@ +--- +title: "OP Stack Rust" +description: "Documentation for Rust implementations of the OP Stack: Kona, op-reth, op-alloy, and op-revm" +--- + +# OP Stack Rust + +Rust implementations for the OP Stack, built by [OP Labs](https://www.oplabs.co/). + +A unified documentation site for OP Stack Rust components: **Kona** (rollup node & fault proofs), +**op-reth** (execution client), **op-alloy** (types & providers), and **op-revm** (OP Stack EVM). + +## Quick Start + + +```bash Run a Kona Node +# Install the kona-node +cargo install kona-node + +# Start the node +kona-node --chain base --port 8545 +``` + +```bash Run op-reth +# Install op-reth +cargo install op-reth + +# Start the execution client +op-reth node --chain base +``` + + +## Components + + + + Modular rollup node and fault proof system. Spec-compliant, performant, and extensible with `no_std` support. + + + High-performance OP Stack execution client built on reth. Full superchain registry support. + + + OP Stack types and providers for the Alloy ecosystem. Consensus, RPC, and network crates. + + + Optimism variant of revm — deposit transactions, L1/operator fee accounting, and OP Stack precompiles. + + + +## Built with Kona SDK + +Production implementations using Kona's modular architecture: + + + + Fault proof program for rollup state transitions (~3K LoC) + + + Modular OP Stack rollup node implementation (~8K LoC) + + + zkVM-based proof system using Kona (~2K LoC) + + + zkVM-based proof system using Kona (~5K LoC) + + diff --git a/rust/docs/docs/pages/kona/glossary.mdx b/docs/public-docs/rust/kona/glossary.mdx similarity index 98% rename from rust/docs/docs/pages/kona/glossary.mdx rename to docs/public-docs/rust/kona/glossary.mdx index e9d99d19e08..ff0d44862ae 100644 --- a/rust/docs/docs/pages/kona/glossary.mdx +++ b/docs/public-docs/rust/kona/glossary.mdx @@ -1,4 +1,6 @@ -# Glossary +--- +title: "Glossary" +--- *This document contains definitions for terms used throughout the Kona book.* diff --git a/rust/docs/docs/pages/kona/intro/contributing.mdx b/docs/public-docs/rust/kona/intro/contributing.mdx similarity index 98% rename from rust/docs/docs/pages/kona/intro/contributing.mdx rename to docs/public-docs/rust/kona/intro/contributing.mdx index 817d0926749..24bfee2f8cd 100644 --- a/rust/docs/docs/pages/kona/intro/contributing.mdx +++ b/docs/public-docs/rust/kona/intro/contributing.mdx @@ -1,4 +1,6 @@ -# Contributing +--- +title: "Contributing" +--- Thank you for looking into contributing! diff --git a/rust/docs/docs/pages/kona/intro/lore.mdx b/docs/public-docs/rust/kona/intro/lore.mdx similarity index 99% rename from rust/docs/docs/pages/kona/intro/lore.mdx rename to docs/public-docs/rust/kona/intro/lore.mdx index 581ab02a4f4..97ec89b0acb 100644 --- a/rust/docs/docs/pages/kona/intro/lore.mdx +++ b/docs/public-docs/rust/kona/intro/lore.mdx @@ -1,4 +1,6 @@ -# Lore +--- +title: "Lore" +--- A long time ago, during ETH Denver 2023 in February, [@clabby][clabby] and [@refcell][refcell] embarked on a journey to apply the minimal diff diff --git a/rust/docs/docs/pages/kona/intro/overview.mdx b/docs/public-docs/rust/kona/intro/overview.mdx similarity index 95% rename from rust/docs/docs/pages/kona/intro/overview.mdx rename to docs/public-docs/rust/kona/intro/overview.mdx index b6ab34d44d3..1c283856897 100644 --- a/rust/docs/docs/pages/kona/intro/overview.mdx +++ b/docs/public-docs/rust/kona/intro/overview.mdx @@ -1,21 +1,22 @@ -import { Callout } from 'vocs/components' - -# Kona [Documentation for Kona users and developers] +--- +title: "Kona" +description: "Documentation for Kona users and developers" +--- Kona is an implementation of the [OP Stack][op-stack] written in Rust, designed to be modular and extensible. `no_std` support is prioritized to provide the building blocks for fault proofs. - + Kona is in active development and should be considered experimental. - + - + These docs may contain inaccuracies as it evolves. Please [open an issue][new-issue] if you find any errors or have any suggestions for improvements, and also feel free to [contribute][contributing] to the project! - + ## Introduction @@ -29,7 +30,7 @@ Protocol crates are `no_std` compatible for use within the Fault Proof. Types de libraries are shared by other components of the OP Stack including the rollup node. Proof crates are available for developing verifiable Rust programs targeting -[Fault Proof VMs](/kona/glossary#fault-proof-vm). +[Fault Proof VMs](/rust/kona/glossary#fault-proof-vm). These libraries provide tooling and abstractions around low-level syscalls, memory management, and other common structures that authors of verifiable programs will need to interact with. It also provides build pipelines for compiling `no_std` Rust programs to a format that can be diff --git a/rust/docs/docs/pages/kona/intro/why.mdx b/docs/public-docs/rust/kona/intro/why.mdx similarity index 96% rename from rust/docs/docs/pages/kona/intro/why.mdx rename to docs/public-docs/rust/kona/intro/why.mdx index 7df0abc762f..ee0163eb7ce 100644 --- a/rust/docs/docs/pages/kona/intro/why.mdx +++ b/docs/public-docs/rust/kona/intro/why.mdx @@ -1,6 +1,6 @@ -import { Callout } from 'vocs/components' - -# Why Kona? +--- +title: "Why Kona?" +--- Kona leverages the abstractions of the Rust Ethereum ecosystem providing a modular, extensible implementation of the OP Stack. diff --git a/rust/docs/docs/pages/kona/node/configuration.mdx b/docs/public-docs/rust/kona/node/configuration.mdx similarity index 99% rename from rust/docs/docs/pages/kona/node/configuration.mdx rename to docs/public-docs/rust/kona/node/configuration.mdx index 6fb9bd6bc32..5c52cda5cca 100644 --- a/rust/docs/docs/pages/kona/node/configuration.mdx +++ b/docs/public-docs/rust/kona/node/configuration.mdx @@ -1,4 +1,6 @@ -# Kona Node CLI Reference +--- +title: "Kona Node CLI Reference" +--- This document lists all CLI flags for the `kona-node node` subcommand, grouped by category. All flags can be provided as command-line arguments or via environment variables. diff --git a/rust/docs/docs/pages/kona/node/design/derivation.mdx b/docs/public-docs/rust/kona/node/design/derivation.mdx similarity index 96% rename from rust/docs/docs/pages/kona/node/design/derivation.mdx rename to docs/public-docs/rust/kona/node/design/derivation.mdx index 6da55d5b3d1..1becde67586 100644 --- a/rust/docs/docs/pages/kona/node/design/derivation.mdx +++ b/docs/public-docs/rust/kona/node/design/derivation.mdx @@ -1,4 +1,6 @@ -# Derivation in Kona Node +--- +title: "Derivation in Kona Node" +--- The derivation system in kona-node is responsible for transforming L1 data into L2 payload attributes that can be executed to produce the canonical L2 blocks. This document covers how the [`kona-derive`][kd] crate is integrated and used within the kona-node architecture. @@ -224,10 +226,10 @@ These metrics help operators monitor the health and progress of the derivation p ## Related Documentation For more details on the underlying derivation pipeline implementation, see: -- [Derivation Pipeline Introduction](/kona/sdk/protocol/derive/intro) -- [Custom Providers](/kona/sdk/protocol/derive/providers) -- [Stage Swapping](/kona/sdk/protocol/derive/stages) -- [Pipeline Signaling](/kona/sdk/protocol/derive/signaling) +- [Derivation Pipeline Introduction](/rust/kona/sdk/protocol/derive/intro) +- [Custom Providers](/rust/kona/sdk/protocol/derive/providers) +- [Stage Swapping](/rust/kona/sdk/protocol/derive/stages) +- [Pipeline Signaling](/rust/kona/sdk/protocol/derive/signaling) [kd]: https://crates.io/crates/kona-derive [na]: /kona/node/design/intro#node-actors diff --git a/rust/docs/docs/pages/kona/node/design/engine.mdx b/docs/public-docs/rust/kona/node/design/engine.mdx similarity index 99% rename from rust/docs/docs/pages/kona/node/design/engine.mdx rename to docs/public-docs/rust/kona/node/design/engine.mdx index 0c0770627aa..2ecffbcae52 100644 --- a/rust/docs/docs/pages/kona/node/design/engine.mdx +++ b/docs/public-docs/rust/kona/node/design/engine.mdx @@ -1,4 +1,6 @@ -# Execution Engine +--- +title: "Execution Engine" +--- The `kona-engine` crate provides a modular execution engine implementation for the OP Stack rollup node. It serves as the bridge between the rollup protocol and the execution layer (EL), managing Engine API interactions through a sophisticated task queue system. diff --git a/rust/docs/docs/pages/kona/node/design/intro.mdx b/docs/public-docs/rust/kona/node/design/intro.mdx similarity index 98% rename from rust/docs/docs/pages/kona/node/design/intro.mdx rename to docs/public-docs/rust/kona/node/design/intro.mdx index 50c3da4e71f..f37edf6d943 100644 --- a/rust/docs/docs/pages/kona/node/design/intro.mdx +++ b/docs/public-docs/rust/kona/node/design/intro.mdx @@ -1,4 +1,6 @@ -# Node Design Overview +--- +title: "Node Design Overview" +--- The entry-point for the `kona-node` is the [`RollupNodeService`][trait] trait which encapsulates the core wiring for the node. The default @@ -60,8 +62,6 @@ but are currently defined as follows. the [OP Stack Specs][specs]. - - [p2p]: ./p2p [engine]: ./engine [derivation]: ./derivation diff --git a/rust/docs/docs/pages/kona/node/design/p2p.mdx b/docs/public-docs/rust/kona/node/design/p2p.mdx similarity index 97% rename from rust/docs/docs/pages/kona/node/design/p2p.mdx rename to docs/public-docs/rust/kona/node/design/p2p.mdx index 52d3ed19383..2339d23cd62 100644 --- a/rust/docs/docs/pages/kona/node/design/p2p.mdx +++ b/docs/public-docs/rust/kona/node/design/p2p.mdx @@ -1,13 +1,13 @@ -import { Callout } from 'vocs/components' +--- +title: "P2P Networking" +--- -# P2P Networking - - + Partly adapted from the [OP Stack P2P Specs][p2p-specs]. Please reference the specs for up-to-date OP Stack requirements. - + The OP Stack uses P2P networking on the consensus layer to share the sequencer's view of the L2 chain with other nodes on the @@ -83,14 +83,14 @@ in the `kona-node` that manages the libp2p swarm, including any interfacing with the swarm like dialing peers, publishing payloads (L2 blocks), handling events from the swarm, and more. - + The libp2p swarm must be polled via Swarm as Stream in order to make progress. Through kona's `GossipDriver`, this can be done by looping over and consuming events from `GossipDriver::next`. - + The `GossipDriver` provides the methods to handle events from the [libp2p Swarm][swarm]. Events should be consumed @@ -126,11 +126,11 @@ published on. Only once the [`OpNetworkPayloadEnvelope`][env] is successfully decoded for the corresponding block topic, is the block validated. - + Block validity in kona follows the [OP Stack block validation specs][validation]. - + As of writing these docs, block validation follows a few rules. @@ -154,9 +154,6 @@ TODO TODO - - - [validation]: https://specs.optimism.io/protocol/rollup-node-p2p.html#block-validation [block-topic]: https://specs.optimism.io/protocol/rollup-node-p2p.html#gossip-topics diff --git a/rust/docs/docs/pages/kona/node/design/sequencer.mdx b/docs/public-docs/rust/kona/node/design/sequencer.mdx similarity index 99% rename from rust/docs/docs/pages/kona/node/design/sequencer.mdx rename to docs/public-docs/rust/kona/node/design/sequencer.mdx index fb2608476d1..c8281e01efb 100644 --- a/rust/docs/docs/pages/kona/node/design/sequencer.mdx +++ b/docs/public-docs/rust/kona/node/design/sequencer.mdx @@ -1,4 +1,6 @@ -# Sequencer Mode +--- +title: "Sequencer Mode" +--- The Kona node can operate in **sequencer mode** to build and produce new L2 blocks. In this mode, the node acts as the sequencer for an OP Stack rollup, building L2 blocks on top of the current unsafe head and extending the L2 chain. diff --git a/docs/public-docs/rust/kona/node/faq/overview.mdx b/docs/public-docs/rust/kona/node/faq/overview.mdx new file mode 100644 index 00000000000..40ea9b1358a --- /dev/null +++ b/docs/public-docs/rust/kona/node/faq/overview.mdx @@ -0,0 +1,8 @@ +--- +title: "FAQ" +--- + +1. [Ports](/rust/kona/node/faq/ports) - Detailed account of ports used by the `kona-node` for P2P communication, JSON-RPC APIs, and the Engine API for execution layer communication. + +2. [Profiling](/rust/kona/node/faq/profiling) - Profile performance of the Kona node including CPU profiling and memory analysis. + diff --git a/rust/docs/docs/pages/kona/node/faq/ports.mdx b/docs/public-docs/rust/kona/node/faq/ports.mdx similarity index 96% rename from rust/docs/docs/pages/kona/node/faq/ports.mdx rename to docs/public-docs/rust/kona/node/faq/ports.mdx index 31c06375774..970409777e5 100644 --- a/rust/docs/docs/pages/kona/node/faq/ports.mdx +++ b/docs/public-docs/rust/kona/node/faq/ports.mdx @@ -1,4 +1,6 @@ -# Node Ports +--- +title: "Node Ports" +--- | Service | Default Port | Flag/Env | |-----------------|--------------|------------------------------------------| diff --git a/docs/public-docs/rust/kona/node/faq/profiling.mdx b/docs/public-docs/rust/kona/node/faq/profiling.mdx new file mode 100644 index 00000000000..de6401c9c28 --- /dev/null +++ b/docs/public-docs/rust/kona/node/faq/profiling.mdx @@ -0,0 +1,5 @@ +--- +title: "Profiling the Node" +--- + +Coming soon... diff --git a/rust/docs/docs/pages/kona/node/install/binaries.mdx b/docs/public-docs/rust/kona/node/install/binaries.mdx similarity index 80% rename from rust/docs/docs/pages/kona/node/install/binaries.mdx rename to docs/public-docs/rust/kona/node/install/binaries.mdx index a35f8f6f65a..5fb59360f8e 100644 --- a/rust/docs/docs/pages/kona/node/install/binaries.mdx +++ b/docs/public-docs/rust/kona/node/install/binaries.mdx @@ -1,3 +1,5 @@ -# Kona Binaries +--- +title: "Kona Binaries" +--- Download the latest pre-built binaries from the [GitHub releases page](https://github.com/ethereum-optimism/optimism/releases). diff --git a/rust/docs/docs/pages/kona/node/install/docker.mdx b/docs/public-docs/rust/kona/node/install/docker.mdx similarity index 87% rename from rust/docs/docs/pages/kona/node/install/docker.mdx rename to docs/public-docs/rust/kona/node/install/docker.mdx index d35d2cd595f..076734dc663 100644 --- a/rust/docs/docs/pages/kona/node/install/docker.mdx +++ b/docs/public-docs/rust/kona/node/install/docker.mdx @@ -1,6 +1,6 @@ -import { Callout } from 'vocs/components' - -# Docker +--- +title: "Docker" +--- There are two ways to obtain a Kona Docker image: @@ -9,7 +9,7 @@ There are two ways to obtain a Kona Docker image: Once you have obtained the Docker image, you can run the node. -Jump ahead to [Run a Node using Docker page](/kona/node/run/docker). +Jump ahead to [Run a Node using Docker page](/rust/kona/node/run/docker). ## GitHub @@ -22,13 +22,13 @@ You can obtain the latest `kona-node` image with: docker pull us-docker.pkg.dev/oplabs-tools-artifacts/images/kona-node ``` - + Specify a specific version (e.g. v0.1.0) like so. ```bash docker pull us-docker.pkg.dev/oplabs-tools-artifacts/images/kona-node:v0.1.0 ``` - + You can test the image with: @@ -48,14 +48,14 @@ To build the image from source, navigate to the root of the repository and run: just build-local kona-node ``` - + This will create an image with the tag `kona:local`. To specify a custom tag, just pass it in after `kona-node` in the command above, like so: ```bash just build-local kona-node my-custom-tag ``` - + The build will likely take several minutes. Once it's built, test it with: diff --git a/rust/docs/docs/pages/kona/node/install/overview.mdx b/docs/public-docs/rust/kona/node/install/overview.mdx similarity index 87% rename from rust/docs/docs/pages/kona/node/install/overview.mdx rename to docs/public-docs/rust/kona/node/install/overview.mdx index 89eb9838b25..957ba340122 100644 --- a/rust/docs/docs/pages/kona/node/install/overview.mdx +++ b/docs/public-docs/rust/kona/node/install/overview.mdx @@ -1,3 +1,6 @@ +--- +--- + --- description: Installation instructions for Kona. --- @@ -46,11 +49,11 @@ cargo install just There are three ways to obtain Kona: -- [Docker images](/kona/node/install/docker) -- [Pre-built binaries](/kona/node/install/binaries) -- [Building from source](/kona/node/install/source) +- [Docker images](/rust/kona/node/install/docker) +- [Pre-built binaries](/rust/kona/node/install/binaries) +- [Building from source](/rust/kona/node/install/source) :::note -If you have Docker installed, we recommend using the [Docker recipe](/kona/node/run/docker) configuration +If you have Docker installed, we recommend using the [Docker recipe](/rust/kona/node/run/docker) configuration that will have kona-node, op-reth, Prometheus and Grafana running and syncing with just one command. ::: diff --git a/rust/docs/docs/pages/kona/node/install/source.mdx b/docs/public-docs/rust/kona/node/install/source.mdx similarity index 83% rename from rust/docs/docs/pages/kona/node/install/source.mdx rename to docs/public-docs/rust/kona/node/install/source.mdx index ef3f09f57c1..e8a4bb7d8f6 100644 --- a/rust/docs/docs/pages/kona/node/install/source.mdx +++ b/docs/public-docs/rust/kona/node/install/source.mdx @@ -1,10 +1,12 @@ -# Building from Source +--- +title: "Building from Source" +--- :::note Building from source requires that the Rust toolchain is installed, as well as the `just` command runner. -Visit the [Prerequisites](/kona/node/install/overview) for details on installing Rust and `just`. +Visit the [Prerequisites](/rust/kona/node/install/overview) for details on installing Rust and `just`. ::: @@ -70,13 +72,13 @@ the `rust-version` key in kona's [Cargo.toml](https://github.com/ethereum-optimi If compilation fails with `(signal: 9, SIGKILL: kill)`, this could mean your machine ran out of memory during compilation. If you are on Docker, consider increasing the memory of the container, or use a [pre-built -binary](/kona/node/install/binaries). +binary](/rust/kona/node/install/binaries). If compilation fails with `error: linking with cc failed: exit code: 1`, try running `cargo clean`. ## Next Steps -- Read the [Overview](/kona/intro/overview) to understand Kona's architecture -- Check out the [Binaries](/kona/node/run/binary) documentation -- Explore the [Examples](/kona/sdk/examples/intro) +- Read the [Overview](/rust/kona/intro/overview) to understand Kona's architecture +- Check out the [Binaries](/rust/kona/node/run/binary) documentation +- Explore the [Examples](/rust/kona/sdk/examples/intro) diff --git a/rust/docs/docs/pages/kona/node/monitoring.mdx b/docs/public-docs/rust/kona/node/monitoring.mdx similarity index 97% rename from rust/docs/docs/pages/kona/node/monitoring.mdx rename to docs/public-docs/rust/kona/node/monitoring.mdx index a84af332d1f..ce2e44cdb4d 100644 --- a/rust/docs/docs/pages/kona/node/monitoring.mdx +++ b/docs/public-docs/rust/kona/node/monitoring.mdx @@ -1,4 +1,6 @@ -# Monitoring +--- +title: "Monitoring" +--- The `kona-node` exposes metrics by default. Optionally, metrics can be disabled with the `--metrics.disabled` cli flag. @@ -34,7 +36,6 @@ the `+` icon > `Import Dashboard` > paste the contents of [kona's dashboard][dashboard] in the textbox > `Load`. - [setup]: https://reth.rs/run/monitoring#prometheus--grafana [dashboard]: https://github.com/ethereum-optimism/optimism/blob/develop/rust/kona/docker/recipes/kona-node/grafana/dashboards/overview.json diff --git a/rust/docs/docs/pages/kona/node/requirements.mdx b/docs/public-docs/rust/kona/node/requirements.mdx similarity index 88% rename from rust/docs/docs/pages/kona/node/requirements.mdx rename to docs/public-docs/rust/kona/node/requirements.mdx index 6d797d728e7..58742f5e101 100644 --- a/rust/docs/docs/pages/kona/node/requirements.mdx +++ b/docs/public-docs/rust/kona/node/requirements.mdx @@ -1,12 +1,12 @@ -import { Callout } from 'vocs/components' - -# System Requirements +--- +title: "System Requirements" +--- `kona-node` is an L2 consensus client, so it stores almost nothing on disk! - + Anything stored on disk is configurable, and can be disabled. - + As a rollup node, it always sends L2 blocks over to the execution client (`op-reth` or `op-geth`) for execution. That way, chain state is entirely diff --git a/rust/docs/docs/pages/kona/node/rpc/admin.mdx b/docs/public-docs/rust/kona/node/rpc/admin.mdx similarity index 99% rename from rust/docs/docs/pages/kona/node/rpc/admin.mdx rename to docs/public-docs/rust/kona/node/rpc/admin.mdx index 992f7d4ac6d..81d4ecdc39b 100644 --- a/rust/docs/docs/pages/kona/node/rpc/admin.mdx +++ b/docs/public-docs/rust/kona/node/rpc/admin.mdx @@ -1,4 +1,6 @@ -# Admin RPC Methods +--- +title: "Admin RPC Methods" +--- The `admin` api provides methods for controlling and monitoring Kona's consensus node operations. diff --git a/rust/docs/docs/pages/kona/node/rpc/overview.mdx b/docs/public-docs/rust/kona/node/rpc/overview.mdx similarity index 64% rename from rust/docs/docs/pages/kona/node/rpc/overview.mdx rename to docs/public-docs/rust/kona/node/rpc/overview.mdx index ca13d931514..13735e28f9a 100644 --- a/rust/docs/docs/pages/kona/node/rpc/overview.mdx +++ b/docs/public-docs/rust/kona/node/rpc/overview.mdx @@ -1,4 +1,6 @@ -# JSON-RPC +--- +title: "JSON-RPC" +--- The `kona-node` supports JSON-RPC for interacting with the node. @@ -13,9 +15,9 @@ JSON-RPC methods are grouped into namespaces, which are listed below: | Namespace | Description | Sensitive | | ---------------------------- | ------------------------------------------------------------------------------------------------------ | --------- | -| [`p2p`](/kona/node/rpc/p2p) | The `p2p` API allows you to configure the p2p stack. | Maybe | -| [`rollup`](/kona/node/rpc/rollup) | The `rollup` API provides OP Stack specific rpc methods. | No | -| [`admin`](/kona/node/rpc/admin) | The `admin` API allows you to configure your node. | **Yes** | +| [`p2p`](/rust/kona/node/rpc/p2p) | The `p2p` API allows you to configure the p2p stack. | Maybe | +| [`rollup`](/rust/kona/node/rpc/rollup) | The `rollup` API provides OP Stack specific rpc methods. | No | +| [`admin`](/rust/kona/node/rpc/admin) | The `admin` API allows you to configure your node. | **Yes** | ### Interacting with the RPC diff --git a/rust/docs/docs/pages/kona/node/rpc/p2p.mdx b/docs/public-docs/rust/kona/node/rpc/p2p.mdx similarity index 99% rename from rust/docs/docs/pages/kona/node/rpc/p2p.mdx rename to docs/public-docs/rust/kona/node/rpc/p2p.mdx index b60bef26838..52270b3672d 100644 --- a/rust/docs/docs/pages/kona/node/rpc/p2p.mdx +++ b/docs/public-docs/rust/kona/node/rpc/p2p.mdx @@ -1,4 +1,6 @@ -# P2P RPC Methods +--- +title: "P2P RPC Methods" +--- The `p2p` api provides methods for interacting with Kona's P2P stack. diff --git a/rust/docs/docs/pages/kona/node/rpc/rollup.mdx b/docs/public-docs/rust/kona/node/rpc/rollup.mdx similarity index 99% rename from rust/docs/docs/pages/kona/node/rpc/rollup.mdx rename to docs/public-docs/rust/kona/node/rpc/rollup.mdx index ba88245c464..bb287a48fef 100644 --- a/rust/docs/docs/pages/kona/node/rpc/rollup.mdx +++ b/docs/public-docs/rust/kona/node/rpc/rollup.mdx @@ -1,4 +1,6 @@ -# Rollup RPC Methods +--- +title: "Rollup RPC Methods" +--- The `optimism` API provides methods for interacting with Kona's rollup state and configuration. diff --git a/rust/docs/docs/pages/kona/node/run/binary.mdx b/docs/public-docs/rust/kona/node/run/binary.mdx similarity index 95% rename from rust/docs/docs/pages/kona/node/run/binary.mdx rename to docs/public-docs/rust/kona/node/run/binary.mdx index e09294a680a..01166f36b30 100644 --- a/rust/docs/docs/pages/kona/node/run/binary.mdx +++ b/docs/public-docs/rust/kona/node/run/binary.mdx @@ -1,11 +1,11 @@ -import { Callout } from 'vocs/components' - -# Run Kona Node as a Binary +--- +title: "Run Kona Node as a Binary" +--- :::note If you haven't already built the `kona-node` binary, head over to the -[Installation](/kona/node/install/overview) guide. +[Installation](/rust/kona/node/install/overview) guide. ::: @@ -26,16 +26,16 @@ to specify the rollup config using the custom `--l2-config-file` cli flag. More on that in the [More Detailed Node Docs Section](#-More-Detailed-Node-Docs). - + This tutorial walks through running the `kona-node` as a binary. To use docker, head over to the -[Docker Guide](/kona/node/run/docker) which uses a `docker-compose` +[Docker Guide](/rust/kona/node/run/docker) which uses a `docker-compose` setup provided by `kona`. The `docker-compose` setup automatically bootstraps the `kona-node` with `op-reth`, provisioning grafana dashboards and a default Prometheus configuration. It is encouraged to follow the -[Docker Guide](/kona/node/run/docker) to avoid misconfigurations. - +[Docker Guide](/rust/kona/node/run/docker) to avoid misconfigurations. + The `kona-node` requires a few CLI flags. @@ -71,7 +71,7 @@ Kona has a `generate-jwt` justfile target that can be used to create the `jwt.hex` file. Run `just generate-jwt`. - + The JWT token file path passed into `--authrpc.jwtsecret` **MUST** be the same as the one passed into the `kona-node`. @@ -85,7 +85,7 @@ find one, it will create a JWT token in a new `jwt.hex` file. To specify the path to the file that contains the JWT token, pass the file path into the `--l2.jwt-secret` CLI flag or use the `KONA_NODE_L2_ENGINE_AUTH` environment variable. - + Then, run the `kona-node` using Base's chain id - `8453`. @@ -169,7 +169,7 @@ There are a number of important defaults. This can be configured using the `--metrics.enabled`, `--metrics.port`, and `--metrics.addr` cli flags. - + If a file path to a rollup config is _not_ specified via the `--l2-config-file` cli flag, the Rollup Config will be loaded via the [superchain registry][scr]. @@ -181,7 +181,7 @@ using a set of override flags provided by the `kona-node`. Override flags (for example `--canyon-override`) can be viewed in the help menu by running `kona-node node --help`. The only overrides currently supported are hardfork timestamps in seconds. - + A set of CLI flags relating to the sequencer and supervisor are also available to the `kona-node` binary. @@ -215,7 +215,7 @@ engine actor to insert into the chain. ### Configuring a Dockerfile To learn more about running a `kona-node` using docker, check -out the [docker guide](/kona/node/run/docker). +out the [docker guide](/rust/kona/node/run/docker). [tracing-env]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#method.from_default_env diff --git a/rust/docs/docs/pages/kona/node/run/docker.mdx b/docs/public-docs/rust/kona/node/run/docker.mdx similarity index 95% rename from rust/docs/docs/pages/kona/node/run/docker.mdx rename to docs/public-docs/rust/kona/node/run/docker.mdx index 548c6b5f1a8..3556f3fc685 100644 --- a/rust/docs/docs/pages/kona/node/run/docker.mdx +++ b/docs/public-docs/rust/kona/node/run/docker.mdx @@ -1,11 +1,13 @@ -# Docker Guide +--- +title: "Docker Guide" +--- :::info This guide uses Kona's pre-packaged docker config. For detailed usage of the `kona-node` binary, head -over to [the binary guide](/kona/node/run/binary). +over to [the binary guide](/rust/kona/node/run/binary). ::: @@ -112,7 +114,7 @@ kona-node: --l2-trust-rpc false # Add this for untrusted L2 RPCs ``` -See the [configuration guide](/kona/node/configuration#rpc-trust-configuration) for more details on RPC trust settings. +See the [configuration guide](/rust/kona/node/configuration#rpc-trust-configuration) for more details on RPC trust settings. ### Port Configuration diff --git a/rust/docs/docs/pages/kona/node/run/mechanics.mdx b/docs/public-docs/rust/kona/node/run/mechanics.mdx similarity index 98% rename from rust/docs/docs/pages/kona/node/run/mechanics.mdx rename to docs/public-docs/rust/kona/node/run/mechanics.mdx index d706022f33c..65a32749a14 100644 --- a/rust/docs/docs/pages/kona/node/run/mechanics.mdx +++ b/docs/public-docs/rust/kona/node/run/mechanics.mdx @@ -1,4 +1,6 @@ -# How it Works +--- +title: "How it Works" +--- Kona brings together a powerful suite of `no-std` and `std` Rust components, purpose-built for the OP Stack. At the heart of this ecosystem is the @@ -40,7 +42,7 @@ There are a few core architectural pieces of the `kona-node`. - **P2P Networking:** Enables block gossip and peer discovery. For an in-depth breakdown of these three pillars and a detailed design -of the `kona-node`, visit the [Node Design section](/kona/node/design/intro). +of the `kona-node`, visit the [Node Design section](/rust/kona/node/design/intro). Additionally, an RPC server exposes essential methods, including the [L2 Output RPC method][l2o-rpc]. diff --git a/rust/docs/docs/pages/kona/node/run/overview.mdx b/docs/public-docs/rust/kona/node/run/overview.mdx similarity index 75% rename from rust/docs/docs/pages/kona/node/run/overview.mdx rename to docs/public-docs/rust/kona/node/run/overview.mdx index c9d76ca5975..df1296b8578 100644 --- a/rust/docs/docs/pages/kona/node/run/overview.mdx +++ b/docs/public-docs/rust/kona/node/run/overview.mdx @@ -1,6 +1,8 @@ -# Run a Node +--- +title: "Run a Node" +--- -Now that you have [installed the `kona-node`](/kona/node/install/overview), +Now that you have [installed the `kona-node`](/rust/kona/node/install/overview), it's time to run it. In this section, we'll guide you through running the kona-node on @@ -33,6 +35,6 @@ to the superchain-registry! Learn how to configure and monitor your node: -- **[Configuration](/kona/node/configuration)** - Configure your node -- **[Monitoring](/kona/node/monitoring)** - Set up logs, metrics, and observability +- **[Configuration](/rust/kona/node/configuration)** - Configure your node +- **[Monitoring](/rust/kona/node/monitoring)** - Set up logs, metrics, and observability diff --git a/rust/docs/docs/pages/kona/node/subcommands.mdx b/docs/public-docs/rust/kona/node/subcommands.mdx similarity index 93% rename from rust/docs/docs/pages/kona/node/subcommands.mdx rename to docs/public-docs/rust/kona/node/subcommands.mdx index 79f15a159ea..cfdc504fb5c 100644 --- a/rust/docs/docs/pages/kona/node/subcommands.mdx +++ b/docs/public-docs/rust/kona/node/subcommands.mdx @@ -1,4 +1,6 @@ -# `kona-node` Subcommands +--- +title: "`kona-node` Subcommands" +--- Below are the available subcommands for `kona-node`: diff --git a/rust/docs/docs/pages/kona/rfc/active/intro.mdx b/docs/public-docs/rust/kona/rfc/active/intro.mdx similarity index 85% rename from rust/docs/docs/pages/kona/rfc/active/intro.mdx rename to docs/public-docs/rust/kona/rfc/active/intro.mdx index 0836df34385..62176e6e9ab 100644 --- a/rust/docs/docs/pages/kona/rfc/active/intro.mdx +++ b/docs/public-docs/rust/kona/rfc/active/intro.mdx @@ -1,4 +1,7 @@ -# Request For Comment [RFC] +--- +title: "Request For Comment" +description: "RFC" +--- Documents in this section are in the request-for-comment stage. diff --git a/rust/docs/docs/pages/kona/rfc/archived/monorepo.mdx b/docs/public-docs/rust/kona/rfc/archived/monorepo.mdx similarity index 98% rename from rust/docs/docs/pages/kona/rfc/archived/monorepo.mdx rename to docs/public-docs/rust/kona/rfc/archived/monorepo.mdx index eccd69104a1..cce3dc96233 100644 --- a/rust/docs/docs/pages/kona/rfc/archived/monorepo.mdx +++ b/docs/public-docs/rust/kona/rfc/archived/monorepo.mdx @@ -1,6 +1,6 @@ -import { Callout } from 'vocs/components' - -# Monorepo Project +--- +title: "Monorepo Project" +--- | Document | Date | Decision | | ----------- | ------------- | -------- | @@ -118,7 +118,7 @@ crates/ ... ``` - + Crates denoted with `<-- Has Maili Shadow` are ported from `maili`, but contain a nested crate with the `maili-` prefix instead of `kona-` prefix. These crates re-export their `kona-` equivalent crates. This @@ -126,7 +126,7 @@ allows downstream users to not need to change their dependencies to keep using `maili-` crates! Eventually once the kona monorepo matures, and downstream consumers use `kona-` crates instead of `maili-`, these can be removed. - + #### Maili Shadow Example diff --git a/rust/docs/docs/pages/kona/rfc/archived/umbrellas.mdx b/docs/public-docs/rust/kona/rfc/archived/umbrellas.mdx similarity index 95% rename from rust/docs/docs/pages/kona/rfc/archived/umbrellas.mdx rename to docs/public-docs/rust/kona/rfc/archived/umbrellas.mdx index 010dc7e61b5..da9cb6a56e3 100644 --- a/rust/docs/docs/pages/kona/rfc/archived/umbrellas.mdx +++ b/docs/public-docs/rust/kona/rfc/archived/umbrellas.mdx @@ -1,18 +1,18 @@ -import { Callout } from 'vocs/components' +--- +title: "Umbrella Crates" +--- -# Umbrella Crates - - + TL;DR, this is a proposal to introduce tiny crates inside each container directory (e.g. `crates/protocol/`) to re-export all crates contained in that directory. - + ## Context #### Repository Structure -Kona now has a [monorepo](/kona/rfc/archived/monorepo) structure that merged +Kona now has a [monorepo](/rust/kona/rfc/archived/monorepo) structure that merged `maili` and `hilo` crates into `kona`. This introduces a number of higher-level directories that hold a variety of crates themselves. As of the time at which this document was written the `kona` repository loosely looks like the following. diff --git a/rust/docs/docs/pages/kona/run.mdx b/docs/public-docs/rust/kona/run.mdx similarity index 66% rename from rust/docs/docs/pages/kona/run.mdx rename to docs/public-docs/rust/kona/run.mdx index 6469bcae282..10d19100f2e 100644 --- a/rust/docs/docs/pages/kona/run.mdx +++ b/docs/public-docs/rust/kona/run.mdx @@ -1,24 +1,24 @@ -import { Callout } from 'vocs/components' +--- +title: "Run a Node" +--- -# Run a Node - - + This tutorial walks through running the `kona-node` as a binary. To use docker, head over to the -[Docker Guide](/kona/node/install/docker) which uses a `docker-compose` +[Docker Guide](/rust/kona/node/install/docker) which uses a `docker-compose` setup provided by `kona`. The `docker-compose` setup automatically bootstraps the `kona-node` with `op-reth`, provisioning grafana dashboards and a default Prometheus data source. - + ## Prerequisites In order to follow this tutorial, you'll need: 1. An L1 Archive node (e.g., `op-geth`) with enough history for the rollup network you want to run. -2. A `kona-node` binary. See [installation](/kona/node/install/binaries) for instructions. -3. A rollup configuration file. See [rollup configuration](/kona/sdk/protocol/genesis/rollup-config) for more information. +2. A `kona-node` binary. See [installation](/rust/kona/node/install/binaries) for instructions. +3. A rollup configuration file. See [rollup configuration](/rust/kona/sdk/protocol/genesis/rollup-config) for more information. ## Quick Start @@ -50,7 +50,7 @@ kona-node --help ## Next Steps -- [Docker Support](/kona/node/run/docker) - Run with Docker -- [Monitoring](/kona/node/monitoring) - Set up monitoring and metrics -- [CLI Reference](/kona/node/configuration) - Complete CLI documentation -- [Subcommands](/kona/node/subcommands) - Available subcommands +- [Docker Support](/rust/kona/node/run/docker) - Run with Docker +- [Monitoring](/rust/kona/node/monitoring) - Set up monitoring and metrics +- [CLI Reference](/rust/kona/node/configuration) - Complete CLI documentation +- [Subcommands](/rust/kona/node/subcommands) - Available subcommands diff --git a/rust/docs/docs/pages/kona/sdk/examples/custom-derivation-pipeline.mdx b/docs/public-docs/rust/kona/sdk/examples/custom-derivation-pipeline.mdx similarity index 98% rename from rust/docs/docs/pages/kona/sdk/examples/custom-derivation-pipeline.mdx rename to docs/public-docs/rust/kona/sdk/examples/custom-derivation-pipeline.mdx index 06a6e218f80..c4e4414f7a1 100644 --- a/rust/docs/docs/pages/kona/sdk/examples/custom-derivation-pipeline.mdx +++ b/docs/public-docs/rust/kona/sdk/examples/custom-derivation-pipeline.mdx @@ -1,6 +1,6 @@ -import { Callout } from 'vocs/components' - -# Custom Derivation Pipeline Stage +--- +title: "Custom Derivation Pipeline Stage" +--- Extend Kona's derivation pipeline by wrapping the top-level `AttributesQueue` stage with custom logic for monitoring, validation, or transformation. @@ -132,9 +132,9 @@ where } ``` - + Custom stages wrap the `AttributesQueue` (top-level stage). For deeper pipeline modifications, you'd need to rebuild the entire pipeline. - + ## Integration diff --git a/rust/docs/docs/pages/kona/sdk/examples/executor-test-fixtures.mdx b/docs/public-docs/rust/kona/sdk/examples/executor-test-fixtures.mdx similarity index 95% rename from rust/docs/docs/pages/kona/sdk/examples/executor-test-fixtures.mdx rename to docs/public-docs/rust/kona/sdk/examples/executor-test-fixtures.mdx index 68388f2a3d5..b9eb4b3a07f 100644 --- a/rust/docs/docs/pages/kona/sdk/examples/executor-test-fixtures.mdx +++ b/docs/public-docs/rust/kona/sdk/examples/executor-test-fixtures.mdx @@ -1,4 +1,6 @@ -# `kona-executor` test fixtures +--- +title: "`kona-executor` test fixtures" +--- The `StatelessL2Builder` type uses static test data fixtures to run stateless execution of certain blocks offline. The test data fixtures include: diff --git a/rust/docs/docs/pages/kona/sdk/examples/frames-to-batch.mdx b/docs/public-docs/rust/kona/sdk/examples/frames-to-batch.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/examples/frames-to-batch.mdx rename to docs/public-docs/rust/kona/sdk/examples/frames-to-batch.mdx index 4def514c4ef..ca36edf6689 100644 --- a/rust/docs/docs/pages/kona/sdk/examples/frames-to-batch.mdx +++ b/docs/public-docs/rust/kona/sdk/examples/frames-to-batch.mdx @@ -1,4 +1,6 @@ -# Transform Frames into a Batch +--- +title: "Transform Frames into a Batch" +--- This example walks through transforming [`Frame`][frame]s into the [`Batch`][batch] types. diff --git a/docs/public-docs/rust/kona/sdk/examples/intro.mdx b/docs/public-docs/rust/kona/sdk/examples/intro.mdx new file mode 100644 index 00000000000..3fa4f2fb110 --- /dev/null +++ b/docs/public-docs/rust/kona/sdk/examples/intro.mdx @@ -0,0 +1,12 @@ +--- +title: "Examples" +--- + +Examples for working with `kona` crates. + +- [Load a Rollup Config for a Chain ID](/rust/kona/sdk/examples/load-a-rollup-config) +- [Transform Frames to a Batch](/rust/kona/sdk/examples/frames-to-batch) +- [Create a new L1BlockInfoTx Hardfork Variant](/rust/kona/sdk/examples/new-l1-block-info-tx-hardfork) +- [Create a new `kona-executor` test fixture](/rust/kona/sdk/examples/executor-test-fixtures) +- [Configuring P2P Network Peer Scoring](/rust/kona/sdk/examples/p2p-peer-scoring) +- [Custom Derivation Pipeline with New Stage](/rust/kona/sdk/examples/custom-derivation-pipeline) diff --git a/rust/docs/docs/pages/kona/sdk/examples/load-a-rollup-config.mdx b/docs/public-docs/rust/kona/sdk/examples/load-a-rollup-config.mdx similarity index 89% rename from rust/docs/docs/pages/kona/sdk/examples/load-a-rollup-config.mdx rename to docs/public-docs/rust/kona/sdk/examples/load-a-rollup-config.mdx index fac36ba7c5e..a666daf73c2 100644 --- a/rust/docs/docs/pages/kona/sdk/examples/load-a-rollup-config.mdx +++ b/docs/public-docs/rust/kona/sdk/examples/load-a-rollup-config.mdx @@ -1,6 +1,6 @@ -import { Callout } from 'vocs/components' - -# Loading a Rollup Config from a Chain ID +--- +title: "Loading a Rollup Config from a Chain ID" +--- In this section, the code examples demonstrate loading the rollup config for the given L2 Chain ID. @@ -21,7 +21,7 @@ let op_mainnet_config = ROLLUP_CONFIGS.get(&OP_MAINNET_CHAIN_ID).expect("infalli assert_eq!(op_mainnet_config.chain_id, OP_MAINNET_CHAIN_ID); ``` - + Available Configs @@ -30,7 +30,7 @@ from the [superchain-registry][registry] for their respective chain ids. Note though, that this requires `serde` since it deserializes the rollup configs dynamically from json files. - + [kona-registry]: https://crates.io/crates/kona-registry [registry]: https://github.com/ethereum-optimism/superchain-registry diff --git a/rust/docs/docs/pages/kona/sdk/examples/new-l1-block-info-tx-hardfork.mdx b/docs/public-docs/rust/kona/sdk/examples/new-l1-block-info-tx-hardfork.mdx similarity index 96% rename from rust/docs/docs/pages/kona/sdk/examples/new-l1-block-info-tx-hardfork.mdx rename to docs/public-docs/rust/kona/sdk/examples/new-l1-block-info-tx-hardfork.mdx index 4c14f028a01..e61cf129acc 100644 --- a/rust/docs/docs/pages/kona/sdk/examples/new-l1-block-info-tx-hardfork.mdx +++ b/docs/public-docs/rust/kona/sdk/examples/new-l1-block-info-tx-hardfork.mdx @@ -1,17 +1,17 @@ -import { Callout } from 'vocs/components' - -# Create a `L1BlockInfoTx` Variant for a new `Hardfork` +--- +title: "Create a `L1BlockInfoTx` Variant for a new `Hardfork`" +--- This example walks through creating a variant of the [`L1BlockInfoTx`][info-tx] for a new Hardfork. - + This example is very verbose. To grok required changes, view [this PR diff][pr-diff] which introduces Isthmus hardfork changes to the `L1BlockInfoTx` with a new variant. - + ## Required Genesis Updates @@ -103,7 +103,6 @@ some new "operator fee" fields as part of it's `L1BlockInfoIsthmus` type. Some new error variants to the [`BlockInfoError`][bie] are needed as well. - [bie]: https://docs.rs/kona-protocol/latest/kona_protocol/enum.BlockInfoError.html [pr-diff]: https://github.com/alloy-rs/op-alloy/pull/130/files [decode-calldata]: https://docs.rs/kona-protocol/latest/kona_protocol/enum.L1BlockInfoTx.html#method.decode_calldata diff --git a/rust/docs/docs/pages/kona/sdk/examples/p2p-peer-scoring.mdx b/docs/public-docs/rust/kona/sdk/examples/p2p-peer-scoring.mdx similarity index 98% rename from rust/docs/docs/pages/kona/sdk/examples/p2p-peer-scoring.mdx rename to docs/public-docs/rust/kona/sdk/examples/p2p-peer-scoring.mdx index 0d6b22b9a61..0d474e5ce73 100644 --- a/rust/docs/docs/pages/kona/sdk/examples/p2p-peer-scoring.mdx +++ b/docs/public-docs/rust/kona/sdk/examples/p2p-peer-scoring.mdx @@ -1,6 +1,6 @@ -import { Callout } from 'vocs/components' - -# Configuring P2P Network Peer Scoring +--- +title: "Configuring P2P Network Peer Scoring" +--- Peer scoring is a critical mechanism in Kona's P2P network that evaluates the behavior and reliability of peers. By assigning scores to peers based on their performance, the network @@ -19,13 +19,13 @@ Peer scoring in Kona evaluates peers based on multiple factors: - **IP Colocation**: Penalties for multiple peers from the same IP - **Behavior Patterns**: General peer behavior and responsiveness - + Peer scoring helps maintain network quality by identifying and removing peers that: - Fail to deliver messages reliably - Send invalid or spam messages - Exhibit malicious behavior patterns - Have poor network connectivity - + ## Peer Score Levels @@ -134,12 +134,12 @@ let topics = vec![ let topic_scores = PeerScoreLevel::topic_scores(topics, block_time); ``` - + Topic scoring is being phased out in the OP Stack. It's disabled by default and should only be enabled for backwards compatibility or debugging purposes. Use the `--p2p.topic-scoring` flag to enable if needed. - + ### Gossipsub Mesh Parameters @@ -325,13 +325,13 @@ assert_eq!(*PEER_SCORE_INSPECT_FREQUENCY, Duration::from_secs(15)); // 4. Updates metrics for monitoring ``` - + Peer scores and banning events are recorded in metrics. Monitor these metrics to understand your node's peer health: - `kona_gossip_peer_scores`: Histogram of peer scores - `kona_gossip_banned_peers`: Counter of banned peers - `kona_gossip_peer_connection_duration_seconds`: Connection durations - + ## Best Practices diff --git a/rust/docs/docs/pages/kona/sdk/fpp-dev/env.mdx b/docs/public-docs/rust/kona/sdk/fpp-dev/env.mdx similarity index 94% rename from rust/docs/docs/pages/kona/sdk/fpp-dev/env.mdx rename to docs/public-docs/rust/kona/sdk/fpp-dev/env.mdx index 61b382380dd..ba283dba901 100644 --- a/rust/docs/docs/pages/kona/sdk/fpp-dev/env.mdx +++ b/docs/public-docs/rust/kona/sdk/fpp-dev/env.mdx @@ -1,6 +1,8 @@ -# Environment +--- +title: "Environment" +--- -Before kicking off the development of your own [Fault Proof Program](/kona/glossary#fault-proof-program), +Before kicking off the development of your own [Fault Proof Program](/rust/kona/glossary#fault-proof-program), it's important to understand the environment that your program will be running in. The FPP runs on top of a custom FPVM target, which is typically a VM with a modified subset of an existing reduced instruction set architecture and a subset of Linux syscalls. The FPVM is designed to @@ -10,7 +12,7 @@ communication with the `host` (the FPVM), and other implementation-specific feat ## Host and Client Communication While the program is running on top of the FPVM, it is considered to be in the `client` role, while the VM is in the `host` role. The only way for the `client` and `host` -to communicate with one another is synchronously through the [Preimage ABI](/kona/glossary#preimage-abi) ([specification][preimage-specs]). +to communicate with one another is synchronously through the [Preimage ABI](/rust/kona/glossary#preimage-abi) ([specification][preimage-specs]). In order for the `client` to read from the `host`, the `read` and `write` syscalls are modified within the FPVM to allow the `client` to request preparation of and read foreign data. @@ -41,9 +43,9 @@ sequenceDiagram Below, we have a full architecture diagram of the [`op-program`][op-program] (source: [fault proof specs][fp-specs]), the reference implementation for the OP Stack's Fault Proof Program, which has the objective of verifying claims about the state of an [OP Stack][op-stack] layer two. -![op-program-architecture](/op-program-fpp.svg) +![op-program-architecture](/rust/op-program-fpp.svg) -In this program, execution and derivation of the L2 chain is performed within it, and ultimately the claimed state of the L2 chain is verified in the [prologue](/kona/sdk/fpp-dev/prologue) stage. +In this program, execution and derivation of the L2 chain is performed within it, and ultimately the claimed state of the L2 chain is verified in the [prologue](/rust/kona/sdk/fpp-dev/prologue) stage. It communicates with the `host` for two reasons: diff --git a/rust/docs/docs/pages/kona/sdk/fpp-dev/epilogue.mdx b/docs/public-docs/rust/kona/sdk/fpp-dev/epilogue.mdx similarity index 75% rename from rust/docs/docs/pages/kona/sdk/fpp-dev/epilogue.mdx rename to docs/public-docs/rust/kona/sdk/fpp-dev/epilogue.mdx index 253f4c7fe5d..8bd053da8a7 100644 --- a/rust/docs/docs/pages/kona/sdk/fpp-dev/epilogue.mdx +++ b/docs/public-docs/rust/kona/sdk/fpp-dev/epilogue.mdx @@ -1,8 +1,10 @@ -# Epilogue +--- +title: "Epilogue" +--- The epilogue stage of the program is intended to perform the final validation on the outputs from the -[execution phase](/kona/sdk/fpp-dev/execution). In most programs, this entails comparing the outputs of the execution phase -to portions of the bootstrap data made available during the [prologue phase](/kona/sdk/fpp-dev/prologue). +[execution phase](/rust/kona/sdk/fpp-dev/execution). In most programs, this entails comparing the outputs of the execution phase +to portions of the bootstrap data made available during the [prologue phase](/rust/kona/sdk/fpp-dev/prologue). Generally, this phase should consist almost entirely of validation steps. diff --git a/rust/docs/docs/pages/kona/sdk/fpp-dev/execution.mdx b/docs/public-docs/rust/kona/sdk/fpp-dev/execution.mdx similarity index 78% rename from rust/docs/docs/pages/kona/sdk/fpp-dev/execution.mdx rename to docs/public-docs/rust/kona/sdk/fpp-dev/execution.mdx index 0bf5d36fbbc..93b27c4a86c 100644 --- a/rust/docs/docs/pages/kona/sdk/fpp-dev/execution.mdx +++ b/docs/public-docs/rust/kona/sdk/fpp-dev/execution.mdx @@ -1,10 +1,12 @@ -# Execution +--- +title: "Execution" +--- The execution phase of the program is commonly the heaviest portion of the fault proof program, where the computation that is being verified is performed. -This phase consumes the outputs of the [prologue phase](/kona/sdk/fpp-dev/prologue), and performs the bulk of the verifiable -computation. After execution has concluded, the outputs are passed along to the [epilogue phase](/kona/sdk/fpp-dev/epilogue) for +This phase consumes the outputs of the [prologue phase](/rust/kona/sdk/fpp-dev/prologue), and performs the bulk of the verifiable +computation. After execution has concluded, the outputs are passed along to the [epilogue phase](/rust/kona/sdk/fpp-dev/epilogue) for final verification. ## Example diff --git a/rust/docs/docs/pages/kona/sdk/fpp-dev/intro.mdx b/docs/public-docs/rust/kona/sdk/fpp-dev/intro.mdx similarity index 88% rename from rust/docs/docs/pages/kona/sdk/fpp-dev/intro.mdx rename to docs/public-docs/rust/kona/sdk/fpp-dev/intro.mdx index f89d3af068c..fe36d06df2e 100644 --- a/rust/docs/docs/pages/kona/sdk/fpp-dev/intro.mdx +++ b/docs/public-docs/rust/kona/sdk/fpp-dev/intro.mdx @@ -1,6 +1,8 @@ -# Fault Proof Program Development +--- +title: "Fault Proof Program Development" +--- -This chapter provides an overview of [Fault Proof Program](/kona/glossary#fault-proof-program) development +This chapter provides an overview of [Fault Proof Program](/rust/kona/glossary#fault-proof-program) development on top of the custom FPVM targets supported by [Kona][kona]. At a high level, a Fault Proof Program is not much different from a regular `no_std` Rust program. A custom entrypoint is provided, and the program diff --git a/docs/public-docs/rust/kona/sdk/fpp-dev/io.mdx b/docs/public-docs/rust/kona/sdk/fpp-dev/io.mdx new file mode 100644 index 00000000000..93d91cbc76e --- /dev/null +++ b/docs/public-docs/rust/kona/sdk/fpp-dev/io.mdx @@ -0,0 +1,4 @@ +--- +title: "IO" +--- + diff --git a/rust/docs/docs/pages/kona/sdk/fpp-dev/prologue.mdx b/docs/public-docs/rust/kona/sdk/fpp-dev/prologue.mdx similarity index 90% rename from rust/docs/docs/pages/kona/sdk/fpp-dev/prologue.mdx rename to docs/public-docs/rust/kona/sdk/fpp-dev/prologue.mdx index a05a4e94ac8..77edbb09c0f 100644 --- a/rust/docs/docs/pages/kona/sdk/fpp-dev/prologue.mdx +++ b/docs/public-docs/rust/kona/sdk/fpp-dev/prologue.mdx @@ -1,10 +1,12 @@ -# Prologue +--- +title: "Prologue" +--- The prologue stage of the program is commonly responsible for bootstrapping the program with inputs from an external -source, pulled in through the [Host-Client communication](/kona/sdk/fpp-dev/env#host---client-communication) implementation. +source, pulled in through the [Host-Client communication](/rust/kona/sdk/fpp-dev/env#host---client-communication) implementation. As a rule of thumb, the prologue implementation should be kept minimal, and should not do much more than establish -the inputs for the [execution phase](/kona/sdk/fpp-dev/execution). +the inputs for the [execution phase](/rust/kona/sdk/fpp-dev/execution). ## Example diff --git a/rust/docs/docs/pages/kona/sdk/fpp-dev/targets.mdx b/docs/public-docs/rust/kona/sdk/fpp-dev/targets.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/fpp-dev/targets.mdx rename to docs/public-docs/rust/kona/sdk/fpp-dev/targets.mdx index 33fd1120bff..f01a4135ab2 100644 --- a/rust/docs/docs/pages/kona/sdk/fpp-dev/targets.mdx +++ b/docs/public-docs/rust/kona/sdk/fpp-dev/targets.mdx @@ -1,4 +1,6 @@ -# Supported Targets +--- +title: "Supported Targets" +--- Kona seeks to support all FPVM targets that LLVM and `rustc` can offer introductory support for. Below is a matrix of features that Kona offers for each FPVM target: diff --git a/rust/docs/docs/pages/kona/sdk/overview.mdx b/docs/public-docs/rust/kona/sdk/overview.mdx similarity index 87% rename from rust/docs/docs/pages/kona/sdk/overview.mdx rename to docs/public-docs/rust/kona/sdk/overview.mdx index 33259ecaef5..458c9e0ac8a 100644 --- a/rust/docs/docs/pages/kona/sdk/overview.mdx +++ b/docs/public-docs/rust/kona/sdk/overview.mdx @@ -1,4 +1,6 @@ -# Kona as a Library +--- +title: "Kona as a Library" +--- Kona is designed as a modular, library-first OP Stack implementation in Rust. This design philosophy allows developers to integrate Kona components into their applications and build custom solutions on top of the OP Stack. @@ -31,4 +33,4 @@ kona-node = "0.1" ## Examples -See the [Examples](/kona/sdk/examples/intro) section for practical usage examples and integration patterns. +See the [Examples](/rust/kona/sdk/examples/intro) section for practical usage examples and integration patterns. diff --git a/rust/docs/docs/pages/kona/sdk/proof/custom-backend.mdx b/docs/public-docs/rust/kona/sdk/proof/custom-backend.mdx similarity index 98% rename from rust/docs/docs/pages/kona/sdk/proof/custom-backend.mdx rename to docs/public-docs/rust/kona/sdk/proof/custom-backend.mdx index d0375302ee1..8dfb0366a13 100644 --- a/rust/docs/docs/pages/kona/sdk/proof/custom-backend.mdx +++ b/docs/public-docs/rust/kona/sdk/proof/custom-backend.mdx @@ -1,4 +1,6 @@ -# Custom Backends +--- +title: "Custom Backends" +--- ## Understanding the OP Stack STF @@ -26,7 +28,7 @@ verifiable manner. ## Backend Traits -Covered in the [FPVM Backend](/kona/sdk/proof/fpvm-backend) section of the book, `kona-client` ships with an implementation of +Covered in the [FPVM Backend](/rust/kona/sdk/proof/fpvm-backend) section of the book, `kona-client` ships with an implementation of `kona-derive` and `kona-executor`'s data source traits which pull in data over the [PreimageOracle ABI][preimage-specs]. However, running `kona-client` on top of a different verifiable environment, i.e. a zkVM or TEE, is also possible diff --git a/rust/docs/docs/pages/kona/sdk/proof/exec-ext.mdx b/docs/public-docs/rust/kona/sdk/proof/exec-ext.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/proof/exec-ext.mdx rename to docs/public-docs/rust/kona/sdk/proof/exec-ext.mdx index 07118c7fbdd..4e7732e2fba 100644 --- a/rust/docs/docs/pages/kona/sdk/proof/exec-ext.mdx +++ b/docs/public-docs/rust/kona/sdk/proof/exec-ext.mdx @@ -1,4 +1,6 @@ -# `kona-executor` Extensions +--- +title: "`kona-executor` Extensions" +--- The `kona-executor` crate offers a to-spec, stateless implementation of the OP Stack STF. However, due to the power of [`alloy-evm`][alloy-evm]'s factory abstractions, the logic of the STF can be easily customized. diff --git a/rust/docs/docs/pages/kona/sdk/proof/fpvm-backend.mdx b/docs/public-docs/rust/kona/sdk/proof/fpvm-backend.mdx similarity index 92% rename from rust/docs/docs/pages/kona/sdk/proof/fpvm-backend.mdx rename to docs/public-docs/rust/kona/sdk/proof/fpvm-backend.mdx index 9262396cf81..26c204ffa52 100644 --- a/rust/docs/docs/pages/kona/sdk/proof/fpvm-backend.mdx +++ b/docs/public-docs/rust/kona/sdk/proof/fpvm-backend.mdx @@ -1,17 +1,19 @@ -# FPVM Backend +--- +title: "FPVM Backend" +--- -> 📖 Before reading this section of the book, it is advised to read the [Fault Proof Program Environment](/kona/sdk/fpp-dev/env) +> 📖 Before reading this section of the book, it is advised to read the [Fault Proof Program Environment](/rust/kona/sdk/fpp-dev/env) > section to familiarize yourself with the PreimageOracle IO pattern. Kona is effectively split into three parts: - OP Stack state transition logic (`kona-derive`, `kona-executor`, `kona-mpt`) - OP Stack state transition proof SDK (`kona-preimage`, `kona-proof`) -- [Fault Proof VM](/kona/glossary#fault-proof-vm) IO and utilities +- [Fault Proof VM](/rust/kona/glossary#fault-proof-vm) IO and utilities (`kona-std-fpvm`, `kona-std-fpvm-proc`) This section of the book focuses on the usage of `kona-std-fpvm` and `kona-preimage` to facilitate communication between host and client -for programs running on top of the [FPVM targets](/kona/sdk/fpp-dev/env). +for programs running on top of the [FPVM targets](/rust/kona/sdk/fpp-dev/env). ## Host and Client Communication API @@ -51,14 +53,14 @@ io::exit(0); With this library, you can implement a custom communication protocol between the host and client, or extend the existing [PreimageOracle ABI][preimage-specs]. However, for most developers, we recommend sticking with `kona-preimage` -when developing programs that target the [FPVMs](/kona/sdk/fpp-dev/env), barring needs like printing directly to +when developing programs that target the [FPVMs](/rust/kona/sdk/fpp-dev/env), barring needs like printing directly to `stdout`. ### `kona-preimage` `kona-preimage` is an implementation of the [PreimageOracle ABI][preimage-specs]. This crate enables synchronous communication between the host and client program, described in -[Host - Client Communication](/kona/sdk/fpp-dev/env#host---client-communication) in the FPP Dev environment section of the +[Host - Client Communication](/rust/kona/sdk/fpp-dev/env#host---client-communication) in the FPP Dev environment section of the book. The crate is built around the [`Channel`](https://docs.rs/kona-preimage/latest/kona_preimage/trait.Channel.html) trait, @@ -98,7 +100,7 @@ of the consumer are not covered by the to-[spec][preimage-specs] implementations Finally, in `kona-proof`, implementations of data source traits from `kona-derive` and `kona-executor` are provided to pull in untyped data from the host by `PreimageKey`. These data source traits are covered in more detail within -the [Custom Backend](/kona/sdk/proof/custom-backend) section, but we'll quickly gloss over them here to build intuition. +the [Custom Backend](/rust/kona/sdk/proof/custom-backend) section, but we'll quickly gloss over them here to build intuition. Let's take, for example, [`OracleL1ChainProvider`](https://github.com/ethereum-optimism/optimism/blob/develop/rust/kona/bin/client/src/l1/chain_provider.rs#L16-L23). The [`ChainProvider`](https://docs.rs/kona-derive/latest/kona_derive/trait.ChainProvider.html) trait in `kona-derive` diff --git a/rust/docs/docs/pages/kona/sdk/proof/intro.mdx b/docs/public-docs/rust/kona/sdk/proof/intro.mdx similarity index 96% rename from rust/docs/docs/pages/kona/sdk/proof/intro.mdx rename to docs/public-docs/rust/kona/sdk/proof/intro.mdx index b47934b105b..9a0592c4107 100644 --- a/rust/docs/docs/pages/kona/sdk/proof/intro.mdx +++ b/docs/public-docs/rust/kona/sdk/proof/intro.mdx @@ -1,4 +1,6 @@ -# Kona Proof SDK +--- +title: "Kona Proof SDK" +--- Welcome to the Kona Proof SDK, a powerful set of libraries designed from first principles to build proofs with the OP Stack STF on top @@ -12,7 +14,7 @@ and developer empowerment. The kona repository is more than a fault proof program for the OP Stack — it's an ecosystem of interoperable components, each crafted with reusability and extensibility as primary goals. While we provide -[Fault Proof VM](/kona/glossary#fault-proof-vm) and "online" backends +[Fault Proof VM](/rust/kona/glossary#fault-proof-vm) and "online" backends for key components like `kona-derive` and `kona-executor`, the true power of `kona` lies in its adaptability. diff --git a/rust/docs/docs/pages/kona/sdk/protocol/derive/intro.mdx b/docs/public-docs/rust/kona/sdk/protocol/derive/intro.mdx similarity index 97% rename from rust/docs/docs/pages/kona/sdk/protocol/derive/intro.mdx rename to docs/public-docs/rust/kona/sdk/protocol/derive/intro.mdx index 029e65f2dbc..e587a813c0c 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/derive/intro.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/derive/intro.mdx @@ -1,6 +1,6 @@ -import { Callout } from 'vocs/components' - -# The `kona-derive` Derivation Pipeline +--- +title: "The `kona-derive` Derivation Pipeline" +--- [`kona-derive`][kd] defines an entirely trait-abstracted, `no_std` derivation pipeline for the OP Stack. It can be used through the [`Pipeline`][p] trait, @@ -12,10 +12,10 @@ in this section will provide a comprehensive overview of Derivation Pipeline extensibility including trait-abstracted providers, custom stages, signaling, and hardfork activation including multiplexed stages. -- [Swapping out a stage](/kona/sdk/protocol/derive/stages) -- [Defining a custom Provider](/kona/sdk/protocol/derive/providers) -- [Extending Pipeline Signals](/kona/sdk/protocol/derive/signaling) -- [Implementing Hardfork Activations](/kona/sdk/protocol/hardforks) +- [Swapping out a stage](/rust/kona/sdk/protocol/derive/stages) +- [Defining a custom Provider](/rust/kona/sdk/protocol/derive/providers) +- [Extending Pipeline Signals](/rust/kona/sdk/protocol/derive/signaling) +- [Implementing Hardfork Activations](/rust/kona/sdk/protocol/hardforks) ## What is a Derivation Pipeline? @@ -34,12 +34,12 @@ output - payload attributes. Stage B "owns" stage A, and stage C then owns stage Using this example, the [`DerivationPipeline`][dp] type in [`kona-derive`][kd] only holds stage C, since ownership of the other stages is nested within stage C. - + In a future architecture of the derivation pipeline, stages could be made standalone such that communication between stages happens through channels. In a multi-threaded, non-fault-proof environment, these stages can then run in parallel since stage ownership is decoupled. - + ## Kona's Derivation Pipeline diff --git a/rust/docs/docs/pages/kona/sdk/protocol/derive/providers.mdx b/docs/public-docs/rust/kona/sdk/protocol/derive/providers.mdx similarity index 98% rename from rust/docs/docs/pages/kona/sdk/protocol/derive/providers.mdx rename to docs/public-docs/rust/kona/sdk/protocol/derive/providers.mdx index 05999248f3e..cd74a9c6d45 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/derive/providers.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/derive/providers.mdx @@ -1,4 +1,6 @@ -# Trait-abstracted Providers +--- +title: "Trait-abstracted Providers" +--- Kona's derivation pipeline pulls in data from sources that are trait abstracted so the pipeline can be generic over various data sources. @@ -90,7 +92,6 @@ impl DataAvailabilityProvider for ExampleAvail { ``` - [dap]: https://docs.rs/kona-derive/latest/kona_derive/trait.DataAvailabilityProvider.html [next]: https://docs.rs/kona-derive/latest/kona_derive/trait.DataAvailabilityProvider.html#tymethod.next [builder]: https://docs.rs/kona-derive/latest/kona_derive/struct.PipelineBuilder.html diff --git a/rust/docs/docs/pages/kona/sdk/protocol/derive/signaling.mdx b/docs/public-docs/rust/kona/sdk/protocol/derive/signaling.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/protocol/derive/signaling.mdx rename to docs/public-docs/rust/kona/sdk/protocol/derive/signaling.mdx index 9376d775e95..94c4b463748 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/derive/signaling.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/derive/signaling.mdx @@ -1,4 +1,6 @@ -# Signals +--- +title: "Signals" +--- Understanding signals first require a more in-depth review of the result returned by stepping on the derivation pipeline. @@ -145,7 +147,6 @@ impl SignalReceiver for IndexedTraversal { ``` - [traversal]: https://docs.rs/kona-derive/latest/kona_derive/struct.IndexedTraversal.html [dp]: https://docs.rs/kona-derive/latest/kona_derive/struct.DerivationPipeline.html [stages]: https://docs.rs/kona-derive/latest/kona_derive/index.html diff --git a/rust/docs/docs/pages/kona/sdk/protocol/derive/stages.mdx b/docs/public-docs/rust/kona/sdk/protocol/derive/stages.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/protocol/derive/stages.mdx rename to docs/public-docs/rust/kona/sdk/protocol/derive/stages.mdx index 1dd61178d36..74f46ad96a8 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/derive/stages.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/derive/stages.mdx @@ -1,4 +1,6 @@ -# Swapping out a Stage +--- +title: "Swapping out a Stage" +--- In the [introduction to the derivation pipeline][intro], the derivation pipeline is broken down to demonstrate the composition of stages, forming the transformation @@ -101,7 +103,6 @@ DapRetrieval> ``` - [intro]: ./intro.mdx [dp]: https://docs.rs/kona-derive/latest/kona_derive/struct.DerivationPipeline.html [retrieval-provider]: https://docs.rs/kona-derive/latest/kona_derive/trait.L1RetrievalProvider.html diff --git a/rust/docs/docs/pages/kona/sdk/protocol/genesis/intro.mdx b/docs/public-docs/rust/kona/sdk/protocol/genesis/intro.mdx similarity index 98% rename from rust/docs/docs/pages/kona/sdk/protocol/genesis/intro.mdx rename to docs/public-docs/rust/kona/sdk/protocol/genesis/intro.mdx index 5fe03ee701f..dc978928ee4 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/genesis/intro.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/genesis/intro.mdx @@ -1,4 +1,6 @@ -# Genesis +--- +title: "Genesis" +--- The genesis crate contains types related to chain genesis. diff --git a/rust/docs/docs/pages/kona/sdk/protocol/genesis/rollup-config.mdx b/docs/public-docs/rust/kona/sdk/protocol/genesis/rollup-config.mdx similarity index 96% rename from rust/docs/docs/pages/kona/sdk/protocol/genesis/rollup-config.mdx rename to docs/public-docs/rust/kona/sdk/protocol/genesis/rollup-config.mdx index 7b87dc1d96e..620a2f836cc 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/genesis/rollup-config.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/genesis/rollup-config.mdx @@ -1,4 +1,6 @@ -# Rollup Configs +--- +title: "Rollup Configs" +--- Rollup configurations are a consensus construct used to configure an Optimism Consensus client. When an OP Stack chain is deployed into production or consensus nodes are configured to sync the chain, diff --git a/rust/docs/docs/pages/kona/sdk/protocol/genesis/system-config.mdx b/docs/public-docs/rust/kona/sdk/protocol/genesis/system-config.mdx similarity index 97% rename from rust/docs/docs/pages/kona/sdk/protocol/genesis/system-config.mdx rename to docs/public-docs/rust/kona/sdk/protocol/genesis/system-config.mdx index 298f82b3cd2..00cfa17799c 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/genesis/system-config.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/genesis/system-config.mdx @@ -1,4 +1,6 @@ -# System Config +--- +title: "System Config" +--- The system configuration is a set of configurable chain parameters defined in a contract on L1. These parameters can be changed through diff --git a/rust/docs/docs/pages/kona/sdk/protocol/hardforks.mdx b/docs/public-docs/rust/kona/sdk/protocol/hardforks.mdx similarity index 98% rename from rust/docs/docs/pages/kona/sdk/protocol/hardforks.mdx rename to docs/public-docs/rust/kona/sdk/protocol/hardforks.mdx index f1b520c6bab..d3e35dd87c9 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/hardforks.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/hardforks.mdx @@ -1,4 +1,6 @@ -# Hardforks +--- +title: "Hardforks" +--- Hardforks are consensus layer types of the OP Stack. diff --git a/rust/docs/docs/pages/kona/sdk/protocol/interop.mdx b/docs/public-docs/rust/kona/sdk/protocol/interop.mdx similarity index 71% rename from rust/docs/docs/pages/kona/sdk/protocol/interop.mdx rename to docs/public-docs/rust/kona/sdk/protocol/interop.mdx index c01c1af35ed..a798e4cde83 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/interop.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/interop.mdx @@ -1,3 +1,5 @@ -# Interop +--- +title: "Interop" +--- `kona-interop` provides core types for the interop protocol. diff --git a/rust/docs/docs/pages/kona/sdk/protocol/intro.mdx b/docs/public-docs/rust/kona/sdk/protocol/intro.mdx similarity index 96% rename from rust/docs/docs/pages/kona/sdk/protocol/intro.mdx rename to docs/public-docs/rust/kona/sdk/protocol/intro.mdx index 876f4d5f53a..5e1c35a8a75 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/intro.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/intro.mdx @@ -1,4 +1,6 @@ -# Kona Protocol Libraries +--- +title: "Kona Protocol Libraries" +--- The Kona monorepo contains a set of protocol crates that are designed to be `no_std` compatible for Kona's fault proof sdk. Protocol crates diff --git a/rust/docs/docs/pages/kona/sdk/protocol/protocol/batches.mdx b/docs/public-docs/rust/kona/sdk/protocol/protocol/batches.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/protocol/protocol/batches.mdx rename to docs/public-docs/rust/kona/sdk/protocol/protocol/batches.mdx index a21e3d38466..bc2e02e7cd4 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/protocol/batches.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/protocol/batches.mdx @@ -1,4 +1,6 @@ -# Batches +--- +title: "Batches" +--- A [Batch][batch] contains a list of transactions to be included in a specific L2 block. Since the [Delta hardfork][delta], there are two Batch types or diff --git a/rust/docs/docs/pages/kona/sdk/protocol/protocol/block-info.mdx b/docs/public-docs/rust/kona/sdk/protocol/protocol/block-info.mdx similarity index 91% rename from rust/docs/docs/pages/kona/sdk/protocol/protocol/block-info.mdx rename to docs/public-docs/rust/kona/sdk/protocol/protocol/block-info.mdx index 78582d7cb31..3463babe7d4 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/protocol/block-info.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/protocol/block-info.mdx @@ -1,4 +1,6 @@ -# BlockInfo +--- +title: "BlockInfo" +--- The [`BlockInfo`][bi] type is straightforward, containing the block hash, number, parent hash, and timestamp. @@ -7,5 +9,4 @@ The `BlockInfo` is a subset of information provided by the block header, used for protocol operations. - [bi]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.BlockInfo.html diff --git a/rust/docs/docs/pages/kona/sdk/protocol/protocol/channels.mdx b/docs/public-docs/rust/kona/sdk/protocol/protocol/channels.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/protocol/protocol/channels.mdx rename to docs/public-docs/rust/kona/sdk/protocol/protocol/channels.mdx index b48439e4f9f..79bbed9cded 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/protocol/channels.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/protocol/channels.mdx @@ -1,4 +1,6 @@ -# Channels +--- +title: "Channels" +--- Taken from the [OP Stack specs][specs], [`Channel`][channel]s are a set of sequencer batches (for any L2 blocks) compressed together. @@ -92,7 +94,6 @@ Notice, [`Frame`][frame]s can be added out-of-order so long as the [`Channel`][c still open, and the frame hasn't already been added. - [is-ready]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.Channel.html#method.is_ready [add-frame]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.Channel.html#method.add_frame diff --git a/rust/docs/docs/pages/kona/sdk/protocol/protocol/frames.mdx b/docs/public-docs/rust/kona/sdk/protocol/protocol/frames.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/protocol/protocol/frames.mdx rename to docs/public-docs/rust/kona/sdk/protocol/protocol/frames.mdx index 7bfeb4f8b14..7f1d22e1a2b 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/protocol/frames.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/protocol/frames.mdx @@ -1,4 +1,6 @@ -# Frames +--- +title: "Frames" +--- [`Frame`][frame]s are the lowest level data format in the OP Stack protocol. @@ -65,7 +67,6 @@ the [`Frame::parse_frame`][parse_frame] and [`Frame::parse_frames`][parse_frames provide ways to decode single and multiple frames, respectively. - [encode]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.Frame.html#method.encode [decode]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.Frame.html#method.decode diff --git a/rust/docs/docs/pages/kona/sdk/protocol/protocol/intro.mdx b/docs/public-docs/rust/kona/sdk/protocol/protocol/intro.mdx similarity index 99% rename from rust/docs/docs/pages/kona/sdk/protocol/protocol/intro.mdx rename to docs/public-docs/rust/kona/sdk/protocol/protocol/intro.mdx index 64094305772..2ed48fd0df4 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/protocol/intro.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/protocol/intro.mdx @@ -1,4 +1,6 @@ -# Protocol +--- +title: "Protocol" +--- The [`kona-protocol`][protocol] crate contains types, constants, and methods specific to Optimism derivation and batch-submission. @@ -48,7 +50,6 @@ type. - [L2BlockInfo](./l2-block-info.mdx) - [decoded]: https://docs.rs/kona-protocol/latest/kona_protocol/enum.Batch.html#method.decode [batch]: https://docs.rs/kona-protocol/latest/kona_protocol/enum.Batch.html [ready]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.Channel.html#method.is_ready diff --git a/rust/docs/docs/pages/kona/sdk/protocol/protocol/l2-block-info.mdx b/docs/public-docs/rust/kona/sdk/protocol/protocol/l2-block-info.mdx similarity index 96% rename from rust/docs/docs/pages/kona/sdk/protocol/protocol/l2-block-info.mdx rename to docs/public-docs/rust/kona/sdk/protocol/protocol/l2-block-info.mdx index 06e96fa7aa4..427b9d47a09 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/protocol/l2-block-info.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/protocol/l2-block-info.mdx @@ -1,4 +1,6 @@ -# L2BlockInfo +--- +title: "L2BlockInfo" +--- The [`L2BlockInfo`][lbi] extends the [`BlockInfo`][bi] type for the canonical L2 chain. It contains the "L1 origin" which is a set of block info for the L1 @@ -11,7 +13,6 @@ provided by a block header, used for protocol operations. construct the [`L2BlockInfo`][lbi] from a block and `ChainGenesis`. - [bi]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.BlockInfo.html [lbi]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.L2BlockInfo.html [fbg]: https://docs.rs/kona-protocol/latest/kona_protocol/struct.L2BlockInfo.html#method.from_block_and_genesis diff --git a/rust/docs/docs/pages/kona/sdk/protocol/registry.mdx b/docs/public-docs/rust/kona/sdk/protocol/registry.mdx similarity index 98% rename from rust/docs/docs/pages/kona/sdk/protocol/registry.mdx rename to docs/public-docs/rust/kona/sdk/protocol/registry.mdx index 9e815770ba7..e54c17f2feb 100644 --- a/rust/docs/docs/pages/kona/sdk/protocol/registry.mdx +++ b/docs/public-docs/rust/kona/sdk/protocol/registry.mdx @@ -1,4 +1,6 @@ -# Registry +--- +title: "Registry" +--- [`kona-registry`][sc] is a `no_std` crate that exports rust type definitions for chains in the [`superchain-registry`][osr]. These are lazily evaluated statics that provide diff --git a/rust/docs/docs/pages/op-alloy/building/consensus.mdx b/docs/public-docs/rust/op-alloy/building/consensus.mdx similarity index 99% rename from rust/docs/docs/pages/op-alloy/building/consensus.mdx rename to docs/public-docs/rust/op-alloy/building/consensus.mdx index 7484725c490..7580d4f03a8 100644 --- a/rust/docs/docs/pages/op-alloy/building/consensus.mdx +++ b/docs/public-docs/rust/op-alloy/building/consensus.mdx @@ -1,4 +1,6 @@ -# Consensus +--- +title: "Consensus" +--- The `op-alloy-consensus` crate provides an Optimism consensus interface. It contains constants, types, and functions for implementing Optimism EL diff --git a/rust/docs/docs/pages/op-alloy/building/engine.mdx b/docs/public-docs/rust/op-alloy/building/engine.mdx similarity index 97% rename from rust/docs/docs/pages/op-alloy/building/engine.mdx rename to docs/public-docs/rust/op-alloy/building/engine.mdx index f1cdde805af..a2cb44778ff 100644 --- a/rust/docs/docs/pages/op-alloy/building/engine.mdx +++ b/docs/public-docs/rust/op-alloy/building/engine.mdx @@ -1,4 +1,6 @@ -# RPC Engine Types +--- +title: "RPC Engine Types" +--- The [`op-alloy-rpc-types-engine`](https://docs.rs/op-alloy-rpc-types-engine/latest/op_alloy_rpc_types_engine/) crate provides Optimism types for interfacing with the Engine API in the OP Stack. diff --git a/rust/docs/docs/pages/op-alloy/building/index.mdx b/docs/public-docs/rust/op-alloy/building/index.mdx similarity index 96% rename from rust/docs/docs/pages/op-alloy/building/index.mdx rename to docs/public-docs/rust/op-alloy/building/index.mdx index 1286a3c89ca..8aafd36234b 100644 --- a/rust/docs/docs/pages/op-alloy/building/index.mdx +++ b/docs/public-docs/rust/op-alloy/building/index.mdx @@ -1,4 +1,6 @@ -# Building +--- +title: "Building" +--- This section offers in-depth documentation into the various `op-alloy` crates. Some of the primary crates and their types are listed below. diff --git a/rust/docs/docs/pages/op-alloy/glossary.mdx b/docs/public-docs/rust/op-alloy/glossary.mdx similarity index 76% rename from rust/docs/docs/pages/op-alloy/glossary.mdx rename to docs/public-docs/rust/op-alloy/glossary.mdx index 37a95d32e3f..bf2e8cc479e 100644 --- a/rust/docs/docs/pages/op-alloy/glossary.mdx +++ b/docs/public-docs/rust/op-alloy/glossary.mdx @@ -1,3 +1,5 @@ -# Glossary +--- +title: "Glossary" +--- *This document contains definitions for terms used throughout the op-alloy book.* diff --git a/rust/docs/docs/pages/op-alloy/intro.mdx b/docs/public-docs/rust/op-alloy/intro.mdx similarity index 91% rename from rust/docs/docs/pages/op-alloy/intro.mdx rename to docs/public-docs/rust/op-alloy/intro.mdx index 7f47b654c69..fdbd850796a 100644 --- a/rust/docs/docs/pages/op-alloy/intro.mdx +++ b/docs/public-docs/rust/op-alloy/intro.mdx @@ -1,4 +1,6 @@ -# op-alloy +--- +title: "op-alloy" +--- Welcome to the hands-on guide for getting started with `op-alloy`! @@ -15,11 +17,11 @@ performance types, traits, and middleware from [Alloy](https://github.com/alloy- ## Sections -### [Getting Started](/op-alloy/starting) +### [Getting Started](/rust/op-alloy/starting) To get started with op-alloy, add its crates as a dependency and take your first steps. -### [Building with op-alloy](/op-alloy/building) +### [Building with op-alloy](/rust/op-alloy/building) Walk through types and functionality available in different `op-alloy` crates. diff --git a/rust/docs/docs/pages/op-alloy/starting.mdx b/docs/public-docs/rust/op-alloy/starting.mdx similarity index 99% rename from rust/docs/docs/pages/op-alloy/starting.mdx rename to docs/public-docs/rust/op-alloy/starting.mdx index 8bfc4187564..c3ace0f3040 100644 --- a/rust/docs/docs/pages/op-alloy/starting.mdx +++ b/docs/public-docs/rust/op-alloy/starting.mdx @@ -1,4 +1,6 @@ -# Installation +--- +title: "Installation" +--- [op-alloy](https://github.com/ethereum-optimism/optimism/tree/develop/rust/op-alloy) consists of a number of crates that provide a range of functionality essential for interfacing with any OP Stack chain. diff --git a/rust/docs/docs/pages/op-reth/cli/cli.mdx b/docs/public-docs/rust/op-reth/cli/cli.mdx similarity index 80% rename from rust/docs/docs/pages/op-reth/cli/cli.mdx rename to docs/public-docs/rust/op-reth/cli/cli.mdx index ba486b26a21..fff5ee453a4 100644 --- a/rust/docs/docs/pages/op-reth/cli/cli.mdx +++ b/docs/public-docs/rust/op-reth/cli/cli.mdx @@ -1,5 +1,7 @@ -# CLI Reference +--- +title: "CLI Reference" +--- The op-reth node is operated via the CLI by running the `op-reth node` command. To stop it, press `ctrl-c`. You may need to wait a bit as op-reth tears down existing p2p connections or performs other cleanup tasks. -However, op-reth has more commands — see the [op-reth CLI reference](/op-reth/cli/op-reth). +However, op-reth has more commands — see the [op-reth CLI reference](/rust/op-reth/cli/op-reth). diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth.mdx b/docs/public-docs/rust/op-reth/cli/op-reth.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth.mdx index 572335c21b1..a6210440688 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth.mdx @@ -1,4 +1,6 @@ -# op-reth +--- +title: "op-reth" +--- Reth diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/config.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/config.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/config.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/config.mdx index 62389fe994e..1b9eefb3247 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/config.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/config.mdx @@ -1,4 +1,6 @@ -# op-reth config +--- +title: "op-reth config" +--- Write config to stdout diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db.mdx index d8a816e23ae..d0da9a914c6 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db.mdx @@ -1,4 +1,6 @@ -# op-reth db +--- +title: "op-reth db" +--- Database debugging utilities diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/account-storage.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/account-storage.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/account-storage.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/account-storage.mdx index f71f08c74f1..85f635d9434 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/account-storage.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/account-storage.mdx @@ -1,4 +1,6 @@ -# op-reth db account-storage +--- +title: "op-reth db account-storage" +--- Gets storage size information for an account diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/checksum.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/checksum.mdx index 8027558cfbc..ad825b1b0d8 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/checksum.mdx @@ -1,4 +1,6 @@ -# op-reth db checksum +--- +title: "op-reth db checksum" +--- Calculates the content checksum of a table or static file segment diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum/mdbx.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/checksum/mdbx.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum/mdbx.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/checksum/mdbx.mdx index aa34fef6941..306a3380b15 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum/mdbx.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/checksum/mdbx.mdx @@ -1,4 +1,6 @@ -# op-reth db checksum mdbx +--- +title: "op-reth db checksum mdbx" +--- Calculates the checksum of a database table diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum/static-file.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/checksum/static-file.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum/static-file.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/checksum/static-file.mdx index a9939730d1a..da5300ae635 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/checksum/static-file.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/checksum/static-file.mdx @@ -1,4 +1,6 @@ -# op-reth db checksum static-file +--- +title: "op-reth db checksum static-file" +--- Calculates the checksum of a static file segment diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/clear.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/clear.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/clear.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/clear.mdx index 1139958cbcb..fa381339e58 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/clear.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/clear.mdx @@ -1,4 +1,6 @@ -# op-reth db clear +--- +title: "op-reth db clear" +--- Deletes all table entries diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/clear/mdbx.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/clear/mdbx.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/clear/mdbx.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/clear/mdbx.mdx index b9f6537b3dd..19e06bdc1f5 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/clear/mdbx.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/clear/mdbx.mdx @@ -1,4 +1,6 @@ -# op-reth db clear mdbx +--- +title: "op-reth db clear mdbx" +--- Deletes all database table entries diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/clear/static-file.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/clear/static-file.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/clear/static-file.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/clear/static-file.mdx index a0ceb8ee50c..94144302137 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/clear/static-file.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/clear/static-file.mdx @@ -1,4 +1,6 @@ -# op-reth db clear static-file +--- +title: "op-reth db clear static-file" +--- Deletes all static file segment entries diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/diff.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/diff.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/diff.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/diff.mdx index 1df107888f5..b668b80e7ff 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/diff.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/diff.mdx @@ -1,4 +1,6 @@ -# op-reth db diff +--- +title: "op-reth db diff" +--- Create a diff between two database tables or two entire databases diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/drop.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/drop.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/drop.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/drop.mdx index ec387677f79..fd16a462786 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/drop.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/drop.mdx @@ -1,4 +1,6 @@ -# op-reth db drop +--- +title: "op-reth db drop" +--- Deletes all database entries diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/get.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/get.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/get.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/get.mdx index 15b7ea8a287..88230e6ee6b 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/get.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/get.mdx @@ -1,4 +1,6 @@ -# op-reth db get +--- +title: "op-reth db get" +--- Gets the content of a table for the given key diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/get/mdbx.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/get/mdbx.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/get/mdbx.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/get/mdbx.mdx index e4f002625c4..e11a39f90ef 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/get/mdbx.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/get/mdbx.mdx @@ -1,4 +1,6 @@ -# op-reth db get mdbx +--- +title: "op-reth db get mdbx" +--- Gets the content of a database table for the given key diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/get/static-file.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/get/static-file.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/get/static-file.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/get/static-file.mdx index af64e2e00a2..e0dcbc6decd 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/get/static-file.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/get/static-file.mdx @@ -1,4 +1,6 @@ -# op-reth db get static-file +--- +title: "op-reth db get static-file" +--- Gets the content of a static file segment for the given key diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/list.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/list.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/list.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/list.mdx index 1b926b1acab..6dbdfaa5725 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/list.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/list.mdx @@ -1,4 +1,6 @@ -# op-reth db list +--- +title: "op-reth db list" +--- Lists the contents of a table diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/path.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/path.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/path.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/path.mdx index b500e225e1e..e302316b0a9 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/path.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/path.mdx @@ -1,4 +1,6 @@ -# op-reth db path +--- +title: "op-reth db path" +--- Returns the full database path diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/repair-trie.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/repair-trie.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/repair-trie.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/repair-trie.mdx index 67d5b8cee0b..d445bd841ab 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/repair-trie.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/repair-trie.mdx @@ -1,4 +1,6 @@ -# op-reth db repair-trie +--- +title: "op-reth db repair-trie" +--- Verifies trie consistency and outputs any inconsistencies diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings.mdx index 95ff5e0df67..41e2ecdf658 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings.mdx @@ -1,4 +1,6 @@ -# op-reth db settings +--- +title: "op-reth db settings" +--- Manage storage settings diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/get.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/get.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/get.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/get.mdx index c4e54307302..b31190186b7 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/get.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/get.mdx @@ -1,4 +1,6 @@ -# op-reth db settings get +--- +title: "op-reth db settings get" +--- Get current storage settings from database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set.mdx index 8aa2ae3cdca..0627c1f5960 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set +--- +title: "op-reth db settings set" +--- Set storage settings in database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/account_changesets.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/account_changesets.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/account_changesets.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/account_changesets.mdx index 40520075834..3d082c8d650 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/account_changesets.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/account_changesets.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set account_changesets +--- +title: "op-reth db settings set account_changesets" +--- Store account changesets in static files instead of the database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/account_history.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/account_history.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/account_history.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/account_history.mdx index 641475ab142..428c1b63768 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/account_history.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/account_history.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set account_history +--- +title: "op-reth db settings set account_history" +--- Store account history in rocksdb instead of MDBX diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/receipts.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/receipts.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/receipts.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/receipts.mdx index 2cbd8647ba2..b09adc4c1f7 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/receipts.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/receipts.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set receipts +--- +title: "op-reth db settings set receipts" +--- Store receipts in static files instead of the database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/receipts_in_static_files.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/receipts_in_static_files.mdx similarity index 98% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/receipts_in_static_files.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/receipts_in_static_files.mdx index 490ee06ce94..6da32bd615f 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/receipts_in_static_files.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/receipts_in_static_files.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set receipts_in_static_files +--- +title: "op-reth db settings set receipts_in_static_files" +--- Store receipts in static files instead of the database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/storage_changesets.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/storage_changesets.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/storage_changesets.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/storage_changesets.mdx index d84b848a6b6..fc6aa09f918 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/storage_changesets.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/storage_changesets.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set storage_changesets +--- +title: "op-reth db settings set storage_changesets" +--- Store storage changesets in static files instead of the database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/storages_history.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/storages_history.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/storages_history.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/storages_history.mdx index bef26be1a49..5ae28e3bbcf 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/storages_history.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/storages_history.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set storages_history +--- +title: "op-reth db settings set storages_history" +--- Store storage history in rocksdb instead of MDBX diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_hash_numbers.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_hash_numbers.mdx similarity index 98% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_hash_numbers.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_hash_numbers.mdx index b7f7dda97f1..9ba939c89dd 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_hash_numbers.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_hash_numbers.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set transaction_hash_numbers +--- +title: "op-reth db settings set transaction_hash_numbers" +--- Store transaction hash to number mapping in rocksdb instead of MDBX diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_senders.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_senders.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_senders.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_senders.mdx index f95ddf7b811..f99e4675bbd 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_senders.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_senders.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set transaction_senders +--- +title: "op-reth db settings set transaction_senders" +--- Store transaction senders in static files instead of the database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_senders_in_static_files.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_senders_in_static_files.mdx similarity index 98% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_senders_in_static_files.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_senders_in_static_files.mdx index 1947c57293b..8570cb8def9 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/settings/set/transaction_senders_in_static_files.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/settings/set/transaction_senders_in_static_files.mdx @@ -1,4 +1,6 @@ -# op-reth db settings set transaction_senders_in_static_files +--- +title: "op-reth db settings set transaction_senders_in_static_files" +--- Store transaction senders in static files instead of the database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header.mdx index 5483848ff01..c454aa007f2 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header.mdx @@ -1,4 +1,6 @@ -# op-reth db static-file-header +--- +title: "op-reth db static-file-header" +--- Reads and displays the static file segment header diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header/block.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header/block.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header/block.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header/block.mdx index bdf56a9804d..0ad2a58216d 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header/block.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header/block.mdx @@ -1,4 +1,6 @@ -# op-reth db static-file-header block +--- +title: "op-reth db static-file-header block" +--- Query by segment and block number diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header/path.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header/path.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header/path.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header/path.mdx index 7f237185cec..6fa62ea4244 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/static-file-header/path.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/static-file-header/path.mdx @@ -1,4 +1,6 @@ -# op-reth db static-file-header path +--- +title: "op-reth db static-file-header path" +--- Query by path to static file diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/stats.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/stats.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/stats.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/stats.mdx index 1a2ed7d4a93..8a40bf9b67a 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/stats.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/stats.mdx @@ -1,4 +1,6 @@ -# op-reth db stats +--- +title: "op-reth db stats" +--- Lists all the tables, their entry count and their size diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/db/version.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/db/version.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/db/version.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/db/version.mdx index a33bf8ef3bd..32b4d619bcf 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/db/version.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/db/version.mdx @@ -1,4 +1,6 @@ -# op-reth db version +--- +title: "op-reth db version" +--- Lists current and local database versions diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/dump-genesis.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/dump-genesis.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/dump-genesis.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/dump-genesis.mdx index 2486fb79aea..8289728ccf8 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/dump-genesis.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/dump-genesis.mdx @@ -1,4 +1,6 @@ -# op-reth dump-genesis +--- +title: "op-reth dump-genesis" +--- Dumps genesis block JSON configuration to stdout diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/import-op.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/import-op.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/import-op.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/import-op.mdx index c5affadf9f5..8e8d7b18e06 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/import-op.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/import-op.mdx @@ -1,4 +1,6 @@ -# op-reth import-op +--- +title: "op-reth import-op" +--- This syncs RLP encoded OP blocks below Bedrock from a file, without executing diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/import-receipts-op.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/import-receipts-op.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/import-receipts-op.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/import-receipts-op.mdx index 398086f9dc6..d82c6dfa2db 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/import-receipts-op.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/import-receipts-op.mdx @@ -1,4 +1,6 @@ -# op-reth import-receipts-op +--- +title: "op-reth import-receipts-op" +--- This imports RLP encoded receipts from a file diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/init-state.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/init-state.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/init-state.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/init-state.mdx index 3e3e1ba019e..6ce0a00b584 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/init-state.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/init-state.mdx @@ -1,4 +1,6 @@ -# op-reth init-state +--- +title: "op-reth init-state" +--- Initialize the database from a state dump file diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/init.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/init.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/init.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/init.mdx index 9a0930b4fe3..e145b1fac9a 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/init.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/init.mdx @@ -1,4 +1,6 @@ -# op-reth init +--- +title: "op-reth init" +--- Initialize the database from a genesis file diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/node.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/node.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/node.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/node.mdx index 98205ad008e..9ec142316fa 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/node.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/node.mdx @@ -1,4 +1,6 @@ -# op-reth node +--- +title: "op-reth node" +--- Start the node diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/p2p.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/p2p.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/p2p.mdx index 3b4efdbd6f0..e98ea26a841 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/p2p.mdx @@ -1,4 +1,6 @@ -# op-reth p2p +--- +title: "op-reth p2p" +--- P2P Debugging utilities diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/body.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/body.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/p2p/body.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/p2p/body.mdx index 7fb5e5fa61c..c8ca7a76f67 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/body.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/body.mdx @@ -1,4 +1,6 @@ -# op-reth p2p body +--- +title: "op-reth p2p body" +--- Download block body diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/bootnode.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/bootnode.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/p2p/bootnode.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/p2p/bootnode.mdx index 387eef511b5..8378eaa1332 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/bootnode.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/bootnode.mdx @@ -1,4 +1,6 @@ -# op-reth p2p bootnode +--- +title: "op-reth p2p bootnode" +--- Bootnode command diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/header.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/header.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/p2p/header.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/p2p/header.mdx index 9ede2d8eb71..a58d0b29b78 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/header.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/header.mdx @@ -1,4 +1,6 @@ -# op-reth p2p header +--- +title: "op-reth p2p header" +--- Download block header diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/rlpx.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/rlpx.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/p2p/rlpx.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/p2p/rlpx.mdx index 5b33e8b850e..3cd932f3a96 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/rlpx.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/rlpx.mdx @@ -1,4 +1,6 @@ -# op-reth p2p rlpx +--- +title: "op-reth p2p rlpx" +--- RLPx commands diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/rlpx/ping.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/rlpx/ping.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/p2p/rlpx/ping.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/p2p/rlpx/ping.mdx index e91e437e943..d749938421c 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/p2p/rlpx/ping.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/p2p/rlpx/ping.mdx @@ -1,4 +1,6 @@ -# op-reth p2p rlpx ping +--- +title: "op-reth p2p rlpx ping" +--- ping node diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/prune.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/prune.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/prune.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/prune.mdx index 603af5d99e4..65665345dbd 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/prune.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/prune.mdx @@ -1,4 +1,6 @@ -# op-reth prune +--- +title: "op-reth prune" +--- Prune according to the configuration without any limits diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/re-execute.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/re-execute.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/re-execute.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/re-execute.mdx index c185b91027d..9432f2b7ee9 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/re-execute.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/re-execute.mdx @@ -1,4 +1,6 @@ -# op-reth re-execute +--- +title: "op-reth re-execute" +--- Re-execute blocks in parallel to verify historical sync correctness diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage.mdx index f1669000555..f5dc1a8ef18 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage.mdx @@ -1,4 +1,6 @@ -# op-reth stage +--- +title: "op-reth stage" +--- Manipulate individual stages diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/drop.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/drop.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/drop.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/drop.mdx index d5034f0d4b8..8c0d998957c 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/drop.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/drop.mdx @@ -1,4 +1,6 @@ -# op-reth stage drop +--- +title: "op-reth stage drop" +--- Drop a stage's tables from the database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/dump.mdx index 9150154c31f..79f6ed94026 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump.mdx @@ -1,4 +1,6 @@ -# op-reth stage dump +--- +title: "op-reth stage dump" +--- Dumps a stage from a range into a new database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/account-hashing.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/account-hashing.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/account-hashing.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/account-hashing.mdx index 05bedbe5b09..cf3ef597ee4 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/account-hashing.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/account-hashing.mdx @@ -1,4 +1,6 @@ -# op-reth stage dump account-hashing +--- +title: "op-reth stage dump account-hashing" +--- `AccountHashing` stage diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/execution.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/execution.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/execution.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/execution.mdx index 95cf1f8e64d..71580395b7e 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/execution.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/execution.mdx @@ -1,4 +1,6 @@ -# op-reth stage dump execution +--- +title: "op-reth stage dump execution" +--- Execution stage diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/merkle.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/merkle.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/merkle.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/merkle.mdx index 523e60bbe92..d6c425376de 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/merkle.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/merkle.mdx @@ -1,4 +1,6 @@ -# op-reth stage dump merkle +--- +title: "op-reth stage dump merkle" +--- Merkle stage diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/storage-hashing.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/storage-hashing.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/storage-hashing.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/storage-hashing.mdx index 2e10a26adee..35feae2f2fc 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/dump/storage-hashing.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/dump/storage-hashing.mdx @@ -1,4 +1,6 @@ -# op-reth stage dump storage-hashing +--- +title: "op-reth stage dump storage-hashing" +--- `StorageHashing` stage diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/run.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/run.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/run.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/run.mdx index 75b39f76c77..9f5134f0039 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/run.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/run.mdx @@ -1,4 +1,6 @@ -# op-reth stage run +--- +title: "op-reth stage run" +--- Run a single stage. diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind.mdx index 37852456cfd..e32a508e7ba 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind.mdx @@ -1,4 +1,6 @@ -# op-reth stage unwind +--- +title: "op-reth stage unwind" +--- Unwinds a certain block range, deleting it from the database diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind/num-blocks.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind/num-blocks.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind/num-blocks.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind/num-blocks.mdx index b8434741573..3c3850e1da5 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind/num-blocks.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind/num-blocks.mdx @@ -1,4 +1,6 @@ -# op-reth stage unwind num-blocks +--- +title: "op-reth stage unwind num-blocks" +--- Unwinds the database from the latest block, until the given number of blocks have been reached diff --git a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind/to-block.mdx b/docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind/to-block.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind/to-block.mdx rename to docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind/to-block.mdx index 363c6adcf37..e4487448d9d 100644 --- a/rust/docs/docs/pages/op-reth/cli/op-reth/stage/unwind/to-block.mdx +++ b/docs/public-docs/rust/op-reth/cli/op-reth/stage/unwind/to-block.mdx @@ -1,4 +1,6 @@ -# op-reth stage unwind to-block +--- +title: "op-reth stage unwind to-block" +--- Unwinds the database from the latest block, until the given block number or hash has been reached, that block is not included diff --git a/rust/docs/docs/pages/op-reth/index.mdx b/docs/public-docs/rust/op-reth/index.mdx similarity index 68% rename from rust/docs/docs/pages/op-reth/index.mdx rename to docs/public-docs/rust/op-reth/index.mdx index fd1ae9f1233..83e04718df9 100644 --- a/rust/docs/docs/pages/op-reth/index.mdx +++ b/docs/public-docs/rust/op-reth/index.mdx @@ -1,8 +1,11 @@ +--- +title: "op-reth" +--- + --- description: op-reth is the OP Stack execution client built on reth. --- -# op-reth `op-reth` is the [OP Stack](https://docs.optimism.io/) execution client built on [reth](https://github.com/paradigmxyz/reth). It provides a high-performance execution layer for Optimism and all OP Stack chains. @@ -14,6 +17,6 @@ description: op-reth is the OP Stack execution client built on reth. ## Getting started -- [Running op-reth on OP Stack chains](/op-reth/run/opstack) — Install and run op-reth with a rollup node. -- [Sync OP Mainnet](/op-reth/run/faq/sync-op-mainnet) — Import Bedrock state and sync OP Mainnet from scratch. -- [CLI Reference](/op-reth/cli/op-reth) — Full command-line reference for op-reth. +- [Running op-reth on OP Stack chains](/rust/op-reth/run/opstack) — Install and run op-reth with a rollup node. +- [Sync OP Mainnet](/rust/op-reth/run/faq/sync-op-mainnet) — Import Bedrock state and sync OP Mainnet from scratch. +- [CLI Reference](/rust/op-reth/cli/op-reth) — Full command-line reference for op-reth. diff --git a/rust/docs/docs/pages/op-reth/run/faq/sync-op-mainnet.mdx b/docs/public-docs/rust/op-reth/run/faq/sync-op-mainnet.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/run/faq/sync-op-mainnet.mdx rename to docs/public-docs/rust/op-reth/run/faq/sync-op-mainnet.mdx index ed857da7c41..587c0f70db1 100644 --- a/rust/docs/docs/pages/op-reth/run/faq/sync-op-mainnet.mdx +++ b/docs/public-docs/rust/op-reth/run/faq/sync-op-mainnet.mdx @@ -1,8 +1,11 @@ +--- +title: "Sync OP Mainnet" +--- + --- description: Syncing Reth with OP Mainnet and Bedrock state. --- -# Sync OP Mainnet To sync OP mainnet, Bedrock state needs to be imported as a starting point. There are currently two ways: diff --git a/rust/docs/docs/pages/op-reth/run/opstack.mdx b/docs/public-docs/rust/op-reth/run/opstack.mdx similarity index 99% rename from rust/docs/docs/pages/op-reth/run/opstack.mdx rename to docs/public-docs/rust/op-reth/run/opstack.mdx index 07992195e37..46567cbb72d 100644 --- a/rust/docs/docs/pages/op-reth/run/opstack.mdx +++ b/docs/public-docs/rust/op-reth/run/opstack.mdx @@ -1,8 +1,11 @@ +--- +title: "Running op-reth on OP Stack chains" +--- + --- description: Running op-reth on Optimism and OP Stack chains. --- -# Running op-reth on OP Stack chains `op-reth` is the OP Stack execution client built on [reth][reth]. It supports OP Stack chains out of the box. Optimism has a small diff from the [L1 EELS][l1-el-spec], comprising of the following key changes: diff --git a/docs/public-docs/rust/op-revm/index.mdx b/docs/public-docs/rust/op-revm/index.mdx new file mode 100644 index 00000000000..80aea622f2c --- /dev/null +++ b/docs/public-docs/rust/op-revm/index.mdx @@ -0,0 +1,88 @@ +--- +title: "op-revm" +description: "op-revm is the Optimism variant of revm, implementing OP Stack modifications to the EVM." +--- + +`op-revm` is the Optimism variant of [revm](https://github.com/bluealloy/revm) — +the OP Stack's modifications to the Ethereum Virtual Machine, packaged as a +custom EVM built on top of the upstream `revm` framework. + +## Features + +`op-revm` extends `revm` with everything the OP Stack needs on top of vanilla +Ethereum execution: + +- **Deposit transactions** — the L1-to-L2 deposit transaction type and its + execution semantics. +- **L1 cost accounting** — `L1BlockInfo` and per-transaction L1 fee / blob fee + calculation. +- **Operator fees** — operator fee handling introduced in Isthmus and refined + in Jovian. +- **OP-specific precompiles** — including accelerated BN254 pairing. +- **OP halt reasons & transaction errors** — OP Stack–specific execution + failure modes. +- **Hardfork-aware spec selection** — `OpSpecId` selects the right behavior for + each OP Stack hardfork (Bedrock, Regolith, Canyon, Ecotone, Fjord, Granite, + Holocene, Isthmus, Jovian, …). + +## Provenance + +`op-revm` is vendored from upstream +[`bluealloy/revm`'s `crates/op-revm`](https://github.com/bluealloy/revm/tree/main/crates/op-revm) +and imported into the monorepo so it can evolve in lock-step with the rest of +the OP Stack Rust code. The upstream release history is preserved in +[`rust/op-revm/CHANGELOG.md`](https://github.com/ethereum-optimism/optimism/blob/develop/rust/op-revm/CHANGELOG.md). + +## Crate features + +The crate exposes the usual `revm` feature knobs, forwarded through to the +underlying `revm` crate: + +- `default = ["std", "c-kzg", "secp256k1", "portable", "blst"]` +- `std` — enables `std`-dependent code paths in `revm`, `alloy-primitives`, + `serde_json`, etc. +- `serde` — derives `serde` impls and forwards to `revm/serde` and + `alloy-primitives/serde`. +- `portable`, `c-kzg`, `secp256k1`, `blst`, `bn` — pass-through feature gates + for the cryptographic backends. +- `dev`, `memory_limit`, `optional_balance_check`, `optional_block_gas_limit`, + `optional_eip3541`, `optional_eip3607`, `optional_no_base_fee`, + `optional_fee_charge` — debugging and testing knobs forwarded to `revm`. + +`op-revm` supports `no_std` builds: disabling default features (or building +with `--no-default-features`) produces a crate suitable for fault-proof and +zkVM targets such as `riscv32imac-unknown-none-elf`. + +## Building & testing + +From the `rust/` workspace root: + +```bash +# Build +cargo build -p op-revm + +# Run tests with all features +cargo nextest run -p op-revm --all-features + +# no_std check (mirrors upstream's riscv32imac CI) +cargo build -p op-revm \ + --target riscv32imac-unknown-none-elf \ + --no-default-features +``` + +The `no_std` build is also exercised by `just check-no-std` and runs in CI on +every PR that touches `rust/**`. + +## Using op-revm + +`op-revm` is the EVM used by [op-reth](/rust/op-reth) for OP Stack block +execution, and by [Kona](/rust/kona/intro/overview) inside the fault-proof +program and rollup node. Most users will consume it transitively through one +of those components rather than depending on it directly; depend on `op-revm` +directly only when building a custom OP Stack execution environment (for +example, an alternate client or a zkVM proof backend). + +## License + +`op-revm` is licensed under the MIT License — see +[`rust/op-revm/LICENSE`](https://github.com/ethereum-optimism/optimism/blob/develop/rust/op-revm/LICENSE). diff --git a/go.mod b/go.mod index 8a71c3ca5e8..bca4861fc07 100644 --- a/go.mod +++ b/go.mod @@ -264,7 +264,7 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.etcd.io/bbolt v1.3.5 // indirect + go.etcd.io/bbolt v1.3.5 go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect diff --git a/justfile b/justfile index 75b39fe2eed..31bb67d4fa7 100644 --- a/justfile +++ b/justfile @@ -266,8 +266,7 @@ _go-tests-ci-internal go_test_flags="": export ENABLE_ANVIL=true export PARALLEL=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) 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" + source ./ops/scripts/source-ci-archive-rpcs.sh export NAT_INTEROP_LOADTEST_TARGET=10 export NAT_INTEROP_LOADTEST_TIMEOUT=30s ALL_PACKAGES="{{ALL_TEST_PACKAGES}}" @@ -283,7 +282,7 @@ _go-tests-ci-internal go_test_flags="": --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" + -- -p=4 -parallel="$PARALLEL" {{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_PACKAGES" | wc -w) packages)" exit 1 @@ -295,7 +294,7 @@ _go-tests-ci-internal go_test_flags="": --rerun-fails=3 \ --rerun-fails-max-failures=50 \ --packages="$ALL_PACKAGES" \ - -- -parallel="$PARALLEL" -coverprofile=coverage.out {{go_test_flags}} -timeout={{TEST_TIMEOUT}} -tags="ci" + -- -p=4 -parallel="$PARALLEL" {{go_test_flags}} -timeout={{TEST_TIMEOUT}} -tags="ci" fi # Runs short Go tests with gotestsum for CI. @@ -306,10 +305,6 @@ go-tests-short-ci: go-tests-ci: just _go-tests-ci-internal "" -# Runs action tests for kona with gotestsum for CI. -go-tests-ci-kona-action: - just _go-tests-ci-internal "-count=1 -timeout 60m -run Test_ProgramAction" - # Runs fraud proofs Go tests with gotestsum for CI. [script('bash')] go-tests-fraud-proofs-ci: @@ -323,8 +318,7 @@ go-tests-fraud-proofs-ci: export ENABLE_ANVIL=true export PARALLEL=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) 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" + source ./ops/scripts/source-ci-archive-rpcs.sh export NAT_INTEROP_LOADTEST_TARGET=10 export NAT_INTEROP_LOADTEST_TIMEOUT=30s ./ops/scripts/gotestsum-split.sh --format=testname \ @@ -333,7 +327,7 @@ go-tests-fraud-proofs-ci: --rerun-fails=3 \ --rerun-fails-max-failures=50 \ --packages="{{FRAUD_PROOF_TEST_PKGS}}" \ - -- -parallel="$PARALLEL" -coverprofile=coverage.out -timeout={{TEST_TIMEOUT}} + -- -parallel="$PARALLEL" -timeout={{TEST_TIMEOUT}} # Runs comprehensive Go tests (alias for go-tests). test: go-tests diff --git a/mise.toml b/mise.toml index eaf9f7ee240..2c3752e2fca 100644 --- a/mise.toml +++ b/mise.toml @@ -6,8 +6,11 @@ go = "1.24.10" golangci-lint = "1.64.8" gotestsum = "1.12.3" mockery = "2.53.3" -rust = "1.91.0" -python = "3.12.0" +rust = [ + { version = "1.94.0", components = "clippy,rustfmt,llvm-tools-preview", targets = "riscv32imac-unknown-none-elf,wasm32-unknown-unknown,wasm32-wasip1" }, + { version = "nightly-2026-02-20", components = "rustfmt,clippy" }, +] +python = "3.12.13" uv = "0.5.5" jq = "1.7.1" yq = "4.44.5" @@ -18,9 +21,14 @@ just = "1.37.0" svm-rs = "0.5.19" +# Rust tooling +"github:crate-ci/cargo-release" = { version = "1.1.2" } + +"github:nextest-rs/nextest" = { version = "0.9.132", version_prefix = "cargo-nextest-", bin = "cargo-nextest" } + # Go dependencies "go:github.com/ethereum/go-ethereum/cmd/abigen" = "1.15.10" -"go:github.com/ethereum/go-ethereum/cmd/geth" = "1.16.4" # Osaka testnet release. +"go:github.com/ethereum/go-ethereum/cmd/geth" = "1.16.4" # Osaka testnet release. # Python dependencies "pipx:slither-analyzer" = "0.10.2" @@ -62,7 +70,6 @@ kurtosis = "ubi:kurtosis-tech/kurtosis-cli-release-artifacts[exe=kurtosis]" mockery = "ubi:vektra/mockery" op-acceptor = "ubi:ethereum-optimism/infra[exe=op-acceptor,tag_prefix=op-acceptor/]" svm-rs = "ubi:alloy-rs/svm-rs[exe=svm]" - # These are disabled, but latest mise versions error if they don't have a known # install source even though it won't ever actually use that source. asterisc = "ubi:ethereum-optimism/fake-asterisc" diff --git a/op-acceptance-tests/README.md b/op-acceptance-tests/README.md index faeda8a0b03..63a14588329 100644 --- a/op-acceptance-tests/README.md +++ b/op-acceptance-tests/README.md @@ -6,8 +6,8 @@ This directory contains the OP Stack acceptance tests. They run against the in-p The supported execution path is: -- `just` or `just acceptance-test-all` -- `gotestsum -- go test ./op-acceptance-tests/tests/...` +- `just` or `just acceptance-test` +- `gotestsum -- go test ./tests/...` `devtest.T.MarkFlaky(...)` is used for tests that should downgrade failures to skips in the normal acceptance run. Set `DEVNET_FAIL_FLAKY_TESTS=true` to force those tests to fail normally. Acceptance runs also emit a `flaky-tests.txt` artifact in `op-acceptance-tests/logs/...` listing the current `MarkFlaky(...)` call sites. @@ -32,21 +32,21 @@ just # Explicit alias just acceptance-test -just acceptance-test-all ``` ### Direct CLI Usage ```bash -gotestsum --format testname --junitfile ./op-acceptance-tests/results/results.xml -- \ - -count=1 -p 4 -parallel 4 -timeout 2h ./op-acceptance-tests/tests/... +mkdir -p results +gotestsum --format testname --junitfile ./results/results.xml -- \ + -count=1 -p 4 -parallel 4 -timeout 30m ./tests/... ``` The `just` wrapper computes defaults from available CPUs: - package jobs: CPU count - in-package `t.Parallel`: half the CPU count -- timeout: `2h` +- timeout: `30m` Override them with `ACCEPTANCE_TEST_JOBS`, `ACCEPTANCE_TEST_PARALLEL`, and `ACCEPTANCE_TEST_TIMEOUT`. diff --git a/op-acceptance-tests/gates/base.txt b/op-acceptance-tests/gates/base.txt deleted file mode 100644 index 6f73a09ba86..00000000000 --- a/op-acceptance-tests/gates/base.txt +++ /dev/null @@ -1,12 +0,0 @@ -./op-acceptance-tests/tests/base -./op-acceptance-tests/tests/base/deposit -./op-acceptance-tests/tests/base/chain -./op-acceptance-tests/tests/ecotone -./op-acceptance-tests/tests/fjord -./op-acceptance-tests/tests/isthmus -./op-acceptance-tests/tests/isthmus/operator_fee -./op-acceptance-tests/tests/isthmus/withdrawal_root -./op-acceptance-tests/tests/isthmus/erc20_bridge -./op-acceptance-tests/tests/isthmus/pectra -./op-acceptance-tests/tests/jovian/bpo2 -./op-acceptance-tests/tests/jovian/pectra diff --git a/op-acceptance-tests/justfile b/op-acceptance-tests/justfile index 8f5839e531d..d81bdd7862d 100644 --- a/op-acceptance-tests/justfile +++ b/op-acceptance-tests/justfile @@ -4,16 +4,43 @@ REPO_ROOT := `realpath ..` # path to the root of the optimism monorepo default: @just acceptance-test -# Run acceptance tests, optionally filtered by a gate (reads packages from gates/.txt) -acceptance-test gate="": +# Build all dependencies for local acceptance test runs. Skipped in CI where jobs handle this. +build-deps: #!/usr/bin/env bash set -euo pipefail - GATE="{{gate}}" - if [[ -n "$GATE" ]]; then - echo -e "DEVNET: in-memory, GATE: $GATE\n" - else - echo -e "DEVNET: in-memory, MODE: all tests\n" - fi + if [[ -n "${CIRCLECI:-}" ]]; then exit 0; fi + + echo "Building contracts (local build)..." + cd {{REPO_ROOT}} + echo " - Installing mise..." + mise install + cd packages/contracts-bedrock + echo " - Installing contracts..." + just install + echo " - Forge build..." + just build-no-tests + cd {{REPO_ROOT}} + + echo "Building cannon dependencies..." + cd {{REPO_ROOT}} + just cannon-prestates + + echo "Building Rust binaries (kona-node, op-rbuilder, rollup-boost)..." + cd {{REPO_ROOT}} + just build-rust-release + +# Run specific acceptance tests. Builds dependencies first, then passes all arguments to go test. +# Examples: +# just test ./op-acceptance-tests/tests/base/... +# just test -run TestMyTest ./op-acceptance-tests/tests/base/ +test *args: build-deps + cd {{REPO_ROOT}} && go test -count=1 -timeout 30m {{args}} + +# Run acceptance tests +acceptance-test: + #!/usr/bin/env bash + set -euo pipefail + echo -e "DEVNET: in-memory\n" # Build dependencies for in-process mode if not in CI. # In CI jobs already take care of this, so we skip it. @@ -66,7 +93,7 @@ acceptance-test gate="": JOBS="${ACCEPTANCE_TEST_JOBS:-$DEFAULT_JOBS}" TEST_PARALLEL="${ACCEPTANCE_TEST_PARALLEL:-$DEFAULT_TEST_PARALLEL}" - TEST_TIMEOUT="${ACCEPTANCE_TEST_TIMEOUT:-2h}" + TEST_TIMEOUT="${ACCEPTANCE_TEST_TIMEOUT:-30m}" echo "CPU COUNT: $CPU_COUNT" echo "PACKAGE JOBS: $JOBS" echo "TEST PARALLELISM: $TEST_PARALLEL" @@ -92,25 +119,7 @@ acceptance-test gate="": grep -RIn "MarkFlaky(" ./op-acceptance-tests/tests > "$FLAKY_REPORT" || true fi - # Determine which packages to test - if [[ -n "$GATE" ]]; then - GATE_FILE="{{REPO_ROOT}}/op-acceptance-tests/gates/${GATE}.txt" - if [[ ! -f "$GATE_FILE" ]]; then - echo "error: gate file not found: $GATE_FILE" >&2 - exit 1 - fi - # Read non-empty, non-comment lines from the gate file - TEST_PACKAGES=() - while IFS= read -r line; do - line="${line%%#*}" # strip comments - line="${line// /}" # strip whitespace - [[ -z "$line" ]] && continue - TEST_PACKAGES+=("$line") - done < "$GATE_FILE" - echo "Gate '$GATE': ${#TEST_PACKAGES[@]} packages" - else - TEST_PACKAGES=("./op-acceptance-tests/tests/...") - fi + TEST_PACKAGES=("./op-acceptance-tests/tests/...") set +e LOG_LEVEL="${LOG_LEVEL}" {{REPO_ROOT}}/ops/scripts/gotestsum-split.sh \ diff --git a/op-acceptance-tests/tests/base/conductor/leadership_transfer_test.go b/op-acceptance-tests/tests/base/conductor/leadership_transfer_test.go index 9f396a6e3d0..769c6787366 100644 --- a/op-acceptance-tests/tests/base/conductor/leadership_transfer_test.go +++ b/op-acceptance-tests/tests/base/conductor/leadership_transfer_test.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" @@ -23,6 +24,12 @@ type conductorWithInfo struct { // TestConductorLeadershipTransfer checks if the leadership transfer works correctly on the conductors func TestConductorLeadershipTransfer(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // --- FAIL: TestConductorLeadershipTransfer (63.04s) + // panic: interface conversion: sysgo.L2CLNode is *sysgo.KonaNode, not *sysgo.OpNode [recovered] + // panic: interface conversion: sysgo.L2CLNode is *sysgo.KonaNode, not *sysgo.OpNode + sysgo.SkipOnKonaNode(t, "not supported") logger := testlog.Logger(t, log.LevelInfo).With("Test", "TestConductorLeadershipTransfer") sys := presets.NewMinimalWithConductors(t) diff --git a/op-acceptance-tests/tests/base/deposit/deposit_test.go b/op-acceptance-tests/tests/base/deposit/deposit_test.go index 0196654d6a9..9fbfeb54ed8 100644 --- a/op-acceptance-tests/tests/base/deposit/deposit_test.go +++ b/op-acceptance-tests/tests/base/deposit/deposit_test.go @@ -12,7 +12,8 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" "github.com/ethereum-optimism/optimism/op-service/txplan" - supervisorTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestL1ToL2Deposit(gt *testing.T) { @@ -63,8 +64,8 @@ func TestL1ToL2Deposit(gt *testing.T) { // Wait for the sequencer to process the deposit t.Require().Eventually(func() bool { - head := sys.L2CL.HeadBlockRef(supervisorTypes.LocalUnsafe) - return head.L1Origin.Number >= receipt.BlockNumber.Uint64() + head := sys.L2CL.HeadBlockRef(safety.LocalUnsafe) + return head.L1Origin.Number >= bigs.Uint64Strict(receipt.BlockNumber) }, sys.L1EL.TransactionTimeout(), time.Second, "awaiting deposit to be processed by L2") alicel2.WaitForBalance(initialL2Balance.Add(depositAmount)) diff --git a/op-acceptance-tests/tests/batcher/batcher_test.go b/op-acceptance-tests/tests/batcher/batcher_test.go index c845491b1e7..465e96248e5 100644 --- a/op-acceptance-tests/tests/batcher/batcher_test.go +++ b/op-acceptance-tests/tests/batcher/batcher_test.go @@ -14,7 +14,8 @@ import ( "github.com/ethereum-optimism/optimism/op-service/apis" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txplan" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -68,7 +69,7 @@ func TestBatcherFullChannelsAfterDowntime(gt *testing.T) { sequenceBlockWithL1Origin(t, ts_L2, parent, l1Origin, cathrine, alice, nonce) nonce++ - parent = sys.L2CL.HeadBlockRef(types.LocalUnsafe).Hash + parent = sys.L2CL.HeadBlockRef(safety.LocalUnsafe).Hash sys.L2EL.WaitForPendingNonceMatch(cathrine.Address(), nonce, 10, 1*time.Second) diff --git a/op-acceptance-tests/tests/depreqres/common/common.go b/op-acceptance-tests/tests/depreqres/common/common.go index 6b4b54d0521..7e07ec43364 100644 --- a/op-acceptance-tests/tests/depreqres/common/common.go +++ b/op-acceptance-tests/tests/depreqres/common/common.go @@ -11,7 +11,9 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" + "github.com/ethereum/go-ethereum/log" ) func syncModeOpt(syncMode sync.Mode) presets.Option { @@ -78,22 +80,27 @@ func UnsafeChainNotStalling_DisconnectT(t devtest.T, syncMode sync.Mode, sleep t l.Info("Confirm that the CL nodes are progressing the unsafe chain") target := uint64(3) dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalUnsafe, target, 30), - sys.L2CLB.AdvancedFn(types.LocalUnsafe, target, 30), + sys.L2CL.AdvancedFn(safety.LocalUnsafe, target, 30), + sys.L2CLB.AdvancedFn(safety.LocalUnsafe, target, 30), ) l.Info("Disconnect L2CL from L2CLB, and vice versa") sys.L2CLB.DisconnectPeer(sys.L2CL) sys.L2CL.DisconnectPeer(sys.L2CLB) - ssA_before := sys.L2CL.SyncStatus() - sys.L2CLB.WaitForStall(types.LocalUnsafe) + sys.L2CLB.WaitForPeerDisconnected(sys.L2CL) + sys.L2CL.WaitForPeerDisconnected(sys.L2CLB) + + sys.L2CLB.WaitForStall(safety.LocalUnsafe) ssB_before := sys.L2CLB.SyncStatus() l.Info("L2CL status before delay", "unsafeL2", ssA_before.UnsafeL2.ID(), "safeL2", ssA_before.SafeL2.ID()) l.Info("L2CLB status before delay", "unsafeL2", ssB_before.UnsafeL2.ID(), "safeL2", ssB_before.SafeL2.ID()) - time.Sleep(sleep) + l.Info("Wait for sequencer to advance while verifier is disconnected", "advanceBlocks", advanceBlocks) + // Allow generous time: advanceBlocks * ~2s block time, plus buffer for CI pressure. + advanceAttempts := int(advanceBlocks*2 + 30) + sys.L2CL.Advanced(safety.LocalUnsafe, advanceBlocks, advanceAttempts) ssA_after := sys.L2CL.SyncStatus() ssB_after := sys.L2CLB.SyncStatus() @@ -109,7 +116,7 @@ func UnsafeChainNotStalling_DisconnectT(t devtest.T, syncMode sync.Mode, sleep t sys.L2CL.ConnectPeer(sys.L2CLB) l.Info("Confirm that the unsafe chain for L2CLB is not stalled") - sys.L2CLB.Reached(types.LocalUnsafe, ssA_after.UnsafeL2.Number, 30) + sys.L2CLB.Reached(safety.LocalUnsafe, ssA_after.UnsafeL2.Number, 30) sys.L2ELB.Reached(eth.Unsafe, ssA_after.UnsafeL2.Number, 30) } @@ -126,16 +133,18 @@ func UnsafeChainNotStalling_RestartOpNodeT(t devtest.T, syncMode sync.Mode, slee l.Info("Confirm that the CL nodes are progressing the unsafe chain") target := uint64(3) dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalUnsafe, target, 30), - sys.L2CLB.AdvancedFn(types.LocalUnsafe, target, 30), + sys.L2CL.AdvancedFn(safety.LocalUnsafe, target, 30), + sys.L2CLB.AdvancedFn(safety.LocalUnsafe, target, 30), ) l.Info("Disconnect L2CL from L2CLB, and vice versa") sys.L2CLB.DisconnectPeer(sys.L2CL) sys.L2CL.DisconnectPeer(sys.L2CLB) - ssA_before := sys.L2CL.SyncStatus() - sys.L2CLB.WaitForStall(types.LocalUnsafe) + sys.L2CLB.WaitForPeerDisconnected(sys.L2CL) + sys.L2CL.WaitForPeerDisconnected(sys.L2CLB) + + sys.L2CLB.WaitForStall(safety.LocalUnsafe) ssB_before := sys.L2CLB.SyncStatus() l.Info("L2CL status before delay", "unsafeL2", ssA_before.UnsafeL2.ID(), "safeL2", ssA_before.SafeL2.ID()) @@ -143,7 +152,9 @@ func UnsafeChainNotStalling_RestartOpNodeT(t devtest.T, syncMode sync.Mode, slee sys.L2CLB.Stop() - time.Sleep(sleep) + l.Info("Wait for sequencer to advance while verifier is stopped", "advanceBlocks", advanceBlocks) + advanceAttempts := int(advanceBlocks*2 + 30) + sys.L2CL.Advanced(safety.LocalUnsafe, advanceBlocks, advanceAttempts) sys.L2CLB.Start() @@ -161,7 +172,7 @@ func UnsafeChainNotStalling_RestartOpNodeT(t devtest.T, syncMode sync.Mode, slee sys.L2CL.ConnectPeer(sys.L2CLB) l.Info("Confirm that the unsafe chain for L2CLB is not stalled") - sys.L2CLB.Reached(types.LocalUnsafe, ssA_after.UnsafeL2.Number, 30) + sys.L2CLB.Reached(safety.LocalUnsafe, ssA_after.UnsafeL2.Number, 30) sys.L2ELB.Reached(eth.Unsafe, ssA_after.UnsafeL2.Number, 30) } diff --git a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/depreqres_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/depreqres_test.go index f6c441985c0..056b1936e5f 100644 --- a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/depreqres_test.go +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/depreqres_test.go @@ -10,7 +10,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) const disabledReqRespSyncFlakyReason = "known flaky in the default acceptance run" @@ -26,8 +27,8 @@ func TestUnsafeChainNotStalling_DisabledReqRespSync(gt *testing.T) { l.Info("Confirm that the CL nodes are progressing the unsafe chain") delta := uint64(3) dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalUnsafe, delta, 30), - sys.L2CLB.AdvancedFn(types.LocalUnsafe, delta, 30), + sys.L2CL.AdvancedFn(safety.LocalUnsafe, delta, 30), + sys.L2CLB.AdvancedFn(safety.LocalUnsafe, delta, 30), ) l.Info("Disconnect L2CL from L2CLB, and vice versa") @@ -37,8 +38,8 @@ func TestUnsafeChainNotStalling_DisabledReqRespSync(gt *testing.T) { ssA_before := sys.L2CL.SyncStatus() ssB_before := sys.L2CLB.SyncStatus() - l.Info("L2CL status before delay", "unsafeL2", ssA_before.UnsafeL2.ID(), "safeL2", ssA_before.SafeL2.ID()) - l.Info("L2CLB status before delay", "unsafeL2", ssB_before.UnsafeL2.ID(), "safeL2", ssB_before.SafeL2.ID()) + l.Info("Wait for L2CLB unsafe head to stall after disconnect") + sys.L2CLB.WaitForStall(safety.LocalUnsafe) time.Sleep(20 * time.Second) @@ -56,8 +57,6 @@ func TestUnsafeChainNotStalling_DisabledReqRespSync(gt *testing.T) { sys.L2CL.ConnectPeer(sys.L2CLB) l.Info("Confirm that the unsafe chain for L2CLB can advance") - dsl.CheckAll(t, - sys.L2CLB.AdvancedFn(types.LocalUnsafe, delta, 30), - sys.L2ELB.AdvancedFn(eth.Unsafe, delta), - ) + sys.L2CLB.Reached(safety.LocalUnsafe, ssA.UnsafeL2.Number, 30) + sys.L2ELB.Reached(eth.Unsafe, ssA.UnsafeL2.Number, 30) } diff --git a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/divergence/divergence_test.go b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/divergence/divergence_test.go index 26f35f7bb4d..ce762e65eed 100644 --- a/op-acceptance-tests/tests/depreqres/reqressyncdisabled/divergence/divergence_test.go +++ b/op-acceptance-tests/tests/depreqres/reqressyncdisabled/divergence/divergence_test.go @@ -7,29 +7,37 @@ import ( "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/depreqres/common" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum/go-ethereum" ) // TestCLELDivergence tests that the CL and EL diverge when the CL advances the unsafe head, due to accepting SYNCING response from the EL, but the EL cannot validate the block (yet), does not canonicalize it, and doesn't serve it. func TestCLELDivergence(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|10:19:50.846] + // assertions.go:387: Error Trace: /Users/josh/repos/optimism/op-acceptance-tests/tests/depreqres/reqressyncdisabled/divergence/divergence_test.go:34 + // assertions.go:387: Error: Not equal: + // assertions.go:387: expected: 0x1 + // assertions.go:387: actual : 0x0 + // assertions.go:387: Test: TestCLELDivergence + sysgo.SkipOnKonaNode(t, "not supported") sys := presets.NewSingleChainMultiNodeWithoutP2PWithoutCheck(t, common.ReqRespSyncDisabledOpts(sync.ELSync)...) require := t.Require() l := t.Logger() - sys.L2CL.Advanced(types.LocalUnsafe, 8, 30) - - // batcher down so safe not advanced - require.Equal(uint64(0), sys.L2CL.HeadBlockRef(types.LocalSafe).Number) - require.Equal(uint64(0), sys.L2CLB.HeadBlockRef(types.LocalSafe).Number) + startNum := sys.L2CLB.HeadBlockRef(safety.LocalUnsafe).Number - startNum := sys.L2CLB.HeadBlockRef(types.LocalUnsafe).Number + // Wait for the sequencer to produce the next block so the verifier initial EL sync can complete. + sys.L2CL.Reached(safety.LocalUnsafe, startNum+1, 30) - // Finish EL sync by supplying the first block - // EL Sync finished because underlying EL has states to validate the payload for block startNum+1 + // Complete initial EL sync by providing the first missing block. + // At this point, the EL has sufficient state to validate block startNum+1. sys.L2CLB.SignalTarget(sys.L2EL, startNum+1) require.Equal(startNum+1, sys.L2ELB.BlockRefByLabel(eth.Unsafe).Number) @@ -37,7 +45,16 @@ func TestCLELDivergence(gt *testing.T) { targetNumber := startNum + delta targetBlock := sys.L2EL.BlockRefByNumber(targetNumber) - l.Info("Sending payload ", "target", targetNumber, "startNum", startNum) + // Choose a future EL sync target for which the EL lacks state to validate. + delta := uint64(5) + targetNumber := startNum + delta + sys.L2CL.Advanced(safety.LocalUnsafe, targetNumber, 30) + targetBlock := sys.L2EL.BlockRefByNumber(targetNumber) + + // The CL advances its unsafe head to the target block, even though there is a gap. + var ss *eth.SyncStatus + require.Eventually(func() bool { + l.Info("Sending payload", "target", targetNumber, "startNum", startNum) sys.L2CLB.SignalTarget(sys.L2EL, targetNumber) // Canonical unsafe head never advances because of the gap diff --git a/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go b/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go index e13bc230309..112952e9b05 100644 --- a/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go +++ b/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go @@ -11,7 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/log/logfilter" "github.com/ethereum-optimism/optimism/op-service/logmods" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" @@ -24,6 +24,23 @@ const maxExpectedFlashblocks = 20 // TestFlashblocksStream checks we can connect to the flashblocks stream across multiple CL backends. func TestFlashblocksStream(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-30|22:44:52.250] + // assertions.go:387: Error Trace: /home/circleci/project/op-devstack/sysgo/l2_cl_kona.go:99 + // assertions.go:387: /home/circleci/project/op-devstack/sysgo/mixed_runtime.go:456 + // assertions.go:387: /home/circleci/project/op-devstack/sysgo/singlechain_build.go:182 + // assertions.go:387: /home/circleci/project/op-devstack/sysgo/singlechain_build.go:276 + // assertions.go:387: /home/circleci/project/op-devstack/sysgo/singlechain_flashblocks.go:36 + // assertions.go:387: /home/circleci/project/op-devstack/sysgo/singlechain_runtime.go:105 + // assertions.go:387: /home/circleci/project/op-devstack/sysgo/singlechain_flashblocks.go:53 + // assertions.go:387: /home/circleci/project/op-devstack/presets/flashblocks.go:43 + // assertions.go:387: /home/circleci/project/op-acceptance-tests/tests/flashblocks/flashblocks_stream_test.go:26 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: context deadline exceeded + // assertions.go:387: Test: TestFlashblocksStream + // assertions.go:387: Messages: need user RPC + sysgo.SkipOnKonaNode(t, "not supported (fail to get user rpc)") logger := t.Logger() sys := presets.NewSingleChainWithFlashblocks(t) filterHandler, ok := logmods.FindHandler[logfilter.FilterHandler](logger.Handler()) diff --git a/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go b/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go index 71a2a690d5e..96d9bdc7a10 100644 --- a/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go +++ b/op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txplan" "github.com/stretchr/testify/require" @@ -32,6 +33,23 @@ type timedMessage struct { // - That Flashblock's time in nanoseconds must be before the approximated transaction confirmation time recorded previously. func TestFlashblocksTransfer(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-30|22:44:52.250] + // assertions.go:387: Error Trace: /op-devstack/sysgo/l2_cl_kona.go:99 + // assertions.go:387: /op-devstack/sysgo/mixed_runtime.go:456 + // assertions.go:387: /op-devstack/sysgo/singlechain_build.go:182 + // assertions.go:387: /op-devstack/sysgo/singlechain_build.go:276 + // assertions.go:387: /op-devstack/sysgo/singlechain_flashblocks.go:36 + // assertions.go:387: /op-devstack/sysgo/singlechain_runtime.go:105 + // assertions.go:387: /op-devstack/sysgo/singlechain_flashblocks.go:53 + // assertions.go:387: /op-devstack/presets/flashblocks.go:43 + // assertions.go:387: /op-acceptance-tests/tests/flashblocks/flashblocks_transfer_test.go:38 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: context deadline exceeded + // assertions.go:387: Test: TestFlashblocksTransfer + // assertions.go:387: Messages: need user RPC + sysgo.SkipOnKonaNode(t, "not supported (fail to get user rpc)") logger := t.Logger() tracer := t.Tracer() ctx := t.Ctx() diff --git a/op-acceptance-tests/tests/interop/filter/interop_filter_test.go b/op-acceptance-tests/tests/interop/filter/interop_filter_test.go new file mode 100644 index 00000000000..d5c17618402 --- /dev/null +++ b/op-acceptance-tests/tests/interop/filter/interop_filter_test.go @@ -0,0 +1,209 @@ +package filter + +import ( + "context" + "math/rand" + "strings" + "testing" + "time" + + "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-acceptance-tests/tests/interop" + "github.com/ethereum-optimism/optimism/op-core/predeploys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txplan" + suptypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func setupInteropFilterTest(t devtest.T) *presets.TwoL2SupernodeInterop { + return presets.NewTwoL2SupernodeInterop(t, 0, presets.WithInteropFilter()) +} + +// interopTxRejectedError returns true if err matches any known interop +// transaction rejection from op-geth, op-reth, or the interop filter. +func interopTxRejectedError(err error) bool { + msg := err.Error() + // op-geth: generic filter rejection wrapping all causes + if strings.Contains(msg, "transaction filtered out") { + return true + } + // op-interop-filter: malformed or unrecognized access list entry + if strings.Contains(msg, "failed to parse access entry") { + return true + } + // op-reth fast-path: cached failsafe state rejects before calling filter + if strings.Contains(msg, "interop failsafe is active") { + return true + } + // op-interop-filter: failsafe enabled at the filter level + if strings.Contains(msg, "failsafe is enabled") { + return true + } + return false +} + +// TestInteropFilter_IngressRejectsInvalid verifies that a transaction with fabricated +// CrossL2Inbox access list entries is rejected by the interop filter. +func TestInteropFilter_IngressRejectsInvalid(gt *testing.T) { + t := devtest.ParallelT(gt) + sys := setupInteropFilterTest(t) + require := t.Require() + + bob := sys.FunderB.NewFundedEOA(eth.OneHundredthEther) + + // Construct a fabricated access list entry with a random storage key + // that the filter won't recognize as a valid cross-chain message + fakeStorageKey := crypto.Keccak256Hash([]byte("fabricated-inbox-entry")) + accessList := types.AccessList{{ + Address: predeploys.CrossL2InboxAddr, + StorageKeys: []common.Hash{fakeStorageKey}, + }} + + // Send a transaction with the fabricated access list. + // The interop filter should reject this because the inbox entry doesn't + // correspond to any real cross-chain message. + ctx, cancel := context.WithTimeout(gt.Context(), 10*time.Second) + defer cancel() + + bobAddr := bob.Address() + elClient := sys.L2ELB.EthClient() + tx := txplan.NewPlannedTx( + bob.Plan(), + // Override retry submission with single-attempt submitter so the + // filter's rejection propagates immediately instead of retrying + // until the context expires. + txplan.WithTransactionSubmitter(elClient), + txplan.WithTo(&bobAddr), + txplan.WithValue(eth.GWei(1)), + txplan.WithAccessList(accessList), + txplan.WithGasLimit(100_000), + ) + + // The transaction should be explicitly rejected by the interop filter. + _, err := tx.Submitted.Eval(ctx) + require.Error(err, "transaction with fabricated access list should not be included") + require.True(interopTxRejectedError(err), + "expected interop filter rejection, got: %v", err) +} + +// TestInteropFilter_FailsafeLifecycle verifies the full failsafe lifecycle: +// interop txs succeed normally, are blocked when failsafe is enabled, +// and recover after failsafe is disabled. +// +// NOT TESTED: txpool eviction of pending interop txs on failsafe activation. +// Both op-reth (poll_failsafe) and op-geth (startBackgroundInteropFailsafeDetection) +// have background tasks that evict interop txs from the pool when failsafe +// transitions from disabled to enabled. Testing this reliably is difficult because +// it requires stopping the sequencer to prevent block inclusion, but StopSequencer +// only stops the op-node from requesting new payloads — an already in-flight +// engine API payload (forkchoiceUpdated → getPayload → newPayload) can still land +// after StopSequencer returns. This creates a race where the tx may be included in +// a block before failsafe eviction runs, causing flaky assertions. Reliably testing +// eviction would require either a deterministic block builder that bypasses the +// mempool (which defeats the purpose) or an engine API hook to block payload +// completion (which doesn't exist in the test framework). +func TestInteropFilter_FailsafeLifecycle(gt *testing.T) { + t := devtest.ParallelT(gt) + sys := setupInteropFilterTest(t) + require := t.Require() + + alice := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) + bob := sys.FunderB.NewFundedEOA(eth.OneHundredthEther) + + eventLoggerAddress := alice.DeployEventLogger() + sys.L2B.CatchUpTo(sys.L2A) + + // Step 1: Send a valid interop tx — should succeed before failsafe + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + initMsg := alice.SendInitMessage(interop.RandomInitTrigger(rng, eventLoggerAddress, 2, 10)) + sys.L2B.WaitForBlock() + execMsg := bob.SendExecMessage(initMsg) + require.Equal(types.ReceiptStatusSuccessful, execMsg.Receipt.Status, + "interop tx should succeed before failsafe") + + // Step 2: Enable failsafe — no wait needed; even if op-reth hasn't polled + // the change yet, checkAccessList will reject interop txs on the filter side. + require.NotNil(sys.InteropFilter, "interop filter must be configured") + sys.InteropFilter.SetFailsafeEnabled(true) + + // Step 3: Send another init message and try exec — should fail + initMsg2 := alice.SendInitMessage(interop.RandomInitTrigger(rng, eventLoggerAddress, 1, 5)) + sys.L2B.WaitForBlock() + + ctx, cancel := context.WithTimeout(gt.Context(), 15*time.Second) + defer cancel() + + // During failsafe, even valid access lists should be rejected + result, err := initMsg2.Tx.Result.Eval(ctx) + require.NoError(err, "init message result must be available") + require.Greater(len(result.Entries), 0, "init message must have entries") + + msg := result.Entries[0] + accessList := types.AccessList{{ + Address: predeploys.CrossL2InboxAddr, + StorageKeys: suptypes.EncodeAccessList([]suptypes.Access{msg.Access()}), + }} + + bobAddr := bob.Address() + elClient := sys.L2ELB.EthClient() + tx := txplan.NewPlannedTx( + bob.Plan(), + txplan.WithTransactionSubmitter(elClient), + txplan.WithTo(&bobAddr), + txplan.WithValue(eth.GWei(1)), + txplan.WithAccessList(accessList), + txplan.WithGasLimit(100_000), + ) + + _, err = tx.Submitted.Eval(ctx) + require.Error(err, "interop tx should be rejected during failsafe") + require.True(interopTxRejectedError(err), + "expected interop filter rejection, got: %v", err) + + // Step 4: Disable failsafe and wait one block for op-reth's poller to pick up the change + sys.InteropFilter.SetFailsafeEnabled(false) + sys.L2B.WaitForBlock() + + // Step 5: Verify interop txs recover after failsafe is disabled + initMsg3 := alice.SendInitMessage(interop.RandomInitTrigger(rng, eventLoggerAddress, 1, 3)) + sys.L2B.WaitForBlock() + execMsg2 := bob.SendExecMessage(initMsg3) + require.Equal(types.ReceiptStatusSuccessful, execMsg2.Receipt.Status, + "interop tx should succeed after failsafe disabled") +} + +// TestInteropFilter_NonInteropUnaffected verifies that regular (non-interop) +// transactions are accepted on both chains regardless of failsafe state. +func TestInteropFilter_NonInteropUnaffected(gt *testing.T) { + t := devtest.ParallelT(gt) + sys := setupInteropFilterTest(t) + require := t.Require() + + aliceA := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) + bobA := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) + aliceB := sys.FunderB.NewFundedEOA(eth.OneHundredthEther) + bobB := sys.FunderB.NewFundedEOA(eth.OneHundredthEther) + + // Enable failsafe — takes effect immediately on the filter side + require.NotNil(sys.InteropFilter, "interop filter must be configured") + sys.InteropFilter.SetFailsafeEnabled(true) + + // Send regular (non-interop) transfers on both chains — should succeed even during failsafe + txA := aliceA.Transfer(bobA.Address(), eth.GWei(1000)) + receiptA, err := txA.Included.Eval(gt.Context()) + require.NoError(err, "regular transfer on chain A should succeed during failsafe") + require.Equal(types.ReceiptStatusSuccessful, receiptA.Status, "regular transfer on chain A should succeed") + + txB := aliceB.Transfer(bobB.Address(), eth.GWei(1000)) + receiptB, err := txB.Included.Eval(gt.Context()) + require.NoError(err, "regular transfer on chain B should succeed during failsafe") + require.Equal(types.ReceiptStatusSuccessful, receiptB.Status, "regular transfer on chain B should succeed") + + // Disable failsafe + sys.InteropFilter.SetFailsafeEnabled(false) +} diff --git a/op-acceptance-tests/tests/interop/message/interop_happy_tx_test.go b/op-acceptance-tests/tests/interop/message/interop_happy_tx_test.go index 9162ce4330c..47c7b7607df 100644 --- a/op-acceptance-tests/tests/interop/message/interop_happy_tx_test.go +++ b/op-acceptance-tests/tests/interop/message/interop_happy_tx_test.go @@ -10,7 +10,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-service/eth" - stypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) // TestInteropHappyTx is testing that a valid init message, followed by a valid exec message are correctly @@ -19,7 +20,7 @@ import ( func TestInteropHappyTx(gt *testing.T) { gt.Skip("Skipping Interop Acceptance Test") t := devtest.ParallelT(gt) - sys := presets.NewSimpleInterop(t) + sys := presets.NewTwoL2SupernodeInterop(t, 0, presets.WithInteropFilter()) // two EOAs for triggering the init and exec interop txs alice := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) @@ -42,10 +43,10 @@ func TestInteropHappyTx(gt *testing.T) { // confirm that the cross-safe safety passed init and exec receipts and that blocks were not reorged dsl.CheckAll(t, - sys.L2CLA.ReachedRefFn(stypes.CrossSafe, initMsg.BlockID(), + sys.L2ACL.ReachedRefFn(safety.CrossSafe, initMsg.BlockID(), // TODO(#16598): Make this relative to the block time 500), - sys.L2CLB.ReachedRefFn(stypes.CrossSafe, execMsg.BlockID(), + sys.L2BCL.ReachedRefFn(safety.CrossSafe, execMsg.BlockID(), // TODO(#16598): Make this relative to the block time 500), ) diff --git a/op-acceptance-tests/tests/interop/proofs-singlechain/interop_fault_proofs_test.go b/op-acceptance-tests/tests/interop/proofs-singlechain/interop_fault_proofs_test.go index 3e02cd30f9f..0d8faa8db4e 100644 --- a/op-acceptance-tests/tests/interop/proofs-singlechain/interop_fault_proofs_test.go +++ b/op-acceptance-tests/tests/interop/proofs-singlechain/interop_fault_proofs_test.go @@ -10,6 +10,6 @@ import ( func TestInteropSingleChainFaultProofs(gt *testing.T) { t := devtest.SerialT(gt) - sys := presets.NewSingleChainInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSingleChainInterop(t) sfp.RunSingleChainSuperFaultProofSmokeTest(t, sys) } diff --git a/op-acceptance-tests/tests/interop/proofs/challenger_test.go b/op-acceptance-tests/tests/interop/proofs/challenger_test.go index 1a4b1159888..e5c804d1d5e 100644 --- a/op-acceptance-tests/tests/interop/proofs/challenger_test.go +++ b/op-acceptance-tests/tests/interop/proofs/challenger_test.go @@ -5,21 +5,22 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/dsl/proofs" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-service/eth" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestChallengerPlaysGame(gt *testing.T) { t := devtest.ParallelT(gt) - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t, presets.WithoutHonestProposer()) dsl.CheckAll(t, - sys.L2CLA.AdvancedFn(types.CrossSafe, 1, 30), - sys.L2CLB.AdvancedFn(types.CrossSafe, 1, 30), + sys.L2CLA.AdvancedFn(safety.CrossSafe, 1, 30), + sys.L2CLB.AdvancedFn(safety.CrossSafe, 1, 30), ) badClaim := common.HexToHash("0xdeadbeef00000000000000000000000000000000000000000000000000000000") @@ -39,10 +40,10 @@ func TestChallengerPlaysGame(gt *testing.T) { func TestChallengerRespondsToMultipleInvalidClaims(gt *testing.T) { gt.Skip("Skipping Interop Acceptance Test") t := devtest.ParallelT(gt) - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t, presets.WithoutHonestProposer()) dsl.CheckAll(t, - sys.L2CLA.AdvancedFn(types.CrossSafe, 1, 30), - sys.L2CLB.AdvancedFn(types.CrossSafe, 1, 30), + sys.L2CLA.AdvancedFn(safety.CrossSafe, 1, 30), + sys.L2CLB.AdvancedFn(safety.CrossSafe, 1, 30), ) attacker := sys.FunderL1.NewFundedEOA(eth.TenEther) @@ -63,10 +64,10 @@ func TestChallengerRespondsToMultipleInvalidClaims(gt *testing.T) { func TestChallengerRespondsToMultipleInvalidClaimsEOA(gt *testing.T) { gt.Skip("Skipping Interop Acceptance Test") t := devtest.ParallelT(gt) - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t, presets.WithoutHonestProposer()) dsl.CheckAll(t, - sys.L2CLA.AdvancedFn(types.CrossSafe, 1, 30), - sys.L2CLB.AdvancedFn(types.CrossSafe, 1, 30), + sys.L2CLA.AdvancedFn(safety.CrossSafe, 1, 30), + sys.L2CLB.AdvancedFn(safety.CrossSafe, 1, 30), ) dgf := sys.DisputeGameFactory() @@ -86,3 +87,21 @@ func TestChallengerRespondsToMultipleInvalidClaimsEOA(gt *testing.T) { require.Equal(t, attacker.Address(), claim.Claimant()) } } + +func TestChallengerCountersPreGenesisGame(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSimpleInterop( + t, + presets.WithPreGenesisSuperGame( + eth.Bytes32(common.HexToHash("0x1111000000000000000000000000000000000000000000000000000000000000")), + eth.Bytes32(common.HexToHash("0x2222000000000000000000000000000000000000000000000000000000000000")), + ), + ) + + game := sys.DisputeGameFactory().SuperGameAtIndex(0) + genesisTime := sys.L2ChainA.Escape().RollupConfig().Genesis.L2Time + require.EqualValues(t, genesisTime, game.StartingL2SequenceNumber(), "pre-genesis game should anchor at rollup genesis") + require.Greater(t, game.L2SequenceNumber(), genesisTime, "pre-genesis game should dispute a post-genesis timestamp") + + game.RootClaim().WaitForCounterClaim() +} diff --git a/op-acceptance-tests/tests/interop/proofs/fpp/fpp_test.go b/op-acceptance-tests/tests/interop/proofs/fpp/fpp_test.go index 5d3c65ad3ea..99a293214da 100644 --- a/op-acceptance-tests/tests/interop/proofs/fpp/fpp_test.go +++ b/op-acceptance-tests/tests/interop/proofs/fpp/fpp_test.go @@ -5,12 +5,13 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestFPP(gt *testing.T) { t := devtest.ParallelT(gt) - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t) startTimestamp := max(sys.L2ChainA.Escape().RollupConfig().TimestampForBlock(1), sys.L2ChainB.Escape().RollupConfig().TimestampForBlock(1)) endTimestamp := sys.L2ChainA.Escape().RollupConfig().TimestampForBlock(5) @@ -22,9 +23,7 @@ func TestFPP(gt *testing.T) { func TestNextSuperRootNotFound(gt *testing.T) { t := devtest.ParallelT(gt) - // TODO(#19180): Unskip this once supernode is updated. - t.Skip("Supernode does not yet return optimistic blocks until blocks are fully validated") - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t) blockTime := sys.L2ChainA.Escape().RollupConfig().BlockTime // Need to setup situation where the next super root is not found but the next block is safe on the first chain, but not safe on the second. @@ -42,7 +41,7 @@ func TestNextSuperRootNotFound(gt *testing.T) { sys.SuperRoots.AwaitValidatedTimestamp(chainBLastBlock.Time) // Wait for safe head to advance on first chain to be sure the next block is also safe. - sys.L2CLA.Advanced(types.LocalSafe, 1, 10) + sys.L2CLA.Advanced(safety.LocalSafe, 1, 10) startTimestamp := chainBLastBlock.Time endTimestamp := startTimestamp + blockTime diff --git a/op-acceptance-tests/tests/interop/proofs/init_test.go b/op-acceptance-tests/tests/interop/proofs/init_test.go deleted file mode 100644 index 19ebe5d1377..00000000000 --- a/op-acceptance-tests/tests/interop/proofs/init_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package proofs - -import ( - "testing" - - "github.com/ethereum-optimism/optimism/op-devstack/presets" -) - -func TestMain(m *testing.M) { - presets.DoMain(m, presets.WithSuperInterop()) -} diff --git a/op-acceptance-tests/tests/interop/proofs/interop_fault_proofs_test.go b/op-acceptance-tests/tests/interop/proofs/interop_fault_proofs_test.go index 41b36d5588c..5df3b27fdb5 100644 --- a/op-acceptance-tests/tests/interop/proofs/interop_fault_proofs_test.go +++ b/op-acceptance-tests/tests/interop/proofs/interop_fault_proofs_test.go @@ -10,14 +10,12 @@ import ( func TestInteropFaultProofs(gt *testing.T) { t := devtest.SerialT(gt) - // TODO(#19180): Unskip this once supernode is updated. - t.Skip("Supernode does not yet return optimistic blocks until blocks are fully validated") - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t) sfp.RunSuperFaultProofTest(t, sys) } func TestInteropFaultProofs_ConsolidateValidCrossChainMessage(gt *testing.T) { t := devtest.SerialT(gt) - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t) sfp.RunConsolidateValidCrossChainMessageTest(t, sys) } diff --git a/op-acceptance-tests/tests/interop/proofs/proposer_test.go b/op-acceptance-tests/tests/interop/proofs/proposer_test.go index 37334b5c0dc..f3fb26677eb 100644 --- a/op-acceptance-tests/tests/interop/proofs/proposer_test.go +++ b/op-acceptance-tests/tests/interop/proofs/proposer_test.go @@ -9,7 +9,7 @@ import ( func TestProposer(gt *testing.T) { t := devtest.ParallelT(gt) - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t) dgf := sys.DisputeGameFactory() diff --git a/op-acceptance-tests/tests/interop/proofs/serial/interop_fault_proofs_test.go b/op-acceptance-tests/tests/interop/proofs/serial/interop_fault_proofs_test.go index 7d9cdf66019..8b733000fd4 100644 --- a/op-acceptance-tests/tests/interop/proofs/serial/interop_fault_proofs_test.go +++ b/op-acceptance-tests/tests/interop/proofs/serial/interop_fault_proofs_test.go @@ -12,23 +12,41 @@ import ( func TestInteropFaultProofs(gt *testing.T) { t := devtest.ParallelT(gt) - // TODO(#19180): Unskip this once supernode is updated. - t.Skip("Supernode does not yet return optimistic blocks until blocks are fully validated") - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t) sfp.RunSuperFaultProofTest(t, sys) } +func TestInteropFaultProofs_PreForkActivation(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSimpleInterop(t, presets.WithSuggestedInteropActivationOffset(365*24*60*60)) + sfp.RunPreForkActivationTest(t, sys) +} + +func TestInteropFaultProofs_ActivationBoundary(gt *testing.T) { + t := devtest.SerialT(gt) + // Set interop activation ~6s (3 blocks) after genesis. A small offset keeps + // the fork reachable within CI timeouts while still having pre-interop blocks. + sys := presets.NewSimpleInterop(t, + presets.WithSuggestedInteropActivationOffset(6), + ) + sfp.RunInteropActivationBoundaryTest(t, sys) +} + func TestInteropFaultProofs_ConsolidateValidCrossChainMessage(gt *testing.T) { t := devtest.ParallelT(gt) - sys := presets.NewSimpleInteropSupernodeProofs(t, presets.WithChallengerCannonKonaEnabled()) + sys := presets.NewSimpleInterop(t) sfp.RunConsolidateValidCrossChainMessageTest(t, sys) } +func TestInteropFaultProofs_DepositMessage(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSimpleInterop(t) + sfp.RunDepositMessageTest(t, sys) +} + func TestInteropFaultProofs_VariedBlockTimes(gt *testing.T) { t := devtest.SerialT(gt) - // TODO(#19010): Unskip once varied block time fault proofs are stable. - t.Skip("Skipping flaky varied block time fault proof test") - sys := presets.NewSimpleInteropSupernodeProofs( + sys := presets.NewSimpleInterop( t, presets.WithChallengerCannonKonaEnabled(), presets.WithL2BlockTimes(map[eth.ChainID]uint64{ @@ -41,9 +59,7 @@ func TestInteropFaultProofs_VariedBlockTimes(gt *testing.T) { func TestInteropFaultProofs_VariedBlockTimes_FasterChainB(gt *testing.T) { t := devtest.SerialT(gt) - // TODO(#19010): Unskip once varied block time fault proofs are stable. - t.Skip("Skipping flaky varied block time fault proof test") - sys := presets.NewSimpleInteropSupernodeProofs( + sys := presets.NewSimpleInterop( t, presets.WithChallengerCannonKonaEnabled(), presets.WithL2BlockTimes(map[eth.ChainID]uint64{ @@ -56,8 +72,43 @@ func TestInteropFaultProofs_VariedBlockTimes_FasterChainB(gt *testing.T) { func TestInteropFaultProofs_InvalidBlock(gt *testing.T) { t := devtest.SerialT(gt) - // TODO(#19411): Unskip once supernode removes invalid transactions - t.Skip("Supernode does not yet remove invalid transactions from blocks") - sys := presets.NewSimpleInteropSupernodeProofs(t) + sys := presets.NewSimpleInterop(t) sfp.RunInvalidBlockTest(t, sys) } + +func TestInteropFaultProofs_IntraBlock(gt *testing.T) { + for _, tc := range sfp.IntraBlockCases() { + gt.Run(tc.Name, func(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSimpleInterop(t) + sfp.RunIntraBlockConsolidationTest(t, sys, tc) + }) + } +} + +func TestInteropFaultProofs_DepositMessage_InvalidExecution(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSimpleInterop(t) + sfp.RunDepositMessageInvalidExecutionTest(t, sys) +} + +func TestInteropFaultProofs_SuperrootOptimisticPairing(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSimpleInterop(t) + sfp.RunOptimisticPairingTest(t, sys, true) +} + +func TestInteropFaultProofs_SuperrootOptimisticPairing_NoReplacement(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSimpleInterop(t) + sfp.RunOptimisticPairingTest(t, sys, false) +} + +func TestInteropFaultProofs_MessageExpiry(gt *testing.T) { + t := devtest.SerialT(gt) + const messageExpiryWindow = uint64(12) // 12 seconds for fast test + sys := presets.NewSimpleInterop(t, + presets.WithMessageExpiryWindow(messageExpiryWindow), + ) + sfp.RunMessageExpiryTest(t, sys, messageExpiryWindow) +} diff --git a/op-acceptance-tests/tests/interop/proofs/withdrawal/withdrawal_test.go b/op-acceptance-tests/tests/interop/proofs/withdrawal/withdrawal_test.go index a365e5773f8..e43c5c2cbfb 100644 --- a/op-acceptance-tests/tests/interop/proofs/withdrawal/withdrawal_test.go +++ b/op-acceptance-tests/tests/interop/proofs/withdrawal/withdrawal_test.go @@ -11,7 +11,7 @@ import ( func TestSuperRootWithdrawal(gt *testing.T) { t := devtest.ParallelT(gt) - sys := presets.NewSimpleInteropSuperProofs(t, presets.WithTimeTravelEnabled()) + sys := presets.NewSimpleInterop(t, presets.WithTimeTravelEnabled()) sys.L1Network.WaitForOnline() initialL1Balance := eth.HalfEther diff --git a/op-acceptance-tests/tests/interop/reorgs/l2_reorgs_after_l1_reorg_test.go b/op-acceptance-tests/tests/interop/reorgs/l2_reorgs_after_l1_reorg_test.go index da35684ad56..8c81d18c2c4 100644 --- a/op-acceptance-tests/tests/interop/reorgs/l2_reorgs_after_l1_reorg_test.go +++ b/op-acceptance-tests/tests/interop/reorgs/l2_reorgs_after_l1_reorg_test.go @@ -7,7 +7,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -65,6 +66,13 @@ func testL2ReorgAfterL1Reorg(gt *testing.T, n int, preChecks, postChecks checksF sys.L1Network.WaitForBlock() + // Build a stable cross-safe foundation before we stop the L1 CL and manually sequence. + // This ensures the supernode has verified state that references canonical L1 blocks, + // so after the reorg it doesn't need to rewind all the way back to genesis. + sys.L2ACL.Advanced(safety.CrossSafe, 20, 100) + + preEarlyChecks(t, sys) + sys.L1CL.Stop() // sequence a few L1 and L2 blocks diff --git a/op-acceptance-tests/tests/interop/seqwindow/expiry_test.go b/op-acceptance-tests/tests/interop/seqwindow/expiry_test.go index 0e79ecf37a6..57b8e7dfd2e 100644 --- a/op-acceptance-tests/tests/interop/seqwindow/expiry_test.go +++ b/op-acceptance-tests/tests/interop/seqwindow/expiry_test.go @@ -14,7 +14,8 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) // TestSequencingWindowExpiry tests that the sequencing window may expire, @@ -145,8 +146,8 @@ func TestSequencingWindowExpiry(gt *testing.T) { // Build the missing blocks, catch up on local-safe chain dsl.CheckAll(t, - sys.L2CLA.AdvancedFn(types.LocalSafe, 20, 100), - sys.L2CLA.AdvancedFn(types.LocalUnsafe, 20, 100), + sys.L2ACL.AdvancedFn(safety.LocalSafe, 20, 100), + sys.L2ACL.AdvancedFn(safety.LocalUnsafe, 20, 100), ) syncStatus := sys.L2CLA.SyncStatus() diff --git a/op-acceptance-tests/tests/interop/super-via-upgrade/super_dispute_game_via_upgrade_test.go b/op-acceptance-tests/tests/interop/super-via-upgrade/super_dispute_game_via_upgrade_test.go new file mode 100644 index 00000000000..3cb9979bb4f --- /dev/null +++ b/op-acceptance-tests/tests/interop/super-via-upgrade/super_dispute_game_via_upgrade_test.go @@ -0,0 +1,21 @@ +package super_via_upgrade + +import ( + "testing" + + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" +) + +// TestSuperRootGamesInstalledViaOPCMUpgrade verifies that opcm.upgrade installs +// the permissionless super games and makes SUPER_CANNON_KONA the respected type. +func TestSuperRootGamesInstalledViaOPCMUpgrade(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewSingleChainInterop(t) + + sys.StandardBridge(sys.L2ChainA).VerifyRespectedGameType(gameTypes.SuperCannonKonaGameType) + sys.DisputeGameFactory().VerifyGameImplPresent(gameTypes.SuperCannonKonaGameType) + sys.DisputeGameFactory().VerifyGameImplPresent(gameTypes.SuperPermissionedGameType) + sys.DisputeGameFactory().VerifyGameImplAbsent(gameTypes.PermissionedGameType) +} diff --git a/op-acceptance-tests/tests/interop/upgrade/post_test.go b/op-acceptance-tests/tests/interop/upgrade/post_test.go index 43538addb2f..847ed087f55 100644 --- a/op-acceptance-tests/tests/interop/upgrade/post_test.go +++ b/op-acceptance-tests/tests/interop/upgrade/post_test.go @@ -15,7 +15,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-service/eth" - stypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum/go-ethereum/common" ) @@ -96,7 +97,14 @@ func testSupervisorAnchorBlock(t devtest.T, sys *presets.SimpleInterop) { activationBlock := net.AwaitActivation(t, forks.Interop) sys.Supervisor.WaitForL2HeadToAdvanceTo(net.ChainID(), stypes.CrossSafe, activationBlock) - logger.Info("Validating anchor block timing", + // Wait for the corresponding CL to reach cross-safe past activation + if net.ChainID() == sys.L2A.ChainID() { + sys.L2ACL.Reached(safety.CrossSafe, activationBlock.Number, 60) + } else { + sys.L2BCL.Reached(safety.CrossSafe, activationBlock.Number, 60) + } + + logger.Info("Validating activation block timing", "chainID", net.ChainID(), "derivedBlockNumber", activationBlock.Number, "interopTime", *forkTimestamp) @@ -112,17 +120,17 @@ func testSupervisorSafetyProgression(t devtest.T, sys *presets.SimpleInterop) { delta := uint64(3) // Minimum blocks of progression expected dsl.CheckAll(t, - sys.L2CLA.AdvancedFn(stypes.LocalUnsafe, delta, 30), - sys.L2CLB.AdvancedFn(stypes.LocalUnsafe, delta, 30), + sys.L2ACL.AdvancedFn(safety.LocalUnsafe, delta, 30), + sys.L2BCL.AdvancedFn(safety.LocalUnsafe, delta, 30), - sys.L2CLA.AdvancedFn(stypes.LocalSafe, delta, 30), - sys.L2CLB.AdvancedFn(stypes.LocalSafe, delta, 30), + sys.L2ACL.AdvancedFn(safety.LocalSafe, delta, 30), + sys.L2BCL.AdvancedFn(safety.LocalSafe, delta, 30), - sys.L2CLA.AdvancedFn(stypes.CrossUnsafe, delta, 30), - sys.L2CLB.AdvancedFn(stypes.CrossUnsafe, delta, 30), + sys.L2ACL.AdvancedFn(safety.CrossUnsafe, delta, 30), + sys.L2BCL.AdvancedFn(safety.CrossUnsafe, delta, 30), - sys.L2CLA.AdvancedFn(stypes.CrossSafe, delta, 60), - sys.L2CLB.AdvancedFn(stypes.CrossSafe, delta, 60), + sys.L2ACL.AdvancedFn(safety.CrossSafe, delta, 60), + sys.L2BCL.AdvancedFn(safety.CrossSafe, delta, 60), ) logger.Info("Supervisor safety progression validation completed successfully") @@ -146,8 +154,11 @@ func testInteropMessageInclusion(t devtest.T, sys *presets.SimpleInterop) { // Single event in tx so index is 0 execMsg := bob.SendExecMessage(initMsg) - // Phase 5: Verify cross-safe progression - verifyInteropMessagesProgression(t, sys, initMsg, execMsg) + // Verify cross-safe progression for both messages + dsl.CheckAll(t, + sys.L2ACL.ReachedRefFn(safety.CrossSafe, initMsg.BlockID(), 60), + sys.L2BCL.ReachedRefFn(safety.CrossSafe, execMsg.BlockID(), 60), + ) logger.Info("Interop message inclusion test completed successfully") } diff --git a/op-acceptance-tests/tests/interop/upgrade/pre_test.go b/op-acceptance-tests/tests/interop/upgrade/pre_test.go index ee4a3d566dd..730b76a7ae0 100644 --- a/op-acceptance-tests/tests/interop/upgrade/pre_test.go +++ b/op-acceptance-tests/tests/interop/upgrade/pre_test.go @@ -16,7 +16,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txintent" - stypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) // This test is known to be flaky @@ -44,9 +45,20 @@ func TestPreNoInbox(gt *testing.T) { require.Equal(common.Address{}, common.BytesToAddress(implAddrBytes[:])) }) - // try access the sync-status of the supervisor, assert that the sync-status returns the expected error - devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { - interopTime := net.Escape().ChainConfig().InteropTime + // Phase 2: Verify the derivation pipeline works pre-interop by checking + // that both chains advance their local-safe heads (batcher submits to L1, + // supernode derives from it), and that CrossSafe and Finalized heads also + // advance. Pre-activation, the supernode must not gate these heads on the + // interop verifier and must instead fall through to local-safe / + // local-finalized. See issue #20191. + dsl.CheckAll(t, + sys.L2ACL.AdvancedFn(safety.LocalSafe, 5, 100), + sys.L2BCL.AdvancedFn(safety.LocalSafe, 5, 100), + sys.L2ACL.AdvancedFn(safety.CrossSafe, 5, 100), + sys.L2BCL.AdvancedFn(safety.CrossSafe, 5, 100), + sys.L2ACL.AdvancedFn(safety.Finalized, 1, 100), + sys.L2BCL.AdvancedFn(safety.Finalized, 1, 100), + ) _, err := sys.Supervisor.Escape().QueryAPI().SyncStatus(t.Ctx()) require.ErrorContains(err, "supervisor status tracker not ready") diff --git a/op-acceptance-tests/tests/isthmus/preinterop/challenger_test.go b/op-acceptance-tests/tests/isthmus/preinterop/challenger_test.go index eca4957bc78..9835b5f363f 100644 --- a/op-acceptance-tests/tests/isthmus/preinterop/challenger_test.go +++ b/op-acceptance-tests/tests/isthmus/preinterop/challenger_test.go @@ -5,11 +5,12 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/dsl/proofs" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-service/eth" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestChallengerPlaysGame(gt *testing.T) { @@ -17,8 +18,8 @@ func TestChallengerPlaysGame(gt *testing.T) { t := devtest.ParallelT(gt) sys := newSimpleInteropPreinterop(t) dsl.CheckAll(t, - sys.L2CLA.AdvancedFn(types.CrossSafe, 1, 30), - sys.L2CLB.AdvancedFn(types.CrossSafe, 1, 30), + sys.L2CLA.AdvancedFn(safety.CrossSafe, 1, 30), + sys.L2CLB.AdvancedFn(safety.CrossSafe, 1, 30), ) badClaim := common.HexToHash("0xdeadbeef00000000000000000000000000000000000000000000000000000000") diff --git a/op-acceptance-tests/tests/isthmus/preinterop/init_test.go b/op-acceptance-tests/tests/isthmus/preinterop/init_test.go deleted file mode 100644 index 6639c931a56..00000000000 --- a/op-acceptance-tests/tests/isthmus/preinterop/init_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package preinterop - -import ( - "testing" -) - -func TestMain(m *testing.M) { - // TODO(#16166): Uncomment once the supervisor endpoint supports super roots before interop - //presets.DoMain(m, presets.WithIsthmusSuper()) -} diff --git a/op-acceptance-tests/tests/osaka_on_l2_test.go b/op-acceptance-tests/tests/osaka_on_l2_test.go new file mode 100644 index 00000000000..8ac0ad4e064 --- /dev/null +++ b/op-acceptance-tests/tests/osaka_on_l2_test.go @@ -0,0 +1,515 @@ +package tests + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop/loadtest" + "github.com/ethereum-optimism/optimism/op-core/predeploys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" + "github.com/ethereum-optimism/optimism/op-service/txplan" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" +) + +var modexpPrecompile = common.HexToAddress("0x0000000000000000000000000000000000000005") +var p256VerifyPrecompile = common.HexToAddress("0x0000000000000000000000000000000000000100") + +// buildModExpInput constructs input data for the MODEXP precompile (address 0x05). +// Format: +func buildModExpInput(base, exp, mod []byte) []byte { + input := make([]byte, 0, 96+len(base)+len(exp)+len(mod)) + input = append(input, common.LeftPadBytes(new(big.Int).SetInt64(int64(len(base))).Bytes(), 32)...) + input = append(input, common.LeftPadBytes(new(big.Int).SetInt64(int64(len(exp))).Bytes(), 32)...) + input = append(input, common.LeftPadBytes(new(big.Int).SetInt64(int64(len(mod))).Bytes(), 32)...) + input = append(input, base...) + input = append(input, exp...) + input = append(input, mod...) + return input +} + +func TestEIP7823UpperBoundModExp(gt *testing.T) { + t := devtest.ParallelT(gt) + sysgo.SkipOnOpGeth(t, "osaka is not supported in op-geth") + + // Modexp input exceeding EIP-7823 limits: modulus length is 1025 bytes (limit is 1024) + oversizeMod := make([]byte, 1025) + oversizeMod[1024] = 5 + planOversized := txplan.Combine( + txplan.WithTo(&modexpPrecompile), + txplan.WithData(buildModExpInput([]byte{2}, []byte{3}, oversizeMod)), + txplan.WithGasLimit(2_000_000), + ) + withinLimitInput := buildModExpInput([]byte{2}, []byte{3}, []byte{5}) + + t.Run("pre-karst", func(t devtest.T) { + t.Parallel() + sys := presets.NewMinimal(t, presets.WithDeployerOptions(sysgo.WithJovianAtGenesis)) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + receipt, err := txplan.NewPlannedTx(eoa.Plan(), planOversized).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) + }) + + t.Run("post-karst", func(t devtest.T) { + t.Parallel() + sys := presets.NewMinimalWithKona(t, presets.WithDeployerOptions(sysgo.WithKarstAtGenesis)) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + // Make sure the chain is past genesis before submitting txs, so the agreed + // block we feed kona below is always >= 1 (genesis output is not reliably + // served by OutputAtBlock). + sys.L2EL.WaitForBlockNumber(1) + + // Post-Karst: oversized modulus is rejected, so the tx is included but reverts. + oversizedReceipt, err := txplan.NewPlannedTx(eoa.Plan(), planOversized).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusFailed, oversizedReceipt.Status) + + // Post-Karst: within-limit modulus still works. + withinLimitReceipt, err := txplan.NewPlannedTx( + eoa.Plan(), + txplan.WithTo(&modexpPrecompile), + txplan.WithData(withinLimitInput), + txplan.WithGasLimit(200_000), + ).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, withinLimitReceipt.Status) + + // Cross-check kona-host agrees with the live chain over a range that + // covers both the rejected oversized call and the within-limit success. + agreedBlock := bigs.Uint64Strict(oversizedReceipt.BlockNumber) - 1 + claimBlock := bigs.Uint64Strict(withinLimitReceipt.BlockNumber) + t.Require().True(sys.RunKonaNative(agreedBlock, claimBlock)) + }) +} + +func TestEIP7883ModExpGasCostIncrease(gt *testing.T) { + t := devtest.ParallelT(gt) + sysgo.SkipOnOpGeth(t, "osaka is not supported in op-geth") + + // Call modexp with empty calldata. The precompile pads missing bytes with + // zeros, giving Bsize=0, Esize=0, Msize=0. This hits exactly the gas floor: + // EIP-2565 (pre-Karst): max(200, floor(0*0/3)) = 200 gas + // EIP-7883 (post-Karst): max(500, floor(0*0)) = 500 gas + // Empty calldata also avoids EIP-7623 calldata cost inflation, so intrinsic + // gas is exactly 21,000 and we can precisely control execution gas via the tx gas limit. + planUnderGas := txplan.Combine( + txplan.WithTo(&modexpPrecompile), + txplan.WithGasLimit(21_300), + ) + + t.Run("pre-karst", func(t devtest.T) { + t.Parallel() + sys := presets.NewMinimal(t, presets.WithDeployerOptions(sysgo.WithJovianAtGenesis)) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + // Pre-Karst: 21,000 + 300 execution gas is enough for 200-gas floor. + receipt, err := txplan.NewPlannedTx(eoa.Plan(), planUnderGas).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) + }) + + t.Run("post-karst", func(t devtest.T) { + t.Parallel() + sys := presets.NewMinimalWithKona(t, presets.WithDeployerOptions(sysgo.WithKarstAtGenesis)) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + // Make sure the chain is past genesis before submitting txs, so the agreed + // block we feed kona below is always >= 1 (genesis output is not reliably + // served by OutputAtBlock). + sys.L2EL.WaitForBlockNumber(1) + + // Post-Karst: 21,000 + 300 execution gas is NOT enough for 500-gas floor. + underGasReceipt, err := txplan.NewPlannedTx(eoa.Plan(), planUnderGas).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusFailed, underGasReceipt.Status) + + // Post-Karst: 21,000 + 600 execution gas is enough for 500-gas floor. + sufficientReceipt, err := txplan.NewPlannedTx( + eoa.Plan(), + txplan.WithTo(&modexpPrecompile), + txplan.WithGasLimit(21_600), + ).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, sufficientReceipt.Status) + + // Cross-check kona-host agrees with the live chain over a range that + // covers both the OOG case and the within-floor success. + agreedBlock := bigs.Uint64Strict(underGasReceipt.BlockNumber) - 1 + claimBlock := bigs.Uint64Strict(sufficientReceipt.BlockNumber) + t.Require().True(sys.RunKonaNative(agreedBlock, claimBlock)) + }) +} + +func TestEIP7825TxGasLimitCap(gt *testing.T) { + t := devtest.ParallelT(gt) + sysgo.SkipOnOpGeth(t, "osaka is not supported in op-geth") + + // EIP-7825 caps transaction gas at 2^24 = 16,777,216. + // This is a tx validity rule enforced at the txpool/block level, not by the + // EVM, so eth_call and eth_simulateV1 don't enforce it. + + t.Run("pre-karst", func(t devtest.T) { + t.Parallel() + + // Live chain is on Jovian (no cap), so a tx with gas > 2^24 lands + // successfully. Run kona two ways to verify it honors its rollup + // config: with a matching Jovian config it accepts the block, with + // Karst forced at genesis it rejects the block on EIP-7825 grounds. + cases := []struct { + name string + konaOpts []presets.Option + konaAccepts bool + }{ + { + name: "kona-with-jovian", + konaAccepts: true, + }, + { + name: "kona-with-karst", + konaOpts: []presets.Option{presets.WithKonaKarstAtGenesis()}, + konaAccepts: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t devtest.T) { + t.Parallel() + opts := append( + []presets.Option{presets.WithDeployerOptions(sysgo.WithJovianAtGenesis)}, + tc.konaOpts..., + ) + sys := presets.NewMinimalWithKona(t, opts...) + sys.L2EL.WaitForBlockNumber(1) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + receipt, err := txplan.NewPlannedTx( + eoa.Plan(), + txplan.WithTo(&common.Address{}), + txplan.WithGasLimit(params.MaxTxGas+1), + ).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) + + agreedBlock := bigs.Uint64Strict(receipt.BlockNumber) - 1 + claimBlock := bigs.Uint64Strict(receipt.BlockNumber) + t.Require().Equal(tc.konaAccepts, sys.RunKonaNative(agreedBlock, claimBlock)) + }) + } + }) + + t.Run("post-karst", func(t devtest.T) { + t.Parallel() + // Live chain is on Karst — op-reth's RPC rejects a tx with gas > 2^24 + // at submission time, so it never lands on-chain. Nothing for kona to + // validate. + sys := presets.NewMinimal(t, presets.WithDeployerOptions(sysgo.WithKarstAtGenesis)) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + _, err := txplan.NewPlannedTx( + eoa.Plan(), + txplan.WithTo(&common.Address{}), + txplan.WithGasLimit(params.MaxTxGas+1), + ).Included.Eval(t.Ctx()) + t.Require().Error(err) + }) +} + +func TestEIP7951P256VerifyGasCostIncrease(gt *testing.T) { + t := devtest.ParallelT(gt) + sysgo.SkipOnOpGeth(t, "osaka is not supported in op-geth") + + // Call P256VERIFY with empty calldata. The precompile charges its full gas + // cost regardless of input length, then returns empty (input != 160 bytes). + // Empty calldata avoids EIP-7623 calldata cost inflation, so intrinsic gas + // is just 21,000 and we can precisely control execution gas via gas limit. + // RIP-7212 (pre-Karst): P256VERIFY costs 3,450 gas + // EIP-7951 (post-Karst): P256VERIFY costs 6,900 gas + planUnderGas := txplan.Combine( + txplan.WithTo(&p256VerifyPrecompile), + txplan.WithGasLimit(24_500), + ) + + t.Run("pre-karst", func(t devtest.T) { + t.Parallel() + + // Live chain is on Jovian (RIP-7212, P256VERIFY costs 3,450 gas), so + // 21,000 + 3,500 execution gas is enough for the call to succeed. Run + // kona two ways to verify it honors its rollup config: with a matching + // Jovian config it accepts the block, with Karst forced at genesis it + // disagrees because under EIP-7951 the call costs 6,900 gas and would + // OOG instead. + cases := []struct { + name string + konaOpts []presets.Option + konaAccepts bool + }{ + { + name: "kona-with-jovian", + konaAccepts: true, + }, + { + name: "kona-with-karst", + konaOpts: []presets.Option{presets.WithKonaKarstAtGenesis()}, + konaAccepts: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t devtest.T) { + t.Parallel() + opts := append( + []presets.Option{presets.WithDeployerOptions(sysgo.WithJovianAtGenesis)}, + tc.konaOpts..., + ) + sys := presets.NewMinimalWithKona(t, opts...) + sys.L2EL.WaitForBlockNumber(1) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + // Pre-Karst: 21,000 + 3,500 execution gas is enough for 3,450-gas precompile. + receipt, err := txplan.NewPlannedTx(eoa.Plan(), planUnderGas).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) + + agreedBlock := bigs.Uint64Strict(receipt.BlockNumber) - 1 + claimBlock := bigs.Uint64Strict(receipt.BlockNumber) + t.Require().Equal(tc.konaAccepts, sys.RunKonaNative(agreedBlock, claimBlock)) + }) + } + }) + + t.Run("post-karst", func(t devtest.T) { + t.Parallel() + sys := presets.NewMinimalWithKona(t, presets.WithDeployerOptions(sysgo.WithKarstAtGenesis)) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + // Make sure the chain is past genesis before submitting txs, so the agreed + // block we feed kona below is always >= 1 (genesis output is not reliably + // served by OutputAtBlock). + sys.L2EL.WaitForBlockNumber(1) + + // Post-Karst: 21,000 + 3,500 execution gas is NOT enough for 6,900-gas precompile. + underGasReceipt, err := txplan.NewPlannedTx(eoa.Plan(), planUnderGas).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusFailed, underGasReceipt.Status) + + // Post-Karst: 21,000 + 7,000 execution gas is enough for 6,900-gas precompile. + sufficientReceipt, err := txplan.NewPlannedTx( + eoa.Plan(), + txplan.WithTo(&p256VerifyPrecompile), + txplan.WithGasLimit(28_000), + ).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, sufficientReceipt.Status) + + // Cross-check kona-host agrees with the live chain over a range that + // covers both the OOG case and the within-cost success. + agreedBlock := bigs.Uint64Strict(underGasReceipt.BlockNumber) - 1 + claimBlock := bigs.Uint64Strict(sufficientReceipt.BlockNumber) + t.Require().True(sys.RunKonaNative(agreedBlock, claimBlock)) + }) +} + +func TestEIP7939CLZ(gt *testing.T) { + t := devtest.ParallelT(gt) + sysgo.SkipOnOpGeth(t, "osaka is not supported in op-geth") + + // EVM init code that computes CLZ(1) and returns the 32-byte result. + // CLZ(1) = 255 because 1 has 255 leading zero bits in a uint256. + clzCode := []byte{ + byte(vm.PUSH1), 1, // stack: [1] + byte(vm.CLZ), // stack: [255] (1 has 255 leading zeros) + byte(vm.PUSH1), 0, // stack: [0, 255] + byte(vm.MSTORE), // mem[0:32] = 255 + byte(vm.PUSH1), 32, // stack: [32] + byte(vm.PUSH1), 0, // stack: [0, 32] + byte(vm.RETURN), // return mem[0:32] + } + deployPlan := txplan.Combine( + txplan.WithData(clzCode), + txplan.WithGasLimit(100_000), + ) + + t.Run("pre-karst", func(t devtest.T) { + t.Parallel() + sys := presets.NewMinimal(t, presets.WithDeployerOptions(sysgo.WithJovianAtGenesis)) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + // Pre-Karst: CLZ opcode (0x1e) is invalid; the init code aborts and + // deployment fails. + receipt, err := txplan.NewPlannedTx(eoa.Plan(), deployPlan).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusFailed, receipt.Status) + }) + + t.Run("post-karst", func(t devtest.T) { + t.Parallel() + sys := presets.NewMinimalWithKona(t, presets.WithDeployerOptions(sysgo.WithKarstAtGenesis)) + eoa := sys.FunderL2.NewFundedEOA(eth.OneEther) + + // Make sure the chain is past genesis before submitting txs, so the agreed + // block we feed kona below is always >= 1 (genesis output is not reliably + // served by OutputAtBlock). + sys.L2EL.WaitForBlockNumber(1) + + // Post-Karst: CLZ executes; init code returns 32 bytes; deployment succeeds. + receipt, err := txplan.NewPlannedTx(eoa.Plan(), deployPlan).Included.Eval(t.Ctx()) + t.Require().NoError(err) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) + + // The deployed code IS the 32-byte CLZ(1) result. + deployedCode, err := sys.L2EL.EthClient().CodeAtHash(t.Ctx(), receipt.ContractAddress, receipt.BlockHash) + t.Require().NoError(err) + t.Require().Equal(common.LeftPadBytes([]byte{0xff}, 32), deployedCode, "CLZ(1) should equal 255") + + // Cross-check kona-host agrees with the live chain over the block where + // CLZ was executed. + agreedBlock := bigs.Uint64Strict(receipt.BlockNumber) - 1 + claimBlock := bigs.Uint64Strict(receipt.BlockNumber) + t.Require().True(sys.RunKonaNative(agreedBlock, claimBlock)) + }) +} + +// TestEIP7825DepositBypassesTxGasLimitCap proves that deposit transactions are not +// subject to the EIP-7825 2^24 gas cap introduced by Karst. Deposits are forced onto +// L2 by the derivation pipeline rather than passing through the txpool, so the cap +// — which is a tx validity rule — must not apply to them; otherwise an attacker could +// trivially brick the rollup by submitting deposits that can never be included. +func TestEIP7825DepositBypassesTxGasLimitCap(gt *testing.T) { + t := devtest.ParallelT(gt) + sysgo.SkipOnOpGeth(t, "osaka is not supported in op-geth") + + sys := presets.NewMinimalWithKona(t, presets.WithDeployerOptions(sysgo.WithKarstAtGenesis)) + sys.L1Network.WaitForOnline() + sys.L2EL.WaitForBlockNumber(1) + + alice := sys.FunderL1.NewFundedEOA(eth.OneEther) + alicel2 := alice.AsEL(sys.L2EL) + + portalAddr := sys.L2Chain.Escape().RollupConfig().DepositContractAddress + portal := bindings.NewBindings[bindings.OptimismPortal2]( + bindings.WithClient(sys.L1EL.EthClient()), + bindings.WithTo(portalAddr), + bindings.WithTest(t), + ) + + // Deposit with gas limit above the EIP-7825 cap of 2^24 = 16,777,216. + depositGasLimit := params.MaxTxGas + 1 + depositAmount := eth.OneHundredthEther + args := portal.DepositTransaction(alice.Address(), depositAmount, depositGasLimit, false, []byte{}) + // Skip eth_estimateGas: the estimator in txplan caps its binary search at + // params.MaxTxGas, but ResourceMetering's Burn.gas inside depositTransaction + // needs to burn ~depositGasLimit gas on L1, so estimation would run out of gas. + l1Receipt := contract.Write(alice, args, + txplan.WithValue(depositAmount), + txplan.WithGasLimit(depositGasLimit+1_000_000), + ) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, l1Receipt.Status) + + var l2DepositTx *ethtypes.DepositTx + for _, log := range l1Receipt.Logs { + var err error + if l2DepositTx, err = derive.UnmarshalDepositLogEvent(log); err == nil { + break + } + } + t.Require().NotNil(l2DepositTx, "no TransactionDeposited event in L1 receipt") + t.Require().Equal(depositGasLimit, l2DepositTx.Gas, "L2 deposit tx gas should match the requested gas limit") + + sys.L2EL.WaitL1OriginReached(eth.Unsafe, bigs.Uint64Strict(l1Receipt.BlockNumber), 120) + l2Receipt := sys.L2EL.WaitForReceipt(ethtypes.NewTx(l2DepositTx).Hash()) + t.Require().Equal(ethtypes.ReceiptStatusSuccessful, l2Receipt.Status, "deposit should be included and succeed on L2") + + alicel2.WaitForBalance(depositAmount) + + // Cross-check kona-host agrees with the live chain on the block where the + // high-gas deposit landed — proves kona implements the deposit-side bypass + // of EIP-7825 (deposits are not subject to the 2^24 cap). + agreedBlock := bigs.Uint64Strict(l2Receipt.BlockNumber) - 1 + claimBlock := bigs.Uint64Strict(l2Receipt.BlockNumber) + t.Require().True(sys.RunKonaNative(agreedBlock, claimBlock)) +} + +// TestEIP7934BlockSizeLimitDisabled proves that EIP-7934 is disabled by building a single block +// whose transaction data alone exceeds the max block size. +func TestEIP7934BlockSizeLimitDisabled(gt *testing.T) { + t := devtest.ParallelT(gt) + sysgo.SkipOnOpGeth(t, "osaka is not supported in op-geth") + + // EIP-7623 inflates zero-byte calldata cost to 10 gas/byte, so packing + // 12 MB into one block requires ~120M gas. + sys := presets.NewMinimal(t, presets.WithDeployerOptions( + sysgo.WithKarstAtGenesis, + sysgo.WithL2GasLimit(120_000_000), + )) + + spamTxs(sys) + + // Find a block whose total transaction data exceeds 10 MiB. + l2Client := sys.L2EL.EthClient() + l2BlockTime := time.Duration(sys.L2Chain.Escape().RollupConfig().BlockTime) * time.Second + for { + select { + case <-time.After(l2BlockTime): + info, blockTxs, err := l2Client.InfoAndTxsByLabel(t.Ctx(), eth.Unsafe) + t.Require().NoError(err) + + var totalTxSize int + for _, tx := range blockTxs { + bin, err := tx.MarshalBinary() + t.Require().NoError(err) + totalTxSize += len(bin) + } + + t.Logger().Info("Checking L2 block...", "number", info.NumberU64(), "size", totalTxSize, "gasUsed", info.GasUsed()) + + // We use tx data size instead of the total block size since we don't have a client + // capable of deserializing block responses. + if totalTxSize > params.MaxBlockSize { + return + } + case <-t.Ctx().Done(): + t.Require().NoError(t.Ctx().Err()) + } + } +} + +func spamTxs(sys *presets.Minimal) { + l2BlockTime := time.Duration(sys.L2Chain.Escape().RollupConfig().BlockTime) * time.Second + eoas := loadtest.FundEOAs(sys.T, eth.HundredEther, 50, l2BlockTime, sys.L2EL, sys.Wallet, sys.FaucetL2) + eoasRR := loadtest.NewRoundRobin(eoas) + spammer := loadtest.SpammerFunc(func(t devtest.T) error { + // Max tx size in op-geth and op-reth mempools is 128 kB per tx. + // We leave an 8 kB buffer for tx data outside the calldata. + const calldataSize = 120 * 1024 + _, err := eoasRR.Get().Include(t, + txplan.WithTo(&predeploys.L1BlockAddr), + txplan.WithData(make([]byte, calldataSize)), + txplan.WithGasLimit(1_250_000), + ) + return err + }) + schedule := loadtest.NewBurst(l2BlockTime, loadtest.WithBaseRPS(50)) + + ctx, cancel := context.WithCancel(sys.T.Ctx()) + var wg sync.WaitGroup + wg.Add(1) + sys.T.Cleanup(func() { + cancel() + wg.Wait() + }) + go func() { + defer wg.Done() + schedule.Run(sys.T.WithCtx(ctx), spammer) + }() +} diff --git a/op-acceptance-tests/tests/proofs/cannon/step_test.go b/op-acceptance-tests/tests/proofs/cannon/step_test.go index addf858771d..525a441c9f4 100644 --- a/op-acceptance-tests/tests/proofs/cannon/step_test.go +++ b/op-acceptance-tests/tests/proofs/cannon/step_test.go @@ -5,17 +5,20 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl/proofs" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestExecuteStep_Cannon(gt *testing.T) { t := devtest.ParallelT(gt) + sysgo.SkipOnOpReth(t, "not supported (timeout)") sys := newSystem(t) l1User := sys.FunderL1.NewFundedEOA(eth.ThousandEther) blockNum := uint64(3) - sys.L2CL.Reached(types.LocalSafe, blockNum, 30) + sys.L2CL.Reached(safety.LocalSafe, blockNum, 30) game := sys.DisputeGameFactory().StartCannonGame(l1User, proofs.WithL2SequenceNumber(blockNum)) claim := game.DisputeL2SequenceNumber(l1User, game.RootClaim(), blockNum) @@ -28,11 +31,24 @@ func TestExecuteStep_Cannon(gt *testing.T) { func TestExecuteStep_CannonKona(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|10:33:38.442] + // assertions.go:387: Error Trace: /optimism/op-devstack/dsl/proofs/game_helper.go:263 + // assertions.go:387: /optimism/op-devstack/dsl/proofs/game_helper.go:196 + // assertions.go:387: /optimism/op-devstack/dsl/proofs/fault_dispute_game.go:124 + // assertions.go:387: /optimism/op-acceptance-tests/tests/proofs/cannon/step_test.go:40 + // assertions.go:387: /.local/share/mise/installs/go/1.24.13/src/runtime/asm_arm64.s:1223 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: failed to get safe head at L1 block 0xe926b953777546729b2607011590354a6a42dc35e36b7ea68c9dd4b470261541:6: not found + // assertions.go:387: Test: TestExecuteStep_CannonKona + // assertions.go:387: Messages: Failed to get honest root claim + sysgo.SkipOnKonaNode(t, "not supported") sys := newSystem(t) l1User := sys.FunderL1.NewFundedEOA(eth.ThousandEther) blockNum := uint64(3) - sys.L2CL.Reached(types.LocalSafe, blockNum, 30) + sys.L2CL.Reached(safety.LocalSafe, blockNum, 30) game := sys.DisputeGameFactory().StartCannonKonaGame(l1User, proofs.WithL2SequenceNumber(blockNum)) claim := game.DisputeL2SequenceNumber(l1User, game.RootClaim(), blockNum) diff --git a/op-acceptance-tests/tests/safeheaddb_clsync/safeheaddb_test.go b/op-acceptance-tests/tests/safeheaddb_clsync/safeheaddb_test.go index da441258a0e..66f034d5e60 100644 --- a/op-acceptance-tests/tests/safeheaddb_clsync/safeheaddb_test.go +++ b/op-acceptance-tests/tests/safeheaddb_clsync/safeheaddb_test.go @@ -8,11 +8,24 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestPreserveDatabaseOnCLResync(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with op-reth: + // + // assertions.go:387: ERROR[03-31|09:40:50.281] + // assertions.go:387: Error Trace: /optimism/op-devstack/sysgo/l2_el_p2p_util.go:61 + // assertions.go:387: /optimism/op-devstack/dsl/l2_el.go:281 + // assertions.go:387: /optimism/op-acceptance-tests/tests/safeheaddb_clsync/safeheaddb_test.go:42 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: context deadline exceeded + // assertions.go:387: Test: TestPreserveDatabaseOnCLResync + // assertions.go:387: Messages: The peer was not connected + // assertions.go:387: + sysgo.SkipOnOpReth(t, "not supported (peering issue)") sys := presets.NewSingleChainMultiNode(t, presets.WithGlobalL2CLOption(sysgo.L2CLOptionFn(func(p devtest.T, _ sysgo.ComponentTarget, cfg *sysgo.L2CLConfig) { cfg.SequencerSyncMode = sync.CLSync @@ -23,10 +36,10 @@ func TestPreserveDatabaseOnCLResync(gt *testing.T) { startSafeBlock := sys.L2CLB.SafeL2BlockRef().Number dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalSafe, 1, 30), - sys.L2CLB.AdvancedFn(types.LocalSafe, 1, 30)) + sys.L2CL.AdvancedFn(safety.LocalSafe, 1, 30), + sys.L2CLB.AdvancedFn(safety.LocalSafe, 1, 30)) - sys.L2CLB.Matched(sys.L2CL, types.LocalSafe, 30) + sys.L2CLB.InSync(sys.L2CL, safety.LocalSafe, 30) sys.L2CLB.VerifySafeHeadDatabaseMatches(sys.L2CL) // Stop the verifier node. Since the sysgo EL uses in-memory storage this also wipes its database. @@ -35,14 +48,14 @@ func TestPreserveDatabaseOnCLResync(gt *testing.T) { sys.L2ELB.Stop() sys.L2CLB.Stop() - sys.L2CL.Advanced(types.LocalSafe, 3, 30) + sys.L2CL.Advanced(safety.LocalSafe, 3, 30) sys.L2ELB.Start() sys.L2CLB.Start() sys.L2ELB.PeerWith(sys.L2EL) - sys.L2CLB.Matched(sys.L2CL, types.LocalSafe, 30) - sys.L2CLB.Advanced(types.LocalSafe, 1, 30) // At least one safe head db update after resync + sys.L2CLB.InSync(sys.L2CL, safety.LocalSafe, 30) + sys.L2CLB.Advanced(safety.LocalSafe, 1, 30) // At least one safe head db update after resync // Safe head db should not have been reset sys.L2CLB.VerifySafeHeadDatabaseMatches(sys.L2CL, dsl.WithMinRequiredL2Block(startSafeBlock)) diff --git a/op-acceptance-tests/tests/safeheaddb_elsync/safeheaddb_test.go b/op-acceptance-tests/tests/safeheaddb_elsync/safeheaddb_test.go index 5ddfe16c5d0..3ad48fc52d1 100644 --- a/op-acceptance-tests/tests/safeheaddb_elsync/safeheaddb_test.go +++ b/op-acceptance-tests/tests/safeheaddb_elsync/safeheaddb_test.go @@ -8,7 +8,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func newSingleChainMultiNodeELSync(t devtest.T) *presets.SingleChainMultiNode { @@ -22,13 +23,25 @@ func newSingleChainMultiNodeELSync(t devtest.T) *presets.SingleChainMultiNode { func TestTruncateDatabaseOnELResync(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with op-reth: + // + // assertions.go:387: ERROR[03-31|09:41:28.788] + // assertions.go:387: Error Trace: /optimism/op-devstack/sysgo/l2_el_p2p_util.go:61 + // assertions.go:387: /optimism/op-devstack/dsl/l2_el.go:281 + // assertions.go:387: /optimism/op-acceptance-tests/tests/safeheaddb_elsync/safeheaddb_test.go:44 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: dial tcp 127.0.0.1:55994: i/o timeout + // assertions.go:387: Test: TestTruncateDatabaseOnELResync + // assertions.go:387: Messages: The peer was not connected + // assertions.go:387: + sysgo.SkipOnOpReth(t, "not supported (peering issue)") sys := newSingleChainMultiNodeELSync(t) dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalSafe, 1, 30), - sys.L2CLB.AdvancedFn(types.LocalSafe, 1, 30)) + sys.L2CL.AdvancedFn(safety.LocalSafe, 1, 30), + sys.L2CLB.AdvancedFn(safety.LocalSafe, 1, 30)) - sys.L2CLB.Matched(sys.L2CL, types.LocalSafe, 30) + sys.L2CLB.InSync(sys.L2CL, safety.LocalSafe, 30) sys.L2CLB.VerifySafeHeadDatabaseMatches(sys.L2CL) // Stop the verifier node. Since the sysgo EL uses in-memory storage this also wipes its database. @@ -37,26 +50,39 @@ func TestTruncateDatabaseOnELResync(gt *testing.T) { sys.L2ELB.Stop() sys.L2CLB.Stop() - sys.L2CL.Advanced(types.LocalSafe, 3, 30) + sys.L2CL.Advanced(safety.LocalSafe, 3, 30) sys.L2ELB.Start() sys.L2CLB.Start() sys.L2ELB.PeerWith(sys.L2EL) - sys.L2CLB.Matched(sys.L2CL, types.LocalSafe, 30) - sys.L2CLB.Advanced(types.LocalSafe, 1, 30) // At least one safe head db update after resync + // EL Sync after a full database wipe requires more time than the initial sync: + // the EL must re-download all blocks via P2P before the CL can begin derivation, + // and node A keeps advancing LocalSafe in the meantime. + sys.L2CLB.InSync(sys.L2CL, safety.LocalSafe, 90) + sys.L2CLB.Advanced(safety.LocalSafe, 1, 90) // At least one safe head db update after resync sys.L2CLB.VerifySafeHeadDatabaseMatches(sys.L2CL) } func TestNotTruncateDatabaseOnRestartWithExistingDatabase(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|10:35:59.154] + // assertions.go:387: Error Trace: /Users/josh/repos/optimism/op-devstack/dsl/safedb.go:22 + // assertions.go:387: /Users/josh/repos/optimism/op-devstack/dsl/l2_cl.go:432 + // assertions.go:387: /Users/josh/repos/optimism/op-acceptance-tests/tests/safeheaddb_elsync/safeheaddb_test.go:74 + // assertions.go:387: Error: Expected value not to be nil. + // assertions.go:387: Test: TestNotTruncateDatabaseOnRestartWithExistingDatabase + // assertions.go:387: Messages: no safe head data available at L1 block 4 + sysgo.SkipOnKonaNode(t, "not supported") sys := newSingleChainMultiNodeELSync(t) dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalSafe, 1, 30), - sys.L2CLB.AdvancedFn(types.LocalSafe, 1, 30)) - sys.L2CLB.Matched(sys.L2CL, types.LocalSafe, 30) + sys.L2CL.AdvancedFn(safety.LocalSafe, 1, 30), + sys.L2CLB.AdvancedFn(safety.LocalSafe, 1, 30)) + sys.L2CLB.InSync(sys.L2CL, safety.LocalSafe, 30) preRestartSafeBlock := sys.L2CLB.SafeL2BlockRef().Number sys.L2CLB.VerifySafeHeadDatabaseMatches(sys.L2CL, dsl.WithMinRequiredL2Block(preRestartSafeBlock)) @@ -64,12 +90,12 @@ func TestNotTruncateDatabaseOnRestartWithExistingDatabase(gt *testing.T) { // Restart the verifier op-node, but not the EL so the existing chain data is not deleted. sys.L2CLB.Stop() - sys.L2CL.Advanced(types.LocalSafe, 3, 30) + sys.L2CL.Advanced(safety.LocalSafe, 3, 30) sys.L2CLB.Start() - sys.L2CLB.Matched(sys.L2CL, types.LocalSafe, 30) - sys.L2CLB.Advanced(types.LocalSafe, 1, 30) // At least one safe head db update after resync + sys.L2CLB.InSync(sys.L2CL, safety.LocalSafe, 60) + sys.L2CLB.Advanced(safety.LocalSafe, 1, 60) // At least one safe head db update after resync sys.L2CLB.VerifySafeHeadDatabaseMatches(sys.L2CL, dsl.WithMinRequiredL2Block(preRestartSafeBlock)) } diff --git a/op-acceptance-tests/tests/superfaultproofs/optimistic_pairing.go b/op-acceptance-tests/tests/superfaultproofs/optimistic_pairing.go new file mode 100644 index 00000000000..931c12f54bf --- /dev/null +++ b/op-acceptance-tests/tests/superfaultproofs/optimistic_pairing.go @@ -0,0 +1,149 @@ +package superfaultproofs + +import ( + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txplan" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" + "github.com/ethereum/go-ethereum/crypto" +) + +// RunOptimisticPairingTest exercises the optimistic branch of a super-root +// transition when chain A has an invalid-exec block at endTimestamp whose +// exec message references a fabricated log at chain B's would-be +// at-endTimestamp block. +// +// If withReplacement is true, chain B's at-endTimestamp block is built +// (empty) and batched after the game L1 head, causing chain A's invalid +// block to be replaced via cross-validation. Otherwise chain B's +// at-endTimestamp block is never built and chain A's invalid block stays +// local-safe at endTimestamp forever. +func RunOptimisticPairingTest(t devtest.T, sys *presets.SimpleInterop, withReplacement bool) { + t.Require().NotNil(sys.SuperRoots, "supernode is required") + t.Require().NotNil(sys.TestSequencer, "test sequencer is required") + + chains := orderedChains(sys) + t.Require().Len(chains, 2) + t.Require().Equal(chains[0].ID, sys.L2ChainA.ChainID()) + t.Require().Equal(chains[1].ID, sys.L2ChainB.ChainID()) + + // EOAs and event logger must be funded/deployed before freezeChains stops + // the sequencers. The event logger address only fills the fabricated + // message's Origin field; the log itself never exists. + aliceA := sys.FunderA.NewFundedEOA(eth.OneEther) + bob := sys.FunderB.NewFundedEOA(eth.OneEther) + eventLoggerB := bob.DeployEventLogger() + + freezeChains(chains) + + // Drive both chains to startTimestamp and batch so they're cross-safe at X. + endTimestamp := nextTimestampAfterSafeHeads(t, chains) + startTimestamp := endTimestamp - 1 + advanceUnsafeToTimestamp(t, sys, chains, startTimestamp) + advanceSafeToCurrentUnsafe(t, chains[0]) + advanceSafeToCurrentUnsafe(t, chains[1]) + sys.SuperRoots.AwaitValidatedTimestamp(startTimestamp) + + // Build chain A's at-endTimestamp block with a fabricated invalid exec + // message referencing a log at (chain B, expectedBlockNumB, logIndex 0) + // that will never exist. + unsafeA := sys.L2ELA.BlockRefByLabel(eth.Unsafe) + unsafeB := sys.L2ELB.BlockRefByLabel(eth.Unsafe) + t.Require().Equalf(endTimestamp, unsafeA.Time+chains[0].Cfg.BlockTime, + "chain A's next scheduled block must land at endTimestamp %d (head time %d, blockTime %d)", + endTimestamp, unsafeA.Time, chains[0].Cfg.BlockTime) + t.Require().Equalf(endTimestamp, unsafeB.Time+chains[1].Cfg.BlockTime, + "chain B's next scheduled block must land at endTimestamp %d (head time %d, blockTime %d)", + endTimestamp, unsafeB.Time, chains[1].Cfg.BlockTime) + + topic := crypto.Keccak256Hash([]byte("DataEmitted(bytes)")) + msgHash := crypto.Keccak256Hash([]byte("optimistic pairing fabricated msg")) + fabricatedPayload := append(append(make([]byte, 0, 64), topic.Bytes()...), msgHash.Bytes()...) + fabricatedMsg := messages.Message{ + Identifier: messages.Identifier{ + Origin: eventLoggerB, + BlockNumber: unsafeB.Number + 1, + LogIndex: 0, + Timestamp: endTimestamp, + ChainID: chains[1].ID, + }, + PayloadHash: crypto.Keccak256Hash(fabricatedPayload), + } + + execTx := dsl.SubmitExecForMessage(fabricatedMsg, aliceA) + txplan.WithStaticNonce(aliceA.PendingNonce())(execTx) + signedTx, err := execTx.Signed.Eval(t.Ctx()) + t.Require().NoError(err) + rawExecTx, err := signedTx.MarshalBinary() + t.Require().NoError(err) + + sys.TestSequencer.SequenceBlockWithTxs(t, chains[0].ID, unsafeA.Hash, [][]byte{rawExecTx}) + newHeadA := sys.L2ELA.BlockRefByLabel(eth.Unsafe) + t.Require().Equal(endTimestamp, newHeadA.Time) + + advanceSafeToCurrentUnsafe(t, chains[0]) + gameL1Head := sys.L1EL.BlockRefByLabel(eth.Unsafe).ID() + + // One L1 block is enough separation: chain B's batch will land strictly + // after gameL1Head. + sys.L1Network.WaitForBlock() + + if withReplacement { + sys.TestSequencer.SequenceBlock(t, chains[1].ID, unsafeB.Hash) + t.Require().Equal(endTimestamp, sys.L2ELB.BlockRefByLabel(eth.Unsafe).Time) + advanceSafeToCurrentUnsafe(t, chains[1]) + sys.L2CLA.Reached(safety.CrossSafe, newHeadA.Number, 60) + } + + // The super root at startTimestamp must be fully verifiable at gameL1Head; + // otherwise the setup itself is broken. + prevRoot := sys.SuperRoots.SuperRootAtTimestamp(startTimestamp) + t.Require().LessOrEqualf(prevRoot.Data.VerifiedRequiredL1.Number, gameL1Head.Number, + "prev super root VerifiedRequiredL1 %d must be <= gameL1Head %d", + prevRoot.Data.VerifiedRequiredL1.Number, gameL1Head.Number) + + firstOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[0].ID, endTimestamp) + start := superRootAtTimestamp(t, chains, startTimestamp) + step1Trace := marshalTransition(start, 1, firstOptimistic) + + tests := []*transitionTest{ + { + Name: "FirstChainOptimisticBlock", + AgreedClaim: start.Marshal(), + DisputedClaim: step1Trace, + DisputedTraceIndex: 0, + L1Head: gameL1Head, + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + { + Name: "SecondChainOptimisticBlock-Invalid", + AgreedClaim: step1Trace, + DisputedClaim: super.InvalidTransition, + DisputedTraceIndex: 1, + L1Head: gameL1Head, + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + } + + challengerCfg := sys.L2ChainA.Escape().L2Challengers()[0].Config() + gameDepth := sys.DisputeGameFactory().GameImpl(gameTypes.SuperCannonKonaGameType).SplitDepth() + for _, test := range tests { + t.Run(test.Name+"-fpp", func(t devtest.T) { + runKonaInteropProgram(t, challengerCfg.CannonKona, test.L1Head.Hash, + test.AgreedClaim, crypto.Keccak256Hash(test.DisputedClaim), + test.ClaimTimestamp, test.ExpectValid) + }) + + t.Run(test.Name+"-challenger", func(t devtest.T) { + runChallengerProviderTest(t, sys.SuperRoots.QueryAPI(), gameDepth, startTimestamp, test.ClaimTimestamp, test) + }) + } +} diff --git a/op-acceptance-tests/tests/superfaultproofs/singlechain.go b/op-acceptance-tests/tests/superfaultproofs/singlechain.go new file mode 100644 index 00000000000..c52160f07f1 --- /dev/null +++ b/op-acceptance-tests/tests/superfaultproofs/singlechain.go @@ -0,0 +1,158 @@ +package superfaultproofs + +import ( + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" + "github.com/ethereum/go-ethereum/crypto" +) + +// singleChain bundles the DSL handles for the single L2 chain in a SingleChainInterop system. +func singleChainFrom(sys *presets.SingleChainInterop) *chain { + return &chain{ + ID: sys.L2ChainA.ChainID(), + Cfg: sys.L2ChainA.Escape().RollupConfig(), + Rollup: sys.L2CLA.Escape().RollupAPI(), + EL: sys.L2ELA, + CLNode: sys.L2CLA, + Batcher: sys.L2BatcherA, + } +} + +// RunSingleChainSuperFaultProofSmokeTest is a minimal smoke test for single-chain super fault proofs. +// It verifies that the super-root transition works correctly when the dependency set has only one chain. +// The test stops the batcher, waits for the safe head to stall, then resumes batching and verifies +// a basic set of valid/invalid transitions through both the FPP and challenger trace provider. +func RunSingleChainSuperFaultProofSmokeTest(t devtest.T, sys *presets.SingleChainInterop) { + t.Require().NotNil(sys.SuperRoots, "supernode is required for this test") + + c := singleChainFrom(sys) + chains := []*chain{c} + + // Stop batch submission so safe head stalls, then we have a known boundary. + c.Batcher.Stop() + sys.L2CLA.WaitForStall(safety.CrossSafe) + + endTimestamp := nextTimestampAfterSafeHeads(t, chains) + startTimestamp := endTimestamp - 1 + + // Ensure the chain has produced the target block as unsafe. + target, err := c.Cfg.TargetBlockNumber(endTimestamp) + t.Require().NoError(err) + c.EL.Reached(eth.Unsafe, target, 60) + + // Batcher is stopped, so no batch data for endTimestamp is on L1. + l1HeadBefore := sys.L1EL.BlockRefByLabel(eth.Unsafe).ID() + + // Resume batching and wait for the safe head to reach the target. + c.Batcher.Start() + sys.SuperRoots.AwaitValidatedTimestamp(endTimestamp) + c.EL.Reached(eth.Safe, target, 60) + l1HeadCurrent := sys.L1EL.BlockRefByLabel(eth.Unsafe).ID() + + // Build expected transition states for a single chain. + start := superRootAtTimestamp(t, chains, startTimestamp) + end := superRootAtTimestamp(t, chains, endTimestamp) + + optimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), c.ID, endTimestamp) + + // With one chain: step 0 = chain's optimistic block, steps 1..consolidateStep-1 = padding, + // consolidateStep = consolidation to next super root. + step1 := marshalTransition(start, 1, optimistic) + padding := func(step uint64) []byte { + return marshalTransition(start, step, optimistic) + } + + tests := []*transitionTest{ + { + Name: "ClaimDirectToNextTimestamp", + AgreedClaim: start.Marshal(), + DisputedClaim: end.Marshal(), + DisputedTraceIndex: 0, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + ExpectValid: false, + }, + { + Name: "ChainOptimisticBlock", + AgreedClaim: start.Marshal(), + DisputedClaim: step1, + DisputedTraceIndex: 0, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + { + Name: "ChainOptimisticBlock-InvalidNoChange", + AgreedClaim: start.Marshal(), + DisputedClaim: start.Marshal(), + DisputedTraceIndex: 0, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + ExpectValid: false, + }, + { + Name: "FirstPaddingStep", + AgreedClaim: step1, + DisputedClaim: padding(2), + DisputedTraceIndex: 1, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + { + Name: "ConsolidateStep", + AgreedClaim: padding(consolidateStep), + DisputedClaim: end.Marshal(), + DisputedTraceIndex: consolidateStep, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + { + Name: "ConsolidateStep-InvalidNoChange", + AgreedClaim: padding(consolidateStep), + DisputedClaim: padding(consolidateStep), + DisputedTraceIndex: consolidateStep, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + ExpectValid: false, + }, + { + Name: "ChainReachesL1Head", + AgreedClaim: start.Marshal(), + DisputedClaim: super.InvalidTransition, + DisputedTraceIndex: 0, + L1Head: l1HeadBefore, + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + { + Name: "SuperRootInvalidIfUnsupportedByL1Data", + AgreedClaim: start.Marshal(), + DisputedClaim: step1, + DisputedTraceIndex: 0, + L1Head: l1HeadBefore, + ClaimTimestamp: endTimestamp, + ExpectValid: false, + }, + } + + challengerCfg := sys.L2ChainA.Escape().L2Challengers()[0].Config() + gameDepth := sys.DisputeGameFactory().GameImpl(gameTypes.SuperCannonKonaGameType).SplitDepth() + + for _, test := range tests { + t.Run(test.Name+"-fpp", func(t devtest.T) { + runKonaInteropProgram(t, challengerCfg.CannonKona, test.L1Head.Hash, + test.AgreedClaim, crypto.Keccak256Hash(test.DisputedClaim), + test.ClaimTimestamp, test.ExpectValid) + }) + t.Run(test.Name+"-challenger", func(t devtest.T) { + runChallengerProviderTest(t, sys.SuperRoots.QueryAPI(), gameDepth, startTimestamp, test.ClaimTimestamp, test) + }) + } +} diff --git a/op-acceptance-tests/tests/superfaultproofs/superfaultproofs.go b/op-acceptance-tests/tests/superfaultproofs/superfaultproofs.go index ea1958f65a6..096e93cf167 100644 --- a/op-acceptance-tests/tests/superfaultproofs/superfaultproofs.go +++ b/op-acceptance-tests/tests/superfaultproofs/superfaultproofs.go @@ -20,11 +20,12 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-program/client/interop" interopTypes "github.com/ethereum-optimism/optimism/op-program/client/interop/types" "github.com/ethereum-optimism/optimism/op-service/apis" "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -43,6 +44,121 @@ type chain struct { Batcher *dsl.L2Batcher } +// freezeChains takes exclusive control of L2 block production. After this +// returns: real sequencers are stopped, batchers are stopped, and on every +// chain LocalSafe == LocalUnsafe. From here, only the caller advances the +// chain via TestSequencer.SequenceBlock. +// +// Order matters: stopping the batcher before LocalSafe catches up to +// LocalUnsafe leaves safe behind unsafe, which breaks downstream code that +// expects "the head" to be unambiguous (e.g. nextTimestampAfterSafeHeads). +// So we stop sequencers first, wait for unsafe to stall, then let the still- +// running batchers drain the remaining unsafe blocks to L1 until safe catches +// up, and only then stop the batchers. +func freezeChains(chains []*chain) { + for _, c := range chains { + c.CLNode.StopSequencer() + } + for _, c := range chains { + c.CLNode.WaitForStall(safety.LocalUnsafe) + } + for _, c := range chains { + unsafeNumber := c.CLNode.HeadBlockRef(safety.LocalUnsafe).Number + c.CLNode.Reached(safety.LocalSafe, unsafeNumber, 30) + } + for _, c := range chains { + c.Batcher.Stop() + } +} + +// advanceUnsafeToTimestamp drives every chain's UnsafeL2 head to its latest +// scheduled block at-or-before target via repeated TestSequencer.SequenceBlock +// calls. Chains whose schedule produces a block exactly at target advance to +// target; chains whose schedule does not produce a block at target are left at +// the previous scheduled block (target - blockTime + offset). Errors if any +// chain's head is already past target. +// +// Callers must ensure the real sequencer is stopped on every chain before +// invoking this — otherwise SequenceBlock can race with the real sequencer. +func advanceUnsafeToTimestamp(t devtest.T, sys *presets.SimpleInterop, chains []*chain, target uint64) { + for _, c := range chains { + for { + head := c.EL.BlockRefByLabel(eth.Unsafe) + t.Require().LessOrEqualf(head.Time, target, + "chain %s head %d is already past target %d", c.ID, head.Time, target) + nextTime := head.Time + c.Cfg.BlockTime + if nextTime > target { + break + } + sys.TestSequencer.SequenceBlock(t, c.ID, head.Hash) + next := c.EL.BlockRefByLabel(eth.Unsafe) + t.Require().Equalf(nextTime, next.Time, + "chain %s SequenceBlock must produce a block at parent.Time+blockTime", c.ID) + t.Require().Equalf(head.Number+1, next.Number, + "chain %s SequenceBlock must produce exactly one new block", c.ID) + } + } +} + +// advanceSafeToCurrentUnsafe starts c.Batcher, waits for c.LocalSafe to catch +// up to c.UnsafeL2, then stops the batcher. The batcher only ever submits up +// to current UnsafeL2; with sequencers stopped, that bounds what lands on L1. +// +// To advance safe to a specific timestamp, pin UnsafeL2 to that timestamp via +// advanceUnsafeToTimestamp first. +func advanceSafeToCurrentUnsafe(t devtest.T, c *chain) { + target := c.EL.BlockRefByLabel(eth.Unsafe).Number + c.Batcher.Start() + c.CLNode.Reached(safety.LocalSafe, target, 60) + c.Batcher.Stop() +} + +// blockExpectedAt reports whether c's rollup schedule produces a block exactly +// at ts. Equivalent to: would the trace provider, transitioning to ts, demand +// batch data for c? +func blockExpectedAt(c *chain, ts uint64) bool { + g := c.Cfg.Genesis.L2Time + if ts < g { + return false + } + return (ts-g)%c.Cfg.BlockTime == 0 +} + +// extendChainsExpectingBoundaryBlock produces a block at boundary on every +// chain whose rollup schedule expects one there, then batches it so the +// supernode picks it up via LocalSafe. l1HeadCurrent was already captured +// before this runs, so the boundary batch lands on L1 *after* l1HeadCurrent — +// the FPP can't derive the boundary block via l1HeadCurrent, and the +// supernode's response for the chain at boundary has RequiredL1 > +// l1HeadCurrent so the challenger top half also returns InvalidTransition. +// +// We need the supernode to actually have the boundary block in its +// OptimisticAtTimestamp data: that lookup goes through LocalSafe state, so an +// UnsafeL2-only block is invisible to it. Batching the new block fixes that +// without making l1HeadCurrent see it (l1HeadCurrent is fixed earlier in the +// flow). +func extendChainsExpectingBoundaryBlock(t devtest.T, sys *presets.SimpleInterop, chains []*chain, boundary uint64) { + extended := make([]*chain, 0, len(chains)) + for _, c := range chains { + if !blockExpectedAt(c, boundary) { + continue + } + head := c.EL.BlockRefByLabel(eth.Unsafe) + t.Require().Equalf(boundary-c.Cfg.BlockTime, head.Time, + "chain %s head %d must be one block-time before boundary %d", c.ID, head.Time, boundary) + sys.TestSequencer.SequenceBlock(t, c.ID, head.Hash) + next := c.EL.BlockRefByLabel(eth.Unsafe) + t.Require().Equalf(boundary, next.Time, + "chain %s extension must land at boundary %d", c.ID, boundary) + extended = append(extended, c) + } + // Batch the boundary blocks so the supernode picks them up via LocalSafe. + // Each chain's batcher submits only its current UnsafeL2 (= boundary block). + for _, c := range extended { + advanceSafeToCurrentUnsafe(t, c) + } +} + // transitionTest describes a single super-root transition test case. type transitionTest struct { Name string @@ -366,6 +482,353 @@ func buildTransitionTests( ClaimTimestamp: endTimestamp, ExpectValid: true, }, + { + Name: "ConsolidateStep", + AgreedClaim: padding(consolidateStep), + DisputedClaim: end.Marshal(), + DisputedTraceIndex: consolidateStep, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + { + Name: "ConsolidateStep-InvalidNoChange", + AgreedClaim: padding(consolidateStep), + DisputedClaim: padding(consolidateStep), + DisputedTraceIndex: consolidateStep, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + ExpectValid: false, + }, + } +} + +// buildAfterChainHeadTests constructs the 6 subtests that probe trace-provider +// behavior at trace indices around and beyond the chain head boundary +// (= endTimestamp + 1). +// +// Per-config branching is keyed on whether each chain's rollup schedule +// produces a block at the boundary (blockExpectedAt). Stage 2b extends the +// UnsafeL2 head of any chain whose schedule expects a boundary block (without +// batching), so the supernode response at the boundary for that chain has +// RequiredL1 > l1HeadCurrent. Three configurations are exercised by callers: +// +// - Uniform 2s/2s with even endTimestamp: neither chain expects a boundary +// block. Tests 1 / 2 / 3 all assert valid TransitionState (rollover); test 4 +// asserts a valid agreed (SuperRoot at boundary) with disputed = InvalidTransition +// because chain A's expected block at boundary+1 isn't derivable. +// +// - 1s/2s (chain A=1s): chain A's schedule expects a boundary block. Its +// optimistic step at boundary returns InvalidTransition (block expected, +// batch missing on L1). Once one step is invalid every subsequent step is +// too, so test 2 is omitted here — including it would just duplicate the +// cascade rule tests 5 and 6 already cover. +// +// - 2s/1s (chain B=1s): chain A=2s rolls over at boundary (test 1 returns a +// valid TransitionState); chain B's optimistic step at boundary returns +// InvalidTransition (block expected on chain B, batch missing on L1). +// Test 2 is omitted here too for symmetry with the 1s/2s config — +// including it would be a unique probe of "chain B becomes invalid first" +// but we don't currently cover that. +// +// Test 4's agreed claim is the trace state at idx 2*sPT-1 (consolidation at +// boundary). Whenever any chain expects a boundary block the trace cascades +// to InvalidTransition by then; otherwise it consolidates to SuperRoot(boundary). +// +// Tests 5 & 6 exercise the cascade rule (Invalid → Invalid) at deeper indices, +// independent of how the trace got invalid in the first place. +// +// claimTimestamp = endTimestamp + 100 ensures the trace covers idx +// 4*stepsPerTimestamp + 1 (test 6). +func buildAfterChainHeadTests( + chains []*chain, + end, endNext eth.SuperV1, + endTimestamp uint64, + l1HeadCurrent eth.BlockID, + firstOptNext, secondOptNext interopTypes.OptimisticBlock, +) []*transitionTest { + boundary := endTimestamp + 1 + const claimBuffer = uint64(100) + claimTimestamp := endTimestamp + claimBuffer + + chainAExpects := blockExpectedAt(chains[0], boundary) + chainBExpects := blockExpectedAt(chains[1], boundary) + anyExpects := chainAExpects || chainBExpects + + tests := []*transitionTest{} + + // Test 1 — chain A's optimistic step at boundary. + // If chain A's schedule expects a block at the boundary but l1HeadCurrent + // doesn't have one, the trace provider returns InvalidTransition. + // Otherwise it rolls over with chain A's previous-block data. + test1Disputed := marshalTransition(end, 1, firstOptNext) + if chainAExpects { + test1Disputed = interop.InvalidTransition + } + tests = append(tests, &transitionTest{ + Name: "DisputeTimestampAfterChainHeadChainA", + AgreedClaim: end.Marshal(), + DisputedClaim: test1Disputed, + L1Head: l1HeadCurrent, + ClaimTimestamp: claimTimestamp, + DisputedTraceIndex: consolidateStep + 1, + ExpectValid: true, + }) + + // Test 2 — chain B's optimistic step at boundary. + // Skipped whenever any chain expects a boundary block: in 1s/2s the trace + // has already cascaded to InvalidTransition (test 5/6 cover the cascade), + // and in 2s/1s we omit for symmetry rather than add new coverage here. + if !anyExpects { + tests = append(tests, &transitionTest{ + Name: "DisputeTimestampAfterChainHeadChainB", + AgreedClaim: marshalTransition(end, 1, firstOptNext), + DisputedClaim: marshalTransition(end, 2, firstOptNext, secondOptNext), + L1Head: l1HeadCurrent, + ClaimTimestamp: claimTimestamp, + DisputedTraceIndex: consolidateStep + 2, + ExpectValid: true, + }) + } + + // Test 3 — Consolidate at boundary. + // In uniform configs the trace at idx 2*sPT-2 builds a clean TransitionState + // and idx 2*sPT-1 consolidates to SuperRoot(boundary). + // In varied configs the trace cascades to InvalidTransition starting at + // the optimistic step where the faster chain's boundary block isn't on + // l1HeadCurrent, so by idx 2*sPT-1 the cascade rule says "always Invalid". + test3Agreed := marshalTransition(end, consolidateStep, firstOptNext, secondOptNext) + test3Disputed := endNext.Marshal() + if anyExpects { + test3Agreed = interop.InvalidTransition + test3Disputed = interop.InvalidTransition + } + tests = append(tests, &transitionTest{ + Name: "DisputeTimestampAfterChainHeadConsolidate", + AgreedClaim: test3Agreed, + DisputedClaim: test3Disputed, + L1Head: l1HeadCurrent, + ClaimTimestamp: claimTimestamp, + DisputedTraceIndex: 2*stepsPerTimestamp - 1, + ExpectValid: true, + }) + + // Test 4 — chain A's optimistic step at boundary+1. + // In uniform: agreed at idx 2*sPT-1 is SuperRoot(boundary) (consolidate + // step), disputed at idx 2*sPT is InvalidTransition because chain A=2s + // expects a block at boundary+1 but its LocalSafe is at endTimestamp. + // In varied: cascade — agreed and disputed both InvalidTransition. + test4Agreed := endNext.Marshal() + if anyExpects { + test4Agreed = interop.InvalidTransition + } + tests = append(tests, &transitionTest{ + Name: "DisputeBlockAfterChainHead-FirstChain", + AgreedClaim: test4Agreed, + DisputedClaim: interop.InvalidTransition, + L1Head: l1HeadCurrent, + ClaimTimestamp: claimTimestamp, + DisputedTraceIndex: 2 * stepsPerTimestamp, + ExpectValid: true, + }) + + // Tests 5 & 6 — far past chain head. Cascade. + tests = append(tests, + &transitionTest{ + Name: "AgreedBlockAfterChainHead-Consolidate", + AgreedClaim: interop.InvalidTransition, + DisputedClaim: interop.InvalidTransition, + L1Head: l1HeadCurrent, + ClaimTimestamp: claimTimestamp, + DisputedTraceIndex: 4*stepsPerTimestamp - 1, + ExpectValid: true, + }, + &transitionTest{ + Name: "AgreedBlockAfterChainHead-Optimistic", + AgreedClaim: interop.InvalidTransition, + DisputedClaim: interop.InvalidTransition, + L1Head: l1HeadCurrent, + ClaimTimestamp: claimTimestamp, + DisputedTraceIndex: 4*stepsPerTimestamp + 1, + ExpectValid: true, + }, + ) + + return tests +} + +// RunTraceExtensionActivationTest verifies that trace extension correctly +// activates (or not) based on whether the claim timestamp has been reached. +func RunTraceExtensionActivationTest(t devtest.T, sys *presets.SimpleInterop) { + t.Require().NotNil(sys.SuperRoots, "supernode is required for this test") + + chains := orderedChains(sys) + t.Require().Len(chains, 2, "expected exactly 2 interop chains") + + endTimestamp := uint64(time.Now().Unix()) + sys.SuperRoots.AwaitValidatedTimestamp(endTimestamp + 1) + l1Head := latestRequiredL1(sys.SuperRoots.SuperRootAtTimestamp(endTimestamp + 1)) + + startTimestamp := endTimestamp - 1 + agreedSuperRoot := superRootAtTimestamp(t, chains, endTimestamp) + agreedClaim := agreedSuperRoot.Marshal() + + // The disputed claim transitions to the next timestamp by including the + // first chain's optimistic block at endTimestamp+1. + firstOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[0].ID, endTimestamp+1) + disputedClaim := marshalTransition(agreedSuperRoot, 1, firstOptimistic) + disputedTraceIndex := int64(stepsPerTimestamp) + + tests := []*transitionTest{ + { + Name: "CorrectlyDidNotActivate", + AgreedClaim: agreedClaim, + DisputedClaim: disputedClaim, + DisputedTraceIndex: disputedTraceIndex, + L1Head: l1Head, + // Trace extension does not activate because we have not reached the proposal timestamp yet. + ClaimTimestamp: endTimestamp + 1, + ExpectValid: true, + }, + { + Name: "IncorrectlyDidNotActivate", + AgreedClaim: agreedClaim, + DisputedClaim: disputedClaim, + DisputedTraceIndex: disputedTraceIndex, + L1Head: l1Head, + // Trace extension should have activated because we have reached the proposal timestamp. + ClaimTimestamp: endTimestamp, + ExpectValid: false, + }, + { + Name: "CorrectlyActivated", + AgreedClaim: agreedClaim, + DisputedClaim: agreedClaim, + DisputedTraceIndex: disputedTraceIndex, + L1Head: l1Head, + // Trace extension activated at the proposal timestamp, claim stays the same. + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + { + Name: "IncorrectlyActivated", + AgreedClaim: agreedClaim, + DisputedClaim: agreedClaim, + DisputedTraceIndex: disputedTraceIndex, + L1Head: l1Head, + // Trace extension should not have activated because we haven't reached the proposal timestamp. + ClaimTimestamp: endTimestamp + 1, + ExpectValid: false, + }, + } + + challengerCfg := sys.L2ChainA.Escape().L2Challengers()[0].Config() + gameDepth := sys.DisputeGameFactory().GameImpl(gameTypes.SuperCannonKonaGameType).SplitDepth() + + for _, test := range tests { + t.Run(test.Name+"-fpp", func(t devtest.T) { + runKonaInteropProgram(t, challengerCfg.CannonKona, test.L1Head.Hash, + test.AgreedClaim, crypto.Keccak256Hash(test.DisputedClaim), + test.ClaimTimestamp, test.ExpectValid) + }) + t.Run(test.Name+"-challenger", func(t devtest.T) { + runChallengerProviderTest(t, sys.SuperRoots.QueryAPI(), gameDepth, startTimestamp, test.ClaimTimestamp, test) + }) + } +} + +// RunUnsafeProposalTest verifies that proposing an unsafe block (one without +// batch data on L1) is correctly identified as invalid. +func RunUnsafeProposalTest(t devtest.T, sys *presets.SimpleInterop) { + t.Require().NotNil(sys.SuperRoots, "supernode is required for this test") + + chains := orderedChains(sys) + t.Require().Len(chains, 2, "expected exactly 2 interop chains") + + // Stop chains[0]'s batcher first so its safe head stalls while chains[1]'s + // batcher continues to advance. This deterministically guarantees chains[0] + // has the lowest safe head — which is required because: + // 1. Step 0 in the super root trace transitions chains[0]. We need step 0 + // to produce InvalidTransition (no batch data for chains[0]'s block). + // 2. The agreed prestate at (endTimestamp - 1) must be verified for ALL + // chains. Using chains[0]'s stalled safe head as the anchor ensures + // that timestamp maps to a block at or below every chain's safe head. + chains[0].Batcher.Stop() + defer chains[0].Batcher.Start() + chains[0].CLNode.WaitForStall(safety.LocalSafe) + + stalledStatus, err := chains[0].Rollup.SyncStatus(t.Ctx()) + t.Require().NoError(err) + stalledSafeHead := stalledStatus.SafeL2.Number + + // Wait for chains[1]'s safe head to surpass chains[0]'s stalled safe head. + // chains[1]'s batcher is still running, so this is guaranteed to happen. + // We need strictly greater so that chains[1]'s block at endTimestamp + // (= TimestampForBlock(stalledSafeHead + 1)) is safe. + t.Require().Eventually(func() bool { + status1, err := chains[1].Rollup.SyncStatus(t.Ctx()) + return err == nil && status1.SafeL2.Number > stalledSafeHead + }, 2*time.Minute, 2*time.Second, "chains[1] safe head should advance past chains[0]'s stalled safe head") + + chains[1].Batcher.Stop() + defer chains[1].Batcher.Start() + chains[1].CLNode.WaitForStall(safety.LocalSafe) + + endTimestamp := chains[0].Cfg.TimestampForBlock(stalledSafeHead + 1) + agreedTimestamp := endTimestamp - 1 + + // Ensure chains[0] has produced the target block as unsafe. + target, err := chains[0].Cfg.TargetBlockNumber(endTimestamp) + t.Require().NoError(err) + chains[0].EL.Reached(eth.Unsafe, target, 60) + + sys.SuperRoots.AwaitValidatedTimestamp(agreedTimestamp) + resp := sys.SuperRoots.SuperRootAtTimestamp(agreedTimestamp) + l1Head := resp.CurrentL1 + + startTimestamp := agreedTimestamp + agreedSuperRoot := superRootAtTimestamp(t, chains, agreedTimestamp) + agreedClaim := agreedSuperRoot.Marshal() + + // Disputed claim: transition state with step 1 but no optimistic blocks. + // This claims a transition happened, but since chains[0]'s block at + // endTimestamp is only unsafe (no batch data on L1), the correct answer + // is InvalidTransition. + disputedClaim := marshalTransition(agreedSuperRoot, 1) + + tests := []*transitionTest{ + { + Name: "ProposedUnsafeBlock-NotValid", + AgreedClaim: agreedClaim, + DisputedClaim: disputedClaim, + DisputedTraceIndex: 0, + L1Head: l1Head, + ClaimTimestamp: endTimestamp, + ExpectValid: false, + }, + { + Name: "ProposedUnsafeBlock-ShouldBeInvalid", + AgreedClaim: agreedClaim, + DisputedClaim: super.InvalidTransition, + DisputedTraceIndex: 0, + L1Head: l1Head, + ClaimTimestamp: endTimestamp, + ExpectValid: true, + }, + } + + challengerCfg := sys.L2ChainA.Escape().L2Challengers()[0].Config() + gameDepth := sys.DisputeGameFactory().GameImpl(gameTypes.SuperCannonKonaGameType).SplitDepth() + + for _, test := range tests { + t.Run(test.Name+"-fpp", func(t devtest.T) { + runKonaInteropProgram(t, challengerCfg.CannonKona, test.L1Head.Hash, + test.AgreedClaim, crypto.Keccak256Hash(test.DisputedClaim), + test.ClaimTimestamp, test.ExpectValid) + }) + t.Run(test.Name+"-challenger", func(t devtest.T) { + runChallengerProviderTest(t, sys.SuperRoots.QueryAPI(), gameDepth, startTimestamp, test.ClaimTimestamp, test) + }) } } @@ -534,7 +997,7 @@ func RunInvalidBlockTest(t devtest.T, sys *presets.SimpleInterop) { startTimestamp := endTimestamp - 1 sys.SuperRoots.AwaitValidatedTimestamp(endTimestamp) - sys.L2CLB.Reached(types.CrossSafe, bigs.Uint64Strict(execMsg.BlockNumber()), 10) + sys.L2CLB.Reached(safety.CrossSafe, bigs.Uint64Strict(execMsg.BlockNumber()), 10) sys.L2ELB.AssertExecMessageNotInBlock(execMsg) l1HeadCurrent := latestRequiredL1(sys.SuperRoots.SuperRootAtTimestamp(endTimestamp)) @@ -542,8 +1005,8 @@ func RunInvalidBlockTest(t devtest.T, sys *presets.SimpleInterop) { start := superRootAtTimestamp(t, chains, startTimestamp) crossSafeSuperRootEnd := superRootAtTimestamp(t, chains, endTimestamp) - firstOptimistic := optimisticBlockAtTimestamp(t, chains[0], endTimestamp) - secondOptimistic := optimisticBlockAtTimestamp(t, chains[1], endTimestamp) + firstOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[0].ID, endTimestamp) + secondOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[1].ID, endTimestamp) paddingStep := func(step uint64) []byte { return marshalTransition(start, step, firstOptimistic, secondOptimistic) } @@ -675,3 +1138,303 @@ func RunInvalidBlockTest(t devtest.T, sys *presets.SimpleInterop) { }) } } + +// RunMessageExpiryTest verifies that when a cross-chain message expires (the +// executing message's block timestamp exceeds the init message timestamp plus +// the message expiry window), the block containing the executing message is +// replaced during consolidation. The system must be configured with a short +// message expiry window via WithMessageExpiryWindow. +// +// msgExpiryWindow is the configured message expiry window in seconds; it must +// match the value passed to WithMessageExpiryWindow when creating the system. +func RunMessageExpiryTest(t devtest.T, sys *presets.SimpleInterop, msgExpiryWindow uint64) { + t.Require().NotNil(sys.SuperRoots, "supernode is required for this test") + rng := rand.New(rand.NewSource(1234)) + + chains := orderedChains(sys) + t.Require().Len(chains, 2, "expected exactly 2 interop chains") + + aliceA := sys.FunderA.NewFundedEOA(eth.OneEther) + aliceB := aliceA.AsEL(sys.L2ELB) + sys.FunderB.Fund(aliceB, eth.OneEther) + + // Send an initiating message on chain A. + eventLogger := aliceA.DeployEventLogger() + initMsg := aliceA.SendRandomInitMessage(rng, eventLogger, 2, 10) + + // Record the init message's block timestamp. + initBlockNum := bigs.Uint64Strict(initMsg.BlockNumber()) + initTimestamp := sys.L2ChainA.TimestampForBlockNum(initBlockNum) + + // Calculate target block numbers past expiry for each chain independently. + // The message expires when: initTimestamp + expiryWindow < execTimestamp. + // Add extra blocks for safety margin. + blockTimeA := sys.L2ChainA.Escape().RollupConfig().BlockTime + blockTimeB := sys.L2ChainB.Escape().RollupConfig().BlockTime + t.Require().NotZero(blockTimeA, "block time A must be non-zero") + t.Require().NotZero(blockTimeB, "block time B must be non-zero") + + currentBlockA := sys.L2ELA.BlockRefByLabel(eth.Unsafe) + currentBlockB := sys.L2ELB.BlockRefByLabel(eth.Unsafe) + blocksNeededA := (msgExpiryWindow / blockTimeA) + 2 + blocksNeededB := (msgExpiryWindow / blockTimeB) + 2 + targetBlockA := currentBlockA.Number + blocksNeededA + targetBlockB := currentBlockB.Number + blocksNeededB + + // Wait for both chains to produce blocks past the expiry window. + sys.L2ELA.Reached(eth.Unsafe, targetBlockA, 60) + sys.L2ELB.Reached(eth.Unsafe, targetBlockB, 60) + + // Stop batcher B so we control block production on chain B. + sys.L2BatcherB.Stop() + + // Build the exec tx without submitting to the mempool. + // InteropMempoolFiltering would reject the expired message, so we + // bypass the mempool by injecting the raw tx via the test sequencer. + // This models a malicious sequencer force-including an invalid tx. + rawExecTx, execTxHash := aliceB.PrepareExecTx(initMsg) + + // Inject the expired exec message into a block on chain B via test sequencer. + parentB := sys.L2ELB.BlockRefByLabel(eth.Unsafe) + chainBID := sys.L2ChainB.ChainID() + sys.TestSequencer.SequenceBlockWithTxs(t, chainBID, parentB.Hash, [][]byte{rawExecTx}) + + // Also advance chain A by one empty block to keep timestamps aligned. + parentA := sys.L2ELA.BlockRefByLabel(eth.Unsafe) + chainAID := sys.L2ChainA.ChainID() + sys.TestSequencer.SequenceBlock(t, chainAID, parentA.Hash) + + // The injected block is the new unsafe head on chain B. + newHeadB := sys.L2ELB.BlockRefByLabel(eth.Unsafe) + execBlockNum := newHeadB.Number + + // Verify the expired exec tx is actually in the injected block before consolidation. + sys.L2ELB.AssertTxInBlock(execBlockNum, execTxHash) + + // Restart batcher B so batch data gets submitted to L1. + sys.L2BatcherB.Start() + + endTimestamp := sys.L2ChainB.TimestampForBlockNum(execBlockNum) + t.Require().Greaterf(endTimestamp, initTimestamp+msgExpiryWindow, + "exec message timestamp %d should exceed init timestamp %d + expiry window %d", + endTimestamp, initTimestamp, msgExpiryWindow) + startTimestamp := endTimestamp - 1 + + // Wait for cross-safe validation, which should replace the invalid block. + sys.SuperRoots.AwaitValidatedTimestamp(endTimestamp) + sys.L2CLB.Reached(safety.CrossSafe, execBlockNum, 30) + + // Verify the expired exec tx was reorged out during consolidation. + sys.L2ELB.AssertTxNotInBlock(execBlockNum, execTxHash) + + l1HeadCurrent := latestRequiredL1(sys.SuperRoots.SuperRootAtTimestamp(endTimestamp)) + + crossSafeSuperRootEnd := superRootAtTimestamp(t, chains, endTimestamp) + + firstOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[0].ID, endTimestamp) + secondOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[1].ID, endTimestamp) + + start := superRootAtTimestamp(t, chains, startTimestamp) + paddingStep := func(step uint64) []byte { + return marshalTransition(start, step, firstOptimistic, secondOptimistic) + } + + preReplacementSuperRoot := eth.NewSuperV1(endTimestamp, + eth.ChainIDAndOutput{ChainID: chains[0].ID, Output: firstOptimistic.OutputRoot}, + eth.ChainIDAndOutput{ChainID: chains[1].ID, Output: secondOptimistic.OutputRoot}) + + tests := []*transitionTest{ + { + Name: "Consolidate-ExpectInvalidPendingBlock", + AgreedClaim: paddingStep(consolidateStep), + DisputedClaim: preReplacementSuperRoot.Marshal(), + DisputedTraceIndex: consolidateStep, + ExpectValid: false, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + }, + { + Name: "Consolidate-ReplaceExpiredMessage", + AgreedClaim: paddingStep(consolidateStep), + DisputedClaim: crossSafeSuperRootEnd.Marshal(), + DisputedTraceIndex: consolidateStep, + ExpectValid: true, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + }, + } + + challengerCfg := sys.L2ChainA.Escape().L2Challengers()[0].Config() + gameDepth := sys.DisputeGameFactory().GameImpl(gameTypes.SuperCannonKonaGameType).SplitDepth() + for _, test := range tests { + t.Run(test.Name+"-fpp", func(t devtest.T) { + runKonaInteropProgram(t, challengerCfg.CannonKona, test.L1Head.Hash, + test.AgreedClaim, crypto.Keccak256Hash(test.DisputedClaim), + test.ClaimTimestamp, test.ExpectValid) + }) + + t.Run(test.Name+"-challenger", func(t devtest.T) { + runChallengerProviderTest(t, sys.SuperRoots.QueryAPI(), gameDepth, startTimestamp, test.ClaimTimestamp, test) + }) + } +} + +// RunDepositMessageTest verifies that the fault proof system correctly handles +// consolidation when a cross-chain message is initiated via an L1 deposit transaction. +func RunDepositMessageTest(t devtest.T, sys *presets.SimpleInterop) { + t.Require().NotNil(sys.SuperRoots, "supernode is required for this test") + rng := rand.New(rand.NewSource(5678)) + + chains := orderedChains(sys) + t.Require().Len(chains, 2, "expected exactly 2 interop chains") + + aliceA := sys.FunderA.NewFundedEOA(eth.OneEther) + aliceL1 := aliceA.AsEL(sys.L1EL) + sys.FunderL1.Fund(aliceL1, eth.OneEther) + aliceB := aliceA.AsEL(sys.L2ELB) + sys.FunderB.Fund(aliceB, eth.OneEther) + + eventLogger := aliceA.DeployEventLogger() + depositEOA := aliceA.ViaDepositTx(aliceL1, sys.L2ELA, sys.L2ChainA) + initMsg := depositEOA.SendRandomInitMessage(rng, eventLogger) + execMsg := aliceB.SendExecMessage(initMsg) + + endTimestamp := sys.L2ChainB.TimestampForBlockNum(bigs.Uint64Strict(execMsg.BlockNumber())) + startTimestamp := endTimestamp - 1 + + sys.SuperRoots.AwaitValidatedTimestamp(endTimestamp) + l1HeadCurrent := latestRequiredL1(sys.SuperRoots.SuperRootAtTimestamp(endTimestamp)) + + start := superRootAtTimestamp(t, chains, startTimestamp) + end := superRootAtTimestamp(t, chains, endTimestamp) + + firstOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[0].ID, endTimestamp) + secondOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[1].ID, endTimestamp) + paddingStep := func(step uint64) []byte { + return marshalTransition(start, step, firstOptimistic, secondOptimistic) + } + + tests := []*transitionTest{ + { + Name: "Consolidate", + AgreedClaim: paddingStep(consolidateStep), + DisputedClaim: end.Marshal(), + DisputedTraceIndex: consolidateStep, + ExpectValid: true, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + }, + { + Name: "Consolidate-InvalidNoChange", + AgreedClaim: paddingStep(consolidateStep), + DisputedClaim: paddingStep(consolidateStep), + DisputedTraceIndex: consolidateStep, + ExpectValid: false, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + }, + } + + challengerCfg := sys.L2ChainA.Escape().L2Challengers()[0].Config() + gameDepth := sys.DisputeGameFactory().GameImpl(gameTypes.SuperCannonKonaGameType).SplitDepth() + for _, test := range tests { + t.Run(test.Name+"-fpp", func(t devtest.T) { + runKonaInteropProgram(t, challengerCfg.CannonKona, test.L1Head.Hash, + test.AgreedClaim, crypto.Keccak256Hash(test.DisputedClaim), + test.ClaimTimestamp, test.ExpectValid) + }) + + t.Run(test.Name+"-challenger", func(t devtest.T) { + runChallengerProviderTest(t, sys.SuperRoots.QueryAPI(), gameDepth, startTimestamp, test.ClaimTimestamp, test) + }) + } +} + +// RunDepositMessageInvalidExecutionTest verifies that the fault proof system correctly +// detects an invalid executing message when the initiating message was sent via an L1 +// deposit transaction. The executing message uses an invalid identifier, so consolidation +// must replace the optimistic block with the cross-safe result. +func RunDepositMessageInvalidExecutionTest(t devtest.T, sys *presets.SimpleInterop) { + t.Require().NotNil(sys.SuperRoots, "supernode is required for this test") + rng := rand.New(rand.NewSource(9012)) + + chains := orderedChains(sys) + t.Require().Len(chains, 2, "expected exactly 2 interop chains") + + aliceA := sys.FunderA.NewFundedEOA(eth.OneEther) + aliceL1 := aliceA.AsEL(sys.L1EL) + sys.FunderL1.Fund(aliceL1, eth.OneEther) + aliceB := aliceA.AsEL(sys.L2ELB) + sys.FunderB.Fund(aliceB, eth.OneEther) + + eventLogger := aliceA.DeployEventLogger() + depositEOA := aliceA.ViaDepositTx(aliceL1, sys.L2ELA, sys.L2ChainA) + initMsg := depositEOA.SendRandomInitMessage(rng, eventLogger) + execMsg := aliceB.SendInvalidExecMessage(initMsg) + + endTimestamp := sys.L2ChainB.TimestampForBlockNum(bigs.Uint64Strict(execMsg.BlockNumber())) + startTimestamp := endTimestamp - 1 + + sys.SuperRoots.AwaitValidatedTimestamp(endTimestamp) + sys.L2CLB.Reached(safety.CrossSafe, bigs.Uint64Strict(execMsg.BlockNumber()), 10) + sys.L2ELB.AssertExecMessageNotInBlock(execMsg) + + l1HeadCurrent := latestRequiredL1(sys.SuperRoots.SuperRootAtTimestamp(endTimestamp)) + + start := superRootAtTimestamp(t, chains, startTimestamp) + crossSafeEnd := superRootAtTimestamp(t, chains, endTimestamp) + + firstOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[0].ID, endTimestamp) + secondOptimistic := optimisticBlockAtTimestamp(t, sys.SuperRoots.QueryAPI(), chains[1].ID, endTimestamp) + paddingStep := func(step uint64) []byte { + return marshalTransition(start, step, firstOptimistic, secondOptimistic) + } + + optimisticEnd := eth.NewSuperV1(endTimestamp, + eth.ChainIDAndOutput{ChainID: chains[0].ID, Output: firstOptimistic.OutputRoot}, + eth.ChainIDAndOutput{ChainID: chains[1].ID, Output: secondOptimistic.OutputRoot}) + + tests := []*transitionTest{ + { + Name: "Consolidate", + AgreedClaim: paddingStep(consolidateStep), + DisputedClaim: crossSafeEnd.Marshal(), + DisputedTraceIndex: consolidateStep, + ExpectValid: true, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + }, + { + Name: "Consolidate-InvalidNoChange", + AgreedClaim: paddingStep(consolidateStep), + DisputedClaim: paddingStep(consolidateStep), + DisputedTraceIndex: consolidateStep, + ExpectValid: false, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + }, + { + Name: "Consolidate-ExpectInvalidPendingBlock", + AgreedClaim: paddingStep(consolidateStep), + DisputedClaim: optimisticEnd.Marshal(), + DisputedTraceIndex: consolidateStep, + ExpectValid: false, + L1Head: l1HeadCurrent, + ClaimTimestamp: endTimestamp, + }, + } + + challengerCfg := sys.L2ChainA.Escape().L2Challengers()[0].Config() + gameDepth := sys.DisputeGameFactory().GameImpl(gameTypes.SuperCannonKonaGameType).SplitDepth() + for _, test := range tests { + t.Run(test.Name+"-fpp", func(t devtest.T) { + runKonaInteropProgram(t, challengerCfg.CannonKona, test.L1Head.Hash, + test.AgreedClaim, crypto.Keccak256Hash(test.DisputedClaim), + test.ClaimTimestamp, test.ExpectValid) + }) + + t.Run(test.Name+"-challenger", func(t devtest.T) { + runChallengerProviderTest(t, sys.SuperRoots.QueryAPI(), gameDepth, startTimestamp, test.ClaimTimestamp, test) + }) + } +} diff --git a/op-acceptance-tests/tests/supernode/interop/eth_bridge_test.go b/op-acceptance-tests/tests/supernode/interop/eth_bridge_test.go new file mode 100644 index 00000000000..243ccda5f0b --- /dev/null +++ b/op-acceptance-tests/tests/supernode/interop/eth_bridge_test.go @@ -0,0 +1,200 @@ +package interop + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-core/predeploys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txintent" + "github.com/ethereum-optimism/optimism/op-service/txplan" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/lmittmann/w3" +) + +const postGenesisInteropActivationDelay = uint64(20) + +var sendETHFn = w3.MustNewFunc("sendETH(address,uint256)", "bytes32") + +type sendETHTrigger struct { + Recipient common.Address + Destination eth.ChainID +} + +func (t *sendETHTrigger) To() (*common.Address, error) { + addr := predeploys.SuperchainETHBridgeAddr + return &addr, nil +} + +func (t *sendETHTrigger) EncodeInput() ([]byte, error) { + return sendETHFn.EncodeArgs(t.Recipient, t.Destination.ToBig()) +} + +func (t *sendETHTrigger) AccessList() (types.AccessList, error) { + return nil, nil +} + +func newPostGenesisSupernodeInterop(t devtest.T) *presets.TwoL2SupernodeInterop { + return presets.NewTwoL2SupernodeInterop(t, postGenesisInteropActivationDelay, + presets.WithSuggestedInteropActivationOffset(postGenesisInteropActivationDelay), + ) +} + +func TestSupernodeInteropETHBridgeActivation(gt *testing.T) { + t := devtest.SerialT(gt) + sys := newPostGenesisSupernodeInterop(t) + + activationA := sys.L2A.AwaitActivation(t, forks.Interop) + activationB := sys.L2B.AwaitActivation(t, forks.Interop) + t.Require().Greater(activationA.Number, uint64(0), "interop must activate after genesis on chain A") + t.Require().Greater(activationB.Number, uint64(0), "interop must activate after genesis on chain B") + + assertPostGenesisETHBridgeActivation(t, sys.L2A, activationA) + assertPostGenesisETHBridgeActivation(t, sys.L2B, activationB) +} + +func TestSupernodeInteropETHBridgeRoundTrip(gt *testing.T) { + t := devtest.SerialT(gt) + sys := newPostGenesisSupernodeInterop(t) + + sys.L2A.AwaitActivation(t, forks.Interop) + sys.L2B.AwaitActivation(t, forks.Interop) + + aliceA := sys.FunderA.NewFundedEOA(eth.OneEther) + daveB := sys.FunderB.NewFundedEOA(eth.OneEther) + relayerA := sys.FunderA.NewFundedEOA(eth.OneEther) + relayerB := sys.FunderB.NewFundedEOA(eth.OneEther) + bobB := sys.FunderB.NewFundedEOA(eth.ZeroWei) + carolA := sys.FunderA.NewFundedEOA(eth.ZeroWei) + + expectedLiquidity := eth.MaxU128Wei + bridgeAmount := eth.OneHundredthEther + + // Bridge A -> B + sendAtoB, relayAtoB := bridgeETH(t, sys, aliceA, bobB, relayerB, bridgeAmount) + bobB.VerifyBalanceExact(bridgeAmount) + assertContractBalanceLatest(t, sys.L2A.PrimaryEL(), predeploys.ETHLiquidityAddr, expectedLiquidity.Add(bridgeAmount)) + assertContractBalanceLatest(t, sys.L2B.PrimaryEL(), predeploys.ETHLiquidityAddr, expectedLiquidity.Sub(bridgeAmount)) + + // Bridge B -> A + sendBtoA, relayBtoA := bridgeETH(t, sys, daveB, carolA, relayerA, bridgeAmount) + carolA.VerifyBalanceExact(bridgeAmount) + assertContractBalanceLatest(t, sys.L2A.PrimaryEL(), predeploys.ETHLiquidityAddr, expectedLiquidity) + assertContractBalanceLatest(t, sys.L2B.PrimaryEL(), predeploys.ETHLiquidityAddr, expectedLiquidity) + + t.Logger().Info("completed post-genesis ETH bridge round-trip", + "a_to_b_send", sendAtoB.TxHash, + "a_to_b_relay", relayAtoB.TxHash, + "b_to_a_send", sendBtoA.TxHash, + "b_to_a_relay", relayBtoA.TxHash, + ) +} + +func assertPostGenesisETHBridgeActivation(t devtest.T, net *dsl.L2Network, activationBlock eth.BlockID) { + require := t.Require() + el := net.PrimaryEL() + client := el.EthClient() + preBlock := el.BlockRefByNumber(activationBlock.Number - 1) + + for _, proxyAddr := range []common.Address{ + predeploys.SuperchainETHBridgeAddr, + predeploys.ETHLiquidityAddr, + } { + implAddrBytes, err := client.GetStorageAt(t.Ctx(), proxyAddr, genesis.ImplementationSlot, preBlock.Hash.String()) + require.NoError(err) + require.Equal(common.Address{}, common.BytesToAddress(implAddrBytes[:]), "proxy should not be initialized before activation") + } + + preBalance, err := client.BalanceAt(t.Ctx(), predeploys.ETHLiquidityAddr, new(big.Int).SetUint64(preBlock.Number)) + require.NoError(err) + require.Zero(preBalance.Sign(), "ETHLiquidity should be unfunded before activation") + + for _, proxyAddr := range []common.Address{ + predeploys.SuperchainETHBridgeAddr, + predeploys.ETHLiquidityAddr, + } { + implAddrBytes, err := client.GetStorageAt(t.Ctx(), proxyAddr, genesis.ImplementationSlot, activationBlock.Hash.String()) + require.NoError(err) + implAddr := common.BytesToAddress(implAddrBytes[:]) + require.NotEqual(common.Address{}, implAddr, "proxy should be initialized at activation") + + code, err := client.CodeAtHash(t.Ctx(), implAddr, activationBlock.Hash) + require.NoError(err) + require.NotEmpty(code, "implementation should have code at activation") + } + + activationBalance, err := client.BalanceAt(t.Ctx(), predeploys.ETHLiquidityAddr, new(big.Int).SetUint64(activationBlock.Number)) + require.NoError(err) + require.Zero(activationBalance.Cmp(eth.MaxU128Wei.ToBig()), "ETHLiquidity should receive full bootstrap liquidity at activation") +} + +func assertContractBalanceLatest(t devtest.T, el *dsl.L2ELNode, addr common.Address, expected eth.ETH) { + balance, err := el.EthClient().BalanceAt(t.Ctx(), addr, nil) + t.Require().NoError(err) + t.Require().Zero(balance.Cmp(expected.ToBig()), "unexpected balance for %s", addr) +} + +func bridgeETH( + t devtest.T, + sys *presets.TwoL2SupernodeInterop, + sender *dsl.EOA, + recipient *dsl.EOA, + relayer *dsl.EOA, + amount eth.ETH, +) (*types.Receipt, *types.Receipt) { + require := t.Require() + + sendTx := txintent.NewIntent[*sendETHTrigger, *txintent.InteropOutput]( + sender.Plan(), + txplan.WithValue(amount), + ) + sendTx.Content.Set(&sendETHTrigger{ + Recipient: recipient.Address(), + Destination: recipient.ChainID(), + }) + + sendReceipt, err := sendTx.PlannedTx.Included.Eval(t.Ctx()) + require.NoError(err, "sendETH receipt not found") + require.Len(sendReceipt.Logs, 3, "sendETH should emit burn, sendMessage, and sendETH logs") + for idx, addr := range []common.Address{ + predeploys.ETHLiquidityAddr, + predeploys.L2toL2CrossDomainMessengerAddr, + predeploys.SuperchainETHBridgeAddr, + } { + require.Equal(addr, sendReceipt.Logs[idx].Address) + } + + sendBlock, err := sendTx.PlannedTx.IncludedBlock.Eval(t.Ctx()) + require.NoError(err, "sendETH block not found") + t.Logger().Info("waiting for supernode validation of bridge send", "timestamp", sendBlock.Time) + sys.Supernode.AwaitValidatedTimestamp(sendBlock.Time) + + relayTx := txintent.NewIntent[*txintent.RelayTrigger, *txintent.InteropOutput](relayer.Plan()) + relayTx.Content.DependOn(&sendTx.Result) + relayTx.Content.Fn(txintent.RelayIndexed( + predeploys.L2toL2CrossDomainMessengerAddr, + &sendTx.Result, + &sendTx.PlannedTx.Included, + 1, + )) + + relayReceipt, err := relayTx.PlannedTx.Included.Eval(t.Ctx()) + require.NoError(err, "relayETH receipt not found") + require.Len(relayReceipt.Logs, 4, "relayETH should emit inbox, mint, relayETH, and relayedMessage logs") + for idx, addr := range []common.Address{ + predeploys.CrossL2InboxAddr, + predeploys.ETHLiquidityAddr, + predeploys.SuperchainETHBridgeAddr, + predeploys.L2toL2CrossDomainMessengerAddr, + } { + require.Equal(addr, relayReceipt.Logs[idx].Address) + } + + return sendReceipt, relayReceipt +} diff --git a/op-acceptance-tests/tests/supernode/interop/follow_l2/p2p_sync_test.go b/op-acceptance-tests/tests/supernode/interop/follow_l2/p2p_sync_test.go new file mode 100644 index 00000000000..e40c7c253b8 --- /dev/null +++ b/op-acceptance-tests/tests/supernode/interop/follow_l2/p2p_sync_test.go @@ -0,0 +1,49 @@ +package follow_l2 + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +// TestFollowSource_P2PSync checks that a follower CL syncs unsafe blocks from the +// sequencer via P2P. After stopping and restarting the follower, it reconnects to +// the sequencer and catches up to the same unsafe head. +func TestFollowSource_P2PSync(gt *testing.T) { + t := devtest.ParallelT(gt) + + sys := presets.NewTwoL2SupernodeFollowL2(t, 0) + logger := sys.Log.With("Test", "TestFollowSource_P2PSync") + + logger.Info("Make sure sequencer and follower unsafe head advances") + dsl.CheckAll(t, + sys.L2ACL.AdvancedFn(safety.LocalUnsafe, 5, 30), + sys.L2AFollowCL.AdvancedFn(safety.LocalUnsafe, 5, 30), + ) + + logger.Info("Stop follower CL") + sys.L2AFollowCL.Stop() + + logger.Info("Make sure follower EL does not advance") + sys.L2AFollowEL.NotAdvanced(eth.Unsafe, 5) + + logger.Info("Restart follower CL") + sys.L2AFollowCL.Start() + + logger.Info("Reconnect follower P2P to sequencer") + sys.L2AFollowCL.ConnectPeer(sys.L2ACL) + + logger.Info("Make sure both advance") + dsl.CheckAll(t, + sys.L2ACL.AdvancedFn(safety.LocalUnsafe, 10, 30), + sys.L2AFollowCL.AdvancedFn(safety.LocalUnsafe, 10, 30), + ) + + logger.Info("Check sequencer and follower converged on the same canonical chain") + sys.L2AFollowCL.InSync(sys.L2ACL, safety.LocalUnsafe, 30) +} diff --git a/op-acceptance-tests/tests/supernode/interop/follow_l2/sync_test.go b/op-acceptance-tests/tests/supernode/interop/follow_l2/sync_test.go index 919771062e9..462084d404f 100644 --- a/op-acceptance-tests/tests/supernode/interop/follow_l2/sync_test.go +++ b/op-acceptance-tests/tests/supernode/interop/follow_l2/sync_test.go @@ -7,7 +7,9 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum-optimism/optimism/op-service/eth" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestFollowSource_HeadsDivergeThenConverge(gt *testing.T) { @@ -39,8 +41,8 @@ func TestFollowSource_HeadsDivergeThenConverge(gt *testing.T) { initialChecks := make([]dsl.CheckFunc, 0, len(chains)*2) for _, chain := range chains { initialChecks = append(initialChecks, - chain.follower.MatchedFn(chain.source, types.LocalSafe, 20), - chain.follower.MatchedFn(chain.source, types.CrossSafe, 20), + chain.follower.InSyncFn(chain.source, safety.LocalSafe, 20), + chain.follower.InSyncFn(chain.source, safety.CrossSafe, 20), ) } dsl.CheckAll(t, initialChecks...) @@ -107,9 +109,9 @@ func TestFollowSource_HeadsDivergeThenConverge(gt *testing.T) { divergenceChecks := make([]dsl.CheckFunc, 0, len(chains)*3) for _, chain := range chains { divergenceChecks = append(divergenceChecks, - chain.follower.MatchedFn(chain.source, types.LocalUnsafe, 20), - chain.follower.MatchedFn(chain.source, types.LocalSafe, 20), - chain.follower.MatchedFn(chain.source, types.CrossSafe, 20), + chain.follower.InSyncFn(chain.source, safety.LocalUnsafe, 20), + chain.follower.InSyncFn(chain.source, safety.LocalSafe, 20), + chain.follower.InSyncFn(chain.source, safety.CrossSafe, 20), ) } dsl.CheckAll(t, divergenceChecks...) @@ -138,9 +140,9 @@ func TestFollowSource_HeadsDivergeThenConverge(gt *testing.T) { finalChecks := make([]dsl.CheckFunc, 0, len(chains)*3) for _, chain := range chains { finalChecks = append(finalChecks, - chain.follower.MatchedFn(chain.source, types.LocalUnsafe, 20), - chain.follower.MatchedFn(chain.source, types.LocalSafe, 20), - chain.follower.MatchedFn(chain.source, types.CrossSafe, 20), + chain.follower.InSyncFn(chain.source, safety.LocalUnsafe, 20), + chain.follower.InSyncFn(chain.source, safety.LocalSafe, 20), + chain.follower.InSyncFn(chain.source, safety.CrossSafe, 20), ) } dsl.CheckAll(t, finalChecks...) diff --git a/op-acceptance-tests/tests/supernode/interop/head_progression_test.go b/op-acceptance-tests/tests/supernode/interop/head_progression_test.go index 624dd681cda..2372394b83e 100644 --- a/op-acceptance-tests/tests/supernode/interop/head_progression_test.go +++ b/op-acceptance-tests/tests/supernode/interop/head_progression_test.go @@ -9,7 +9,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) // TestSupernodeInterop_SafeHeadProgression tests that the cross-safe head @@ -49,19 +50,19 @@ func TestSupernodeInterop_SafeHeadProgression(gt *testing.T) { // check safe heads get to at least that height, // let local safe heads run ahead dsl.CheckAll(t, - sys.L2ACL.ReachedFn(types.LocalSafe, finalTargetBlockNum, attempts), - sys.L2BCL.ReachedFn(types.LocalSafe, finalTargetBlockNum, attempts), - sys.L2ACL.ReachedFn(types.CrossSafe, initialTargetBlockNumA-1, attempts), - sys.L2BCL.ReachedFn(types.CrossSafe, initialTargetBlockNumB-1, attempts), + sys.L2ACL.ReachedFn(safety.LocalSafe, finalTargetBlockNum, attempts), + sys.L2BCL.ReachedFn(safety.LocalSafe, finalTargetBlockNum, attempts), + sys.L2ACL.ReachedFn(safety.CrossSafe, initialTargetBlockNumA-1, attempts), + sys.L2BCL.ReachedFn(safety.CrossSafe, initialTargetBlockNumB-1, attempts), ) // Expect cross safe and finalized to stall since we paused the interop activity numAttempts := 2 // implies a 4s wait dsl.CheckAll(t, - sys.L2ACL.NotAdvancedFn(types.CrossSafe, numAttempts), - sys.L2BCL.NotAdvancedFn(types.CrossSafe, numAttempts), - sys.L2ACL.NotAdvancedFn(types.Finalized, numAttempts), - sys.L2BCL.NotAdvancedFn(types.Finalized, numAttempts), + sys.L2ACL.NotAdvancedFn(safety.CrossSafe, numAttempts), + sys.L2BCL.NotAdvancedFn(safety.CrossSafe, numAttempts), + sys.L2ACL.NotAdvancedFn(safety.Finalized, numAttempts), + sys.L2BCL.NotAdvancedFn(safety.Finalized, numAttempts), ) // Check EL labels - cross-safeand finalized should be @@ -79,8 +80,10 @@ func TestSupernodeInterop_SafeHeadProgression(gt *testing.T) { // expect cross safe to catch up sys.Supernode.ResumeInterop() dsl.CheckAll(t, - sys.L2ACL.ReachedFn(types.CrossSafe, finalTargetBlockNum, attempts), - sys.L2BCL.ReachedFn(types.CrossSafe, finalTargetBlockNum, attempts), + sys.L2ACL.ReachedFn(safety.CrossSafe, finalTargetBlockNum, attempts), + sys.L2BCL.ReachedFn(safety.CrossSafe, finalTargetBlockNum, attempts), + sys.L2ELA.ReachedFn(eth.Safe, finalTargetBlockNum, attempts), + sys.L2ELB.ReachedFn(eth.Safe, finalTargetBlockNum, attempts), ) // check EL labels @@ -114,8 +117,10 @@ func TestSupernodeInterop_SafeHeadProgression(gt *testing.T) { // Finalized advancement depends on L1 finality, so use more attempts finalizedAttempts := 30 dsl.CheckAll(t, - sys.L2ACL.ReachedFn(types.Finalized, snapshotSafeA, finalizedAttempts), - sys.L2BCL.ReachedFn(types.Finalized, snapshotSafeB, finalizedAttempts), + sys.L2ACL.ReachedFn(safety.Finalized, snapshotSafeA, finalizedAttempts), + sys.L2BCL.ReachedFn(safety.Finalized, snapshotSafeB, finalizedAttempts), + sys.L2ELA.ReachedFn(eth.Finalized, snapshotSafeA, finalizedAttempts), + sys.L2ELB.ReachedFn(eth.Finalized, snapshotSafeB, finalizedAttempts), ) // Verify finalized heads on EL @@ -161,8 +166,8 @@ func TestSupernodeInterop_SafeHeadWithUnevenProgress(gt *testing.T) { // Wait for initial sync dsl.CheckAll(t, - sys.L2ACL.ReachedFn(types.LocalSafe, initialTargetBlockNum, attempts), - sys.L2BCL.ReachedFn(types.LocalSafe, initialTargetBlockNum, attempts), + sys.L2ACL.ReachedFn(safety.LocalSafe, initialTargetBlockNum, attempts), + sys.L2BCL.ReachedFn(safety.LocalSafe, initialTargetBlockNum, attempts), ) baselineLocalSafeB := sys.L2BCL.SyncStatus().LocalSafeL2.Number @@ -172,7 +177,7 @@ func TestSupernodeInterop_SafeHeadWithUnevenProgress(gt *testing.T) { // Chain A advances while B is frozen dsl.CheckAll(t, - sys.L2ACL.ReachedFn(types.LocalSafe, finalTargetBlockNum, attempts), + sys.L2ACL.ReachedFn(safety.LocalSafe, finalTargetBlockNum, attempts), ) unevenStatusA := sys.L2ACL.SyncStatus() @@ -193,13 +198,13 @@ func TestSupernodeInterop_SafeHeadWithUnevenProgress(gt *testing.T) { // Chain B catches up dsl.CheckAll(t, - sys.L2BCL.ReachedFn(types.LocalSafe, finalTargetBlockNum, attempts), + sys.L2BCL.ReachedFn(safety.LocalSafe, finalTargetBlockNum, attempts), ) // Cross-safe heads advance after chain B catches up dsl.CheckAll(t, - sys.L2ACL.ReachedFn(types.CrossSafe, snapshotCrossSafeA+5, attempts), - sys.L2BCL.ReachedFn(types.CrossSafe, snapshotCrossSafeA+5, attempts), + sys.L2ACL.ReachedFn(safety.CrossSafe, snapshotCrossSafeA+5, attempts), + sys.L2BCL.ReachedFn(safety.CrossSafe, snapshotCrossSafeA+5, attempts), ) // Check EL labels diff --git a/op-acceptance-tests/tests/supernode/interop/reorg/invalid_message_reorg_test.go b/op-acceptance-tests/tests/supernode/interop/reorg/invalid_message_reorg_test.go index f5322fa3c0d..01708407f30 100644 --- a/op-acceptance-tests/tests/supernode/interop/reorg/invalid_message_reorg_test.go +++ b/op-acceptance-tests/tests/supernode/interop/reorg/invalid_message_reorg_test.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -26,8 +25,6 @@ import ( // - The replacement block's timestamp eventually becomes verified func TestSupernodeInteropInvalidMessageReplacement(gt *testing.T) { t := devtest.SerialT(gt) - // TODO(ethereum-optimism/optimism#19411): remove skip once op-reth safe head mismatch is fixed - sysgo.SkipOnOpReth(t, "panics due to safe head mismatch in EngineController") sys := presets.NewTwoL2SupernodeInterop(t, 0) ctx := t.Ctx() diff --git a/op-acceptance-tests/tests/supernode/interop/startup_resync/startup_resync_test.go b/op-acceptance-tests/tests/supernode/interop/startup_resync/startup_resync_test.go new file mode 100644 index 00000000000..290cfe83d15 --- /dev/null +++ b/op-acceptance-tests/tests/supernode/interop/startup_resync/startup_resync_test.go @@ -0,0 +1,92 @@ +// Package startup_resync contains acceptance tests for the op-supernode +// interop startup rework's cold-start resync path: stopping the supernode, +// deleting its on-disk data dir, and starting a fresh supernode against the +// same chain containers and virtual nodes. +package startup_resync + +import ( + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +const ( + l2BlockTime = uint64(1) + backfillDepth = 3 * time.Second + preRestartFinalized = uint64(5) +) + +// TestSupernodeResyncResumesAtActivation_PostActivation drives a full +// supernode data-dir wipe after the chain has crossed activation, and +// asserts that cross-safe keeps advancing post-restart and that the +// cold-start backfill restored history into the logs DB. +func TestSupernodeResyncResumesAtActivation_PostActivation(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewTwoL2SupernodeInterop(t, 0, + presets.WithUniformL2BlockTimes(l2BlockTime), + presets.WithInteropLogBackfillDepth(backfillDepth), + ) + + sys.Supernode.AwaitBackfillCompleted() + + // Setup: let L2 finalized advance several blocks on both chains. On + // restart, op-node may drop back as part of its safe start process, + // but won't go past the finalized head. With finalized well past + // genesis the post-restart cold-start backfill has a real window to + // populate, instead of collapsing to empty against a re-recorded + // genesis SafeDB entry. + dsl.CheckAll(t, + sys.L2ACL.AdvancedFn(safety.Finalized, preRestartFinalized, 180), + sys.L2BCL.AdvancedFn(safety.Finalized, preRestartFinalized, 180), + ) + + sys.Supernode.RestartWithFreshDataDir() + sys.Supernode.AwaitBackfillCompleted() + + dsl.CheckAll(t, + sys.L2ACL.AdvancedFn(safety.CrossSafe, 1, 60), + sys.L2BCL.AdvancedFn(safety.CrossSafe, 1, 60), + ) + + // Verify the cold-start backfill repopulated the logs DB. + sys.Supernode.AssertBackfillCovers(backfillDepth, l2BlockTime, + sys.L2A.ChainID(), sys.L2B.ChainID()) +} + +// TestSupernodeResyncSchedulesAtActivation_PreActivation drives a full +// supernode data-dir wipe while interop is scheduled but not yet active, +// and asserts that cold-start init parks the verifier at the (future) +// activation timestamp while cross-safe keeps advancing on both chains. +func TestSupernodeResyncSchedulesAtActivation_PreActivation(gt *testing.T) { + t := devtest.SerialT(gt) + // 60-minute delay: ensures the chain never approaches activation during + // the test, so we always exercise the genuine pre-activation cold-start + // path regardless of CI scheduling variance. + sys := presets.NewTwoL2SupernodeInterop(t, 60*60, + presets.WithUniformL2BlockTimes(l2BlockTime), + presets.WithInteropLogBackfillDepth(backfillDepth), + ) + + sys.Supernode.AwaitBackfillCompleted() + activation := sys.Supernode.ActivationTimestamp() + + // Setup: let local-safe accumulate enough that op-node's SafeDB has + // entries to serve to the post-restart cold-start init. + dsl.CheckAll(t, + sys.L2ACL.AdvancedFn(safety.LocalSafe, 2, 30), + sys.L2BCL.AdvancedFn(safety.LocalSafe, 2, 30), + ) + + sys.Supernode.RestartWithFreshDataDir() + sys.Supernode.AwaitVerificationStartsAt(activation) + + dsl.CheckAll(t, + sys.L2ACL.AdvancedFn(safety.CrossSafe, 1, 60), + sys.L2BCL.AdvancedFn(safety.CrossSafe, 1, 60), + ) +} diff --git a/op-acceptance-tests/tests/sync/clsync/gap_clp2p/sync_test.go b/op-acceptance-tests/tests/sync/clsync/gap_clp2p/sync_test.go index 60709b1670d..55b5bb9e468 100644 --- a/op-acceptance-tests/tests/sync/clsync/gap_clp2p/sync_test.go +++ b/op-acceptance-tests/tests/sync/clsync/gap_clp2p/sync_test.go @@ -4,23 +4,37 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) // TestSyncAfterInitialELSync tests that blocks received out of order would be processed in order when running in CL sync mode. Note that this is not going to happen when running in EL sync mode, which relies on healthy ELP2P, something that is disabled in this test. func TestSyncAfterInitialELSync(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|10:38:08.992] + // "\n\tError Trace:\t/optimism/op-devstack/dsl/l2_el.go:192\n\t + // \t\t\t\t/optimism/op-acceptance-tests/tests/sync/clsync/gap_clp2p/sync_test.go:46 + // \n\tError: + // \tReceived unexpected error:\n\t + // \toperation failed permanently after 2 attempts: expected head for label=latest to advance to target=5, + // but got current=2 + // \tTest: \tTestSyncAfterInitialELSync\n" + + sysgo.SkipOnKonaNode(t, "not supported") sys := newGapCLP2PSystem(t) require := t.Require() - sys.L2CL.Advanced(types.LocalUnsafe, 7, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 7, 30) // batcher down so safe not advanced - require.Zero(sys.L2CL.HeadBlockRef(types.LocalSafe).Number) - require.Zero(sys.L2CLB.HeadBlockRef(types.LocalSafe).Number) + require.Zero(sys.L2CL.HeadBlockRef(safety.LocalSafe).Number) + require.Zero(sys.L2CLB.HeadBlockRef(safety.LocalSafe).Number) - startNum := sys.L2CLB.HeadBlockRef(types.LocalUnsafe).Number + startNum := sys.L2CLB.HeadBlockRef(safety.LocalUnsafe).Number // Finish EL sync by supplying the first block // EL Sync finished because underlying EL has states to validate the payload for block startNum+1 diff --git a/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go b/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go index 2dd083f7cee..a5623a6e98f 100644 --- a/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go +++ b/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go @@ -5,8 +5,10 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestReachUnsafeTipByAppendingUnsafePayload(gt *testing.T) { @@ -14,13 +16,13 @@ func TestReachUnsafeTipByAppendingUnsafePayload(gt *testing.T) { sys := newGapCLP2PSystem(t) logger := t.Logger() - sys.L2CL.Advanced(types.LocalUnsafe, 7, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 7, 30) // First make verifier reach unsafe tip logger.Info("Initial trial for appending payload until tip") sys.L2CLB.AppendUnsafePayloadUntilTip(sys.L2ELB, sys.L2EL, 400) - sys.L2CL.Advanced(types.LocalUnsafe, 7, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 7, 30) // Try once more to check that filling in the gap works again logger.Info("Second trial for appending payload until tip") @@ -46,12 +48,22 @@ func TestReachUnsafeTipByAppendingUnsafePayload(gt *testing.T) { // while maintaining correct Engine API semantics. func TestCLUnsafeNotRewoundOnInvalidDuringELSync(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with op-reth: + // + // assertions.go:387: ERROR[03-31|09:41:58.089] + // assertions.go:387: Error Trace: /optimism/op-devstack/dsl/l2_cl.go:279 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/elsync/gap_clp2p/sync_test.go:96 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: expected head not to advance: unsafe + // assertions.go:387: Test: TestCLUnsafeNotRewoundOnInvalidDuringELSync + // assertions.go:387: + sysgo.SkipOnOpReth(t, "not supported") sys := newGapCLP2PSystem(t) logger := t.Logger() require := t.Require() // Advance few blocks to make sure reference node advanced - sys.L2CL.Advanced(types.LocalUnsafe, 7, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 7, 30) // Restart L2CLB to always trigger an EL Sync sys.L2CLB.Stop() @@ -93,7 +105,7 @@ func TestCLUnsafeNotRewoundOnInvalidDuringELSync(gt *testing.T) { _, ok = payload.CheckBlockHash() require.True(ok) sys.L2CLB.PostUnsafePayload(payload) - sys.L2CLB.NotAdvanced(types.LocalUnsafe, attempts) + sys.L2CLB.NotAdvanced(safety.LocalUnsafe, attempts) sys.L2ELB.NotAdvanced(eth.Unsafe, attempts) // EL did not advance sys.L2ELB.UnsafeHead().NumEqualTo(startNum) diff --git a/op-acceptance-tests/tests/sync/elsync/gap_elp2p/sync_test.go b/op-acceptance-tests/tests/sync/elsync/gap_elp2p/sync_test.go index ae2a15255e7..9e3b61c614b 100644 --- a/op-acceptance-tests/tests/sync/elsync/gap_elp2p/sync_test.go +++ b/op-acceptance-tests/tests/sync/elsync/gap_elp2p/sync_test.go @@ -4,8 +4,10 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" ) @@ -55,12 +57,23 @@ import ( // - With ELP2P enabled, repeated FCU attempts eventually validate and advance the canonical chain. func TestL2ELP2PCanonicalChainAdvancedByFCU(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with op-reth: + // + // assertions.go:387: ERROR[03-31|09:58:15.522] + // assertions.go:387: Error Trace: /optimism/op-devstack/dsl/l2_el.go:64 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/elsync/gap_elp2p/sync_test.go:111 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: failed to determine block-hash of hash 0x8b94830f261ef4568bc2ba248f52b27f9a7c366e8157794c93bdd05ca4735564, could not get payload: not found + // assertions.go:387: Test: TestL2ELP2PCanonicalChainAdvancedByFCU + // assertions.go:387: Messages: block not found using block hash + // assertions.go:387: + sysgo.SkipOnOpReth(t, "not supported") sys := newGapELP2PSystem(t) require := t.Require() logger := t.Logger() // Advance few blocks to make sure reference node advanced - sys.L2CL.Advanced(types.LocalUnsafe, 10, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 10, 30) sys.L2CLB.Stop() @@ -255,7 +268,7 @@ func TestELP2PFCUUnavailableHash(gt *testing.T) { genesis := sys.L2ELB.BlockRefByNumber(0) // Advance few blocks to make sure reference node advanced - sys.L2CL.Advanced(types.LocalUnsafe, 10, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 10, 30) sys.L2CLB.Stop() @@ -310,7 +323,7 @@ func TestSafeDoesNotAdvanceWhenUnsafeIsSyncing_NoELP2P(gt *testing.T) { logger := t.Logger() // Advance few blocks to make sure reference node advanced - sys.L2CL.Advanced(types.LocalUnsafe, 10, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 10, 30) sys.L2CLB.Stop() @@ -393,13 +406,21 @@ func TestSafeDoesNotAdvanceWhenUnsafeIsSyncing_NoELP2P(gt *testing.T) { // invalid payloads—whether rejected at the CL or EL—do not advance the chain. func TestInvalidPayloadThroughCLP2P(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|10:42:03.034] + // assertions.go:387: Error Trace: /Users/josh/repos/optimism/op-acceptance-tests/tests/sync/elsync/gap_elp2p/sync_test.go:436 + // assertions.go:387: Error: An error is expected but got nil. + // assertions.go:387: Test: TestInvalidPayloadThroughCLP2P + // assertions.go:387: + sysgo.SkipOnKonaNode(t, "not supported") sys := newGapELP2PSystem(t) logger := t.Logger() require := t.Require() ctx := t.Ctx() // Advance few blocks to make sure reference node advanced - sys.L2CL.Advanced(types.LocalUnsafe, 4, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 4, 30) // At this point, L2ELB has no ELP2P, and L2CL connection startNum := sys.L2ELB.BlockRefByLabel(eth.Unsafe).Number @@ -443,7 +464,7 @@ func TestInvalidPayloadThroughCLP2P(gt *testing.T) { // Post invalid payload with the fault that can be only checked at the EL side sys.L2CLB.PostUnsafePayload(payload) // ex) op-geth error msg: "ignoring bad block: invalid merkle root" - sys.L2CLB.NotAdvanced(types.LocalUnsafe, attempts) + sys.L2CLB.NotAdvanced(safety.LocalUnsafe, attempts) sys.L2ELB.NotAdvanced(eth.Unsafe, attempts) // EL did not advance sys.L2ELB.UnsafeHead().NumEqualTo(startNum + 1) @@ -464,7 +485,7 @@ func TestInvalidPayloadThroughCLP2P(gt *testing.T) { // Post invalid payload with the fault that can be only checked at the EL side sys.L2CLB.PostUnsafePayload(payload) // ex) op-geth error msg: "ignoring bad block: links to previously rejected block" - sys.L2CLB.NotAdvanced(types.LocalUnsafe, attempts) + sys.L2CLB.NotAdvanced(safety.LocalUnsafe, attempts) sys.L2ELB.NotAdvanced(eth.Unsafe, attempts) // EL did not advance sys.L2ELB.UnsafeHead().NumEqualTo(startNum + 1) diff --git a/op-acceptance-tests/tests/sync/elsync/offset_el_safe/sync_test.go b/op-acceptance-tests/tests/sync/elsync/offset_el_safe/sync_test.go new file mode 100644 index 00000000000..fd1d0506858 --- /dev/null +++ b/op-acceptance-tests/tests/sync/elsync/offset_el_safe/sync_test.go @@ -0,0 +1,86 @@ +package offset_el_safe + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-service/eth" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +// TestELSyncSafeRetractedByOffset verifies that when OffsetELSafe is configured on +// the verifier, the safe and finalized EL heads are set behind the unsafe head after +// EL sync completes. +// +// Flow: +// 1. Both nodes advance LocalSafe (ensures the CL has real finalized state). +// 2. Stop/wipe the verifier EL to force a full EL sync on restart. +// 3. Stop the batcher, then advance the sequencer's unsafe chain further. +// Stopping the batcher makes the gap permanent: derivation can never advance +// safe past the last batched block, eliminating timing flakes. +// 4. Restart verifier, peer ELs, and wait for EL sync to complete. +// 5. Assert safe/finalized are behind unsafe. +func TestELSyncSafeRetractedByOffset(gt *testing.T) { + t := devtest.ParallelT(gt) + sysgo.SkipOnOpReth(t, "not supported (peering issue)") + sys := newOffsetELSafeSystem(t) + require := t.Require() + logger := t.Logger() + + // Advance both CLs to LocalSafe so the verifier CL has valid finalized state + // before we stop it (prevents "forkchoice not initialized" after EL wipe). + dsl.CheckAll(t, + sys.L2CL.AdvancedFn(safety.LocalSafe, 1, 30), + sys.L2CLB.AdvancedFn(safety.LocalSafe, 1, 30)) + sys.L2CLB.InSync(sys.L2CL, safety.LocalSafe, 30) + + // Stop verifier and wipe its EL to force a full EL sync on restart. + sys.L2ELB.Stop() + sys.L2CLB.Stop() + + // Stop the batcher so new sequencer blocks are unsafe-only. + // This creates a permanent gap between safe and unsafe after EL sync: + // derivation can only advance safe up to the last batched block. + sys.L2Batcher.Stop() + + // Advance sequencer further while verifier is down (unsafe only, no batches). + sys.L2CL.Advanced(safety.LocalUnsafe, 3, 30) + + sys.L2ELB.Start() + sys.L2CLB.Start() + sys.L2ELB.PeerWith(sys.L2EL) + + // Wait for the verifier CL to advance LocalSafe, which confirms: + // - EL sync completed (blocks transferred via p2p) + // - CL detected completion and sent the forkchoice update (setting safe/finalized) + // - Derivation began processing old batched data + // Because the batcher is stopped, derivation can only reach the last batched + // block — the gap between safe and unsafe is permanent. + sys.L2CLB.Advanced(safety.LocalSafe, 1, 30) + + unsafeHead := sys.L2ELB.BlockRefByLabel(eth.Unsafe) + safeHead := sys.L2ELB.SafeHead().BlockRef + finalizedHead := sys.L2ELB.FinalizedHead().BlockRef + + logger.Info("Verifier heads after EL sync", + "unsafe", unsafeHead.Number, + "safe", safeHead.Number, + "finalized", finalizedHead.Number) + + // Safe and finalized must be behind unsafe when offset is configured. + // The batcher is stopped, so derivation can never close the gap — these + // assertions are deterministic regardless of timing. + require.Greater(unsafeHead.Number, safeHead.Number, + "safe head should be behind unsafe head when OffsetELSafe is configured") + require.Greater(unsafeHead.Number, finalizedHead.Number, + "finalized head should be behind unsafe head when OffsetELSafe is configured") + require.GreaterOrEqual(safeHead.Number, finalizedHead.Number, + "safe head should be at or ahead of finalized head") + + retraction := unsafeHead.Number - safeHead.Number + logger.Info("Observed retraction", "blocks", retraction) + require.Greater(retraction, uint64(0), "retraction must be nonzero") +} diff --git a/op-acceptance-tests/tests/sync/elsync/reorg/sync_test.go b/op-acceptance-tests/tests/sync/elsync/reorg/sync_test.go index 01935ea8057..0cc8637d516 100644 --- a/op-acceptance-tests/tests/sync/elsync/reorg/sync_test.go +++ b/op-acceptance-tests/tests/sync/elsync/reorg/sync_test.go @@ -5,8 +5,10 @@ import ( "time" "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" "github.com/ethereum/go-ethereum/common" ) @@ -18,6 +20,15 @@ import ( // 4. CLP2P is restored, the verifier backfills and the unsafe gap is closed. func TestUnsafeGapFillAfterSafeReorg(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-30|22:17:00.549] + // assertions.go:387: Error Trace: /op-devstack/dsl/l2_el.go:204 + // assertions.go:387: /op-acceptance-tests/tests/sync/elsync/reorg/sync_test.go:78 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: operation failed permanently after 30 attempts: expected head to reorg 0xae5a516a6654d4ee6a2edfb9a8e2db12106991b1a29fbb3953dd5afb8a60914e:12, but got 0xae5a516a6654d4ee6a2edfb9a8e2db12106991b1a29fbb3953dd5afb8a60914e:12 + // assertions.go:387: Test: TestUnsafeGapFillAfterSafeReorg + sysgo.SkipOnKonaNode(t, "not supported (timeout)") sys := newReorgSystem(t) require := t.Require() logger := t.Logger() @@ -126,19 +137,7 @@ func TestUnsafeGapFillAfterSafeReorg(gt *testing.T) { // using RR Sync(soon be deprecated), or rely on EL Sync(desired) sys.L2CLB.ConnectPeer(sys.L2CL) sys.L2CL.ConnectPeer(sys.L2CLB) - - // Unsafe gap is closed - sys.L2ELB.Matched(sys.L2EL, types.LocalUnsafe, 50) - - seqUnsafe = sys.L2EL.BlockRefByLabel(eth.Unsafe) - verUnsafe = sys.L2ELB.BlockRefByLabel(eth.Unsafe) - logger.Info("Verifier unsafe gap closed", "gap", seqUnsafe.Number-verUnsafe.Number, "seqUnsafe", seqUnsafe.Number, "verUnsafe", verUnsafe.Number) - - gt.Cleanup(func() { - sys.L2CLB.Start() - sys.L2CLB.ConnectPeer(sys.L2CL) - sys.L2CL.ConnectPeer(sys.L2CLB) - }) + sys.L2ELB.InSync(sys.L2EL, safety.LocalUnsafe, 50) } // TestUnsafeGapFillAfterUnsafeReorg_RestartL2CL demonstrates the flow where: @@ -148,6 +147,24 @@ func TestUnsafeGapFillAfterSafeReorg(gt *testing.T) { // 4. Verifier then backfills and closes the unsafe gap once reconnected via CLP2P. func TestUnsafeGapFillAfterUnsafeReorg_RestartL2CL(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-30|22:17:07.231] + // assertions.go:387: Error Trace: /optimism/op-devstack/dsl/l2_el.go:204 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/elsync/reorg/sync_test.go:211 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: operation failed permanently after 30 attempts: expected head to reorg 0x893d77533b0ff9b37a92090679bf256d987b4535f06186ec71f29e68ddccd9a5:14, but got 0x893d77533b0ff9b37a92090679bf256d987b4535f06186ec71f29e68ddccd9a5:14 + // assertions.go:387: Test: TestUnsafeGapFillAfterUnsafeReorg_RestartL2CL + sysgo.SkipOnKonaNode(t, "not supported (timeout)") + // Example error with op-reth: + // + // assertions.go:387: + // Error Trace: /op-devstack/dsl/l2_el.go:430 + // /op-acceptance-tests/tests/sync/elsync/reorg/sync_test.go:218 + // Error: Received unexpected error: + // operation failed permanently after 50 attempts: expected head to match: unsafe + // Test: TestUnsafeGapFillAfterUnsafeReorg_RestartL2CL + sysgo.FlakyOnOpReth(t, "") sys := newReorgSystem(t) require := t.Require() logger := t.Logger() @@ -176,7 +193,7 @@ func TestUnsafeGapFillAfterUnsafeReorg_RestartL2CL(gt *testing.T) { return l2Unsafe.Number > 0 && l2Unsafe.L1Origin.Number > startL1Block.Number }, 120*time.Second, 2*time.Second) - sys.L2ELB.Matched(sys.L2EL, types.LocalUnsafe, 5) + sys.L2ELB.InSync(sys.L2EL, safety.LocalUnsafe, 30) // Pick reorg block l2BlockBeforeReorg := sys.L2EL.BlockRefByLabel(eth.Unsafe) @@ -184,7 +201,7 @@ func TestUnsafeGapFillAfterUnsafeReorg_RestartL2CL(gt *testing.T) { // Make few more unsafe blocks which will be reorged out sys.L2EL.Advanced(eth.Unsafe, 4) - sys.L2ELB.Matched(sys.L2EL, types.LocalUnsafe, 5) + sys.L2ELB.InSync(sys.L2EL, safety.LocalUnsafe, 30) // Stop Verifier CL sys.L2CLB.Stop() @@ -248,8 +265,8 @@ func TestUnsafeGapFillAfterUnsafeReorg_RestartL2CL(gt *testing.T) { // Unsafe gap will be observed by the L2CLB, and it will be smart enough to close the gap, // using RR Sync(soon be deprecated), or rely on EL Sync(desired) - // Unsafe gap is closed - sys.L2ELB.Matched(sys.L2EL, types.LocalUnsafe, 50) + // Verifier converged with sequencer's canonical unsafe chain + sys.L2ELB.InSync(sys.L2EL, safety.LocalUnsafe, 50) seqUnsafe = sys.L2EL.BlockRefByLabel(eth.Unsafe) verUnsafe = sys.L2ELB.BlockRefByLabel(eth.Unsafe) @@ -270,6 +287,15 @@ func TestUnsafeGapFillAfterUnsafeReorg_RestartL2CL(gt *testing.T) { // 4. CLP2P is restored Verifier, the verifier backfills and the unsafe gap is closed. func TestUnsafeGapFillAfterUnsafeReorg_RestartCLP2P(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|11:15:39.398] + // assertions.go:387: Error Trace: /optimism/op-devstack/dsl/l2_el.go:204 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/elsync/reorg/sync_test.go:356 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: operation failed permanently after 30 attempts: expected head to reorg 0x166970054ad16ad090210e5d1045538eeccd2afd88ea991b010de026d0106870:18, but got 0x166970054ad16ad090210e5d1045538eeccd2afd88ea991b010de026d0106870:18 + // assertions.go:387: Test: TestUnsafeGapFillAfterUnsafeReorg_RestartCLP2P + sysgo.SkipOnKonaNode(t, "not supported (timeout)") sys := newReorgSystem(t) require := t.Require() logger := t.Logger() @@ -298,7 +324,7 @@ func TestUnsafeGapFillAfterUnsafeReorg_RestartCLP2P(gt *testing.T) { return l2Unsafe.Number > 0 && l2Unsafe.L1Origin.Number > startL1Block.Number }, 120*time.Second, 2*time.Second) - sys.L2ELB.Matched(sys.L2EL, types.LocalUnsafe, 5) + sys.L2ELB.InSync(sys.L2EL, safety.LocalUnsafe, 5) // Pick reorg block l2BlockBeforeReorg := sys.L2EL.BlockRefByLabel(eth.Unsafe) @@ -306,7 +332,7 @@ func TestUnsafeGapFillAfterUnsafeReorg_RestartCLP2P(gt *testing.T) { // Make few more unsafe blocks which will be reorged out sys.L2EL.Advanced(eth.Unsafe, 4) - sys.L2ELB.Matched(sys.L2EL, types.LocalUnsafe, 5) + sys.L2ELB.InSync(sys.L2EL, safety.LocalUnsafe, 5) // Disconnect CLP2P sys.L2CLB.DisconnectPeer(sys.L2CL) @@ -357,7 +383,7 @@ func TestUnsafeGapFillAfterUnsafeReorg_RestartCLP2P(gt *testing.T) { logger.Info("Verifier diverged", "rewindTo", rewindTo) // Wait until verifier reset and dropped all reorg blocks - sys.L2CLB.Reset(types.LocalUnsafe, rewindTo) + sys.L2CLB.Reset(safety.LocalUnsafe, rewindTo) logger.Info("Verifier rewind done", "rewindTo", rewindTo) // Make sure CLP2P is connected @@ -368,8 +394,8 @@ func TestUnsafeGapFillAfterUnsafeReorg_RestartCLP2P(gt *testing.T) { // Unsafe gap will be observed by the L2CLB, and it will be smart enough to close the gap, // using RR Sync(soon be deprecated), or rely on EL Sync(desired) - // Unsafe gap is closed - sys.L2ELB.Matched(sys.L2EL, types.LocalUnsafe, 50) + // Verifier converged with sequencer's canonical unsafe chain + sys.L2ELB.InSync(sys.L2EL, safety.LocalUnsafe, 50) seqUnsafe := sys.L2EL.BlockRefByLabel(eth.Unsafe) verUnsafe = sys.L2ELB.BlockRefByLabel(eth.Unsafe) diff --git a/op-acceptance-tests/tests/sync/follow_l2/sync_test.go b/op-acceptance-tests/tests/sync/follow_l2/sync_test.go index f3e33c476d9..31096b81fcc 100644 --- a/op-acceptance-tests/tests/sync/follow_l2/sync_test.go +++ b/op-acceptance-tests/tests/sync/follow_l2/sync_test.go @@ -6,14 +6,29 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" "github.com/ethereum/go-ethereum/common" ) func TestFollowL2_Safe_Finalized_CurrentL1(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|11:33:11.255] + // assertions.go:387: Error Trace: /optimism/op-devstack/sysgo/singlechain_variants.go:143 + // assertions.go:387: /optimism/op-devstack/sysgo/singlechain_variants.go:53 + // assertions.go:387: /optimism/op-devstack/presets/singlechain_twoverifiers.go:24 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/follow_l2/setup_test.go:24 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/follow_l2/sync_test.go:18 + // assertions.go:387: Error: Should be true + // assertions.go:387: Test: TestFollowL2_Safe_Finalized_CurrentL1 + // assertions.go:387: Messages: single-chain test sequencer requires an op-node CL node + sysgo.SkipOnKonaNode(t, "not supported") + sysgo.FlakyOnOpReth(t, "timeouts in merge queue but not locally") sys := newSingleChainTwoVerifiersFollowL2(t) logger := t.Logger() @@ -25,7 +40,7 @@ func TestFollowL2_Safe_Finalized_CurrentL1(gt *testing.T) { // L2CLB is the verifier without follow source, derivation enabled // L2CLC is the verifier with CL follow source, derivation disabled // All verifiers must eventually advance unsafe, safe, finalized - checkMatchedAll := func(lvl types.SafetyLevel) { + checkMatchedAll := func(lvl safety.Level) { dsl.CheckAll(t, sys.L2CL.ReachedFn(lvl, target, attempts), sys.L2CLB.ReachedFn(lvl, target, attempts), @@ -37,13 +52,13 @@ func TestFollowL2_Safe_Finalized_CurrentL1(gt *testing.T) { ) } - checkMatchedAll(types.LocalUnsafe) + checkMatchedAll(safety.LocalUnsafe) logger.Info("Unsafe head advanced due to CLP2P", "target", target) - checkMatchedAll(types.LocalSafe) + checkMatchedAll(safety.LocalSafe) logger.Info("Safe head followed source", "target", target) - checkMatchedAll(types.Finalized) + checkMatchedAll(safety.Finalized) logger.Info("Finalized head followed source", "target", target) attempts = 10 @@ -56,6 +71,18 @@ func TestFollowL2_Safe_Finalized_CurrentL1(gt *testing.T) { func TestFollowL2_ReorgRecovery(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|11:31:11.567] + // assertions.go:387: Error Trace: /optimism/op-devstack/sysgo/singlechain_variants.go:143 + // assertions.go:387: /optimism/op-devstack/sysgo/singlechain_variants.go:53 + // assertions.go:387: /optimism/op-devstack/presets/singlechain_twoverifiers.go:24 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/follow_l2/setup_test.go:24 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/follow_l2/sync_test.go:60 + // assertions.go:387: Error: Should be true + // assertions.go:387: Test: TestFollowL2_ReorgRecovery + // assertions.go:387: Messages: single-chain test sequencer requires an op-node CL node + sysgo.SkipOnKonaNode(t, "not supported") sys := newSingleChainTwoVerifiersFollowL2(t) require := t.Require() logger := t.Logger() @@ -124,15 +151,27 @@ func TestFollowL2_ReorgRecovery(gt *testing.T) { attempts := 30 dsl.CheckAll(t, - sys.L2CL.MatchedFn(sys.L2CLB, types.LocalUnsafe, attempts), - sys.L2CLC.MatchedFn(sys.L2CLB, types.LocalUnsafe, attempts), - sys.L2CL.MatchedFn(sys.L2CLB, types.LocalSafe, attempts), - sys.L2CLC.MatchedFn(sys.L2CLB, types.LocalSafe, attempts), + sys.L2CL.InSyncFn(sys.L2CLB, safety.LocalUnsafe, attempts), + sys.L2CLC.InSyncFn(sys.L2CLB, safety.LocalUnsafe, attempts), + sys.L2CL.InSyncFn(sys.L2CLB, safety.LocalSafe, attempts), + sys.L2CLC.InSyncFn(sys.L2CLB, safety.LocalSafe, attempts), ) } func TestFollowL2_WithoutCLP2P(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with kona-node: + // + // assertions.go:387: ERROR[03-31|11:27:57.797] + // assertions.go:387: Error Trace: /optimism/op-devstack/sysgo/singlechain_variants.go:143 + // assertions.go:387: /optimism/op-devstack/sysgo/singlechain_variants.go:53 + // assertions.go:387: /optimism/op-devstack/presets/singlechain_twoverifiers.go:24 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/follow_l2/setup_test.go:24 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/follow_l2/sync_test.go:136 + // assertions.go:387: Error: Should be true + // assertions.go:387: Test: TestFollowL2_WithoutCLP2P + // assertions.go:387: Messages: single-chain test sequencer requires an op-node CL nod + sysgo.SkipOnKonaNode(t, "not supported") sys := newSingleChainTwoVerifiersFollowL2(t) require := t.Require() logger := t.Logger() @@ -141,7 +180,7 @@ func TestFollowL2_WithoutCLP2P(gt *testing.T) { target := uint64(3) // L2CLB is the verifier without follow source, derivation enabled - sys.L2CLB.Advanced(types.LocalUnsafe, target, attempts) + sys.L2CLB.Advanced(safety.LocalUnsafe, target, attempts) // The test's primary target is the L2CLC, with follow source and derivation disabled // There is often a gap between safe and unsafe before disconnect, but the @@ -159,24 +198,24 @@ func TestFollowL2_WithoutCLP2P(gt *testing.T) { sys.L2CL.DisconnectPeer(sys.L2CLC) // Advance few safe blocks - sys.L2CLC.Advanced(types.LocalSafe, target, attempts) - sys.L2CLC.Matched(sys.L2CLB, types.LocalSafe, attempts) + sys.L2CLC.Advanced(safety.LocalSafe, target, attempts) + sys.L2CLC.ReachedRef(safety.LocalSafe, sys.L2CLB.HeadBlockRef(safety.LocalSafe).ID(), attempts) // Make sure the safe head reaches non-moving unsafe head - sys.L2CLC.Reached(types.LocalSafe, sys.L2CLC.UnsafeHead().BlockRef.Number, attempts) + sys.L2CLC.Reached(safety.LocalSafe, sys.L2CLC.UnsafeHead().BlockRef.Number, attempts) // The only data source for L2CLC is the follow source. // L2CLC unsafe head will only be advancing with safe head together status = sys.L2CLC.SyncStatus() require.Equal(status.LocalSafeL2, status.UnsafeL2) - sys.L2CLC.Advanced(types.LocalSafe, target, attempts) + sys.L2CLC.Advanced(safety.LocalSafe, target, attempts) // Advance few safe blocks - sys.L2CLC.Advanced(types.LocalSafe, target, attempts) + sys.L2CLC.Advanced(safety.LocalSafe, target, attempts) // Check once again that the unsafe head is moving together with safe head status = sys.L2CLC.SyncStatus() require.Equal(status.LocalSafeL2, status.UnsafeL2) - sys.L2CLC.Advanced(types.LocalSafe, target, attempts) + sys.L2CLC.Advanced(safety.LocalSafe, target, attempts) // Recover CLP2P logger.Info("Recover CLP2P") @@ -187,12 +226,12 @@ func TestFollowL2_WithoutCLP2P(gt *testing.T) { // Sequencer unsafe payload will arrive to the verifier, triggering EL sync and filling in the unsafe gap dsl.CheckAll(t, - // Match with sequencer with derivation disabled - sys.L2CLC.MatchedFn(sys.L2CL, types.LocalSafe, attempts), - sys.L2CLC.MatchedFn(sys.L2CL, types.LocalUnsafe, attempts), - // Match with other verifier with derivation enabled - sys.L2CLC.MatchedFn(sys.L2CLB, types.LocalSafe, attempts), - sys.L2CLC.MatchedFn(sys.L2CLB, types.LocalUnsafe, attempts), + // In sync with sequencer, with derivation disabled + sys.L2CLC.InSyncFn(sys.L2CL, safety.LocalSafe, attempts), + sys.L2CLC.InSyncFn(sys.L2CL, safety.LocalUnsafe, attempts), + // In sync with other verifier, with derivation enabled + sys.L2CLC.InSyncFn(sys.L2CLB, safety.LocalSafe, attempts), + sys.L2CLC.InSyncFn(sys.L2CLB, safety.LocalUnsafe, attempts), ) t.Cleanup(func() { diff --git a/op-acceptance-tests/tests/sync/manual/sync_test.go b/op-acceptance-tests/tests/sync/manual/sync_test.go index 7b0bfdb9bf3..0c48a807b9d 100644 --- a/op-acceptance-tests/tests/sync/manual/sync_test.go +++ b/op-acceptance-tests/tests/sync/manual/sync_test.go @@ -8,12 +8,23 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum/go-ethereum" ) func TestVerifierManualSync(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error using op-reth: + // + // assertions.go:387: ERROR[03-31|10:02:09.740] + // assertions.go:387: Error Trace: /optimism/op-devstack/dsl/l2_el.go:64 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync/manual/sync_test.go:53 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: failed to determine block-hash of hash 0x2f6324eaff3942802eff5ddd47872544aa02182c222bf90042d5f715dfa72308, could not get payload: not found + // assertions.go:387: Test: TestVerifierManualSync + // assertions.go:387: Messages: block not found using block hash + sysgo.SkipOnOpReth(t, "not supported") // Disable ELP2P and Batcher sys := presets.NewSingleChainMultiNodeWithoutP2PWithoutCheck(t, @@ -26,7 +37,7 @@ func TestVerifierManualSync(gt *testing.T) { logger := t.Logger() delta := uint64(7) - sys.L2CL.Advanced(types.LocalUnsafe, delta, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, delta, 30) // Disable Derivation sys.L2CLB.Stop() diff --git a/op-acceptance-tests/tests/sync_tester/sync_tester_e2e/sync_tester_e2e_test.go b/op-acceptance-tests/tests/sync_tester/sync_tester_e2e/sync_tester_e2e_test.go index 954a6aece6f..206930da9db 100644 --- a/op-acceptance-tests/tests/sync_tester/sync_tester_e2e/sync_tester_e2e_test.go +++ b/op-acceptance-tests/tests/sync_tester/sync_tester_e2e/sync_tester_e2e_test.go @@ -7,7 +7,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestSyncTesterE2E(gt *testing.T) { @@ -47,8 +48,8 @@ func TestSyncTesterE2E(gt *testing.T) { target := uint64(5) dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalUnsafe, target, 30), - sys.L2CL2.AdvancedFn(types.LocalUnsafe, target, 30), + sys.L2CL.AdvancedFn(safety.LocalUnsafe, target, 30), + sys.L2CL2.AdvancedFn(safety.LocalUnsafe, target, 30), ) // Test that we can get chain ID from SyncTester diff --git a/op-acceptance-tests/tests/sync_tester/sync_tester_elsync/elsync_test.go b/op-acceptance-tests/tests/sync_tester/sync_tester_elsync/elsync_test.go index 7e337d6e9a7..6315adaee31 100644 --- a/op-acceptance-tests/tests/sync_tester/sync_tester_elsync/elsync_test.go +++ b/op-acceptance-tests/tests/sync_tester/sync_tester_elsync/elsync_test.go @@ -9,7 +9,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func simpleWithSyncTesterOpts() []presets.Option { @@ -33,8 +34,8 @@ func TestSyncTesterELSync(gt *testing.T) { startDelta := uint64(5) attempts := 30 dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalUnsafe, startDelta, attempts), - sys.L2CL2.AdvancedFn(types.LocalUnsafe, startDelta, attempts), + sys.L2CL.AdvancedFn(safety.LocalUnsafe, startDelta, attempts), + sys.L2CL2.AdvancedFn(safety.LocalUnsafe, startDelta, attempts), ) // Stop L2CL2 attached to Sync Tester EL Endpoint @@ -53,7 +54,7 @@ func TestSyncTesterELSync(gt *testing.T) { // Wait for L2CL to advance more unsafe blocks delta := uint64(5) - sys.L2CL.Advanced(types.LocalUnsafe, startDelta+delta, attempts) + sys.L2CL.Advanced(safety.LocalUnsafe, startDelta+delta, attempts) // EL Sync active session, err := syncTesterClient.GetSession(ctx) @@ -69,7 +70,7 @@ func TestSyncTesterELSync(gt *testing.T) { // Sequencer EL and SyncTester EL advances together target := sys.L2EL.BlockRefByLabel(eth.Unsafe).Number + 5 dsl.CheckAll(t, - sys.L2CL2.ReachedFn(types.LocalUnsafe, target, attempts), + sys.L2CL2.ReachedFn(safety.LocalUnsafe, target, attempts), // EL Sync complete sys.SyncTesterL2EL.ReachedFn(eth.Unsafe, target, attempts), ) diff --git a/op-acceptance-tests/tests/sync_tester/sync_tester_elsync_multi/sync_test.go b/op-acceptance-tests/tests/sync_tester/sync_tester_elsync_multi/sync_test.go index 8e9127f7467..414793b994c 100644 --- a/op-acceptance-tests/tests/sync_tester/sync_tester_elsync_multi/sync_test.go +++ b/op-acceptance-tests/tests/sync_tester/sync_tester_elsync_multi/sync_test.go @@ -9,7 +9,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func simpleWithSyncTesterOpts() []presets.Option { @@ -29,6 +30,7 @@ func simpleWithSyncTesterOpts() []presets.Option { func TestMultiELSync(gt *testing.T) { t := devtest.ParallelT(gt) + sysgo.FlakyOnKonaNode(t, "fails in ci but passes locally") sys := presets.NewSimpleWithSyncTester(t, simpleWithSyncTesterOpts()...) require := t.Require() @@ -36,7 +38,7 @@ func TestMultiELSync(gt *testing.T) { sys.L2CL2.Stop() // Advance few blocks to make sure reference node advanced - sys.L2CL.Advanced(types.LocalUnsafe, 10, 30) + sys.L2CL.Advanced(safety.LocalUnsafe, 10, 30) // Manually trigger multiple EL Syncs using the engine API startNum := sys.SyncTesterL2EL.BlockRefByLabel(eth.Unsafe).Number diff --git a/op-acceptance-tests/tests/sync_tester/sync_tester_hfs/sync_tester_hfs_test.go b/op-acceptance-tests/tests/sync_tester/sync_tester_hfs/sync_tester_hfs_test.go index 2264100c46c..43f6bdfe0e6 100644 --- a/op-acceptance-tests/tests/sync_tester/sync_tester_hfs/sync_tester_hfs_test.go +++ b/op-acceptance-tests/tests/sync_tester/sync_tester_hfs/sync_tester_hfs_test.go @@ -11,7 +11,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func ptrToUint64(v uint64) *uint64 { @@ -35,6 +36,15 @@ func simpleWithSyncTesterOpts() []presets.Option { func TestSyncTesterHardforks(gt *testing.T) { t := devtest.ParallelT(gt) + // Example error with op-reth: + // + // assertions.go:387: ERROR[03-31|10:08:04.055] + // assertions.go:387: Error Trace: /optimism/op-devstack/dsl/check.go:26 + // assertions.go:387: /optimism/op-acceptance-tests/tests/sync_tester/sync_tester_hfs/sync_tester_hfs_test.go:54 + // assertions.go:387: Error: Received unexpected error: + // assertions.go:387: operation failed permanently after 42 attempts: expected head to advance: unsafe + // assertions.go:387: Test: TestSyncTesterHardforks + sysgo.SkipOnOpReth(t, "not supported") sys := presets.NewSimpleWithSyncTester(t, simpleWithSyncTesterOpts()...) require := t.Require() @@ -51,11 +61,11 @@ func TestSyncTesterHardforks(gt *testing.T) { // Unsafe advancement: NewPayload -> ForkchoiceUpdated(no attr) dsl.CheckAll(t, - sys.L2CL.AdvancedFn(types.LocalUnsafe, uint64(targetNum), targetNum+10), - sys.L2CL2.AdvancedFn(types.LocalUnsafe, uint64(targetNum), targetNum+10), + sys.L2CL.AdvancedFn(safety.LocalUnsafe, uint64(targetNum), targetNum+10), + sys.L2CL2.AdvancedFn(safety.LocalUnsafe, uint64(targetNum), targetNum+10), ) - current := sys.L2CL2.HeadBlockRef(types.LocalUnsafe) + current := sys.L2CL2.HeadBlockRef(safety.LocalUnsafe) require.Greater(current.Time, *jovianTime, "must pass jovian block") // Check block hash state from L2CL2 which was synced using the sync tester require.Equal(sys.L2EL.BlockRefByNumber(current.Number).Hash, current.Hash, "hash mismatch") @@ -75,15 +85,15 @@ func TestSyncTesterHardforks(gt *testing.T) { sys.SyncTesterL2EL.UnsafeHead().NumEqualTo(0) // Wait until safe head reached Jovian - sys.L2CL.Reached(types.LocalSafe, current.Number, 20) + sys.L2CL.Reached(safety.LocalSafe, current.Number, 20) // Check safe head advancement can solely rely on derivation reaching Jovian // Safe advancement: ForkchoiceUpdated(with attr) -> GetPayload -> NewPayload -> ForkchoiceUpdated(no attr) sys.L2CL2.Start() - sys.L2CL2.Reached(types.LocalSafe, current.Number, 20) + sys.L2CL2.Reached(safety.LocalSafe, current.Number, 20) sys.SyncTesterL2EL.Reached(eth.Safe, current.Number, 10) - current = sys.L2CL2.HeadBlockRef(types.LocalSafe) + current = sys.L2CL2.HeadBlockRef(safety.LocalSafe) require.Greater(current.Time, *jovianTime, "must pass jovian block") // Check block hash state from L2CL2 which was synced using the sync tester require.Equal(sys.L2EL.BlockRefByNumber(current.Number).Hash, current.Hash, "hash mismatch") diff --git a/op-chain-ops/README.md b/op-chain-ops/README.md index f39b2cc5b6b..7ec2eaca8d3 100644 --- a/op-chain-ops/README.md +++ b/op-chain-ops/README.md @@ -30,7 +30,6 @@ Utils: cmd/ ├── check-canyon - Checks for Canyon network upgrade ├── check-delta - Checks for Delta network upgrade -├── check-deploy-config - Checks of the (legacy) Deploy Config ├── check-derivation - Check that transactions can be confirmed and safety can be consolidated ├── check-ecotone - Checks for Ecotone network upgrade ├── check-fjord - Checks for Fjord network upgrade @@ -38,7 +37,6 @@ cmd/ ├── deposit-hash - Determine the L2 deposit tx hash, based on log event(s) emitted by a L1 tx. ├── ecotone-scalar - Translate between serialized and human-readable L1 fee scalars (introduced in Ecotone upgrade). ├── op-simulate - Simulate a remote transaction in a local Geth EVM for block-processing debugging. -├── protocol-version - Translate between serialized and human-readable protocol versions. ├── receipt-reference-builder - Receipt data collector for pre-Canyon deposit-nonce metadata. └── unclaimed-credits - Utility to inspect credits of resolved fault-proof games. ``` diff --git a/op-chain-ops/addresses/contracts.go b/op-chain-ops/addresses/contracts.go index 88ab066a52a..f6df0aef721 100644 --- a/op-chain-ops/addresses/contracts.go +++ b/op-chain-ops/addresses/contracts.go @@ -19,27 +19,19 @@ type SuperchainContracts struct { SuperchainProxyAdminImpl common.Address SuperchainConfigProxy common.Address SuperchainConfigImpl common.Address - ProtocolVersionsProxy common.Address - ProtocolVersionsImpl common.Address } // ImplementationsContracts struct contains all the implementation contracts for a superchain // - these contracts are shared by all OpChains that are members of the same superchain // - these contracts are not upgradable, but can be replaced by new contract releases/deployments type ImplementationsContracts struct { - OpcmImpl common.Address - OpcmContractsContainerImpl common.Address - OpcmGameTypeAdderImpl common.Address - OpcmDeployerImpl common.Address - OpcmUpgraderImpl common.Address - OpcmInteropMigratorImpl common.Address OpcmStandardValidatorImpl common.Address OpcmUtilsImpl common.Address + OpcmMigratorImpl common.Address OpcmV2Impl common.Address OpcmContainerImpl common.Address DelayedWethImpl common.Address OptimismPortalImpl common.Address - OptimismPortalInteropImpl common.Address EthLockboxImpl common.Address PreimageOracleImpl common.Address MipsImpl common.Address @@ -50,9 +42,12 @@ type ImplementationsContracts struct { OptimismMintableErc20FactoryImpl common.Address DisputeGameFactoryImpl common.Address AnchorStateRegistryImpl common.Address - FaultDisputeGameV2Impl common.Address - PermissionedDisputeGameV2Impl common.Address + FaultDisputeGameImpl common.Address + PermissionedDisputeGameImpl common.Address + ZkDisputeGameImpl common.Address StorageSetterImpl common.Address + SuperFaultDisputeGameImpl common.Address + SuperPermissionedDisputeGameImpl common.Address } // OpChainContracts struct contains all the contracts for a specific L2 OpChain diff --git a/op-chain-ops/addresses/roles.go b/op-chain-ops/addresses/roles.go index c7c32d0b479..f24b66e35df 100644 --- a/op-chain-ops/addresses/roles.go +++ b/op-chain-ops/addresses/roles.go @@ -12,7 +12,6 @@ type L1Roles struct { type SuperchainRoles struct { SuperchainProxyAdminOwner common.Address SuperchainGuardian common.Address - ProtocolVersionsOwner common.Address Challenger common.Address } diff --git a/op-chain-ops/addresses/utils_test.go b/op-chain-ops/addresses/utils_test.go index 832ad08dedb..c9c853a48d7 100644 --- a/op-chain-ops/addresses/utils_test.go +++ b/op-chain-ops/addresses/utils_test.go @@ -12,7 +12,6 @@ func TestCheckNoZeroAddresses(t *testing.T) { roles := SuperchainRoles{ SuperchainProxyAdminOwner: common.HexToAddress("0x1111111111111111111111111111111111111111"), SuperchainGuardian: common.HexToAddress("0x2222222222222222222222222222222222222222"), - ProtocolVersionsOwner: common.HexToAddress("0x3333333333333333333333333333333333333333"), Challenger: common.HexToAddress("0x4444444444444444444444444444444444444444"), } @@ -23,7 +22,6 @@ func TestCheckNoZeroAddresses(t *testing.T) { t.Run("detects zero address", func(t *testing.T) { roles := SuperchainRoles{ SuperchainProxyAdminOwner: common.HexToAddress("0x1111111111111111111111111111111111111111"), - ProtocolVersionsOwner: common.HexToAddress("0x3333333333333333333333333333333333333333"), Challenger: common.HexToAddress("0x4444444444444444444444444444444444444444"), } @@ -37,11 +35,9 @@ func TestCheckNoZeroAddresses(t *testing.T) { t.Run("error for non-address fields", func(t *testing.T) { roles := struct { SuperchainProxyAdminOwner common.Address - ProtocolVersionsOwner common.Address chainId uint64 }{ SuperchainProxyAdminOwner: common.HexToAddress("0x1111111111111111111111111111111111111111"), - ProtocolVersionsOwner: common.HexToAddress("0x3333333333333333333333333333333333333333"), chainId: 1, } @@ -55,7 +51,6 @@ func TestCheckNoZeroAddresses(t *testing.T) { roles := SuperchainRoles{ SuperchainProxyAdminOwner: common.HexToAddress("0x1111111111111111111111111111111111111111"), SuperchainGuardian: common.HexToAddress("0x2222222222222222222222222222222222222222"), - ProtocolVersionsOwner: common.HexToAddress("0x3333333333333333333333333333333333333333"), Challenger: common.HexToAddress("0x4444444444444444444444444444444444444444"), } diff --git a/op-chain-ops/cmd/check-derivation/main.go b/op-chain-ops/cmd/check-derivation/main.go index 026e821c01e..38ff87aa559 100644 --- a/op-chain-ops/cmd/check-derivation/main.go +++ b/op-chain-ops/cmd/check-derivation/main.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/holiman/uint256" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -335,7 +336,7 @@ func confirmTransaction(ctx context.Context, ethClient *ethclient.Client, l2Bloc } block := eth.BlockID{ Hash: receipt.BlockHash, - Number: receipt.BlockNumber.Uint64(), + Number: bigs.Uint64Strict(receipt.BlockNumber), } log.Info("Transaction receipt found", "block", block, "status", receipt.Status) return block, nil @@ -365,7 +366,7 @@ func checkConsolidation(cliCtx *cli.Context) error { } l2ChainID := new(big.Int).SetUint64(cliCtx.Uint64("l2-chain-id")) l2BlockTime := uint64(2) - rollupCfg, err := rollup.LoadOPStackRollupConfig(l2ChainID.Uint64()) + rollupCfg, err := rollup.LoadOPStackRollupConfig(bigs.Uint64Strict(l2ChainID)) if err == nil { l2BlockTime = rollupCfg.BlockTime } else { diff --git a/op-chain-ops/cmd/check-ecotone/main.go b/op-chain-ops/cmd/check-ecotone/main.go index a1726d72c71..c81069921cf 100644 --- a/op-chain-ops/cmd/check-ecotone/main.go +++ b/op-chain-ops/cmd/check-ecotone/main.go @@ -30,6 +30,7 @@ import ( nbindings "github.com/ethereum-optimism/optimism/op-node/bindings" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" @@ -111,7 +112,7 @@ type actionEnv struct { func (ae *actionEnv) RecordGasUsed(rec *types.Receipt) { ae.gasUsed += rec.GasUsed - ae.l1GasUsed += rec.L1GasUsed.Uint64() + ae.l1GasUsed += bigs.Uint64Strict(rec.L1GasUsed) ae.log.Debug("Recorded tx receipt gas", "gas_used", rec.GasUsed, "l1_gas_used", rec.L1GasUsed) } diff --git a/op-chain-ops/cmd/check-fjord/checks/checks.go b/op-chain-ops/cmd/check-fjord/checks/checks.go index e24cfdada85..975e3be5a6b 100644 --- a/op-chain-ops/cmd/check-fjord/checks/checks.go +++ b/op-chain-ops/cmd/check-fjord/checks/checks.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/bigs" ) type CheckFjordConfig struct { @@ -38,7 +39,7 @@ type CheckFjordConfig struct { func (ae *CheckFjordConfig) RecordGasUsed(rec *types.Receipt) { ae.GasUsed += rec.GasUsed - ae.L1GasUsed += rec.L1GasUsed.Uint64() + ae.L1GasUsed += bigs.Uint64Strict(rec.L1GasUsed) ae.Log.Debug("Recorded tx receipt gas", "gas_used", rec.GasUsed, "l1_gas_used", rec.L1GasUsed) } @@ -183,7 +184,7 @@ func sendTxAndCheckFees(ctx context.Context, env *CheckFjordConfig, to *common.A return fmt.Errorf("calling GasPriceOracle.GetL1GasUsed: %w", err) } - env.Log.Info("retrieved L1 gas used", "gpoL1GasUsed", gpoL1GasUsed.Uint64()) + env.Log.Info("retrieved L1 gas used", "gpoL1GasUsed", bigs.Uint64Strict(gpoL1GasUsed)) // Check that GetL1Fee takes into account fast LZ gpoFee, err := gasPriceOracle.GetL1Fee(opts, txUnsigned) @@ -195,7 +196,7 @@ func sendTxAndCheckFees(ctx context.Context, env *CheckFjordConfig, to *common.A if err != nil { return fmt.Errorf("calculating GPO fjordL1Cost: %w", err) } - if gethGPOFee.Uint64() != gpoFee.Uint64() { + if !bigs.Equal(gethGPOFee, gpoFee) { return fmt.Errorf("gethGPOFee (%s) does not match gpoFee (%s)", gethGPOFee, gpoFee) } env.Log.Info("gethGPOFee matches gpoFee") @@ -204,7 +205,7 @@ func sendTxAndCheckFees(ctx context.Context, env *CheckFjordConfig, to *common.A if err != nil { return fmt.Errorf("calculating receipt fjordL1Cost: %w", err) } - if gethFee.Uint64() != tx.receipt.L1Fee.Uint64() { + if !bigs.Equal(gethFee, tx.receipt.L1Fee) { return fmt.Errorf("gethFee (%s) does not match receipt L1Fee (%s)", gethFee, tx.receipt.L1Fee) } env.Log.Info("gethFee matches receipt fee") @@ -221,7 +222,7 @@ func sendTxAndCheckFees(ctx context.Context, env *CheckFjordConfig, to *common.A if err != nil { return fmt.Errorf("failed to calculate fjordL1Cost: %w", err) } - if upperBoundCost.Uint64() != upperBound.Uint64() { + if !bigs.Equal(upperBoundCost, upperBound) { return fmt.Errorf("upperBound (%s) does not meet expectation (%s)", upperBound, upperBoundCost) } env.Log.Info("GPO upper bound matches") diff --git a/op-chain-ops/cmd/check-interop/run.sh b/op-chain-ops/cmd/check-interop/run.sh new file mode 100755 index 00000000000..ea4ddf03825 --- /dev/null +++ b/op-chain-ops/cmd/check-interop/run.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Run the op-up interop smoke bridge test against a devnet with two L2 chains. +# +# Usage: +# ./run.sh [loops] +# +# Example: +# ./run.sh ~/workspace/ethereum-optimism/devnets-private/dev/sdg-v1 abc123...def 10 + +set -euo pipefail + +DEVNET_DIR="${1:?Usage: $0 [loops]}" +PRIVATE_KEY="${2:?Usage: $0 [loops]}" +LOOPS="${3:-10}" + +MANIFEST="$DEVNET_DIR/manifest.yaml" +if [[ ! -f "$MANIFEST" ]]; then + echo "Error: manifest.yaml not found at $MANIFEST" >&2 + exit 1 +fi + +# Parse the first two chain names from manifest.yaml. +mapfile -t CHAINS < <(grep '^\s*- name:' "$MANIFEST" | head -2 | awk '{print $3}') + +if [[ ${#CHAINS[@]} -lt 2 ]]; then + echo "Error: expected at least 2 chains in $MANIFEST, found ${#CHAINS[@]}" >&2 + exit 1 +fi + +# Construct RPC URLs from chain names. +RPC_BASE="us.networks.ent.dev.oplabs.cloud" +SOURCE_RPC="https://an-${CHAINS[0]}-opn-reth-a-rpc-0-op-reth.${RPC_BASE}" +DEST_RPC="https://an-${CHAINS[1]}-opn-reth-a-rpc-0-op-reth.${RPC_BASE}" + +echo "Devnet: $(basename "$DEVNET_DIR")" +echo "Chain A: ${CHAINS[0]} -> $SOURCE_RPC" +echo "Chain B: ${CHAINS[1]} -> $DEST_RPC" +if ! [[ "$LOOPS" =~ ^[0-9]+$ ]]; then + echo "Error: loops must be a non-negative integer, got $LOOPS" >&2 + exit 1 +fi + +TRIPS=$(( LOOPS == 0 ? 1 : LOOPS * 2 )) + +echo "Loops: $LOOPS ($TRIPS bridge trips)" +echo "" + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +BUILD_DIR="$(mktemp -d)" +trap 'rm -rf "$BUILD_DIR"' EXIT + +go build -o "$BUILD_DIR/op-up" "$REPO_ROOT/op-up" + +for ((trip = 1; trip <= TRIPS; trip++)); do + if (( trip % 2 == 1 )); then + L2A_RPC="$SOURCE_RPC" + L2B_RPC="$DEST_RPC" + else + L2A_RPC="$DEST_RPC" + L2B_RPC="$SOURCE_RPC" + fi + + echo "Bridge trip $trip/$TRIPS" + "$BUILD_DIR/op-up" smoke-interop bridge \ + --l2a-rpc "$L2A_RPC" \ + --l2b-rpc "$L2B_RPC" \ + --private-key "$PRIVATE_KEY" +done diff --git a/op-chain-ops/cmd/check-jovian/main.go b/op-chain-ops/cmd/check-jovian/main.go index 211f0ecd478..23ae5c79c80 100644 --- a/op-chain-ops/cmd/check-jovian/main.go +++ b/op-chain-ops/cmd/check-jovian/main.go @@ -17,7 +17,6 @@ import ( "github.com/ethereum-optimism/optimism/op-core/predeploys" "github.com/ethereum-optimism/optimism/op-e2e/bindings" - "github.com/ethereum-optimism/optimism/op-node/rollup" op_service "github.com/ethereum-optimism/optimism/op-service" "github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" @@ -190,7 +189,7 @@ func checkBlock(ctx context.Context, env *actionEnv) error { return fmt.Errorf("tx mined receipt was nil") } - env.log.Info("tx mined", "txHash", receipt.TxHash.Hex(), "blockNumber", receipt.BlockNumber.Uint64(), "blobGasUsed", receipt.BlobGasUsed) + env.log.Info("tx mined", "txHash", receipt.TxHash.Hex(), "blockNumber", receipt.BlockNumber, "blobGasUsed", receipt.BlobGasUsed) if receipt.BlobGasUsed == 0 { return fmt.Errorf("receipt.BlobGasUsed was zero (required with Jovian)") @@ -254,7 +253,7 @@ func checkExtraData(ctx context.Context, env *actionEnv) error { } // Decode the validated extra data using op-geth's decode function - denominator, elasticity, minBaseFee := rollup.DecodeJovianExtraData(extra) + denominator, elasticity, minBaseFee := eip1559.DecodeJovianExtraData(extra) env.log.Info("ExtraData format test: success", "blockNumber", latest.Number, diff --git a/op-chain-ops/cmd/check-karst/karsttest/checks.go b/op-chain-ops/cmd/check-karst/karsttest/checks.go new file mode 100644 index 00000000000..bd68f3058dd --- /dev/null +++ b/op-chain-ops/cmd/check-karst/karsttest/checks.go @@ -0,0 +1,541 @@ +// Package karsttest implements the post-Karst conformance checks shared between +// the op-acceptance-tests acceptance suite and the check-karst CLI. Each +// Check{EIPName} function sends the relevant transactions, asserts the expected +// receipt status, and returns the L2 block range exercised so callers can run +// kona-host cross-checks (acceptance tests) or simply verify the checks ran +// (CLI). +package karsttest + +import ( + "bytes" + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" + "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" + "github.com/ethereum-optimism/optimism/op-service/txplan" +) + +// CLZBytecode is EVM init code that computes CLZ(1) and returns the 32-byte +// result. CLZ(1) = 255 because 1 has 255 leading zero bits in a uint256. Used +// by both CheckEIP7939 (post-Karst, where deployment must succeed) and the +// pre-Karst acceptance sub-test (where deployment must fail because the CLZ +// opcode is invalid). +var CLZBytecode = []byte{ + byte(vm.PUSH1), 1, // stack: [1] + byte(vm.CLZ), // stack: [255] (1 has 255 leading zeros) + byte(vm.PUSH1), 0, // stack: [0, 255] + byte(vm.MSTORE), // mem[0:32] = 255 + byte(vm.PUSH1), 32, // stack: [32] + byte(vm.PUSH1), 0, // stack: [0, 32] + byte(vm.RETURN), // return mem[0:32] +} + +// Precompile addresses referenced by post-Karst checks. +var ( + ModExpPrecompile = common.HexToAddress("0x0000000000000000000000000000000000000005") + Bn256PairPrecompile = common.HexToAddress("0x0000000000000000000000000000000000000008") + P256VerifyPrecompile = common.HexToAddress("0x0000000000000000000000000000000000000100") +) + +// Per-EIP probe parameters shared between the pre-Karst acceptance sub-tests +// and the post-Karst Check{EIP} functions. Each value is the exact input that +// makes the pre-/post-Karst behaviors diverge — pre-Karst the call succeeds, +// post-Karst it reverts — so the pre-/post-Karst symmetry can never silently +// drift if a floor moves on one side. +const ( + // EIP7883BoundaryGas is the MODEXP probe gas limit: 21,000 intrinsic + 300 + // execution. Empty calldata avoids EIP-7623 calldata cost inflation, so + // intrinsic gas is exactly 21,000 and the 300-gas execution budget covers + // EIP-2565's 200-gas floor (pre-Karst) but not EIP-7883's 500-gas floor + // (post-Karst). + EIP7883BoundaryGas = 21_300 + + // EIP7951BoundaryGas is the P256VERIFY probe gas limit: 21,000 intrinsic + + // 3,500 execution. Empty calldata avoids EIP-7623 calldata cost inflation, + // so intrinsic gas is exactly 21,000 and the 3,500-gas execution budget + // covers RIP-7212's 3,450-gas cost (pre-Karst) but not EIP-7951's + // 6,900-gas cost (post-Karst). + EIP7951BoundaryGas = 24_500 + + // EIP7823OversizedGasLimit is enough gas to fully process a MODEXP call + // with the oversized modulus produced by NewEIP7823OversizedModExpInput. + EIP7823OversizedGasLimit = 2_000_000 + + // Bn256PairElementLen is the byte length of one (G1, G2) pair fed to the + // bn256 pairing precompile: 64 bytes for the G1 point + 128 bytes for the G2 + // point. + Bn256PairElementLen = 192 + + // KarstBn256PairMaxInputSize is the post-Karst max input size for the bn256 + // pairing precompile in bytes: 300 pairs × 192 bytes/pair. Down from Jovian's + // 81,984 bytes (427 pairs). The same curve is variously called bn128, bn254, + // or bn256 across the codebase; this matches `BN256_MAX_PAIRING_SIZE_KARST` + // in kona's FPVM module and `bn254_pair::KARST_MAX_INPUT_SIZE` in op-revm. + KarstBn256PairMaxInputSize = 300 * Bn256PairElementLen + + // KarstBn256PairProbeGasLimit is the tx gas limit used by every bn256 + // pairing probe (pre-Karst 301-pair success, post-Karst 301-pair length + // halt, and post-Karst 300-pair success). It must be high enough to fully + // execute 301 pairs pre-Karst — 301 × 34,000 + 45,000 = 10,279,000 + // precompile gas plus calldata + intrinsic — otherwise an OOG-revert would + // masquerade as the post-Karst length halt and the post-Karst check would + // pass against a pre-Karst chain. 12M leaves headroom and stays under the + // post-Karst EIP-7825 tx-gas cap of 2^24 = 16,777,216. + KarstBn256PairProbeGasLimit = 12_000_000 +) + +// NewEIP7823OversizedModExpInput returns MODEXP input whose declared modulus +// is 1025 bytes — one byte over the EIP-7823 1024-byte cap. The high byte is +// non-zero so the modulus parses as a non-zero value. Pre-Karst the call +// succeeds; post-Karst it reverts. +func NewEIP7823OversizedModExpInput() []byte { + const oversizeModSize = 1025 + mod := make([]byte, oversizeModSize) + mod[oversizeModSize-1] = 5 + return BuildModExpInput([]byte{2}, []byte{3}, mod) +} + +// NewBasePlan returns a txplan.Option. Each per-tx Check{EIPName} composes its +// own options on top of this base plan; gas-limit overrides via txplan.WithGasLimit +// reset the estimator, so the same base plan handles both reverting and successful txs. +func NewBasePlan(cl *ethclient.Client, key *ecdsa.PrivateKey) txplan.Option { + return txplan.Combine( + txplan.WithChainID(cl), + txplan.WithPrivateKey(key), + txplan.WithPendingNonce(cl), + txplan.WithAgainstLatestBlockEthClient(cl), + txplan.WithEstimator(cl, true), + txplan.WithRetrySubmission(cl, 5, retry.Exponential()), + txplan.WithRetryInclusion(cl, 5, retry.Exponential()), + ) +} + +// BuildModExpInput constructs input data for the MODEXP precompile +// (address 0x05). Format: +// +// +func BuildModExpInput(base, exp, mod []byte) []byte { + input := make([]byte, 0, 96+len(base)+len(exp)+len(mod)) + input = append(input, common.LeftPadBytes(new(big.Int).SetInt64(int64(len(base))).Bytes(), 32)...) + input = append(input, common.LeftPadBytes(new(big.Int).SetInt64(int64(len(exp))).Bytes(), 32)...) + input = append(input, common.LeftPadBytes(new(big.Int).SetInt64(int64(len(mod))).Bytes(), 32)...) + input = append(input, base...) + input = append(input, exp...) + input = append(input, mod...) + return input +} + +// CheckEIP7823 verifies the post-Karst MODEXP upper-bound rule: a MODEXP call +// whose declared input lengths exceed 1024 bytes is included on-chain but +// reverts, while a within-limit call still succeeds. It returns the block numbers +// where its two transactions landed (smaller number first). +func CheckEIP7823(ctx context.Context, logger log.Logger, basePlan txplan.Option) (uint64, uint64, error) { + logger.Info("EIP-7823: oversized MODEXP call must revert") + overReceipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&ModExpPrecompile), + txplan.WithData(NewEIP7823OversizedModExpInput()), + txplan.WithGasLimit(EIP7823OversizedGasLimit), + ).Included.Eval(ctx) + if err != nil { + return 0, 0, fmt.Errorf("oversized MODEXP submission: %w", err) + } + if overReceipt.Status != types.ReceiptStatusFailed { + return 0, 0, fmt.Errorf("oversized MODEXP: expected revert, got success (block=%v, tx=%s)", + overReceipt.BlockNumber, overReceipt.TxHash) + } + logger.Info("EIP-7823: oversized MODEXP reverted as expected", "block", overReceipt.BlockNumber, "tx", overReceipt.TxHash) + + logger.Info("EIP-7823: within-limit MODEXP call must succeed") + okReceipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&ModExpPrecompile), + txplan.WithData(BuildModExpInput([]byte{2}, []byte{3}, []byte{5})), + txplan.WithGasLimit(200_000), + ).Included.Eval(ctx) + if err != nil { + return 0, 0, fmt.Errorf("within-limit MODEXP submission: %w", err) + } + if okReceipt.Status != types.ReceiptStatusSuccessful { + return 0, 0, fmt.Errorf("within-limit MODEXP: expected success, got revert (block=%v, tx=%s)", + okReceipt.BlockNumber, okReceipt.TxHash) + } + logger.Info("EIP-7823: within-limit MODEXP succeeded", "block", okReceipt.BlockNumber, "tx", okReceipt.TxHash) + + return bigs.Uint64Strict(overReceipt.BlockNumber), bigs.Uint64Strict(okReceipt.BlockNumber), nil +} + +// CheckEIP7883 verifies the post-Karst MODEXP gas-cost increase: an empty-input +// MODEXP call landing exactly on the gas floor (21,000 intrinsic + 300 execution +// gas) reverts under EIP-7883's 500-gas floor where it would have succeeded +// against EIP-2565's 200-gas floor, and a within-floor call (21,000 + 600) +// succeeds. Empty calldata avoids EIP-7623 calldata cost inflation, so intrinsic +// gas is exactly 21,000 and tx gas limit minus 21,000 is the execution budget. +// It returns the block numbers where its two transactions landed (smaller +// number first). +func CheckEIP7883(ctx context.Context, logger log.Logger, basePlan txplan.Option) (uint64, uint64, error) { + logger.Info("EIP-7883: under-gas MODEXP call must OOG-revert against the 500-gas floor") + underGasReceipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&ModExpPrecompile), + txplan.WithGasLimit(EIP7883BoundaryGas), + ).Included.Eval(ctx) + if err != nil { + return 0, 0, fmt.Errorf("under-gas MODEXP submission: %w", err) + } + if underGasReceipt.Status != types.ReceiptStatusFailed { + return 0, 0, fmt.Errorf("under-gas MODEXP: expected revert, got success (block=%v, tx=%s)", + underGasReceipt.BlockNumber, underGasReceipt.TxHash) + } + logger.Info("EIP-7883: under-gas MODEXP reverted as expected", "block", underGasReceipt.BlockNumber, "tx", underGasReceipt.TxHash) + + logger.Info("EIP-7883: within-floor MODEXP call must succeed") + sufficientReceipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&ModExpPrecompile), + txplan.WithGasLimit(21_600), + ).Included.Eval(ctx) + if err != nil { + return 0, 0, fmt.Errorf("within-floor MODEXP submission: %w", err) + } + if sufficientReceipt.Status != types.ReceiptStatusSuccessful { + return 0, 0, fmt.Errorf("within-floor MODEXP: expected success, got revert (block=%v, tx=%s)", + sufficientReceipt.BlockNumber, sufficientReceipt.TxHash) + } + logger.Info("EIP-7883: within-floor MODEXP succeeded", "block", sufficientReceipt.BlockNumber, "tx", sufficientReceipt.TxHash) + + return bigs.Uint64Strict(underGasReceipt.BlockNumber), bigs.Uint64Strict(sufficientReceipt.BlockNumber), nil +} + +// CheckEIP7951 verifies the post-Karst P256VERIFY gas-cost increase: an +// empty-input call landing exactly on the pre-Karst gas budget (21,000 +// intrinsic + 3,500 execution gas) reverts under EIP-7951's 6,900-gas cost +// where it would have succeeded against RIP-7212's 3,450-gas cost, and a +// within-cost call (21,000 + 7,000) succeeds. The precompile returns empty +// for non-160-byte input but charges its full cost regardless. Empty calldata +// avoids EIP-7623 calldata cost inflation, so intrinsic gas is exactly 21,000 +// and tx gas limit minus 21,000 is the execution budget. It returns the block +// numbers where its two transactions landed (smaller number first). +func CheckEIP7951(ctx context.Context, logger log.Logger, basePlan txplan.Option) (uint64, uint64, error) { + logger.Info("EIP-7951: under-gas P256VERIFY call must OOG-revert against the 6,900-gas cost") + underGasReceipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&P256VerifyPrecompile), + txplan.WithGasLimit(EIP7951BoundaryGas), + ).Included.Eval(ctx) + if err != nil { + return 0, 0, fmt.Errorf("under-gas P256VERIFY submission: %w", err) + } + if underGasReceipt.Status != types.ReceiptStatusFailed { + return 0, 0, fmt.Errorf("under-gas P256VERIFY: expected revert, got success (block=%v, tx=%s)", + underGasReceipt.BlockNumber, underGasReceipt.TxHash) + } + logger.Info("EIP-7951: under-gas P256VERIFY reverted as expected", "block", underGasReceipt.BlockNumber, "tx", underGasReceipt.TxHash) + + logger.Info("EIP-7951: within-cost P256VERIFY call must succeed") + sufficientReceipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&P256VerifyPrecompile), + txplan.WithGasLimit(28_000), + ).Included.Eval(ctx) + if err != nil { + return 0, 0, fmt.Errorf("within-cost P256VERIFY submission: %w", err) + } + if sufficientReceipt.Status != types.ReceiptStatusSuccessful { + return 0, 0, fmt.Errorf("within-cost P256VERIFY: expected success, got revert (block=%v, tx=%s)", + sufficientReceipt.BlockNumber, sufficientReceipt.TxHash) + } + logger.Info("EIP-7951: within-cost P256VERIFY succeeded", "block", sufficientReceipt.BlockNumber, "tx", sufficientReceipt.TxHash) + + return bigs.Uint64Strict(underGasReceipt.BlockNumber), bigs.Uint64Strict(sufficientReceipt.BlockNumber), nil +} + +// CheckKarstBn256PairInputLimit verifies the post-Karst bn256 pairing +// precompile input-size cap of 57,600 bytes (300 pairs × 192). A 301-pair +// (57,792-byte) call halts the precompile with Bn254PairLength — consuming +// all tx gas and surfacing as a failed receipt — while a 300-pair within- +// limit call succeeds. Both inputs are all zeros; per EIP-197, (0,0) decodes +// as the G1/G2 point at infinity, and pairing identity pairs yields 1 +// (the identity element of F_p12), so the precompile returns the 32-byte +// little-endian 1 for the within-limit call. Returns the block numbers +// where its two transactions landed (smaller number first). +func CheckKarstBn256PairInputLimit(ctx context.Context, logger log.Logger, basePlan txplan.Option) (uint64, uint64, error) { + logger.Info("Karst bn256 pair: over-limit (301-pair) call must revert") + overInput := make([]byte, KarstBn256PairMaxInputSize+Bn256PairElementLen) // 301 pairs × 192 + overReceipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&Bn256PairPrecompile), + txplan.WithData(overInput), + txplan.WithGasLimit(KarstBn256PairProbeGasLimit), + ).Included.Eval(ctx) + if err != nil { + return 0, 0, fmt.Errorf("over-limit bn256 pair submission: %w", err) + } + if overReceipt.Status != types.ReceiptStatusFailed { + return 0, 0, fmt.Errorf("over-limit bn256 pair: expected revert, got success (block=%v, tx=%s)", + overReceipt.BlockNumber, overReceipt.TxHash) + } + logger.Info("Karst bn256 pair: over-limit call reverted as expected", + "block", overReceipt.BlockNumber, "tx", overReceipt.TxHash) + + logger.Info("Karst bn256 pair: within-limit (300-pair) call must succeed") + okInput := make([]byte, KarstBn256PairMaxInputSize) // 300 pairs × 192 + okReceipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&Bn256PairPrecompile), + txplan.WithData(okInput), + txplan.WithGasLimit(KarstBn256PairProbeGasLimit), + ).Included.Eval(ctx) + if err != nil { + return 0, 0, fmt.Errorf("within-limit bn256 pair submission: %w", err) + } + if okReceipt.Status != types.ReceiptStatusSuccessful { + return 0, 0, fmt.Errorf("within-limit bn256 pair: expected success, got revert (block=%v, tx=%s)", + okReceipt.BlockNumber, okReceipt.TxHash) + } + logger.Info("Karst bn256 pair: within-limit call succeeded", + "block", okReceipt.BlockNumber, "tx", okReceipt.TxHash) + + return bigs.Uint64Strict(overReceipt.BlockNumber), bigs.Uint64Strict(okReceipt.BlockNumber), nil +} + +// CheckEIP7939 verifies the post-Karst CLZ opcode (0x1e). It deploys a contract +// whose init code computes CLZ(1) = 255 and returns the 32-byte result. Pre-Karst +// the opcode is invalid and the init code aborts; post-Karst it executes and +// the deployed code is the 32-byte left-padded CLZ(1) value. Returns the block +// number where the deployment landed. +func CheckEIP7939(ctx context.Context, logger log.Logger, l2 apis.EthCode, basePlan txplan.Option) (uint64, error) { + logger.Info("EIP-7939: CLZ contract deployment must succeed") + receipt, err := txplan.NewPlannedTx(basePlan, + txplan.WithData(CLZBytecode), + txplan.WithGasLimit(100_000), + ).Included.Eval(ctx) + if err != nil { + return 0, fmt.Errorf("CLZ deploy submission: %w", err) + } + if receipt.Status != types.ReceiptStatusSuccessful { + return 0, fmt.Errorf("CLZ deploy: expected success, got revert (block=%v, tx=%s)", + receipt.BlockNumber, receipt.TxHash) + } + logger.Info("EIP-7939: CLZ deployment succeeded", "block", receipt.BlockNumber, "tx", receipt.TxHash, "addr", receipt.ContractAddress) + + // The deployed code IS the 32-byte CLZ(1) result. + deployedCode, err := l2.CodeAtHash(ctx, receipt.ContractAddress, receipt.BlockHash) + if err != nil { + return 0, fmt.Errorf("CLZ deployed code lookup: %w", err) + } + expected := common.LeftPadBytes([]byte{0xff}, 32) + if !bytes.Equal(deployedCode, expected) { + return 0, fmt.Errorf("CLZ(1) deployed code mismatch: expected %x, got %x", expected, deployedCode) + } + logger.Info("EIP-7939: CLZ(1) = 255 verified via deployed code") + return bigs.Uint64Strict(receipt.BlockNumber), nil +} + +// CheckEIP7825 verifies the post-Karst transaction-gas-limit cap of 2^24: +// op-reth's RPC must reject a tx whose gas limit exceeds the cap at submission +// time, so the tx never lands on chain. Returns no block range because no tx +// is included — the cap is a tx-validity rule, not an EVM rule, so there is +// nothing for kona-host to cross-check. +func CheckEIP7825(ctx context.Context, logger log.Logger, basePlan txplan.Option) error { + logger.Info("EIP-7825: tx with gas > 2^24 must be rejected at submission") + _, err := txplan.NewPlannedTx(basePlan, + txplan.WithTo(&common.Address{}), + txplan.WithGasLimit(params.MaxTxGas+1), + ).Included.Eval(ctx) + if err == nil { + return fmt.Errorf("expected rejection for gas > 2^24, got success") + } + logger.Info("EIP-7825: high-gas tx rejected as expected", "err", err) + return nil +} + +// CheckEIP7825DepositBypass verifies that deposit transactions bypass the +// post-Karst EIP-7825 2^24 gas cap. It submits an L1 deposit with a gas limit +// above the cap, finds the resulting `TransactionDeposited` event, waits for +// the L2 deposit receipt, and asserts the L2 inclusion succeeded with the +// requested gas. Deposits are forced onto L2 by the derivation pipeline rather +// than passing through the txpool; if the cap (a tx-validity rule) applied to +// them, an attacker could trivially brick the rollup. Returns the L2 block +// where the deposit landed. +func CheckEIP7825DepositBypass( + ctx context.Context, + logger log.Logger, + l2 apis.ReceiptFetcher, + portalAddr, l1Sender common.Address, + l1Plan txplan.Option, + depositAmount eth.ETH, +) (uint64, error) { + depositGasLimit := params.MaxTxGas + 1 + + portal := bindings.NewBindings[bindings.OptimismPortal2]( + bindings.WithTo(portalAddr), + ) + callPlan, err := contractio.Plan( + portal.DepositTransaction(l1Sender, depositAmount, depositGasLimit, false, []byte{}), + ) + if err != nil { + return 0, fmt.Errorf("plan deposit call: %w", err) + } + + logger.Info("EIP-7825-deposit: submitting high-gas deposit on L1", + "gas", depositGasLimit, "amount", depositAmount, "portal", portalAddr) + // Skip eth_estimateGas: the estimator caps its binary search at MaxTxGas, + // but ResourceMetering's Burn.gas inside depositTransaction needs to burn + // ~depositGasLimit gas on L1. WithGasLimit overrides the estimator. + l1Receipt, err := txplan.NewPlannedTx(l1Plan, callPlan, + txplan.WithValue(depositAmount), + txplan.WithGasLimit(depositGasLimit+1_000_000), + ).Included.Eval(ctx) + if err != nil { + return 0, fmt.Errorf("L1 deposit submission: %w", err) + } + if l1Receipt.Status != types.ReceiptStatusSuccessful { + return 0, fmt.Errorf("L1 deposit failed: status=%d, tx=%s", l1Receipt.Status, l1Receipt.TxHash) + } + logger.Info("EIP-7825-deposit: L1 deposit included", "block", l1Receipt.BlockNumber, "tx", l1Receipt.TxHash) + + var l2DepositTx *types.DepositTx + for _, log := range l1Receipt.Logs { + var unmarshalErr error + if l2DepositTx, unmarshalErr = derive.UnmarshalDepositLogEvent(log); unmarshalErr == nil { + break + } + } + if l2DepositTx == nil { + return 0, fmt.Errorf("no TransactionDeposited event in L1 receipt: tx=%s", l1Receipt.TxHash) + } + if l2DepositTx.Gas != depositGasLimit { + return 0, fmt.Errorf("L2 deposit tx gas: got %d, want %d", l2DepositTx.Gas, depositGasLimit) + } + + l2DepositHash := types.NewTx(l2DepositTx).Hash() + logger.Info("EIP-7825-deposit: waiting for L2 deposit receipt", "tx", l2DepositHash) + var l2Receipt *types.Receipt + for { + var err error + l2Receipt, err = l2.TransactionReceipt(ctx, l2DepositHash) + if err == nil && l2Receipt != nil { + break + } + logger.Info("Could not find transaction receipt", "err", err) + select { + case <-ctx.Done(): + return 0, ctx.Err() + case <-time.After(2 * time.Second): + } + } + if l2Receipt.Status != types.ReceiptStatusSuccessful { + return 0, fmt.Errorf("L2 deposit reverted: tx=%s, block=%v", l2DepositHash, l2Receipt.BlockNumber) + } + logger.Info("EIP-7825-deposit: L2 deposit included successfully", + "block", l2Receipt.BlockNumber, "tx", l2DepositHash) + + return bigs.Uint64Strict(l2Receipt.BlockNumber), nil +} + +// LatestBlockFetcher fetches the latest block's info and transactions. It is +// the minimum-surface interface satisfied by `apis.EthClient` (the acceptance +// test passes one directly) and by a thin adapter the CLI builds around +// `*ethclient.Client`. +type LatestBlockFetcher interface { + InfoAndTxsByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, types.Transactions, error) +} + +// CheckEIP7934BlockSizeDisabled verifies that the OP Stack disables the +// EIP-7934 max-block-size limit (8 MiB) by polling the unsafe head until it +// finds a block whose total transaction-data size exceeds `params.MaxBlockSize`. +// Tx data size is a strict lower bound on RLP-encoded block size, so observing +// txData > MaxBlockSize proves the block exceeds the limit. The check is +// contingent on chain traffic — on a quiet chain it will block forever, so +// callers must bound the wait via `ctx`. Returns the L2 block number where +// the oversized block was observed. +func CheckEIP7934BlockSizeDisabled( + ctx context.Context, + logger log.Logger, + l2 LatestBlockFetcher, + blockTime time.Duration, +) error { + for { + info, txs, err := l2.InfoAndTxsByLabel(ctx, eth.Unsafe) + if err != nil { + logger.Warn("EIP-7934: failed to fetch unsafe head, retrying", "err", err) + } else { + var totalTxSize int + for _, tx := range txs { + bin, marshalErr := tx.MarshalBinary() + if marshalErr != nil { + return fmt.Errorf("marshal tx %s: %w", tx.Hash(), marshalErr) + } + totalTxSize += len(bin) + } + logger.Info("EIP-7934: checking L2 block", + "number", info.NumberU64(), "txDataSize", totalTxSize, "gasUsed", info.GasUsed()) + if totalTxSize > params.MaxBlockSize { + logger.Info("EIP-7934: observed oversized block — block-size limit is disabled", + "number", info.NumberU64(), "txDataSize", totalTxSize, "limit", params.MaxBlockSize) + return nil + } + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(blockTime): + } + } +} + +type L2 interface { + apis.EthCode + apis.ReceiptFetcher + InfoAndTxsByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, types.Transactions, error) +} + +// CheckAll runs every implemented post-Karst check in sequence. It is intended +// for the CLI; the acceptance test invokes individual Check functions per +// sub-test so each can run in parallel and gate its own kona-host cross-check. +func CheckAll( + ctx context.Context, + logger log.Logger, + l2 L2, + basePlan txplan.Option, + portalAddr, l1Sender common.Address, + l1Plan txplan.Option, + depositAmount eth.ETH, +) error { + logger.Info("starting Karst checks") + if _, _, err := CheckEIP7823(ctx, logger, basePlan); err != nil { + return fmt.Errorf("EIP-7823: %w", err) + } + if _, _, err := CheckEIP7883(ctx, logger, basePlan); err != nil { + return fmt.Errorf("EIP-7883: %w", err) + } + if _, _, err := CheckEIP7951(ctx, logger, basePlan); err != nil { + return fmt.Errorf("EIP-7951: %w", err) + } + if _, _, err := CheckKarstBn256PairInputLimit(ctx, logger, basePlan); err != nil { + return fmt.Errorf("Karst bn256 pair input limit: %w", err) + } + if _, err := CheckEIP7939(ctx, logger, l2, basePlan); err != nil { + return fmt.Errorf("EIP-7939: %w", err) + } + if err := CheckEIP7825(ctx, logger, basePlan); err != nil { + return fmt.Errorf("EIP-7825: %w", err) + } + if _, err := CheckEIP7825DepositBypass(ctx, logger, l2, portalAddr, l1Sender, l1Plan, depositAmount); err != nil { + return fmt.Errorf("EIP-7825-deposit: %w", err) + } + if err := CheckEIP7934BlockSizeDisabled(ctx, logger, l2, 2*time.Second); err != nil { + return fmt.Errorf("EIP-7934: %w", err) + } + logger.Info("completed all Karst checks successfully") + return nil +} diff --git a/op-chain-ops/cmd/check-karst/main.go b/op-chain-ops/cmd/check-karst/main.go new file mode 100644 index 00000000000..846ab3ee08e --- /dev/null +++ b/op-chain-ops/cmd/check-karst/main.go @@ -0,0 +1,307 @@ +package main + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + "os" + "strings" + "time" + + "github.com/urfave/cli/v2" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-chain-ops/cmd/check-karst/karsttest" + op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + "github.com/ethereum-optimism/optimism/op-service/eth" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-service/txplan" +) + +var ( + prefix = "CHECK_KARST" + EndpointL2 = &cli.StringFlag{ + Name: "l2", + Usage: "L2 execution RPC endpoint", + EnvVars: op_service.PrefixEnvVar(prefix, "L2"), + Value: "http://localhost:9545", + } + AccountKey = &cli.StringFlag{ + Name: "account", + Usage: "Hex-encoded private key of a funded test account used to send check txs", + EnvVars: op_service.PrefixEnvVar(prefix, "ACCOUNT"), + Required: true, + } + EndpointL1 = &cli.StringFlag{ + Name: "l1", + Usage: "L1 execution RPC endpoint", + EnvVars: op_service.PrefixEnvVar(prefix, "L1"), + Required: true, + } + L1AccountKey = &cli.StringFlag{ + Name: "l1-account", + Usage: "Hex-encoded private key of a funded L1 test account used to submit the deposit", + EnvVars: op_service.PrefixEnvVar(prefix, "L1_ACCOUNT"), + Required: true, + } + PortalAddress = &cli.StringFlag{ + Name: "portal", + Usage: "L1 OptimismPortal contract address", + EnvVars: op_service.PrefixEnvVar(prefix, "PORTAL"), + Required: true, + } +) + +func makeFlags() []cli.Flag { + flags := []cli.Flag{EndpointL2, AccountKey} + return append(flags, oplog.CLIFlags(prefix)...) +} + +// checkEnv bundles the resolved per-invocation inputs that every subcommand +// needs. +type checkEnv struct { + ctx context.Context + logger log.Logger + l2 *ethclient.Client + key *ecdsa.PrivateKey + basePlan txplan.Option +} + +func (e *checkEnv) close() { + if e.l2 != nil { + e.l2.Close() + } +} + +func resolveEnv(c *cli.Context) (*checkEnv, error) { + logCfg := oplog.ReadCLIConfig(c) + logger := oplog.NewLogger(c.App.Writer, logCfg) + + c.Context = ctxinterrupt.WithCancelOnInterrupt(c.Context) + l2Cl, err := ethclient.DialContext(c.Context, c.String(EndpointL2.Name)) + if err != nil { + return nil, fmt.Errorf("failed to dial L2 RPC: %w", err) + } + key, err := crypto.HexToECDSA(strings.TrimPrefix(c.String(AccountKey.Name), "0x")) + if err != nil { + l2Cl.Close() + return nil, fmt.Errorf("failed to parse account private key: %w", err) + } + return &checkEnv{ + ctx: c.Context, + logger: logger, + l2: l2Cl, + key: key, + basePlan: karsttest.NewBasePlan(l2Cl, key), + }, nil +} + +// CheckAction is the shared signature for every karsttest check function the +// CLI exposes. CheckResult is discarded by the CLI; its block range is only +// useful to the acceptance test (for kona-host cross-checks). +type CheckAction func(ctx context.Context, logger log.Logger, l2 apis.EthCode, basePlan txplan.Option) error + +func makeCommand(name string, fn CheckAction) *cli.Command { + return &cli.Command{ + Name: name, + Flags: cliapp.ProtectFlags(makeFlags()), + Action: func(c *cli.Context) error { + env, err := resolveEnv(c) + if err != nil { + return err + } + defer env.close() + if err := fn(env.ctx, env.logger, env.l2, env.basePlan); err != nil { + return fmt.Errorf("command error: %w", err) + } + return nil + }, + } +} + +func makeAllCommand() *cli.Command { + flags := append(makeFlags(), EndpointL1, L1AccountKey, PortalAddress) + return &cli.Command{ + Name: "all", + Flags: cliapp.ProtectFlags(flags), + Action: func(c *cli.Context) error { + env, err := resolveEnv(c) + if err != nil { + return err + } + defer env.close() + + l1Cl, err := ethclient.DialContext(env.ctx, c.String(EndpointL1.Name)) + if err != nil { + return fmt.Errorf("dial L1: %w", err) + } + defer l1Cl.Close() + + l1Key, err := crypto.HexToECDSA(strings.TrimPrefix(c.String(L1AccountKey.Name), "0x")) + if err != nil { + return fmt.Errorf("parse L1 account: %w", err) + } + + portalHex := c.String(PortalAddress.Name) + if !common.IsHexAddress(portalHex) { + return fmt.Errorf("--portal must be a hex address, got %q", portalHex) + } + + if err := karsttest.CheckAll( + env.ctx, env.logger, + ðclientLatestBlockAdapter{env.l2}, + env.basePlan, + common.HexToAddress(portalHex), + crypto.PubkeyToAddress(l1Key.PublicKey), + karsttest.NewBasePlan(l1Cl, l1Key), + eth.OneHundredthEther, + ); err != nil { + return fmt.Errorf("command error: %w", err) + } + return nil + }, + } +} + +// ethclientLatestBlockAdapter satisfies karsttest.LatestBlockFetcher by +// translating InfoAndTxsByLabel(Unsafe) → BlockByNumber(nil). Other labels +// aren't needed by the EIP-7934 check. +type ethclientLatestBlockAdapter struct{ *ethclient.Client } + +func (a *ethclientLatestBlockAdapter) InfoAndTxsByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, types.Transactions, error) { + if label != eth.Unsafe { + return nil, nil, fmt.Errorf("unsupported block label %q (only %q is supported)", label, eth.Unsafe) + } + block, err := a.Client.BlockByNumber(ctx, nil) + if err != nil { + return nil, nil, err + } + return eth.BlockToInfo(block), block.Transactions(), nil +} + +// makeBlockSizeCommand wires up the EIP-7934 block-size-disabled check. +// Unlike the EVM-level checks, it polls latest blocks for one whose tx data +// exceeds MaxBlockSize. Callers control the deadline via Ctrl+C +// (which cancels the interrupt-aware ctx). +func makeBlockSizeCommand() *cli.Command { + return &cli.Command{ + Name: "eip-7934", + Flags: cliapp.ProtectFlags(makeFlags()), + Action: func(c *cli.Context) error { + env, err := resolveEnv(c) + if err != nil { + return err + } + defer env.close() + if err := karsttest.CheckEIP7934BlockSizeDisabled( + env.ctx, env.logger, + ðclientLatestBlockAdapter{env.l2}, + 2*time.Second, + ); err != nil { + return fmt.Errorf("command error: %w", err) + } + return nil + }, + } +} + +// makeDepositCommand wires up the deposit-bypass check, which (unlike the +// EVM-level checks) needs an L1 endpoint, an L1 funded account, and the L1 +// portal address. These extra inputs are not part of the shared CheckAction +// signature, so the deposit command builds its own flag list and resolves L1 +// state inline. +func makeDepositCommand() *cli.Command { + flags := append(makeFlags(), EndpointL1, L1AccountKey, PortalAddress) + return &cli.Command{ + Name: "eip-7825-deposit", + Flags: cliapp.ProtectFlags(flags), + Action: func(c *cli.Context) error { + env, err := resolveEnv(c) + if err != nil { + return err + } + defer env.close() + + l1Cl, err := ethclient.DialContext(env.ctx, c.String(EndpointL1.Name)) + if err != nil { + return fmt.Errorf("dial L1: %w", err) + } + defer l1Cl.Close() + + l1Key, err := crypto.HexToECDSA(strings.TrimPrefix(c.String(L1AccountKey.Name), "0x")) + if err != nil { + return fmt.Errorf("parse L1 account: %w", err) + } + + portalHex := c.String(PortalAddress.Name) + if !common.IsHexAddress(portalHex) { + return fmt.Errorf("--portal must be a hex address, got %q", portalHex) + } + + if _, err := karsttest.CheckEIP7825DepositBypass( + env.ctx, env.logger, env.l2, + common.HexToAddress(portalHex), + crypto.PubkeyToAddress(l1Key.PublicKey), + karsttest.NewBasePlan(l1Cl, l1Key), + eth.OneHundredthEther, + ); err != nil { + return fmt.Errorf("command error: %w", err) + } + return nil + }, + } +} + +func main() { + app := cli.NewApp() + app.Name = "check-karst" + app.Usage = "Check Karst upgrade results against an external network." + app.Description = "Run post-Karst conformance checks against an external L2 RPC endpoint." + app.Action = func(c *cli.Context) error { + return errors.New("see sub-commands") + } + app.Writer = os.Stdout + app.ErrWriter = os.Stderr + app.Commands = []*cli.Command{ + makeAllCommand(), + makeCommand("eip-7823", func(ctx context.Context, logger log.Logger, _ apis.EthCode, basePlan txplan.Option) error { + _, _, err := karsttest.CheckEIP7823(ctx, logger, basePlan) + return err + }), + makeCommand("eip-7883", func(ctx context.Context, logger log.Logger, _ apis.EthCode, basePlan txplan.Option) error { + _, _, err := karsttest.CheckEIP7883(ctx, logger, basePlan) + return err + }), + makeCommand("eip-7951", func(ctx context.Context, logger log.Logger, _ apis.EthCode, basePlan txplan.Option) error { + _, _, err := karsttest.CheckEIP7951(ctx, logger, basePlan) + return err + }), + makeCommand("karst-bn256-pair", func(ctx context.Context, logger log.Logger, _ apis.EthCode, basePlan txplan.Option) error { + _, _, err := karsttest.CheckKarstBn256PairInputLimit(ctx, logger, basePlan) + return err + }), + makeCommand("eip-7939", func(ctx context.Context, logger log.Logger, l2 apis.EthCode, basePlan txplan.Option) error { + _, err := karsttest.CheckEIP7939(ctx, logger, l2, basePlan) + return err + }), + makeCommand("eip-7825", func(ctx context.Context, logger log.Logger, _ apis.EthCode, basePlan txplan.Option) error { + return karsttest.CheckEIP7825(ctx, logger, basePlan) + }), + makeBlockSizeCommand(), + makeDepositCommand(), + } + + if err := app.Run(os.Args); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Application failed: %v\n", err) + os.Exit(1) + } +} diff --git a/op-chain-ops/cmd/check-output-root/main.go b/op-chain-ops/cmd/check-output-root/main.go index 6b34bcaaa02..271efc7e30e 100644 --- a/op-chain-ops/cmd/check-output-root/main.go +++ b/op-chain-ops/cmd/check-output-root/main.go @@ -75,8 +75,8 @@ func CalculateOutputRoot(ctx context.Context, rpcUrl string, blockNum uint64) (c } // Isthmus assumes WithdrawalsHash is present in the header. if header.WithdrawalsHash == nil { - return common.Hash{}, fmt.Errorf("target block %d (%s) is missing withdrawals hash, required for Isthmus output root calculation", - header.Number.Uint64(), header.Hash()) + return common.Hash{}, fmt.Errorf("target block %v (%s) is missing withdrawals hash, required for Isthmus output root calculation", + header.Number, header.Hash()) } // Construct OutputV0 using StateRoot, WithdrawalsHash (as MessagePasserStorageRoot), and BlockHash diff --git a/op-chain-ops/cmd/check-prestate/main.go b/op-chain-ops/cmd/check-prestate/main.go index a3a5864ddf1..11bea6791a2 100644 --- a/op-chain-ops/cmd/check-prestate/main.go +++ b/op-chain-ops/cmd/check-prestate/main.go @@ -5,7 +5,9 @@ import ( "encoding/json" "flag" "fmt" + "maps" "os" + "slices" "strings" "github.com/BurntSushi/toml" @@ -18,7 +20,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/superchain" "github.com/mattn/go-isatty" - "golang.org/x/exp/maps" ) type FPProgramType interface { @@ -65,7 +66,7 @@ func main() { chainFilter = func(chainName string) bool { return chains[chainName] } - filteredChainNames = maps.Keys(chains) + filteredChainNames = slices.Collect(maps.Keys(chains)) } prestateHash := common.HexToHash(prestateHashStr) if prestateHash == (common.Hash{}) { @@ -97,7 +98,7 @@ func main() { switch prestateType { case "cannon32", "cannon64", "interop": prestateImpl = prestate.NewOPProgramPrestate() - case "cannon-kona": + case "cannon64-kona": prestateImpl = prestate.NewKonaPrestate() default: log.Crit("Invalid prestate type", "type", prestateType) @@ -115,8 +116,8 @@ func main() { } knownChains := make(map[string]bool) - supportedChains := make([]string, 0) // Not null for json serialization - outdatedChains := make(map[string]types.OutdatedChain) + supportedChains := make([]string, 0) // Not null for json serialization + outdatedChains := make([]types.OutdatedChain, 0) // Not null for json serialization for _, name := range prestateNames { if !chainFilter(name) { continue @@ -127,10 +128,10 @@ func main() { log.Crit("Failed to check config", "chain", name, "err", err) } if diff != nil { - outdatedChains[name] = types.OutdatedChain{ + outdatedChains = append(outdatedChains, types.OutdatedChain{ Name: name, Diff: diff, - } + }) } else { supportedChains = append(supportedChains, name) } @@ -158,7 +159,7 @@ func main() { ExecutionClient: elCommitInfo, SuperchainRegistry: commitInfo("superchain-registry", commit, "main", "superchain"), UpToDateChains: supportedChains, - OutdatedChains: maps.Values(outdatedChains), + OutdatedChains: outdatedChains, MissingChains: missingChains, } encoder := json.NewEncoder(os.Stdout) diff --git a/op-chain-ops/cmd/check-prestate/prestate/kona.go b/op-chain-ops/cmd/check-prestate/prestate/kona.go index e9ba0cb30e7..d77ac8b861d 100644 --- a/op-chain-ops/cmd/check-prestate/prestate/kona.go +++ b/op-chain-ops/cmd/check-prestate/prestate/kona.go @@ -1,10 +1,10 @@ package prestate import ( - "encoding/json" + "bytes" "fmt" - "net/http" - "net/url" + "os/exec" + "strings" "github.com/ethereum-optimism/optimism/op-chain-ops/cmd/check-prestate/registry" "github.com/ethereum-optimism/optimism/op-chain-ops/cmd/check-prestate/types" @@ -27,7 +27,7 @@ func (p *KonaPrestate) FindVersions(log log.Logger, prestateVersion string) ( prestateTag := fmt.Sprintf("kona-client/v%s", prestateVersion) log.Info("Found prestate tag", "tag", prestateTag) - fppCommitInfo = types.NewCommitInfo("op-rs", "kona", prestateTag, "main", "") + fppCommitInfo = types.NewCommitInfo("ethereum-optimism", "optimism", prestateTag, "develop", "rust/kona") superChainRegistryCommit, err := fetchSuperchainRegistryCommit(prestateTag) if err != nil { @@ -51,37 +51,57 @@ func (p *KonaPrestate) FindVersions(log log.Logger, prestateVersion string) ( return } +// fetchSuperchainRegistryCommit returns the superchain-registry commit SHA that +// the kona-client release identified by ref was built against, by reading the +// pinned commit file from the local optimism monorepo checkout at that tag. +// +// Only kona-client tags that have op-core/superchain/superchain-registry-commit.txt +// are supported (v1.5.1 and later). If the tag isn't present locally, the +// function fetches it from origin before giving up. func fetchSuperchainRegistryCommit(ref string) (string, error) { - endpoint := "https://api.github.com/repos/op-rs/kona/contents/crates/protocol/registry/superchain-registry?ref=" + - url.QueryEscape(ref) + const path = "op-core/superchain/superchain-registry-commit.txt" - req, err := http.NewRequest(http.MethodGet, endpoint, nil) - if err != nil { - return "", fmt.Errorf("build request: %w", err) + if err := ensureRefAvailable(ref); err != nil { + return "", err } - req.Header.Set("Accept", "application/vnd.github+json") - resp, err := http.DefaultClient.Do(req) + stdout, stderr, err := runGit("show", fmt.Sprintf("%s:%s", ref, path)) if err != nil { - return "", fmt.Errorf("http request: %w", err) + return "", fmt.Errorf("git show %s:%s failed: %w (%s)", ref, path, err, strings.TrimSpace(stderr)) } - defer resp.Body.Close() - - // Parse error payloads from GitHub if status != 200. - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to fetch superchain-registry version, http status: %s", resp.Status) + sha := strings.TrimSpace(stdout) + if sha == "" { + return "", fmt.Errorf("empty commit SHA at %s@%s", path, ref) } + return sha, nil +} - // Success path: expect a single "submodule" content object with "sha". - var content struct { - Type string `json:"type"` // should be "submodule" - SHA string `json:"sha"` +// ensureRefAvailable verifies that ref resolves in the local repo; if not, it +// attempts to fetch the tag from origin. +func ensureRefAvailable(ref string) error { + if refExists(ref) { + return nil } - if err := json.NewDecoder(resp.Body).Decode(&content); err != nil { - return "", fmt.Errorf("decode response: %w", err) + refspec := fmt.Sprintf("refs/tags/%s:refs/tags/%s", ref, ref) + if _, stderr, err := runGit("fetch", "--quiet", "origin", refspec); err != nil { + return fmt.Errorf("ref %q not found locally and git fetch origin %s failed: %w (%s)", ref, refspec, err, strings.TrimSpace(stderr)) } - if content.Type != "submodule" { - return "", fmt.Errorf("expected a submodule got type %q", content.Type) + if !refExists(ref) { + return fmt.Errorf("ref %q still not found after git fetch origin %s", ref, refspec) } - return content.SHA, nil + return nil +} + +func refExists(ref string) bool { + _, _, err := runGit("rev-parse", "--verify", "--quiet", ref+"^{commit}") + return err == nil +} + +func runGit(args ...string) (string, string, error) { + var stdout, stderr bytes.Buffer + cmd := exec.Command("git", args...) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + return stdout.String(), stderr.String(), err } diff --git a/op-chain-ops/cmd/check-prestate/prestate/opprogram.go b/op-chain-ops/cmd/check-prestate/prestate/opprogram.go index 5360851c2e7..f6868014f1b 100644 --- a/op-chain-ops/cmd/check-prestate/prestate/opprogram.go +++ b/op-chain-ops/cmd/check-prestate/prestate/opprogram.go @@ -9,12 +9,13 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/superchain" "golang.org/x/mod/modfile" + "golang.org/x/mod/module" ) const ( monorepoGoModAtTag = "https://github.com/ethereum-optimism/optimism/raw/refs/tags/%s/go.mod" superchainRegistryCommitAtRef = "https://github.com/ethereum-optimism/op-geth/raw/%s/superchain-registry-commit.txt" - superchainConfigsZipAtTag = "https://github.com/ethereum-optimism/op-geth/raw/refs/tags/%s/superchain/superchain-configs.zip" + superchainConfigsZipAtRef = "https://github.com/ethereum-optimism/op-geth/raw/%s/superchain/superchain-configs.zip" ) type OPProgramPrestate struct { @@ -38,7 +39,7 @@ func (p *OPProgramPrestate) FindVersions(log log.Logger, prestateVersion string) if err != nil { log.Crit("Failed to fetch go mod", "err", err) } - elVersion := p.findOpGethVersion(log, modFile) + elVersion := resolvePseudoVersion(p.findOpGethVersion(log, modFile)) elCommitInfo = types.NewCommitInfo("ethereum-optimism", "op-geth", elVersion, "optimism", "") registryCommitBytes, err := util.Fetch(fmt.Sprintf(superchainRegistryCommitAtRef, elVersion)) @@ -48,7 +49,7 @@ func (p *OPProgramPrestate) FindVersions(log log.Logger, prestateVersion string) superChainRegistryCommit = strings.TrimSpace(string(registryCommitBytes)) log.Info("Found superchain registry commit info", "commit", superChainRegistryCommit) - prestateConfigData, err := util.Fetch(fmt.Sprintf(superchainConfigsZipAtTag, elVersion)) + prestateConfigData, err := util.Fetch(fmt.Sprintf(superchainConfigsZipAtRef, elVersion)) if err != nil { log.Crit("Failed to fetch prestate's superchain registry config zip", "err", err) } @@ -84,3 +85,17 @@ func fetchMonorepoGoMod(opProgramTag string) (*modfile.File, error) { return modfile.Parse("go.mod", goMod, nil) } + +// resolvePseudoVersion converts a Go module version to a git ref. +// For pseudo-versions like "v1.101604.0-synctest.0.0.20251208094937-ba6bdcfef423", +// it extracts the commit hash suffix. For regular tags, it returns the version as-is. +func resolvePseudoVersion(version string) string { + if module.IsPseudoVersion(version) { + rev, err := module.PseudoVersionRev(version) + if err != nil { + log.Crit("Failed to extract commit hash from pseudo-version", "version", version, "err", err) + } + return rev + } + return version +} diff --git a/op-chain-ops/cmd/ecotone-scalar/README.md b/op-chain-ops/cmd/ecotone-scalar/README.md index a888f8d3f3a..e5826ea3e1d 100644 --- a/op-chain-ops/cmd/ecotone-scalar/README.md +++ b/op-chain-ops/cmd/ecotone-scalar/README.md @@ -7,12 +7,12 @@ configuring the base fee scalar and blob base fee scalars separately. #### Usage -Build and run using the [Makefile](../../Makefile) `ecotone-scalar` target. Inside of `/op-chain-ops`, run: +Build and run using the `ecotone-scalar` target. Inside of `/op-chain-ops`, run: ```sh -make ecotone-scalar +just ecotone-scalar ``` -to create a binary in [../../bin/ecotone-scalar](../../bin/ecotone-scalar) that can -be executed, providing the `--scalar` and `--blob-scalar` flags to specify the base bee scalar and +to create a binary in `./bin/ecotone-scalar` that can +be executed, providing the `--scalar` and `--blob-scalar` flags to specify the base fee scalar and blob base fee parameters respectively, for example: ```sh diff --git a/op-chain-ops/cmd/op-run-block/main.go b/op-chain-ops/cmd/op-run-block/main.go index 04195fc142a..a8ae29c8969 100644 --- a/op-chain-ops/cmd/op-run-block/main.go +++ b/op-chain-ops/cmd/op-run-block/main.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/triedb" op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" oplog "github.com/ethereum-optimism/optimism/op-service/log" @@ -154,11 +155,11 @@ func mainAction(c *cli.Context) error { Overrides: nil, }, outW) - witness, err := stateless.NewWitness(header, chCtx) + witness, err := stateless.NewWitness(header, chCtx, false) if err != nil { return fmt.Errorf("failed to prepare witness data collector: %w", err) } - state.StartPrefetcher("debug", witness, nil) + state.StartPrefetcher("debug", witness) defer func() { // Even if the EVM fails, try to export witness data for the state-transition up to the error. witnessDump := witness.ToExecutionWitness() out, err := json.MarshalIndent(witnessDump, "", " ") @@ -224,7 +225,7 @@ func (r remoteChainCtx) CurrentHeader() *types.Header { // GetHeaderByNumber is part of consensus.ChainHeaderReader func (r remoteChainCtx) GetHeaderByNumber(u uint64) *types.Header { - if r.hdr.Number.Uint64() == u { + if bigs.Uint64Strict(r.hdr.Number) == u { return r.hdr } ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) @@ -304,13 +305,12 @@ func Process(logger log.Logger, config *params.ChainConfig, chainCtx *remoteChainCtx, outW io.Writer) (*core.ProcessResult, error) { var ( receipts types.Receipts - usedGas = new(uint64) header = block.CreateGethHeader() blockHash = block.Hash blockNumber = new(big.Int).SetUint64(uint64(block.Number)) blockTime = uint64(block.Time) allLogs []*types.Log - gp = new(core.GasPool).AddGas(uint64(block.GasLimit)) + gp = core.NewGasPool(uint64(block.GasLimit)) ) // Mutate the block and state according to any hard-fork specs @@ -343,7 +343,7 @@ func Process(logger log.Logger, config *params.ChainConfig, } statedb.SetTxContext(tx.Hash(), i) - receipt, err := core.ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, blockTime, tx, usedGas, vmenv) + receipt, err := core.ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, blockTime, tx, vmenv) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } @@ -365,6 +365,6 @@ func Process(logger log.Logger, config *params.ChainConfig, Receipts: receipts, Requests: nil, Logs: allLogs, - GasUsed: *usedGas, + GasUsed: gp.Used(), }, nil } diff --git a/op-chain-ops/cmd/op-simulate/main.go b/op-chain-ops/cmd/op-simulate/main.go index 42381a0e914..bf07506037a 100644 --- a/op-chain-ops/cmd/op-simulate/main.go +++ b/op-chain-ops/cmd/op-simulate/main.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" oplog "github.com/ethereum-optimism/optimism/op-service/log" @@ -183,7 +184,7 @@ func fetchChainConfig(ctx context.Context, cl *rpc.Client) (*params.ChainConfig, // if we recognize the chain ID, we can get the chain config id := (*big.Int)(&idResult) if id.IsUint64() { - cfg, err := superutil.LoadOPStackChainConfigFromChainID(id.Uint64()) + cfg, err := superutil.LoadOPStackChainConfigFromChainID(bigs.Uint64Strict(id)) if err == nil { return cfg, nil } @@ -238,17 +239,37 @@ type simChainContext struct { cfg *params.ChainConfig } +var _ core.ChainContext = (*simChainContext)(nil) + func (d *simChainContext) Engine() consensus.Engine { return d.eng } func (d *simChainContext) GetHeader(h common.Hash, n uint64) *types.Header { - if n == d.head.Number.Uint64() { + if n == bigs.Uint64Strict(d.head.Number) { return d.head } panic(fmt.Errorf("header retrieval not supported, cannot fetch %s %d", h, n)) } +func (d *simChainContext) CurrentHeader() *types.Header { + return d.head +} + +func (d *simChainContext) GetHeaderByHash(hash common.Hash) *types.Header { + if d.head.Hash() == hash { + return d.head + } + panic(fmt.Errorf("header retrieval not supported, cannot fetch %s", hash)) +} + +func (d *simChainContext) GetHeaderByNumber(number uint64) *types.Header { + if bigs.Uint64Strict(d.head.Number) == number { + return d.head + } + panic(fmt.Errorf("header retrieval not supported, cannot fetch %d", number)) +} + func (d *simChainContext) Config() *params.ChainConfig { return d.cfg } @@ -267,14 +288,14 @@ func simulate(ctx context.Context, logger log.Logger, conf *params.ChainConfig, } for addr, acc := range dump { state.CreateAccount(addr) - state.SetBalance(addr, uint256.MustFromBig((*big.Int)(&acc.Balance))) + state.SetBalance(addr, uint256.MustFromBig((*big.Int)(&acc.Balance)), tracing.BalanceChangeUnspecified) state.SetNonce(addr, acc.Nonce, tracing.NonceChangeUnspecified) - state.SetCode(addr, acc.Code) + state.SetCode(addr, acc.Code, tracing.CodeChangeUnspecified) state.SetStorage(addr, acc.Storage) } // load prestate data into memory db state - _, err = state.Commit(header.Number.Uint64()-1, true, conf.IsCancun(header.Number, header.Time)) + _, err = state.Commit(bigs.Uint64Strict(header.Number)-1, true, conf.IsCancun(header.Number, header.Time)) if err != nil { return fmt.Errorf("failed to write state data to underlying DB: %w", err) } @@ -291,8 +312,7 @@ func simulate(ctx context.Context, logger log.Logger, conf *params.ChainConfig, state.SetTxContext(tx.Hash(), 0) cCtx := &simChainContext{eng: beacon.New(ethash.NewFaker()), head: header, cfg: conf} - gp := core.GasPool(tx.Gas()) - usedGas := uint64(0) + gp := core.NewGasPool(tx.Gas()) vmConfig := vm.Config{} if doProfile { @@ -305,7 +325,7 @@ func simulate(ctx context.Context, logger log.Logger, conf *params.ChainConfig, // nil block-author, since it defaults to header.coinbase blockCtx := core.NewEVMBlockContext(header, cCtx, nil, conf, state) evm := vm.NewEVM(blockCtx, state, conf, vmConfig) - receipt, err := core.ApplyTransaction(evm, &gp, state, header, tx, &usedGas) + receipt, err := core.ApplyTransaction(evm, gp, state, header, tx) if err != nil { return fmt.Errorf("failed to apply tx: %w", err) } diff --git a/op-chain-ops/cmd/receipt-reference-builder/pull.go b/op-chain-ops/cmd/receipt-reference-builder/pull.go index 3c692955026..335ebf5b2ed 100644 --- a/op-chain-ops/cmd/receipt-reference-builder/pull.go +++ b/op-chain-ops/cmd/receipt-reference-builder/pull.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/sources/batching" @@ -49,7 +50,7 @@ func pull(ctx *cli.Context) error { log.Error("Failed to Get Chain ID", "Err", err) return err } - chainID := cid.Uint64() + chainID := bigs.Uint64Strict(cid) // record start time startT := time.Now() diff --git a/op-chain-ops/cmd/unclaimed-credits/main.go b/op-chain-ops/cmd/unclaimed-credits/main.go index 8c208264c25..0534fdd7aaa 100644 --- a/op-chain-ops/cmd/unclaimed-credits/main.go +++ b/op-chain-ops/cmd/unclaimed-credits/main.go @@ -4,8 +4,10 @@ import ( "context" "encoding/json" "fmt" + "maps" "math/big" "os" + "slices" "time" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" @@ -20,7 +22,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli/v2" - "golang.org/x/exp/maps" ) var ( @@ -130,7 +131,7 @@ func unclaimedCreditsForGame(ctx context.Context, game contracts.FaultDisputeGam players[claim.CounteredBy] = true } } - playerList := maps.Keys(players) + playerList := slices.Collect(maps.Keys(players)) credits, err := game.GetCredits(ctx, rpcblock.Latest, playerList...) if err != nil { return fmt.Errorf("failed to retrieve credits: %w", err) diff --git a/op-chain-ops/cmd/withdrawal/README.md b/op-chain-ops/cmd/withdrawal/README.md index fcf76b74ade..82ed4af55ed 100644 --- a/op-chain-ops/cmd/withdrawal/README.md +++ b/op-chain-ops/cmd/withdrawal/README.md @@ -27,15 +27,6 @@ valid proposal is made for a L2 block at or after the initiating transaction was go run . prove --l1 --l2 --tx --portal-address --private-key ``` -When proving super roots, you'll need to provide additional flags: - -``` -shell -go run . prove --l1 --l2 --tx --portal-address --private-key \ - --supervisor --rollup.config --depset -``` - - ### finalize The `finalize` subcommand finalizes a withdrawal that has previously been proven. The dispute game must have resolved as diff --git a/op-chain-ops/cmd/withdrawal/prove.go b/op-chain-ops/cmd/withdrawal/prove.go index 9aa0a8fdc6d..8970d2aa793 100644 --- a/op-chain-ops/cmd/withdrawal/prove.go +++ b/op-chain-ops/cmd/withdrawal/prove.go @@ -10,10 +10,7 @@ import ( bindingspreview "github.com/ethereum-optimism/optimism/op-node/bindings/preview" "github.com/ethereum-optimism/optimism/op-node/withdrawals" op_service "github.com/ethereum-optimism/optimism/op-service" - "github.com/ethereum-optimism/optimism/op-service/apis" oplog "github.com/ethereum-optimism/optimism/op-service/log" - "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" - "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -40,23 +37,6 @@ var ( Usage: "Address of the optimism portal contract.", EnvVars: op_service.PrefixEnvVar(EnvVarPrefix, "PORTAL_ADDRESS"), } - - // Prove using SuperRoots Flags - SupervisorFlag = &cli.StringFlag{ - Name: "supervisor", - Usage: "HTTP provider URL for supervisor. Only required for proving using super roots.", - EnvVars: op_service.PrefixEnvVar(EnvVarPrefix, "SUPERVISOR"), - } - DepSetFlag = &cli.StringFlag{ - Name: "depset", - Usage: "Path to the dependency set file. Only required for proving using super roots.", - EnvVars: op_service.PrefixEnvVar(EnvVarPrefix, "DEPSET"), - } - RollupConfigFlag = &cli.StringFlag{ - Name: "rollup.config", - Usage: "Path to the rollup config of the target chain. Only required for proving using super roots.", - EnvVars: op_service.PrefixEnvVar(EnvVarPrefix, "ROLLUP_CONFIG"), - } ) func ProveWithdrawal(ctx *cli.Context) error { @@ -112,29 +92,10 @@ func ProveWithdrawal(ctx *cli.Context) error { return fmt.Errorf("could not find a dispute game at or above l2 block number %v: %w", rcpt.BlockNumber, err) } - l1EthClient, err := createEthClient(ctx, L1Flag.Name) + logger.Info("Proving withdrawal") + txData, err := txDataForOutputRootProof(ctx.Context, proofClient, l2Client, txHash, factory, portal) if err != nil { - return fmt.Errorf("failed to create L1 eth client: %w", err) - } - boundPortal := bindings.NewBindings[bindings.OptimismPortal2](bindings.WithClient(l1EthClient), bindings.WithTo(portalAddr)) - usesSuperRoots, err := contractio.Read(boundPortal.SuperRootsActive(), ctx.Context) - if err != nil { - return fmt.Errorf("failed to fetch uses super roots from portal: %w", err) - } - - var txData []byte - if !usesSuperRoots { - logger.Info("Proving withdrawal using output root proof") - txData, err = txDataForOutputRootProof(ctx.Context, proofClient, l2Client, txHash, factory, portal) - if err != nil { - return err - } - } else { - logger.Info("Proving withdrawal using super root proof") - txData, err = txDataForSuperRootProof(ctx, l1EthClient, proofClient, l2Client, txHash, factory, portal) - if err != nil { - return err - } + return err } rcpt, err = txMgr.Send(ctx.Context, txmgr.TxCandidate{ @@ -178,93 +139,12 @@ func txDataForOutputRootProof(ctx context.Context, proofClient *gethclient.Clien return txData, nil } -func txDataForSuperRootProof(ctx *cli.Context, l1EthClient apis.EthClient, proofClient *gethclient.Client, l2Client *ethclient.Client, txHash common.Hash, factory *opnode_bindings.DisputeGameFactoryCaller, portal *bindingspreview.OptimismPortal2) ([]byte, error) { - supervisorClient, err := createSupervisorClient(ctx, SupervisorFlag.Name) - if err != nil { - return nil, fmt.Errorf("failed to create supervisor client: %w", err) - } - rollupCfg, err := loadRollupConfig(ctx, RollupConfigFlag.Name) - if err != nil { - return nil, err - } - depSet, err := loadDepsetConfig(ctx, DepSetFlag.Name) - if err != nil { - return nil, err - } - - portalL2ChainID, err := l2ChainIDForPortal(ctx.Context, l1EthClient, portal) - if err != nil { - return nil, fmt.Errorf("failed to get portal chain ID: %w", err) - } - if portalL2ChainID != rollupCfg.L2ChainID.Uint64() { - return nil, fmt.Errorf("portal chain ID %d does not match the provided rollup config chain ID %d", portalL2ChainID, rollupCfg.L2ChainID.Uint64()) - } - - params, err := withdrawals.ProveWithdrawalParametersSuperRoots( - ctx.Context, - rollupCfg, - depSet, - proofClient, - l2Client, - l2Client, - txHash, - supervisorClient, - factory, - &portal.OptimismPortal2Caller, - ) - if err != nil { - return nil, fmt.Errorf("failed to create withdrawal proof parameters: %w", err) - } - txData, err := w3.MustNewFunc("proveWithdrawalTransaction("+ - "(uint256 Nonce, address Sender, address Target, uint256 Value, uint256 GasLimit, bytes Data),"+ - "address DisputeGameProxy,"+ - "uint256 OutputRootIndex,"+ - "(bytes1 Version, uint64 Timestamp, (uint256 ChainID, bytes32 Root)[] OutputRoots),"+ - "(bytes32 Version, bytes32 StateRoot, bytes32 MessagePasserStorageRoot, bytes32 LatestBlockhash),"+ - "bytes[])", "").EncodeArgs( - bindingspreview.TypesWithdrawalTransaction{ - Nonce: params.Nonce, - Sender: params.Sender, - Target: params.Target, - Value: params.Value, - GasLimit: params.GasLimit, - Data: params.Data, - }, - params.DisputeGameProxy, - params.OutputRootIndex, - params.SuperRootProof, - params.OutputRootProof, - params.WithdrawalProof, - ) - if err != nil { - return nil, fmt.Errorf("failed to pack super root prove withdrawal transaction: %w", err) - } - return txData, nil -} - -func l2ChainIDForPortal(ctx context.Context, l1EthClient apis.EthClient, portal *bindingspreview.OptimismPortal2) (uint64, error) { - systemConfigAddr, err := portal.SystemConfig(&bind.CallOpts{Context: ctx}) - if err != nil { - return 0, fmt.Errorf("failed to get system config address from portal: %w", err) - } - systemConfig := bindings.NewSystemConfig(bindings.WithClient(l1EthClient), bindings.WithTo(systemConfigAddr)) - l2ChainID, err := contractio.Read(systemConfig.L2ChainID(), ctx) - if err != nil { - return 0, fmt.Errorf("failed to read L2 chain ID from system config: %w", err) - } - return l2ChainID.Uint64(), nil -} - func proveFlags() []cli.Flag { cliFlags := []cli.Flag{ L1Flag, L2Flag, TxFlag, PortalAddressFlag, - // Super Roots Flags - SupervisorFlag, - DepSetFlag, - RollupConfigFlag, } cliFlags = append(cliFlags, txmgr.CLIFlagsWithDefaults(EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...) cliFlags = append(cliFlags, oplog.CLIFlags(EnvVarPrefix)...) diff --git a/op-chain-ops/cmd/withdrawal/util.go b/op-chain-ops/cmd/withdrawal/util.go index e54a3a26b34..80ef8c1fa26 100644 --- a/op-chain-ops/cmd/withdrawal/util.go +++ b/op-chain-ops/cmd/withdrawal/util.go @@ -1,20 +1,13 @@ package main import ( - "encoding/json" "fmt" - "os" "time" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" - "github.com/ethereum-optimism/optimism/op-service/dial" oplog "github.com/ethereum-optimism/optimism/op-service/log" - "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli/v2" ) @@ -46,47 +39,3 @@ func createTxMgr(ctx *cli.Context, logger log.Logger, rpcUrlFlag string) (*txmgr } return txMgr, nil } - -func createEthClient(ctx *cli.Context, rpcUrlFlag string) (*sources.EthClient, error) { - url := ctx.String(rpcUrlFlag) - l1RPC, err := client.NewRPC(ctx.Context, log.Root(), url, client.WithDialAttempts(10)) - if err != nil { - return nil, fmt.Errorf("failed to create RPC client: %w", err) - } - l1Client, err := sources.NewEthClient(l1RPC, log.Root(), nil, sources.DefaultEthClientConfig(10)) - if err != nil { - return nil, fmt.Errorf("failed to create L1 client: %w", err) - } - return l1Client, nil -} - -func loadRollupConfig(ctx *cli.Context, rollupConfigFlag string) (*rollup.Config, error) { - file, err := os.Open(ctx.String(rollupConfigFlag)) - if err != nil { - return nil, fmt.Errorf("failed to open rollup config file: %w", err) - } - defer file.Close() - var rollupConfig rollup.Config - return &rollupConfig, rollupConfig.ParseRollupConfig(file) -} - -func loadDepsetConfig(ctx *cli.Context, depSetFlag string) (depset.DependencySet, error) { - data, err := os.ReadFile(ctx.String(depSetFlag)) - if err != nil { - return nil, fmt.Errorf("failed to read depset config: %w", err) - } - var depsetConfig depset.StaticConfigDependencySet - err = json.Unmarshal(data, &depsetConfig) - if err != nil { - return nil, fmt.Errorf("failed to parse depset config: %w", err) - } - return &depsetConfig, nil -} - -func createSupervisorClient(ctx *cli.Context, supervisorFlag string) (*sources.SupervisorClient, error) { - cl, err := dial.DialSupervisorClientWithTimeout(ctx.Context, log.Root(), ctx.String(supervisorFlag)) - if err != nil { - return nil, fmt.Errorf("failed to dial supervisor: %w", err) - } - return cl, nil -} diff --git a/op-chain-ops/crossdomain/encoding_test.go b/op-chain-ops/crossdomain/encoding_test.go index 3d9730d0ff9..9d0c7ee651e 100644 --- a/op-chain-ops/crossdomain/encoding_test.go +++ b/op-chain-ops/crossdomain/encoding_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/stretchr/testify/require" ) @@ -24,7 +25,7 @@ func FuzzVersionedNonce(f *testing.F) { decodedNonce, decodedVersion := crossdomain.DecodeVersionedNonce(encodedNonce) - require.Equal(t, decodedNonce.Uint64(), inputNonce.Uint64()) - require.Equal(t, decodedVersion.Uint64(), inputVersion.Uint64()) + require.True(t, bigs.Equal(decodedNonce, inputNonce)) + require.True(t, bigs.Equal(decodedVersion, inputVersion)) }) } diff --git a/op-chain-ops/crossdomain/message.go b/op-chain-ops/crossdomain/message.go index 4dc176e6919..febb227123a 100644 --- a/op-chain-ops/crossdomain/message.go +++ b/op-chain-ops/crossdomain/message.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum/go-ethereum/common" ) @@ -42,7 +43,7 @@ func NewCrossDomainMessage( // It does this by looking at the first byte of the nonce. func (c *CrossDomainMessage) Version() uint64 { _, version := DecodeVersionedNonce(c.Nonce) - return version.Uint64() + return bigs.Uint64Strict(version) } // Encode will encode a cross domain message based on the version. diff --git a/op-chain-ops/crossdomain/withdrawal_test.go b/op-chain-ops/crossdomain/withdrawal_test.go index e72d96a5411..38aefbe8697 100644 --- a/op-chain-ops/crossdomain/withdrawal_test.go +++ b/op-chain-ops/crossdomain/withdrawal_test.go @@ -6,26 +6,28 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/holiman/uint256" "github.com/stretchr/testify/require" ) // FuzzEncodeDecodeWithdrawal will fuzz encoding and decoding of a Withdrawal func FuzzEncodeDecodeWithdrawal(f *testing.F) { f.Fuzz(func(t *testing.T, _nonce, _sender, _target, _value, _gasLimit, data []byte) { - nonce := new(big.Int).SetBytes(_nonce) + nonce := new(uint256.Int).SetBytes(_nonce) sender := common.BytesToAddress(_sender) target := common.BytesToAddress(_target) - value := new(big.Int).SetBytes(_value) - gasLimit := new(big.Int).SetBytes(_gasLimit) + value := new(uint256.Int).SetBytes(_value) + gasLimit := new(uint256.Int).SetBytes(_gasLimit) withdrawal := crossdomain.NewWithdrawal( - nonce, + nonce.ToBig(), &sender, &target, - value, - gasLimit, + value.ToBig(), + gasLimit.ToBig(), data, ) @@ -36,11 +38,11 @@ func FuzzEncodeDecodeWithdrawal(f *testing.F) { err = w.Decode(encoded) require.Nil(t, err) - require.Equal(t, withdrawal.Nonce.Uint64(), w.Nonce.Uint64()) + require.Truef(t, bigs.Equal(withdrawal.Nonce, w.Nonce), "nonce: %s != %s", withdrawal.Nonce, w.Nonce) require.Equal(t, withdrawal.Sender, w.Sender) require.Equal(t, withdrawal.Target, w.Target) - require.Equal(t, withdrawal.Value.Uint64(), w.Value.Uint64()) - require.Equal(t, withdrawal.GasLimit.Uint64(), w.GasLimit.Uint64()) + require.Truef(t, bigs.Equal(withdrawal.Value, w.Value), "value: %s != %s", withdrawal.Value, w.Value) + require.Truef(t, bigs.Equal(withdrawal.GasLimit, w.GasLimit), "gasLimit: %s != %s", withdrawal.GasLimit, w.GasLimit) require.Equal(t, withdrawal.Data, w.Data) }) } diff --git a/op-chain-ops/devkeys/devkeys.go b/op-chain-ops/devkeys/devkeys.go index 9c754112621..f490d13b272 100644 --- a/op-chain-ops/devkeys/devkeys.go +++ b/op-chain-ops/devkeys/devkeys.go @@ -63,8 +63,8 @@ const ( SuperchainProxyAdminOwner SuperchainOperatorRole = 1 // SuperchainConfigGuardianKey is the Guardian of the SuperchainConfig. SuperchainConfigGuardianKey SuperchainOperatorRole = 2 - // SuperchainProtocolVersionsOwner is the key that can make ProtocolVersions changes. - SuperchainProtocolVersionsOwner SuperchainOperatorRole = 3 + // Slot 3 is reserved-but-unused so the deterministic key derivation of + // subsequent roles stays stable. // DependencySetManagerKey is the key used to manage the dependency set of a superchain. DependencySetManagerKey SuperchainOperatorRole = 4 ) @@ -77,8 +77,6 @@ func (role SuperchainOperatorRole) String() string { return "superchain-proxy-admin-owner" case SuperchainConfigGuardianKey: return "superchain-config-guardian" - case SuperchainProtocolVersionsOwner: - return "superchain-protocol-versions-owner" case DependencySetManagerKey: return "dependency-set-manager" default: @@ -160,8 +158,6 @@ const ( SystemConfigOwner ChainOperatorRole = 10 // OperatorFeeVaultRecipientRole is the key that receives from the OperatorFeeVault predeploy OperatorFeeVaultRecipientRole ChainOperatorRole = 11 - // ChainFeesRecipientRole is the key that receives the chain's share from the FeeSplitter - ChainFeesRecipientRole ChainOperatorRole = 12 ) func (role ChainOperatorRole) String() string { @@ -190,8 +186,6 @@ func (role ChainOperatorRole) String() string { return "operator-fee-vault-recipient" case SystemConfigOwner: return "system-config-owner" - case ChainFeesRecipientRole: - return "chain-fees-recipient" default: return fmt.Sprintf("unknown-operator-%d", uint64(role)) } diff --git a/op-chain-ops/foundry/allocs_test.go b/op-chain-ops/foundry/allocs_test.go index 8fdeee6da0d..608ef0d549a 100644 --- a/op-chain-ops/foundry/allocs_test.go +++ b/op-chain-ops/foundry/allocs_test.go @@ -54,7 +54,7 @@ func TestForgeAllocs_FromState(t *testing.T) { st.CreateContract(contract) st.SetNonce(contract, 30, tracing.NonceChangeUnspecified) st.SetBalance(contract, uint256.NewInt(0), tracing.BalanceChangeUnspecified) - st.SetCode(contract, []byte{10, 11, 12, 13, 14}) + st.SetCode(contract, []byte{10, 11, 12, 13, 14}, tracing.CodeChangeUnspecified) // Commit and make a new state, we cannot reuse the state after Commit // (see doc-comment in Commit, absolute footgun) diff --git a/op-chain-ops/foundry/sourcefs.go b/op-chain-ops/foundry/sourcefs.go index eecf787e123..aac65ab3d2c 100644 --- a/op-chain-ops/foundry/sourcefs.go +++ b/op-chain-ops/foundry/sourcefs.go @@ -5,12 +5,12 @@ import ( "errors" "fmt" "io/fs" + "maps" "path" "path/filepath" + "slices" "strings" - "golang.org/x/exp/maps" - "github.com/ethereum-optimism/optimism/op-chain-ops/srcmap" ) @@ -122,7 +122,7 @@ func (s *SourceMapFS) ReadSourceIDs(path string, contract string, compilerVersio return nil, errors.New("no known build, unspecified compiler version") } if len(byCompilerVersion) > 1 { - return nil, fmt.Errorf("no compiler version specified, and more than one option: %s", strings.Join(maps.Keys(byCompilerVersion), ", ")) + return nil, fmt.Errorf("no compiler version specified, and more than one option: %s", strings.Join(slices.Collect(maps.Keys(byCompilerVersion)), ", ")) } // select the only remaining entry for _, v := range byCompilerVersion { @@ -140,7 +140,7 @@ func (s *SourceMapFS) ReadSourceIDs(path string, contract string, compilerVersio return nil, errors.New("no known build, unspecified profile") } if len(byProfile) > 1 { - return nil, fmt.Errorf("no profile specified, and more than one option: %s", strings.Join(maps.Keys(byProfile), ", ")) + return nil, fmt.Errorf("no profile specified, and more than one option: %s", strings.Join(slices.Collect(maps.Keys(byProfile)), ", ")) } // select the only remaining entry for _, v := range byProfile { diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index c835e437f15..6ebfdd96bfb 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -86,22 +86,6 @@ type DevDeployConfig struct { FundDevAccounts bool `json:"fundDevAccounts"` } -type RevenueShareDeployConfig struct { - UseRevenueShare bool `json:"useRevenueShare"` - ChainFeesRecipient common.Address `json:"chainFeesRecipient"` -} - -var _ ConfigChecker = (*RevenueShareDeployConfig)(nil) - -func (d *RevenueShareDeployConfig) Check(log log.Logger) error { - if d.UseRevenueShare { - if d.ChainFeesRecipient == (common.Address{}) { - return fmt.Errorf("%w: ChainFeesRecipient cannot be address(0)", ErrInvalidDeployConfig) - } - } - return nil -} - type L2GenesisBlockDeployConfig struct { L2GenesisBlockNonce hexutil.Uint64 `json:"l2GenesisBlockNonce"` L2GenesisBlockGasLimit hexutil.Uint64 `json:"l2GenesisBlockGasLimit"` @@ -407,6 +391,9 @@ type UpgradeScheduleDeployConfig struct { // L2GenesisJovianTimeOffset is the number of seconds after genesis block that the Jovian hard fork activates. // Set it to 0 to activate at genesis. Nil to disable Jovian. L2GenesisJovianTimeOffset *hexutil.Uint64 `json:"l2GenesisJovianTimeOffset,omitempty"` + // L2GenesisKarstTimeOffset is the number of seconds after genesis block that the Karst hard fork activates. + // Set it to 0 to activate at genesis. Nil to disable Karst. + L2GenesisKarstTimeOffset *hexutil.Uint64 `json:"l2GenesisKarstTimeOffset,omitempty"` // 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. L2GenesisInteropTimeOffset *hexutil.Uint64 `json:"l2GenesisInteropTimeOffset,omitempty"` @@ -468,6 +455,8 @@ func (d *UpgradeScheduleDeployConfig) ForkTimeOffset(fork rollup.ForkName) *uint return (*uint64)(d.L2GenesisIsthmusTimeOffset) case forks.Jovian: return (*uint64)(d.L2GenesisJovianTimeOffset) + case forks.Karst: + return (*uint64)(d.L2GenesisKarstTimeOffset) case forks.Interop: return (*uint64)(d.L2GenesisInteropTimeOffset) default: @@ -495,6 +484,8 @@ func (d *UpgradeScheduleDeployConfig) SetForkTimeOffset(fork rollup.ForkName, of d.L2GenesisIsthmusTimeOffset = (*hexutil.Uint64)(offset) case forks.Jovian: d.L2GenesisJovianTimeOffset = (*hexutil.Uint64)(offset) + case forks.Karst: + d.L2GenesisKarstTimeOffset = (*hexutil.Uint64)(offset) case forks.Interop: d.L2GenesisInteropTimeOffset = (*hexutil.Uint64)(offset) default: @@ -571,6 +562,10 @@ func (d *UpgradeScheduleDeployConfig) JovianTime(genesisTime uint64) *uint64 { return offsetToUpgradeTime(d.L2GenesisJovianTimeOffset, genesisTime) } +func (d *UpgradeScheduleDeployConfig) KarstTime(genesisTime uint64) *uint64 { + return offsetToUpgradeTime(d.L2GenesisKarstTimeOffset, genesisTime) +} + func (d *UpgradeScheduleDeployConfig) InteropTime(genesisTime uint64) *uint64 { return offsetToUpgradeTime(d.L2GenesisInteropTimeOffset, genesisTime) } @@ -605,6 +600,7 @@ func (d *UpgradeScheduleDeployConfig) forks() []Fork { {L2GenesisTimeOffset: d.L2GenesisHoloceneTimeOffset, Name: string(L2AllocsHolocene)}, {L2GenesisTimeOffset: d.L2GenesisIsthmusTimeOffset, Name: string(L2AllocsIsthmus)}, {L2GenesisTimeOffset: d.L2GenesisJovianTimeOffset, Name: string(L2AllocsJovian)}, + {L2GenesisTimeOffset: d.L2GenesisKarstTimeOffset, Name: string(L2AllocsKarst)}, {L2GenesisTimeOffset: d.L2GenesisInteropTimeOffset, Name: string(L2AllocsInterop)}, } } @@ -786,7 +782,6 @@ type L2InitializationConfig struct { L2CoreDeployConfig FeeMarketConfig AltDADeployConfig - RevenueShareDeployConfig } func (d *L2InitializationConfig) Check(log log.Logger) error { @@ -818,24 +813,11 @@ type DevL1DeployConfig struct { // SuperchainL1DeployConfig configures parameters of the superchain-wide deployed contracts to L1. // This deployment is global, and can be reused between L2s that target the same superchain. type SuperchainL1DeployConfig struct { - // RequiredProtocolVersion indicates the protocol version that - // nodes are required to adopt, to stay in sync with the network. - RequiredProtocolVersion params.ProtocolVersion `json:"requiredProtocolVersion"` - // RequiredProtocolVersion indicates the protocol version that - // nodes are recommended to adopt, to stay in sync with the network. - RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"` - // SuperchainConfigGuardian represents the GUARDIAN account in the SuperchainConfig. Has the ability to pause withdrawals. SuperchainConfigGuardian common.Address `json:"superchainConfigGuardian"` } func (d *SuperchainL1DeployConfig) Check(log log.Logger) error { - if d.RequiredProtocolVersion == (params.ProtocolVersion{}) { - log.Warn("RequiredProtocolVersion is empty") - } - if d.RecommendedProtocolVersion == (params.ProtocolVersion{}) { - log.Warn("RecommendedProtocolVersion is empty") - } if d.SuperchainConfigGuardian == (common.Address{}) { return fmt.Errorf("%w: SuperchainConfigGuardian cannot be address(0)", ErrInvalidDeployConfig) } @@ -955,8 +937,6 @@ type L1DependenciesConfig struct { // DAChallengeProxy represents the L1 address of the DataAvailabilityChallenge contract. DAChallengeProxy common.Address `json:"daChallengeProxy"` - - ProtocolVersionsProxy common.Address `json:"protocolVersionsProxy"` } // DependencyContext is the contextual configuration needed to verify the L1 dependencies, @@ -1144,29 +1124,29 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *eth.BlockRef, l2GenesisBlockHa L2Time: l1StartBlock.Time, SystemConfig: d.GenesisSystemConfig(), }, - BlockTime: d.L2BlockTime, - MaxSequencerDrift: d.MaxSequencerDrift, - SeqWindowSize: d.SequencerWindowSize, - ChannelTimeoutBedrock: d.ChannelTimeoutBedrock, - L1ChainID: new(big.Int).SetUint64(d.L1ChainID), - L2ChainID: new(big.Int).SetUint64(d.L2ChainID), - BatchInboxAddress: d.BatchInboxAddress, - DepositContractAddress: d.OptimismPortalProxy, - L1SystemConfigAddress: d.SystemConfigProxy, - RegolithTime: d.RegolithTime(l1StartTime), - CanyonTime: d.CanyonTime(l1StartTime), - DeltaTime: d.DeltaTime(l1StartTime), - EcotoneTime: d.EcotoneTime(l1StartTime), - FjordTime: d.FjordTime(l1StartTime), - GraniteTime: d.GraniteTime(l1StartTime), - HoloceneTime: d.HoloceneTime(l1StartTime), - PectraBlobScheduleTime: d.PectraBlobScheduleTime(l1StartTime), - IsthmusTime: d.IsthmusTime(l1StartTime), - JovianTime: d.JovianTime(l1StartTime), - InteropTime: d.InteropTime(l1StartTime), - ProtocolVersionsAddress: d.ProtocolVersionsProxy, - AltDAConfig: altDA, - ChainOpConfig: chainOpConfig, + BlockTime: d.L2BlockTime, + MaxSequencerDrift: d.MaxSequencerDrift, + SeqWindowSize: d.SequencerWindowSize, + ChannelTimeoutBedrock: d.ChannelTimeoutBedrock, + L1ChainID: new(big.Int).SetUint64(d.L1ChainID), + L2ChainID: new(big.Int).SetUint64(d.L2ChainID), + BatchInboxAddress: d.BatchInboxAddress, + DepositContractAddress: d.OptimismPortalProxy, + L1SystemConfigAddress: d.SystemConfigProxy, + RegolithTime: d.RegolithTime(l1StartTime), + CanyonTime: d.CanyonTime(l1StartTime), + DeltaTime: d.DeltaTime(l1StartTime), + EcotoneTime: d.EcotoneTime(l1StartTime), + FjordTime: d.FjordTime(l1StartTime), + GraniteTime: d.GraniteTime(l1StartTime), + HoloceneTime: d.HoloceneTime(l1StartTime), + PectraBlobScheduleTime: d.PectraBlobScheduleTime(l1StartTime), + IsthmusTime: d.IsthmusTime(l1StartTime), + JovianTime: d.JovianTime(l1StartTime), + KarstTime: d.KarstTime(l1StartTime), + InteropTime: d.InteropTime(l1StartTime), + AltDAConfig: altDA, + ChainOpConfig: chainOpConfig, }, nil } @@ -1229,15 +1209,12 @@ type L1Deployments struct { OptimismMintableERC20Factory common.Address `json:"OptimismMintableERC20Factory"` OptimismMintableERC20FactoryProxy common.Address `json:"OptimismMintableERC20FactoryProxy"` OptimismPortal common.Address `json:"OptimismPortal"` - OptimismPortalInterop common.Address `json:"OptimismPortalInterop"` OptimismPortalProxy common.Address `json:"OptimismPortalProxy"` ETHLockbox common.Address `json:"ETHLockbox"` ETHLockboxProxy common.Address `json:"ETHLockboxProxy"` ProxyAdmin common.Address `json:"ProxyAdmin"` SystemConfig common.Address `json:"SystemConfig"` SystemConfigProxy common.Address `json:"SystemConfigProxy"` - ProtocolVersions common.Address `json:"ProtocolVersions"` - ProtocolVersionsProxy common.Address `json:"ProtocolVersionsProxy"` DataAvailabilityChallenge common.Address `json:"DataAvailabilityChallenge"` DataAvailabilityChallengeProxy common.Address `json:"DataAvailabilityChallengeProxy"` } @@ -1257,15 +1234,12 @@ func CreateL1DeploymentsFromContracts(contracts *addresses.L1Contracts) *L1Deplo OptimismMintableERC20Factory: contracts.OptimismMintableErc20FactoryImpl, OptimismMintableERC20FactoryProxy: contracts.OptimismMintableErc20FactoryProxy, OptimismPortal: contracts.OptimismPortalImpl, - OptimismPortalInterop: contracts.OptimismPortalInteropImpl, OptimismPortalProxy: contracts.OptimismPortalProxy, ETHLockbox: contracts.EthLockboxImpl, ETHLockboxProxy: contracts.EthLockboxProxy, ProxyAdmin: contracts.OpChainProxyAdminImpl, SystemConfig: contracts.SystemConfigImpl, SystemConfigProxy: contracts.SystemConfigProxy, - ProtocolVersions: contracts.ProtocolVersionsImpl, - ProtocolVersionsProxy: contracts.ProtocolVersionsProxy, DataAvailabilityChallenge: contracts.AltDAChallengeImpl, DataAvailabilityChallengeProxy: contracts.AltDAChallengeProxy, } diff --git a/op-chain-ops/genesis/config_test.go b/op-chain-ops/genesis/config_test.go index 98fe0e1f9e2..daef54bbe63 100644 --- a/op-chain-ops/genesis/config_test.go +++ b/op-chain-ops/genesis/config_test.go @@ -142,8 +142,6 @@ func TestL1Deployments(t *testing.T) { require.NotEqual(t, deployments.ProxyAdmin, common.Address{}) require.NotEqual(t, deployments.SystemConfig, common.Address{}) require.NotEqual(t, deployments.SystemConfigProxy, common.Address{}) - require.NotEqual(t, deployments.ProtocolVersions, common.Address{}) - require.NotEqual(t, deployments.ProtocolVersionsProxy, common.Address{}) require.Equal(t, "AddressManager", deployments.GetName(deployments.AddressManager)) require.Equal(t, "OptimismPortalProxy", deployments.GetName(deployments.OptimismPortalProxy)) @@ -209,7 +207,8 @@ func TestUpgradeScheduleDeployConfig_SolidityForkNumber(t *testing.T) { {forks.Holocene, 5}, {forks.Isthmus, 6}, {forks.Jovian, 7}, - {forks.Interop, 8}, + {forks.Karst, 8}, + {forks.Interop, 9}, } for _, tt := range tests { var d UpgradeScheduleDeployConfig diff --git a/op-chain-ops/genesis/genesis.go b/op-chain-ops/genesis/genesis.go index 0e0daabf7cb..082a0a831f6 100644 --- a/op-chain-ops/genesis/genesis.go +++ b/op-chain-ops/genesis/genesis.go @@ -7,7 +7,6 @@ import ( "time" "github.com/ethereum-optimism/optimism/op-core/predeploys" - "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -24,7 +23,7 @@ const defaultGasLimit = 30_000_000 var HoloceneExtraData = eip1559.EncodeHoloceneExtraData(250, 6) // MinBaseFeeExtraData represents the default extra data for Jovian-genesis chains. -var MinBaseFeeExtraData = rollup.EncodeJovianExtraData(250, 6, 0) +var MinBaseFeeExtraData = eip1559.EncodeJovianExtraData(250, 6, 0) // NewL2Genesis will create a new L2 genesis func NewL2Genesis(config *DeployConfig, l1StartHeader *eth.BlockRef) (*core.Genesis, error) { @@ -77,6 +76,7 @@ func NewL2Genesis(config *DeployConfig, l1StartHeader *eth.BlockRef) (*core.Gene HoloceneTime: config.HoloceneTime(l1StartTime), IsthmusTime: config.IsthmusTime(l1StartTime), JovianTime: config.JovianTime(l1StartTime), + KarstTime: config.KarstTime(l1StartTime), PragueTime: config.IsthmusTime(l1StartTime), InteropTime: config.InteropTime(l1StartTime), Optimism: ¶ms.OptimismConfig{ diff --git a/op-chain-ops/genesis/layer_two.go b/op-chain-ops/genesis/layer_two.go index 20181596451..2f8d88c09a2 100644 --- a/op-chain-ops/genesis/layer_two.go +++ b/op-chain-ops/genesis/layer_two.go @@ -30,6 +30,7 @@ const ( L2AllocsHolocene L2AllocsMode = "holocene" L2AllocsIsthmus L2AllocsMode = "isthmus" L2AllocsJovian L2AllocsMode = "jovian" + L2AllocsKarst L2AllocsMode = "karst" L2AllocsInterop L2AllocsMode = "interop" ) diff --git a/op-chain-ops/genesis/testdata/l1-deployments.json b/op-chain-ops/genesis/testdata/l1-deployments.json index 8dd43832d88..80831e0e7e1 100644 --- a/op-chain-ops/genesis/testdata/l1-deployments.json +++ b/op-chain-ops/genesis/testdata/l1-deployments.json @@ -16,7 +16,5 @@ "OptimismPortalProxy": "0xaC425EECd4Fd8E9E669a62906D99aF89B9951516", "ProxyAdmin": "0x3218c3b0dC0386BAe83A58E5F908c4b070210b4F", "SystemConfig": "0x36bAcDD96F28e1ac0780bB9CbE6e20780840730F", - "SystemConfigProxy": "0x14065A373936533A0c88b7986CADabDD62d471e6", - "ProtocolVersions": "0x883C06a27D76B1CEbdf7EB376f5556c355afC8e5", - "ProtocolVersionsProxy": "0x3732a4D4Ab006cA4825822baEA1569A107683fa1" + "SystemConfigProxy": "0x14065A373936533A0c88b7986CADabDD62d471e6" } diff --git a/op-chain-ops/genesis/testdata/test-deploy-config-full.json b/op-chain-ops/genesis/testdata/test-deploy-config-full.json index 55b3df25c7f..fd5b1c40003 100644 --- a/op-chain-ops/genesis/testdata/test-deploy-config-full.json +++ b/op-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -54,7 +54,6 @@ "systemConfigProxy": "0x4200000000000000000000000000000000000061", "optimismPortalProxy": "0x4200000000000000000000000000000000000062", "proxyAdminOwner": "0x0000000000000000000000000000000000000222", - "protocolVersionsProxy": "0x0000000000000000000000000000000000000000", "gasPriceOracleBaseFeeScalar": 0, "gasPriceOracleBlobBaseFeeScalar": 0, "gasPriceOracleOperatorFeeScalar": 0, @@ -83,8 +82,6 @@ "preimageOracleMinProposalSize": 1800000, "preimageOracleChallengePeriod": 86400, "systemConfigStartBlock": 0, - "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", - "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", "proofMaturityDelaySeconds": 12, "disputeGameFinalityDelaySeconds": 6, "respectedGameType": 0, @@ -100,7 +97,5 @@ "daChallengeProxy": "0x0000000000000000000000000000000000000000", "daChallengeWindow": 0, "daResolveWindow": 0, - "daResolverRefundPercentage": 0, - "useRevenueShare": true, - "chainFeesRecipient": "0x0000000000000000000000000000000000000444" + "daResolverRefundPercentage": 0 } diff --git a/op-chain-ops/interopgen/config/full_config_set.go b/op-chain-ops/interopgen/config/full_config_set.go new file mode 100644 index 00000000000..6b2d03d642c --- /dev/null +++ b/op-chain-ops/interopgen/config/full_config_set.go @@ -0,0 +1,96 @@ +package config + +import ( + "context" + "errors" + "fmt" + + coredepset "github.com/ethereum-optimism/optimism/op-core/interop/depset" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type FullConfigSet interface { + RollupConfigSet + coredepset.DependencySet +} + +type FullConfigSetMerged struct { + RollupConfigSet + coredepset.DependencySet +} + +func (f FullConfigSetMerged) HasChain(chainID eth.ChainID) bool { + // Any is ok, since the FullConfigSetMerged constructor checks that the two sets contain the same chains. + return f.DependencySet.HasChain(chainID) +} + +func (f FullConfigSetMerged) Chains() []eth.ChainID { + // Any is ok, since the FullConfigSetMerged constructor checks that the two sets contain the same chains. + return f.DependencySet.Chains() +} + +func (f FullConfigSetMerged) LoadFullConfigSet(_ context.Context) (FullConfigSet, error) { + return f, f.CheckChains() +} + +// NewFullConfigSetMerged creates a new FullConfigSetMerged from a RollupConfigSet and a DependencySet. +// It checks that the two sets contain the same chains. +func NewFullConfigSetMerged(rollupConfigSet RollupConfigSet, dependencySet coredepset.DependencySet) (FullConfigSetMerged, error) { + f := FullConfigSetMerged{ + RollupConfigSet: rollupConfigSet, + DependencySet: dependencySet, + } + return f, f.CheckChains() +} + +func (f FullConfigSetMerged) CheckChains() error { + if f.RollupConfigSet == nil { + return errors.New("missing RollupConfigSet") + } + if f.DependencySet == nil { + return errors.New("missing DependencySet") + } + rollupChains := make(map[eth.ChainID]struct{}) + for _, chainID := range f.RollupConfigSet.Chains() { + rollupChains[chainID] = struct{}{} + } + + dependencyChains := make(map[eth.ChainID]struct{}) + for _, chainID := range f.DependencySet.Chains() { + dependencyChains[chainID] = struct{}{} + } + + // Check that both sets contain the same chains + for chainID := range rollupChains { + if _, ok := dependencyChains[chainID]; !ok { + return fmt.Errorf("chain %s in rollup config set but not in dependency set", chainID) + } + } + for chainID := range dependencyChains { + if _, ok := rollupChains[chainID]; !ok { + return fmt.Errorf("chain %s in dependency set but not in rollup config set", chainID) + } + } + return nil +} + +type FullConfigSetSource interface { + LoadFullConfigSet(ctx context.Context) (FullConfigSet, error) +} + +type FullConfigSetSourceMerged struct { + RollupConfigSetSource + coredepset.DependencySetSource +} + +func (l *FullConfigSetSourceMerged) LoadFullConfigSet(ctx context.Context) (FullConfigSet, error) { + rollupConfigSet, err := l.RollupConfigSetSource.LoadRollupConfigSet(ctx) + if err != nil { + return nil, fmt.Errorf("failed to load rollup config set: %w", err) + } + dependencySet, err := l.DependencySetSource.LoadDependencySet() + if err != nil { + return nil, fmt.Errorf("failed to load dependency set: %w", err) + } + return NewFullConfigSetMerged(rollupConfigSet, dependencySet) +} diff --git a/op-chain-ops/interopgen/config/json.go b/op-chain-ops/interopgen/config/json.go new file mode 100644 index 00000000000..20922dfc949 --- /dev/null +++ b/op-chain-ops/interopgen/config/json.go @@ -0,0 +1,96 @@ +package config + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +var ( + _ RollupConfigSetSource = (*JSONRollupConfigSetLoader)(nil) + _ RollupConfigSetSource = (*JSONRollupConfigsLoader)(nil) +) + +type JSONRollupConfigSetLoader struct { + Path string +} + +func (j *JSONRollupConfigSetLoader) LoadRollupConfigSet(ctx context.Context) (RollupConfigSet, error) { + f, err := os.Open(j.Path) + if err != nil { + return nil, fmt.Errorf("failed to open rollup config set: %w", err) + } + defer f.Close() + dec := json.NewDecoder(f) + var out StaticRollupConfigSet + if err := dec.Decode(&out); err != nil { + return nil, fmt.Errorf("failed to decode rollup config set: %w", err) + } + return &out, nil +} + +// JSONRollupConfigsLoader loads a set of op-node rollup.json configs into +// a supervisor rollup config set. +// The [PathPattern] is a glob pattern that matches the rollup.json files, e.g. +// "configs/rollup-*.json". See https://pkg.go.dev/path/filepath#Glob for more details. +// Because the [rollup.Config] doesn't include the genesis L1 block timestamp, they are +// queried from the L1 RPC at URL [L1RPCURL]. +type JSONRollupConfigsLoader struct { + PathPattern string + L1RPCURL string +} + +func (j *JSONRollupConfigsLoader) LoadRollupConfigSet(ctx context.Context) (RollupConfigSet, error) { + client, err := ethclient.Dial(j.L1RPCURL) + if err != nil { + return nil, fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + defer client.Close() + return j.loadRollupConfigSet(ctx, client) +} + +type headerByHashClient interface { + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) +} + +// loadRollupConfigSet splits out the core logic for better testing. +func (j *JSONRollupConfigsLoader) loadRollupConfigSet(ctx context.Context, client headerByHashClient) (RollupConfigSet, error) { + matches, err := filepath.Glob(j.PathPattern) + if err != nil { + return nil, fmt.Errorf("failed to glob files: %w", err) + } + + configs := make(map[eth.ChainID]*StaticRollupConfig) + + for _, path := range matches { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("failed to open rollup config %s: %w", path, err) + } + defer file.Close() + + var cfg rollup.Config + if err = cfg.ParseRollupConfig(file); err != nil { + return nil, fmt.Errorf("failed to parse rollup config %s: %w", path, err) + } + chainID := eth.ChainIDFromBig(cfg.L2ChainID) + + l1Genesis, err := client.HeaderByHash(ctx, cfg.Genesis.L1.Hash) + if err != nil { + return nil, fmt.Errorf("failed to get L1 genesis header for hash %s (chainID: %s): %w", cfg.Genesis.L1.Hash, chainID, err) + } + + configs[chainID] = StaticRollupConfigFromRollupConfig(&cfg, l1Genesis.Time) + } + + return StaticRollupConfigSet(configs), nil +} diff --git a/op-chain-ops/interopgen/config/json_test.go b/op-chain-ops/interopgen/config/json_test.go new file mode 100644 index 00000000000..879c9fa8fe9 --- /dev/null +++ b/op-chain-ops/interopgen/config/json_test.go @@ -0,0 +1,115 @@ +package config + +import ( + "context" + "fmt" + "math/big" + "path/filepath" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// mockHeaderClient implements headerByHashClient for testing +type mockHeaderClient struct { + headers map[common.Hash]*types.Header +} + +func (m *mockHeaderClient) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + if header, ok := m.headers[hash]; ok { + return header, nil + } + return nil, fmt.Errorf("header not found for %s", hash.Hex()) +} + +func TestJSONRollupConfigsLoader_LoadRollupConfigSet(t *testing.T) { + // Create mock headers with timestamps matching our test files + mockHeaders := map[common.Hash]*types.Header{ + common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"): { + Number: big.NewInt(1), + Time: 1000, + }, + common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000003"): { + Number: big.NewInt(2), + Time: 2000, + }, + } + + loader := &JSONRollupConfigsLoader{ + PathPattern: filepath.Join("testfiles", "rollup-*.json"), + } + + configSet, err := loader.loadRollupConfigSet(context.Background(), &mockHeaderClient{headers: mockHeaders}) + require.NoError(t, err) + + // Verify the configs were loaded correctly + require.True(t, configSet.HasChain(eth.ChainIDFromUInt64(10))) + require.True(t, configSet.HasChain(eth.ChainIDFromUInt64(20))) + + // Check first chain config + genesis1 := configSet.Genesis(eth.ChainIDFromUInt64(10)) + require.Equal(t, uint64(1), genesis1.L1.Number) + require.Equal(t, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), genesis1.L1.Hash) + require.Equal(t, uint64(1000), genesis1.L1.Timestamp) + require.Equal(t, uint64(0), genesis1.L2.Number) + require.Equal(t, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002"), genesis1.L2.Hash) + require.Equal(t, uint64(1000), genesis1.L2.Timestamp) + + // Check second chain config + genesis2 := configSet.Genesis(eth.ChainIDFromUInt64(20)) + require.Equal(t, uint64(2), genesis2.L1.Number) + require.Equal(t, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000003"), genesis2.L1.Hash) + require.Equal(t, uint64(2000), genesis2.L1.Timestamp) + require.Equal(t, uint64(0), genesis2.L2.Number) + require.Equal(t, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000004"), genesis2.L2.Hash) + require.Equal(t, uint64(2000), genesis2.L2.Timestamp) +} + +func TestJSONRollupConfigSetLoader_LoadRollupConfigSet(t *testing.T) { + loader := &JSONRollupConfigSetLoader{ + Path: filepath.Join("testfiles", "rollup_set.json"), + } + + configSet, err := loader.LoadRollupConfigSet(context.Background()) + require.NoError(t, err) + + // Verify both chains are present + require.True(t, configSet.HasChain(eth.ChainIDFromUInt64(10))) + require.True(t, configSet.HasChain(eth.ChainIDFromUInt64(20))) + + // Check first chain config (chain ID 10) + genesis1 := configSet.Genesis(eth.ChainIDFromUInt64(10)) + require.Equal(t, uint64(1), genesis1.L1.Number) + require.Equal(t, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), genesis1.L1.Hash) + require.Equal(t, uint64(1000), genesis1.L1.Timestamp) + require.Equal(t, uint64(0), genesis1.L2.Number) + require.Equal(t, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002"), genesis1.L2.Hash) + require.Equal(t, uint64(1100), genesis1.L2.Timestamp) + + // Check second chain config (chain ID 20) + genesis2 := configSet.Genesis(eth.ChainIDFromUInt64(20)) + require.Equal(t, uint64(2), genesis2.L1.Number) + require.Equal(t, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000003"), genesis2.L1.Hash) + require.Equal(t, uint64(2000), genesis2.L1.Timestamp) + require.Equal(t, uint64(0), genesis2.L2.Number) + require.Equal(t, common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000004"), genesis2.L2.Hash) + require.Equal(t, uint64(2100), genesis2.L2.Timestamp) + + // Test Interop activation checks + require.False(t, configSet.IsInterop(eth.ChainIDFromUInt64(10), 1500)) + require.True(t, configSet.IsInterop(eth.ChainIDFromUInt64(10), 2500)) + require.False(t, configSet.IsInterop(eth.ChainIDFromUInt64(20), 2500)) + require.True(t, configSet.IsInterop(eth.ChainIDFromUInt64(20), 3500)) + + // Test Interop activation block checks + require.False(t, configSet.IsInteropActivationBlock(eth.ChainIDFromUInt64(10), 1998)) + require.True(t, configSet.IsInteropActivationBlock(eth.ChainIDFromUInt64(10), 2000)) + require.False(t, configSet.IsInteropActivationBlock(eth.ChainIDFromUInt64(10), 2002)) + require.False(t, configSet.IsInteropActivationBlock(eth.ChainIDFromUInt64(20), 2998)) + require.True(t, configSet.IsInteropActivationBlock(eth.ChainIDFromUInt64(20), 3000)) + require.False(t, configSet.IsInteropActivationBlock(eth.ChainIDFromUInt64(20), 3002)) +} diff --git a/op-chain-ops/interopgen/config/registry_config.go b/op-chain-ops/interopgen/config/registry_config.go new file mode 100644 index 00000000000..445a3b69dda --- /dev/null +++ b/op-chain-ops/interopgen/config/registry_config.go @@ -0,0 +1,70 @@ +package config + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/superchain" + + coredepset "github.com/ethereum-optimism/optimism/op-core/interop/depset" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type RegistryFullConfigSetSource struct { + l1RPCURL string + rollupCfgs []*rollup.Config + dependencySet coredepset.DependencySet +} + +func NewRegistryFullConfigSetSource(l1RPCURL string, networks []string) (*RegistryFullConfigSetSource, error) { + rollupCfgs := make([]*rollup.Config, 0, len(networks)) + var dependencySet coredepset.DependencySet + for _, network := range networks { + chainID, err := superchain.ChainIDByName(network) + if err != nil { + return nil, err + } + // Use the dependency set from the first chain. + // superchain-registry has checks to ensure consistency for all chains in the same set + if dependencySet == nil { + depSet, err := coredepset.FromRegistry(eth.ChainIDFromUInt64(chainID)) + if err != nil { + return nil, fmt.Errorf("failed to load dependency set for network %s: %w", network, err) + } + dependencySet = depSet + } + + rollupCfg, err := rollup.LoadOPStackRollupConfig(chainID) + if err != nil { + return nil, fmt.Errorf("failed to load rollup config for network %s: %w", network, err) + } + + rollupCfgs = append(rollupCfgs, rollupCfg) + } + return &RegistryFullConfigSetSource{ + l1RPCURL: l1RPCURL, + rollupCfgs: rollupCfgs, + dependencySet: dependencySet, + }, nil +} + +func (s *RegistryFullConfigSetSource) LoadFullConfigSet(ctx context.Context) (FullConfigSet, error) { + client, err := ethclient.Dial(s.l1RPCURL) + if err != nil { + return nil, fmt.Errorf("failed to connect to L1 RPC: %w", err) + } + defer client.Close() + + rollupConfigs := make(map[eth.ChainID]*StaticRollupConfig, len(s.rollupCfgs)) + for _, rollupCfg := range s.rollupCfgs { + l1Genesis, err := client.HeaderByHash(ctx, rollupCfg.Genesis.L1.Hash) + if err != nil { + return nil, fmt.Errorf("failed to get L1 genesis header for hash %s (chainID: %s): %w", rollupCfg.Genesis.L1.Hash, rollupCfg.L2ChainID, err) + } + + rollupConfigs[eth.ChainIDFromBig(rollupCfg.L2ChainID)] = StaticRollupConfigFromRollupConfig(rollupCfg, l1Genesis.Time) + } + return NewFullConfigSetMerged(StaticRollupConfigSet(rollupConfigs), s.dependencySet) +} diff --git a/op-chain-ops/interopgen/config/rollup_config_set.go b/op-chain-ops/interopgen/config/rollup_config_set.go new file mode 100644 index 00000000000..9e61b7e598e --- /dev/null +++ b/op-chain-ops/interopgen/config/rollup_config_set.go @@ -0,0 +1,166 @@ +package config + +import ( + "context" + + coredepset "github.com/ethereum-optimism/optimism/op-core/interop/depset" + "github.com/ethereum-optimism/optimism/op-core/interop/messages" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type RollupConfigSetSource interface { + LoadRollupConfigSet(ctx context.Context) (RollupConfigSet, error) +} + +// RollupConfigSet provides access to minimal rollup configuration for a set of chains. +// Implementations should panic if any of the methods, besides HasChain, are called for a chain +// that is not part of the rollup config set. +type RollupConfigSet interface { + // HasChain returns true if the chain is part of the rollup config set. + HasChain(chainID eth.ChainID) bool + + // Chains returns the list of chains in the rollup config set. + Chains() []eth.ChainID + + // Genesis returns the genesis configuration for the given chain. + // It panics if the chain is not part of the rollup config set. + // Use HasChain first to check if the chain is part of the rollup config set if + // guarantee of existence isn't provided by the caller context. + Genesis(chainID eth.ChainID) Genesis + + coredepset.ActivationConfig +} + +type StaticRollupConfigSet map[eth.ChainID]*StaticRollupConfig + +// StaticRollupConfig provides the rollup information relevant for Interop. +// It's a trimmed down version of [rollup.Config]. +type StaticRollupConfig struct { + // Genesis anchor point of the rollup + Genesis Genesis `json:"genesis"` + + // Seconds per L2 block + BlockTime uint64 `json:"block_time"` + + // InteropTime sets the activation time for the Interop network upgrade. + // Active if InteropTime != nil && L2 block timestamp >= *InteropTime, inactive otherwise. + InteropTime *uint64 `json:"interop_time,omitempty"` +} + +// Genesis provides the genesis information relevant for Interop. +// It's a trimmed down version of [rollup.Genesis]. +type Genesis struct { + // The L1 block that the rollup starts *after* (no derived transactions) + L1 messages.BlockSeal `json:"l1"` + // The L2 block the rollup starts from (no transactions, pre-configured state, no parent) + L2 messages.BlockSeal `json:"l2"` +} + +func (c *StaticRollupConfigSet) LoadRollupConfigSet(ctx context.Context) (RollupConfigSet, error) { + return c, nil +} + +var ( + _ RollupConfigSetSource = (*StaticRollupConfigSet)(nil) + _ RollupConfigSet = (*StaticRollupConfigSet)(nil) +) + +func GenesisFromRollupGenesis(genesis *rollup.Genesis, l1Time uint64) Genesis { + return Genesis{ + L1: messages.BlockSeal{ + Hash: genesis.L1.Hash, + Number: genesis.L1.Number, + Timestamp: l1Time, + }, + L2: messages.BlockSeal{ + Hash: genesis.L2.Hash, + Number: genesis.L2.Number, + Timestamp: genesis.L2Time, + }, + } +} + +func StaticRollupConfigFromRollupConfig(cfg *rollup.Config, l1Time uint64) *StaticRollupConfig { + return &StaticRollupConfig{ + Genesis: GenesisFromRollupGenesis(&cfg.Genesis, l1Time), + BlockTime: cfg.BlockTime, + InteropTime: cfg.InteropTime, + } +} + +// IsInterop returns true if the Interop hardfork is active at or past the given timestamp. +func (c *StaticRollupConfig) IsInterop(ts uint64) bool { + return c.InteropTime != nil && ts >= *c.InteropTime +} + +func (c *StaticRollupConfig) IsInteropActivationBlock(ts uint64) bool { + return c.IsInterop(ts) && + ts >= c.BlockTime && + !c.IsInterop(ts-c.BlockTime) +} + +func NewStaticRollupConfigSet(cfgs map[eth.ChainID]*StaticRollupConfig) StaticRollupConfigSet { + return cfgs +} + +type ChainTimestamper interface { + Timestamp(id eth.ChainID) uint64 +} + +type StaticTimestamp uint64 + +func (ts StaticTimestamp) Timestamp(eth.ChainID) uint64 { return uint64(ts) } + +func StaticRollupConfigSetFromRollupConfigMap(rcfgs map[eth.ChainID]*rollup.Config, l1Timestamps ChainTimestamper) StaticRollupConfigSet { + cfgs := make(map[eth.ChainID]*StaticRollupConfig, len(rcfgs)) + for id, cfg := range rcfgs { + cfgs[id] = StaticRollupConfigFromRollupConfig(cfg, l1Timestamps.Timestamp(id)) + } + return NewStaticRollupConfigSet(cfgs) +} + +// HasChain returns true if the chain is part of the rollup config set. +func (s StaticRollupConfigSet) HasChain(chainID eth.ChainID) bool { + _, ok := s[chainID] + return ok +} + +// Chains returns the list of chains in the rollup config set. +func (s StaticRollupConfigSet) Chains() []eth.ChainID { + ids := make([]eth.ChainID, 0, len(s)) + for id := range s { + ids = append(ids, id) + } + return ids +} + +// Genesis returns the genesis configuration for the given chain. +// Panics if the chain is not part of the rollup config set. +func (s StaticRollupConfigSet) Genesis(chainID eth.ChainID) Genesis { + cfg, ok := s[chainID] + if !ok { + panic("chain not found in rollup config set") + } + return cfg.Genesis +} + +// IsInterop returns true if the Interop hardfork is active for the given chain at the given timestamp. +// Panics if the chain is not part of the rollup config set. +func (s StaticRollupConfigSet) IsInterop(chainID eth.ChainID, ts uint64) bool { + cfg, ok := s[chainID] + if !ok { + panic("chain not found in rollup config set") + } + return cfg.IsInterop(ts) +} + +// IsInteropActivationBlock returns true if the given timestamp is the activation block for the Interop hardfork. +// Panics if the chain is not part of the rollup config set. +func (s StaticRollupConfigSet) IsInteropActivationBlock(chainID eth.ChainID, ts uint64) bool { + cfg, ok := s[chainID] + if !ok { + panic("chain not found in rollup config set") + } + return cfg.IsInteropActivationBlock(ts) +} diff --git a/op-chain-ops/interopgen/config/testfiles/rollup-10.json b/op-chain-ops/interopgen/config/testfiles/rollup-10.json new file mode 100644 index 00000000000..a966c7dbb20 --- /dev/null +++ b/op-chain-ops/interopgen/config/testfiles/rollup-10.json @@ -0,0 +1,28 @@ +{ + "genesis": { + "l1": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "number": 1 + }, + "l2": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000002", + "number": 0 + }, + "l2_time": 1000, + "system_config": { + "batcherAddr": "0x0000000000000000000000000000000000000001", + "overhead": "0x0000000000000000000000000000000000000000000000000000000000000001", + "scalar": "0x0000000000000000000000000000000000000000000000000000000000000001", + "gasLimit": 1000000 + } + }, + "block_time": 2, + "max_sequencer_drift": 600, + "seq_window_size": 10000, + "channel_timeout": 300, + "l1_chain_id": 1, + "l2_chain_id": 10, + "batch_inbox_address": "0x0000000000000000000000000000000000000002", + "deposit_contract_address": "0x0000000000000000000000000000000000000003", + "l1_system_config_address": "0x0000000000000000000000000000000000000004" +} diff --git a/op-chain-ops/interopgen/config/testfiles/rollup-20.json b/op-chain-ops/interopgen/config/testfiles/rollup-20.json new file mode 100644 index 00000000000..7bac86ee72e --- /dev/null +++ b/op-chain-ops/interopgen/config/testfiles/rollup-20.json @@ -0,0 +1,28 @@ +{ + "genesis": { + "l1": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000003", + "number": 2 + }, + "l2": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000004", + "number": 0 + }, + "l2_time": 2000, + "system_config": { + "batcherAddr": "0x0000000000000000000000000000000000000005", + "overhead": "0x0000000000000000000000000000000000000000000000000000000000000002", + "scalar": "0x0000000000000000000000000000000000000000000000000000000000000002", + "gasLimit": 2000000 + } + }, + "block_time": 3, + "max_sequencer_drift": 900, + "seq_window_size": 10000, + "channel_timeout": 400, + "l1_chain_id": 1, + "l2_chain_id": 20, + "batch_inbox_address": "0x0000000000000000000000000000000000000006", + "deposit_contract_address": "0x0000000000000000000000000000000000000007", + "l1_system_config_address": "0x0000000000000000000000000000000000000008" +} diff --git a/op-chain-ops/interopgen/config/testfiles/rollup_set.json b/op-chain-ops/interopgen/config/testfiles/rollup_set.json new file mode 100644 index 00000000000..8d6f2d08e1f --- /dev/null +++ b/op-chain-ops/interopgen/config/testfiles/rollup_set.json @@ -0,0 +1,34 @@ +{ + "10": { + "genesis": { + "l1": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "number": 1, + "timestamp": 1000 + }, + "l2": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000002", + "number": 0, + "timestamp": 1100 + } + }, + "block_time": 2, + "interop_time": 2000 + }, + "20": { + "genesis": { + "l1": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000003", + "number": 2, + "timestamp": 2000 + }, + "l2": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000004", + "number": 0, + "timestamp": 2100 + } + }, + "block_time": 2, + "interop_time": 3000 + } +} diff --git a/op-chain-ops/interopgen/configs.go b/op-chain-ops/interopgen/configs.go index 5182a41fb14..5ed64ec4f97 100644 --- a/op-chain-ops/interopgen/configs.go +++ b/op-chain-ops/interopgen/configs.go @@ -41,9 +41,8 @@ type OPCMImplementationsConfig struct { type SuperchainConfig struct { Deployer common.Address - ProxyAdminOwner common.Address - ProtocolVersionsOwner common.Address - Challenger common.Address + ProxyAdminOwner common.Address + Challenger common.Address Paused bool @@ -71,15 +70,18 @@ type L2Config struct { Challenger common.Address SystemConfigOwner common.Address genesis.L2InitializationConfig - Prefund map[common.Address]*big.Int - SaltMixer string - GasLimit uint64 - DisputeGameType uint32 - DisputeAbsolutePrestate common.Hash - DisputeMaxGameDepth uint64 - DisputeSplitDepth uint64 - DisputeClockExtension uint64 - DisputeMaxClockDuration uint64 + Prefund map[common.Address]*big.Int + SaltMixer string + GasLimit uint64 + DisputeGameType uint32 + DisputeAbsolutePrestate common.Hash + DisputeKonaAbsolutePrestate common.Hash + DisputeMaxGameDepth uint64 + DisputeSplitDepth uint64 + DisputeClockExtension uint64 + DisputeMaxClockDuration uint64 + // TODO(#20084): remove once L2CM is the default codepath and DevFeatures are removed. + UseL2CM bool } func (c *L2Config) Check(log log.Logger) error { diff --git a/op-chain-ops/interopgen/deploy.go b/op-chain-ops/interopgen/deploy.go index aaaeb32456f..bb2f16ab97d 100644 --- a/op-chain-ops/interopgen/deploy.go +++ b/op-chain-ops/interopgen/deploy.go @@ -3,11 +3,11 @@ package interopgen import ( "errors" "fmt" + "maps" "math/big" + "slices" "sort" - "golang.org/x/exp/maps" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -18,10 +18,10 @@ import ( "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis/beacondeposit" "github.com/ethereum-optimism/optimism/op-chain-ops/script" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-core/devfeatures" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/manage" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -29,6 +29,9 @@ var ( // sysGenesisDeployer is used as tx.origin/msg.sender on system genesis script calls. // At the end we verify none of the deployed contracts persist (there may be temporary ones, to insert bytecode). sysGenesisDeployer = common.Address(crypto.Keccak256([]byte("System genesis deployer"))[12:]) + + // defaultInitBond matches Deploy.s.sol DEFAULT_INIT_BOND (0.08 ether). + defaultInitBond = big.NewInt(8e16) ) func Deploy(logger log.Logger, fa *foundry.ArtifactsFS, srcFS *foundry.SourceMapFS, cfg *WorldConfig) (*WorldDeployment, *WorldOutput, error) { @@ -37,7 +40,7 @@ func Deploy(logger log.Logger, fa *foundry.ArtifactsFS, srcFS *foundry.SourceMap if fmt.Sprintf("%d", l2Cfg.L2ChainID) != id { return nil, nil, fmt.Errorf("chain L2 %s declared different L2 chain ID %d in config", id, l2Cfg.L2ChainID) } - if !cfg.L1.ChainID.IsUint64() || cfg.L1.ChainID.Uint64() != l2Cfg.L1ChainID { + if !cfg.L1.ChainID.IsUint64() || bigs.Uint64Strict(cfg.L1.ChainID) != l2Cfg.L1ChainID { return nil, nil, fmt.Errorf("chain L2 %s declared different L1 chain ID %d in config than global %d", id, l2Cfg.L1ChainID, cfg.L1.ChainID) } } @@ -137,7 +140,7 @@ func CreateL1(logger log.Logger, fa *foundry.ArtifactsFS, srcFS *foundry.SourceM PrevRandao: cfg.L1GenesisBlockMixHash, BlobHashes: nil, } - l1Host := script.NewHost(logger.New("role", "l1", "chain", cfg.ChainID), fa, srcFS, l1Context, script.WithCreate2Deployer()) + l1Host := script.NewHost(logger.New("role", "l1", "chain", cfg.ChainID), fa, srcFS, l1Context, script.WithCreate2Deployer(), script.WithNoMaxCodeSize()) return l1Host } @@ -175,12 +178,9 @@ func DeploySuperchainToL1(l1Host *script.Host, opcmScripts *opcm.Scripts, superC l1Host.SetTxOrigin(superCfg.Deployer) superDeployment, err := opcmScripts.DeploySuperchain.Run(opcm.DeploySuperchainInput{ - SuperchainProxyAdminOwner: superCfg.ProxyAdminOwner, - ProtocolVersionsOwner: superCfg.ProtocolVersionsOwner, - Guardian: superCfg.SuperchainConfigGuardian, - Paused: superCfg.Paused, - RequiredProtocolVersion: superCfg.RequiredProtocolVersion, - RecommendedProtocolVersion: superCfg.RecommendedProtocolVersion, + SuperchainProxyAdminOwner: superCfg.ProxyAdminOwner, + Guardian: superCfg.SuperchainConfigGuardian, + Paused: superCfg.Paused, }) if err != nil { return nil, fmt.Errorf("failed to deploy Superchain contracts: %w", err) @@ -193,14 +193,13 @@ func DeploySuperchainToL1(l1Host *script.Host, opcmScripts *opcm.Scripts, superC ProofMaturityDelaySeconds: superCfg.Implementations.FaultProof.ProofMaturityDelaySeconds, DisputeGameFinalityDelaySeconds: superCfg.Implementations.FaultProof.DisputeGameFinalityDelaySeconds, MipsVersion: superCfg.Implementations.FaultProof.MipsVersion, - DevFeatureBitmap: deployer.OptimismPortalInteropDevFlag, + DevFeatureBitmap: devfeatures.OptimismPortalInteropFlag, FaultGameV2MaxGameDepth: big.NewInt(73), FaultGameV2SplitDepth: big.NewInt(30), FaultGameV2ClockExtension: big.NewInt(10800), FaultGameV2MaxClockDuration: big.NewInt(302400), SuperchainProxyAdmin: superDeployment.SuperchainProxyAdmin, SuperchainConfigProxy: superDeployment.SuperchainConfigProxy, - ProtocolVersionsProxy: superDeployment.ProtocolVersionsProxy, L1ProxyAdminOwner: superCfg.ProxyAdminOwner, Challenger: superCfg.Challenger, }) @@ -213,8 +212,6 @@ func DeploySuperchainToL1(l1Host *script.Host, opcmScripts *opcm.Scripts, superC return &SuperchainDeployment{ Implementations: Implementations(implementationsDeployment), ProxyAdmin: superDeployment.SuperchainProxyAdmin, - ProtocolVersions: superDeployment.ProtocolVersionsImpl, - ProtocolVersionsProxy: superDeployment.ProtocolVersionsProxy, SuperchainConfig: superDeployment.SuperchainConfigImpl, SuperchainConfigProxy: superDeployment.SuperchainConfigProxy, }, nil @@ -242,7 +239,7 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme BasefeeScalar: cfg.GasPriceOracleBaseFeeScalar, BlobBaseFeeScalar: cfg.GasPriceOracleBlobBaseFeeScalar, L2ChainId: new(big.Int).SetUint64(cfg.L2ChainID), - Opcm: superDeployment.Opcm, + Opcm: superDeployment.OpcmV2, SaltMixer: cfg.SaltMixer, GasLimit: cfg.GasLimit, DisputeGameType: cfg.DisputeGameType, @@ -254,6 +251,7 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme AllowCustomDisputeParameters: true, OperatorFeeScalar: cfg.GasPriceOracleOperatorFeeScalar, OperatorFeeConstant: cfg.GasPriceOracleOperatorFeeConstant, + SuperchainConfig: superDeployment.SuperchainConfigProxy, UseCustomGasToken: cfg.UseCustomGasToken, }) if err != nil { @@ -269,35 +267,53 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme func MigrateInterop( l1Host *script.Host, l1GenesisTimestamp uint64, superCfg *SuperchainConfig, superDeployment *SuperchainDeployment, l2Cfgs map[string]*L2Config, l2Deployments map[string]*L2Deployment, ) (*InteropDeployment, error) { - l2ChainIDs := maps.Keys(l2Deployments) + l2ChainIDs := slices.Collect(maps.Keys(l2Deployments)) sort.Strings(l2ChainIDs) - chainConfigs := make([]manage.OPChainConfig, len(l2Deployments)) + + // We don't have a super root at genesis. But stub the starting anchor root anyways to facilitate super DG testing. + startingAnchorRoot := common.Hash(opcm.PermissionedGameStartingAnchorRoot) + + // Build chain system config addresses for V2 migrate input. + chainSystemConfigs := make([]common.Address, len(l2Deployments)) for i, l2ChainID := range l2ChainIDs { - l2Deployment := l2Deployments[l2ChainID] - chainConfigs[i] = manage.OPChainConfig{ - SystemConfigProxy: l2Deployment.SystemConfigProxy, - CannonPrestate: l2Cfgs[l2ChainID].DisputeAbsolutePrestate, - } + chainSystemConfigs[i] = l2Deployments[l2ChainID].SystemConfigProxy } - // For now get the fault game parameters from the first chain + // ABI-encode the cannon prestates as game args (from the first chain config). l2ChainID := l2ChainIDs[0] - // We don't have a super root at genesis. But stub the starting anchor root anyways to facilitate super DG testing. - startingAnchorRoot := common.Hash(opcm.PermissionedGameStartingAnchorRoot) + cannonGameArgs := common.LeftPadBytes(l2Cfgs[l2ChainID].DisputeAbsolutePrestate.Bytes(), 32) + cannonKonaGameArgs := common.LeftPadBytes(l2Cfgs[l2ChainID].DisputeKonaAbsolutePrestate.Bytes(), 32) + + const ( + GameTypeCannon = uint32(0) + GameTypeSuperCannonKona = uint32(9) + ) + imi := manage.InteropMigrationInput{ - Prank: superCfg.ProxyAdminOwner, - Opcm: superDeployment.Opcm, - UsePermissionlessGame: true, - StartingAnchorRoot: startingAnchorRoot, - StartingAnchorL2SequenceNumber: big.NewInt(int64(l1GenesisTimestamp)), - Proposer: l2Cfgs[l2ChainID].Proposer, - Challenger: l2Cfgs[l2ChainID].Challenger, - MaxGameDepth: l2Cfgs[l2ChainID].DisputeMaxGameDepth, - SplitDepth: l2Cfgs[l2ChainID].DisputeSplitDepth, - InitBond: big.NewInt(0), - ClockExtension: l2Cfgs[l2ChainID].DisputeClockExtension, - MaxClockDuration: l2Cfgs[l2ChainID].DisputeMaxClockDuration, - EncodedChainConfigs: chainConfigs, + Prank: superCfg.ProxyAdminOwner, + Opcm: superDeployment.OpcmV2, + MigrateInputV2: &manage.MigrateInputV2{ + ChainSystemConfigs: chainSystemConfigs, + DisputeGameConfigs: []manage.DisputeGameConfig{ + { + Enabled: true, + InitBond: new(big.Int).Set(defaultInitBond), + GameType: GameTypeCannon, + GameArgs: cannonGameArgs, + }, + { + Enabled: true, + InitBond: new(big.Int).Set(defaultInitBond), + GameType: GameTypeSuperCannonKona, + GameArgs: cannonKonaGameArgs, + }, + }, + StartingAnchorRoot: manage.Proposal{ + Root: startingAnchorRoot, + L2SequenceNumber: big.NewInt(int64(l1GenesisTimestamp)), + }, + StartingRespectedGameType: GameTypeSuperCannonKona, + }, } output, err := manage.Migrate(l1Host, imi) if err != nil { @@ -336,17 +352,15 @@ func GenesisL2(l2Host *script.Host, cfg *L2Config, deployment *L2Deployment, mul OperatorFeeVaultWithdrawalNetwork: big.NewInt(int64(cfg.OperatorFeeVaultWithdrawalNetwork.ToUint8())), GovernanceTokenOwner: cfg.GovernanceTokenOwner, Fork: big.NewInt(cfg.SolidityForkNumber(1)), - DeployCrossL2Inbox: multichainDepSet, EnableGovernance: cfg.EnableGovernance, FundDevAccounts: cfg.FundDevAccounts, - UseRevenueShare: cfg.UseRevenueShare, - ChainFeesRecipient: cfg.ChainFeesRecipient, - L1FeesDepositor: standard.L1FeesDepositor, UseCustomGasToken: cfg.UseCustomGasToken, GasPayingTokenName: cfg.GasPayingTokenName, GasPayingTokenSymbol: cfg.GasPayingTokenSymbol, NativeAssetLiquidityAmount: cfg.NativeAssetLiquidityAmount.ToInt(), LiquidityControllerOwner: cfg.LiquidityControllerOwner, + DevFeatureBitmap: devFeatureBitmapForL2Genesis(multichainDepSet && interopAtGenesis(cfg.L2GenesisInteropTimeOffset), cfg.UseL2CM), + UseInterop: multichainDepSet && interopAtGenesis(cfg.L2GenesisInteropTimeOffset), }); err != nil { return fmt.Errorf("failed L2 genesis: %w", err) } @@ -354,11 +368,30 @@ func GenesisL2(l2Host *script.Host, cfg *L2Config, deployment *L2Deployment, mul return nil } +// interopAtGenesis returns true if the Interop fork is scheduled to activate at genesis. +// Using a nil offset means Interop is not scheduled at all. +func interopAtGenesis(interopOffset *hexutil.Uint64) bool { + return interopOffset != nil && *interopOffset == 0 +} + +// devFeatureBitmapForL2Genesis returns the dev feature bitmap for the Interop and L2CM flags. +// TODO(#20084): drop useL2CM and the L2CMFlag branch once DevFeatures are removed. +func devFeatureBitmapForL2Genesis(enableInterop, useL2CM bool) common.Hash { + var bitmap common.Hash + if enableInterop { + bitmap = devfeatures.EnableDevFeature(bitmap, devfeatures.OptimismPortalInteropFlag) + } + if useL2CM { + bitmap = devfeatures.EnableDevFeature(bitmap, devfeatures.L2CMFlag) + } + return bitmap +} + func CompleteL1(l1Host *script.Host, cfg *L1Config) (*L1Output, error) { l1Genesis, err := genesis.NewL1Genesis(&genesis.DeployConfig{ L2InitializationConfig: genesis.L2InitializationConfig{ L2CoreDeployConfig: genesis.L2CoreDeployConfig{ - L1ChainID: cfg.ChainID.Uint64(), + L1ChainID: bigs.Uint64Strict(cfg.ChainID), }, UpgradeScheduleDeployConfig: genesis.UpgradeScheduleDeployConfig{ L1CancunTimeOffset: new(hexutil.Uint64), diff --git a/op-chain-ops/interopgen/deploy_test.go b/op-chain-ops/interopgen/deploy_test.go new file mode 100644 index 00000000000..8879e5b3911 --- /dev/null +++ b/op-chain-ops/interopgen/deploy_test.go @@ -0,0 +1,55 @@ +package interopgen + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-core/devfeatures" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/require" +) + +func TestInteropAtGenesis(t *testing.T) { + zero := hexutil.Uint64(0) + nonzero := hexutil.Uint64(24) + + tests := []struct { + name string + offset *hexutil.Uint64 + want bool + }{ + {"nil offset: Interop not scheduled", nil, false}, + {"zero offset: Interop active at genesis", &zero, true}, + {"non-zero offset: Interop delayed activation", &nonzero, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, interopAtGenesis(tt.offset)) + }) + } +} + +// devFeatureBitmapForL2Genesis sets the OptimismPortalInteropFlag when interop is enabled and the L2CMFlag when L2CM +// is enabled. +func TestDevFeatureBitmapForL2Genesis(t *testing.T) { + interopOnly := devfeatures.EnableDevFeature(common.Hash{}, devfeatures.OptimismPortalInteropFlag) + l2cmOnly := devfeatures.EnableDevFeature(common.Hash{}, devfeatures.L2CMFlag) + both := devfeatures.EnableDevFeature(interopOnly, devfeatures.L2CMFlag) + + tests := []struct { + name string + enableInterop bool + useL2CM bool + want common.Hash + }{ + {"both disabled", false, false, common.Hash{}}, + {"interop only", true, false, interopOnly}, + {"L2CM only", false, true, l2cmOnly}, + {"both enabled", true, true, both}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, devFeatureBitmapForL2Genesis(tt.enableInterop, tt.useL2CM)) + }) + } +} diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index 86bde012dc0..8e1a99fb2d7 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -10,19 +10,13 @@ type L1Deployment struct { } type Implementations struct { - Opcm common.Address `json:"OPCM"` - OpcmContractsContainer common.Address `json:"OPCMContractsContainer"` - OpcmGameTypeAdder common.Address `json:"OPCMGameTypeAdder"` - OpcmDeployer common.Address `json:"OPCMDeployer"` - OpcmUpgrader common.Address `json:"OPCMUpgrader"` - OpcmInteropMigrator common.Address `json:"OPCMInteropMigrator"` OpcmStandardValidator common.Address `json:"OPCMStandardValidator"` OpcmUtils common.Address `json:"OPCMUtils"` + OpcmMigrator common.Address `json:"OPCMMigrator"` OpcmV2 common.Address `json:"OPCMV2"` OpcmContainer common.Address `json:"OPCMContainer"` DelayedWETHImpl common.Address `json:"DelayedWETHImpl"` OptimismPortalImpl common.Address `json:"OptimismPortalImpl"` - OptimismPortalInteropImpl common.Address `json:"OptimismPortalInteropImpl"` ETHLockboxImpl common.Address `json:"ETHLockboxImpl"` PreimageOracleSingleton common.Address `json:"PreimageOracleSingleton"` MipsSingleton common.Address `json:"MipsSingleton"` @@ -34,11 +28,11 @@ type Implementations struct { DisputeGameFactoryImpl common.Address `json:"DisputeGameFactoryImpl"` AnchorStateRegistryImpl common.Address `json:"AnchorStateRegistryImpl"` SuperchainConfigImpl common.Address `json:"SuperchainConfigImpl"` - ProtocolVersionsImpl common.Address `json:"ProtocolVersionsImpl"` - FaultDisputeGameV2Impl common.Address `json:"FaultDisputeGameV2Impl"` - PermissionedDisputeGameV2Impl common.Address `json:"PermissionedDisputeGameV2Impl"` + FaultDisputeGameImpl common.Address `json:"FaultDisputeGameImpl"` + PermissionedDisputeGameImpl common.Address `json:"PermissionedDisputeGameImpl"` SuperFaultDisputeGameImpl common.Address `json:"SuperFaultDisputeGameImpl"` SuperPermissionedDisputeGameImpl common.Address `json:"SuperPermissionedDisputeGameImpl"` + ZkDisputeGameImpl common.Address `json:"ZkDisputeGameImpl"` StorageSetterImpl common.Address `json:"StorageSetterImpl"` } @@ -47,9 +41,6 @@ type SuperchainDeployment struct { ProxyAdmin common.Address `json:"ProxyAdmin"` - ProtocolVersions common.Address `json:"ProtocolVersions"` - ProtocolVersionsProxy common.Address `json:"ProtocolVersionsProxy"` - SuperchainConfig common.Address `json:"SuperchainConfig"` SuperchainConfigProxy common.Address `json:"SuperchainConfigProxy"` } diff --git a/op-chain-ops/interopgen/outputs.go b/op-chain-ops/interopgen/outputs.go index 06ccc7ed963..32955d67c9d 100644 --- a/op-chain-ops/interopgen/outputs.go +++ b/op-chain-ops/interopgen/outputs.go @@ -3,9 +3,9 @@ package interopgen import ( "github.com/ethereum/go-ethereum/core" + "github.com/ethereum-optimism/optimism/op-chain-ops/interopgen/config" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" ) type L1Output struct { @@ -22,10 +22,10 @@ type WorldOutput struct { L2s map[string]*L2Output } -func (wo *WorldOutput) RollupConfigSet() depset.StaticRollupConfigSet { +func (wo *WorldOutput) RollupConfigSet() config.StaticRollupConfigSet { rcfgs := make(map[eth.ChainID]*rollup.Config) for _, rcfg := range wo.L2s { rcfgs[eth.ChainIDFromBig(rcfg.RollupCfg.L2ChainID)] = rcfg.RollupCfg } - return depset.StaticRollupConfigSetFromRollupConfigMap(rcfgs, depset.StaticTimestamp(wo.L1.Genesis.Timestamp)) + return config.StaticRollupConfigSetFromRollupConfigMap(rcfgs, config.StaticTimestamp(wo.L1.Genesis.Timestamp)) } diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 9a40618f72e..4a73ac8453f 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -54,10 +54,6 @@ func (recipe *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, er if err != nil { return nil, err } - superchainProtocolVersionsOwner, err := addrs.Address(superchainOps(devkeys.SuperchainProtocolVersionsOwner)) - if err != nil { - return nil, err - } superchainConfigGuardian, err := addrs.Address(superchainOps(devkeys.SuperchainConfigGuardianKey)) if err != nil { return nil, err @@ -72,10 +68,9 @@ func (recipe *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, er l1Cfg.Prefund[challenger] = Ether(10_000_000) superchainCfg := &SuperchainConfig{ - ProxyAdminOwner: superchainProxyAdmin, - ProtocolVersionsOwner: superchainProtocolVersionsOwner, - Challenger: challenger, - Deployer: superchainDeployer, + ProxyAdminOwner: superchainProxyAdmin, + Challenger: challenger, + Deployer: superchainDeployer, Implementations: OPCMImplementationsConfig{ FaultProof: SuperFaultProofConfig{ WithdrawalDelaySeconds: big.NewInt(302400), @@ -87,9 +82,7 @@ func (recipe *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, er }, }, SuperchainL1DeployConfig: genesis.SuperchainL1DeployConfig{ - RequiredProtocolVersion: params.OPStackSupport, - RecommendedProtocolVersion: params.OPStackSupport, - SuperchainConfigGuardian: superchainConfigGuardian, + SuperchainConfigGuardian: superchainConfigGuardian, }, } world := &WorldConfig{ @@ -131,6 +124,8 @@ type InteropDevL2Recipe struct { ChainID uint64 BlockTime uint64 InteropOffset uint64 + // TODO(#20084): remove alongside L2Config.UseL2CM. + UseL2CM bool } func prefundL2Accounts(l1Cfg *L1Config, l2Cfg *L2Config, addrs devkeys.Addresses) error { @@ -274,6 +269,7 @@ func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (* L2GenesisHoloceneTimeOffset: new(hexutil.Uint64), L2GenesisIsthmusTimeOffset: new(hexutil.Uint64), L2GenesisJovianTimeOffset: new(hexutil.Uint64), + L2GenesisKarstTimeOffset: new(hexutil.Uint64), L2GenesisInteropTimeOffset: (*hexutil.Uint64)(&r.InteropOffset), L1CancunTimeOffset: new(hexutil.Uint64), L1PragueTimeOffset: new(hexutil.Uint64), @@ -292,20 +288,18 @@ func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (* AltDADeployConfig: genesis.AltDADeployConfig{ UseAltDA: false, }, - RevenueShareDeployConfig: genesis.RevenueShareDeployConfig{ - UseRevenueShare: false, - ChainFeesRecipient: common.Address{}, - }, }, - Prefund: make(map[common.Address]*big.Int), - SaltMixer: "", - GasLimit: 60_000_000, - DisputeGameType: 1, // PERMISSIONED_CANNON Game Type - DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), - DisputeMaxGameDepth: 73, - DisputeSplitDepth: 30, - DisputeClockExtension: 10800, // 3 hours (input in seconds) - DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) + Prefund: make(map[common.Address]*big.Int), + SaltMixer: "", + GasLimit: 60_000_000, + DisputeGameType: 1, // PERMISSIONED_CANNON Game Type + DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), + DisputeKonaAbsolutePrestate: common.HexToHash("0x035ef680a6fa34c50d8d8169075b5d133ecd7b38fe2b2a83cc76fc81ae5d7c52"), + DisputeMaxGameDepth: 73, + DisputeSplitDepth: 30, + DisputeClockExtension: 10800, // 3 hours (input in seconds) + DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) + UseL2CM: r.UseL2CM, } l2Users := devkeys.ChainUserKeys(new(big.Int).SetUint64(r.ChainID)) diff --git a/op-chain-ops/interopgen/recipe_test.go b/op-chain-ops/interopgen/recipe_test.go new file mode 100644 index 00000000000..213339648e4 --- /dev/null +++ b/op-chain-ops/interopgen/recipe_test.go @@ -0,0 +1,25 @@ +package interopgen + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" +) + +func TestInteropDevRecipeBuildUseL2CM(t *testing.T) { + rec := InteropDevRecipe{ + L1ChainID: 900100, + L2s: []InteropDevL2Recipe{{ChainID: 900200, UseL2CM: true}, {ChainID: 900201}}, + GenesisTimestamp: uint64(1234567), + } + hd, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + require.NoError(t, err) + + worldCfg, err := rec.Build(hd) + require.NoError(t, err) + + require.True(t, worldCfg.L2s["900200"].UseL2CM) + require.False(t, worldCfg.L2s["900201"].UseL2CM) +} diff --git a/op-chain-ops/opcmregistry/registry.go b/op-chain-ops/opcmregistry/registry.go new file mode 100644 index 00000000000..d2df181f539 --- /dev/null +++ b/op-chain-ops/opcmregistry/registry.go @@ -0,0 +1,512 @@ +// Package opcmregistry provides utilities for loading OPCM (OP Contracts Manager) +// information from the superchain-registry. This package is used by both Go code +// and Solidity tests via FFI. +package opcmregistry + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "net/http" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/BurntSushi/toml" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/httputil" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum-optimism/optimism/op-service/sources/caching" +) + +// Configuration +const ( + httpTimeout = 30 * time.Second + maxResponseSize = 10 * 1024 * 1024 // 10MB + maxRetries = 12 // 1 initial + 11 retries, ~2 min total with backoff + memoryCacheSize = 10 // Number of URLs to cache in memory + fileCacheTTL = 30 * time.Minute // How long file cache entries remain valid + cacheSubdir = "opcmregistry" +) + +// Chain ID constants +const ( + MainnetChainID = uint64(1) + SepoliaChainID = uint64(11155111) +) + +// GitHub raw URLs for the standard versions TOML files +const ( + standardVersionsMainnetURL = "https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/main/validation/standard/standard-versions-mainnet.toml" + standardVersionsSepoliaURL = "https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/main/validation/standard/standard-versions-sepolia.toml" +) + +// Global registry instance +var ( + globalRegistry *Registry + globalRegistryOnce sync.Once +) + +// Registry fetches and caches OPCM version data from the superchain-registry. +type Registry struct { + log log.Logger + memoryCache *caching.LRUCache[string, Versions] + downloader *httputil.Downloader + fileCacheDir string +} + +// NewRegistry creates a new Registry with the given logger. +// If logger is nil, a no-op logger is used. +func NewRegistry(logger log.Logger) *Registry { + if logger == nil { + logger = log.Root() + } + return &Registry{ + log: logger, + memoryCache: caching.NewLRUCache[string, Versions](nil, "opcmregistry", memoryCacheSize), + downloader: &httputil.Downloader{ + Client: &http.Client{Timeout: httpTimeout}, + MaxSize: maxResponseSize, + }, + fileCacheDir: defaultCacheDir(), + } +} + +func defaultCacheDir() string { + if dir := os.Getenv("XDG_CACHE_HOME"); dir != "" { + return filepath.Join(dir, cacheSubdir) + } + home, err := os.UserHomeDir() + if err != nil { + return "" + } + return filepath.Join(home, ".cache", cacheSubdir) +} + +func getGlobalRegistry() *Registry { + globalRegistryOnce.Do(func() { + globalRegistry = NewRegistry(nil) + }) + return globalRegistry +} + +// FetchVersions retrieves the versions data for the given chain, using cache when available. +// Cache priority: memory -> file -> network (with retry) +func (r *Registry) FetchVersions(ctx context.Context, chainID uint64) (Versions, error) { + url, err := urlForChain(chainID) + if err != nil { + return nil, err + } + + // Check memory cache + if versions, ok := r.memoryCache.Get(url); ok { + return versions, nil + } + + // Check file cache + if data, ok := r.loadFromFileCache(url); ok { + var versions Versions + if err := toml.Unmarshal(data, &versions); err == nil { + r.memoryCache.Add(url, versions) + return versions, nil + } + // Corrupted cache file, fall through to fetch + } + + // Fetch with retry + data, err := retry.Do(ctx, maxRetries, retry.Exponential(), func() ([]byte, error) { + var buf bytes.Buffer + if err := r.downloader.Download(ctx, url, &buf); err != nil { + return nil, err + } + return buf.Bytes(), nil + }) + if err != nil { + return nil, err + } + + // Parse + var versions Versions + if err := toml.Unmarshal(data, &versions); err != nil { + return nil, fmt.Errorf("failed to parse TOML: %w", err) + } + + // Store in both caches + r.memoryCache.Add(url, versions) + r.saveToFileCache(url, data) + + return versions, nil +} + +// loadFromFileCache attempts to load data from the file cache. +// Returns the data and true if found and not expired, nil and false otherwise. +func (r *Registry) loadFromFileCache(url string) ([]byte, bool) { + path := r.fileCachePath(url) + if path == "" { + return nil, false + } + + info, err := os.Stat(path) + if err != nil || time.Since(info.ModTime()) > fileCacheTTL { + return nil, false + } + + data, err := os.ReadFile(path) + if err != nil { + r.log.Debug("failed to read cache file", "path", path, "err", err) + return nil, false + } + + return data, true +} + +// saveToFileCache saves data to the file cache. +func (r *Registry) saveToFileCache(url string, data []byte) { + path := r.fileCachePath(url) + if path == "" { + return + } + + // Ensure directory exists + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + r.log.Debug("failed to create cache directory", "path", filepath.Dir(path), "err", err) + return + } + + // Atomic write via temp file + rename + tmp := path + ".tmp" + if err := os.WriteFile(tmp, data, 0644); err != nil { + r.log.Debug("failed to write cache temp file", "path", tmp, "err", err) + return + } + if err := os.Rename(tmp, path); err != nil { + r.log.Debug("failed to rename cache temp file", "from", tmp, "to", path, "err", err) + } +} + +// fileCachePath returns the file path for caching the given URL. +func (r *Registry) fileCachePath(url string) string { + if r.fileCacheDir == "" { + return "" + } + hash := sha256.Sum256([]byte(url)) + return filepath.Join(r.fileCacheDir, hex.EncodeToString(hash[:])+".cache") +} + +func urlForChain(chainID uint64) (string, error) { + switch chainID { + case MainnetChainID: + return standardVersionsMainnetURL, nil + case SepoliaChainID: + return standardVersionsSepoliaURL, nil + default: + return "", fmt.Errorf("unsupported chain ID: %d", chainID) + } +} + +// getVersionsForChain is the internal function used by public APIs. +func getVersionsForChain(chainID uint64) (Versions, error) { + return getGlobalRegistry().FetchVersions(context.Background(), chainID) +} + +// ----------------------------------------------------------------------------- +// TOML data types +// ----------------------------------------------------------------------------- + +// Dummy prestates for testing - actual values don't matter for upgrade tests +var ( + DummyCannonPrestate = crypto.Keccak256Hash([]byte("CANNON")) + DummyCannonKonaPrestate = crypto.Keccak256Hash([]byte("CANNON_KONA")) +) + +// Address is a hex-encoded address used in TOML parsing +type Address common.Address + +func (a *Address) UnmarshalText(text []byte) error { + addr := common.HexToAddress(string(text)) + *a = Address(addr) + return nil +} + +// ContractData represents the version and address information for a contract in the TOML +type ContractData struct { + Version string `toml:"version"` + Address *Address `toml:"address,omitempty"` + ImplementationAddress *Address `toml:"implementation_address,omitempty"` +} + +// VersionConfig represents all contracts for a specific release version in the TOML +type VersionConfig struct { + OPContractsManager *ContractData `toml:"op_contracts_manager,omitempty"` +} + +// Versions maps release tags to their contract configurations +type Versions map[string]VersionConfig + +// ----------------------------------------------------------------------------- +// OPCM types and functions +// ----------------------------------------------------------------------------- + +// OPCMInfo contains information about an OPCM from the registry. +// Note: This only contains registry metadata. The actual OPCM version (and whether it's V1/V2) +// must be determined by querying opcm.version() on-chain. +type OPCMInfo struct { + // ReleaseVersion is the contracts release version from the registry (e.g., "1.6.0"). + // This is NOT the OPCM contract's semver - use opcm.version() on-chain to get that. + ReleaseVersion string + Address common.Address + ChainID uint64 +} + +// Semver represents a parsed semantic version +type Semver struct { + Major int + Minor int + Patch int + Raw string + Prerelease string // e.g., "rc.1", "beta.2", empty for stable releases +} + +// ParseSemver parses a semantic version string like "6.0.0" or "6.0.0-rc.1" +func ParseSemver(v string) (Semver, error) { + base := v + prerelease := "" + if idx := strings.Index(v, "-"); idx != -1 { + base = v[:idx] + prerelease = v[idx+1:] + } + + parts := strings.Split(base, ".") + if len(parts) < 3 { + return Semver{}, fmt.Errorf("invalid semver: %s", v) + } + + var major, minor, patch int + if _, err := fmt.Sscanf(parts[0], "%d", &major); err != nil { + return Semver{}, fmt.Errorf("invalid major version: %s", v) + } + if _, err := fmt.Sscanf(parts[1], "%d", &minor); err != nil { + return Semver{}, fmt.Errorf("invalid minor version: %s", v) + } + if _, err := fmt.Sscanf(parts[2], "%d", &patch); err != nil { + return Semver{}, fmt.Errorf("invalid patch version: %s", v) + } + + return Semver{Major: major, Minor: minor, Patch: patch, Raw: v, Prerelease: prerelease}, nil +} + +// IsPrerelease returns true if this is a prerelease version (e.g., rc, beta, alpha) +func (s Semver) IsPrerelease() bool { + return s.Prerelease != "" +} + +// Compare returns -1 if s < other, 0 if s == other, 1 if s > other +func (s Semver) Compare(other Semver) int { + if s.Major != other.Major { + if s.Major < other.Major { + return -1 + } + return 1 + } + if s.Minor != other.Minor { + if s.Minor < other.Minor { + return -1 + } + return 1 + } + if s.Patch != other.Patch { + if s.Patch < other.Patch { + return -1 + } + return 1 + } + return 0 +} + +// GetOPCMsForChain returns all OPCMs for a given chain ID by fetching from the superchain-registry GitHub. +// Returns unique OPCMs sorted by release version ascending, deduplicated by address. +// Note: ReleaseVersion (e.g., "1.6.0") is NOT the OPCM contract's semver (e.g., "6.0.0"). +// The actual OPCM version must be queried on-chain via opcm.version(). +// Prerelease versions (e.g., rc, beta) are excluded - only stable releases are returned. +func GetOPCMsForChain(chainID uint64) ([]OPCMInfo, error) { + versions, err := getVersionsForChain(chainID) + if err != nil { + return nil, err + } + + return opcmsFromVersions(chainID, versions), nil +} + +func opcmsFromVersions(chainID uint64, versions Versions) []OPCMInfo { + var opcms []OPCMInfo + + for releaseTag, versionConfig := range versions { + if versionConfig.OPContractsManager == nil { + continue + } + if versionConfig.OPContractsManager.Address == nil { + continue + } + + releaseVersion, err := releaseVersionFromTag(releaseTag) + if err != nil { + continue + } + + // Skip prerelease versions (rc, beta, alpha, etc.) + sv, err := ParseSemver(releaseVersion) + if err != nil { + continue + } + if sv.IsPrerelease() { + continue + } + + opcms = append(opcms, OPCMInfo{ + ReleaseVersion: releaseVersion, + Address: common.Address(*versionConfig.OPContractsManager.Address), + ChainID: chainID, + }) + } + + // Sort by release version ascending + sort.Slice(opcms, func(i, j int) bool { + vi, _ := ParseSemver(opcms[i].ReleaseVersion) + vj, _ := ParseSemver(opcms[j].ReleaseVersion) + return vi.Compare(vj) < 0 + }) + + // Deduplicate by address (keep first occurrence which has lowest version) + seen := make(map[common.Address]bool) + var result []OPCMInfo + for _, opcm := range opcms { + if !seen[opcm.Address] { + seen[opcm.Address] = true + result = append(result, opcm) + } + } + + return result +} + +func releaseVersionFromTag(releaseTag string) (string, error) { + const prefix = "op-contracts/v" + if strings.HasPrefix(releaseTag, prefix) { + return strings.TrimPrefix(releaseTag, prefix), nil + } + return "", fmt.Errorf("invalid release tag: %s", releaseTag) +} + +// FilterOPCMsByReleaseVersion filters OPCMs to only include those with release version > lastVersion. +// If lastVersion is empty, returns all OPCMs. +func FilterOPCMsByReleaseVersion(opcms []OPCMInfo, lastVersion string) ([]OPCMInfo, error) { + if lastVersion == "" { + return opcms, nil + } + + lastSV, err := ParseSemver(lastVersion) + if err != nil { + return nil, fmt.Errorf("invalid lastVersion: %w", err) + } + + var result []OPCMInfo + for _, opcm := range opcms { + sv, err := ParseSemver(opcm.ReleaseVersion) + if err != nil { + continue + } + if sv.Compare(lastSV) > 0 { + result = append(result, opcm) + } + } + + return result, nil +} + +// OPCMVersionQuerier is a function that queries the OPCM contract's version() on-chain. +// It takes an OPCM address and returns the OPCM semver string (e.g., "6.0.0", "7.0.0"). +type OPCMVersionQuerier func(addr common.Address) (string, error) + +// ResolvedOPCM contains an OPCM with its on-chain version resolved via opcm.version(). +type ResolvedOPCM struct { + Address common.Address + OPCMVersion Semver // The actual OPCM semver from opcm.version() (e.g., "7.0.0") +} + +// GetResolvedOPCMs fetches OPCM addresses from the registry, queries their OPCM versions +// on-chain using the provided querier, filters to only include stable versions >= 6.x.x, +// and returns them sorted by OPCM version ascending. +// Prerelease versions (e.g., rc, beta) are excluded. +func GetResolvedOPCMs(chainID uint64, queryOPCMVersion OPCMVersionQuerier) ([]ResolvedOPCM, error) { + registryOPCMs, err := GetOPCMsForChain(chainID) + if err != nil { + return nil, fmt.Errorf("failed to get OPCMs from registry: %w", err) + } + + var resolved []ResolvedOPCM + for _, opcm := range registryOPCMs { + opcmVersion, err := queryOPCMVersion(opcm.Address) + if err != nil { + // Skip OPCMs we can't query + continue + } + + sv, err := ParseSemver(opcmVersion) + if err != nil { + // Skip OPCMs with invalid versions + continue + } + + // Skip prerelease versions (rc, beta, alpha, etc.) + if sv.IsPrerelease() { + continue + } + + // Only include versions >= 6.x.x (V1 OPCMs start at 6.x.x) + if sv.Major < 6 { + continue + } + + resolved = append(resolved, ResolvedOPCM{ + Address: opcm.Address, + OPCMVersion: sv, + }) + } + + // Sort by OPCM version ascending + sort.Slice(resolved, func(i, j int) bool { + return resolved[i].OPCMVersion.Compare(resolved[j].OPCMVersion) < 0 + }) + + return resolved, nil +} + +// FilterByLastUsedOPCMVersion filters resolved OPCMs to only include those with OPCM version > lastVersion. +// If lastVersion is empty, returns all OPCMs. +func FilterByLastUsedOPCMVersion(opcms []ResolvedOPCM, lastVersion string) ([]ResolvedOPCM, error) { + if lastVersion == "" { + return opcms, nil + } + + lastSV, err := ParseSemver(lastVersion) + if err != nil { + return nil, fmt.Errorf("invalid lastVersion: %w", err) + } + + var result []ResolvedOPCM + for _, opcm := range opcms { + if opcm.OPCMVersion.Compare(lastSV) > 0 { + result = append(result, opcm) + } + } + + return result, nil +} diff --git a/op-chain-ops/opcmregistry/registry_test.go b/op-chain-ops/opcmregistry/registry_test.go new file mode 100644 index 00000000000..8840cd94740 --- /dev/null +++ b/op-chain-ops/opcmregistry/registry_test.go @@ -0,0 +1,569 @@ +package opcmregistry + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +func TestParseSemver(t *testing.T) { + tests := []struct { + name string + input string + wantMajor int + wantMinor int + wantPatch int + wantPre string + wantErr bool + }{ + { + name: "simple version", + input: "1.0.0", + wantMajor: 1, + wantMinor: 0, + wantPatch: 0, + }, + { + name: "version with all parts", + input: "6.1.2", + wantMajor: 6, + wantMinor: 1, + wantPatch: 2, + }, + { + name: "release candidate", + input: "6.0.0-rc.1", + wantMajor: 6, + wantMinor: 0, + wantPatch: 0, + wantPre: "rc.1", + }, + { + name: "beta version", + input: "7.0.0-beta.2", + wantMajor: 7, + wantMinor: 0, + wantPatch: 0, + wantPre: "beta.2", + }, + { + name: "alpha version", + input: "1.2.3-alpha", + wantMajor: 1, + wantMinor: 2, + wantPatch: 3, + wantPre: "alpha", + }, + { + name: "invalid - only two parts", + input: "1.0", + wantErr: true, + }, + { + name: "invalid - single part", + input: "1", + wantErr: true, + }, + { + name: "invalid - non-numeric major", + input: "a.0.0", + wantErr: true, + }, + { + name: "invalid - non-numeric minor", + input: "1.b.0", + wantErr: true, + }, + { + name: "invalid - non-numeric patch", + input: "1.0.c", + wantErr: true, + }, + { + name: "empty string", + input: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sv, err := ParseSemver(tt.input) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.wantMajor, sv.Major) + require.Equal(t, tt.wantMinor, sv.Minor) + require.Equal(t, tt.wantPatch, sv.Patch) + require.Equal(t, tt.wantPre, sv.Prerelease) + require.Equal(t, tt.input, sv.Raw) + }) + } +} + +func TestSemverCompare(t *testing.T) { + tests := []struct { + name string + a string + b string + want int + }{ + {name: "equal versions", a: "1.0.0", b: "1.0.0", want: 0}, + {name: "a major less", a: "1.0.0", b: "2.0.0", want: -1}, + {name: "a major greater", a: "3.0.0", b: "2.0.0", want: 1}, + {name: "a minor less", a: "1.1.0", b: "1.2.0", want: -1}, + {name: "a minor greater", a: "1.3.0", b: "1.2.0", want: 1}, + {name: "a patch less", a: "1.0.1", b: "1.0.2", want: -1}, + {name: "a patch greater", a: "1.0.3", b: "1.0.2", want: 1}, + {name: "major trumps minor", a: "2.0.0", b: "1.9.9", want: 1}, + {name: "minor trumps patch", a: "1.2.0", b: "1.1.9", want: 1}, + // Compare does not consider prerelease, only major.minor.patch + {name: "prerelease ignored in compare", a: "6.0.0-rc.1", b: "6.0.0", want: 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + svA, err := ParseSemver(tt.a) + require.NoError(t, err) + svB, err := ParseSemver(tt.b) + require.NoError(t, err) + require.Equal(t, tt.want, svA.Compare(svB)) + }) + } +} + +func TestSemverIsPrerelease(t *testing.T) { + tests := []struct { + version string + isPrerelease bool + }{ + {"1.0.0", false}, + {"6.0.0", false}, + {"6.0.0-rc.1", true}, + {"7.0.0-beta.2", true}, + {"1.2.3-alpha", true}, + } + + for _, tt := range tests { + t.Run(tt.version, func(t *testing.T) { + sv, err := ParseSemver(tt.version) + require.NoError(t, err) + require.Equal(t, tt.isPrerelease, sv.IsPrerelease()) + }) + } +} + +func TestAddressUnmarshalText(t *testing.T) { + tests := []struct { + name string + input string + want common.Address + }{ + { + name: "valid address lowercase", + input: "0x1234567890abcdef1234567890abcdef12345678", + want: common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"), + }, + { + name: "valid address mixed case", + input: "0x1234567890AbCdEf1234567890AbCdEf12345678", + want: common.HexToAddress("0x1234567890AbCdEf1234567890AbCdEf12345678"), + }, + { + name: "zero address", + input: "0x0000000000000000000000000000000000000000", + want: common.Address{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var addr Address + err := addr.UnmarshalText([]byte(tt.input)) + require.NoError(t, err) + require.Equal(t, tt.want, common.Address(addr)) + }) + } +} + +func TestUrlForChain(t *testing.T) { + tests := []struct { + name string + chainID uint64 + wantURL string + wantErr bool + }{ + { + name: "mainnet", + chainID: MainnetChainID, + wantURL: standardVersionsMainnetURL, + }, + { + name: "sepolia", + chainID: SepoliaChainID, + wantURL: standardVersionsSepoliaURL, + }, + { + name: "unsupported chain", + chainID: 999, + wantErr: true, + }, + { + name: "zero chain id", + chainID: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + url, err := urlForChain(tt.chainID) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported chain ID") + return + } + require.NoError(t, err) + require.Equal(t, tt.wantURL, url) + }) + } +} + +func TestFileCachePath(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + + t.Run("empty cache dir returns empty path", func(t *testing.T) { + r := &Registry{log: logger, fileCacheDir: ""} + path := r.fileCachePath("https://example.com/test.toml") + require.Empty(t, path) + }) + + t.Run("valid cache dir returns hashed path", func(t *testing.T) { + r := &Registry{log: logger, fileCacheDir: "/tmp/test-cache"} + path := r.fileCachePath("https://example.com/test.toml") + require.NotEmpty(t, path) + require.True(t, filepath.IsAbs(path)) + require.Contains(t, path, "/tmp/test-cache/") + require.Equal(t, ".cache", filepath.Ext(path)) + }) + + t.Run("different urls produce different paths", func(t *testing.T) { + r := &Registry{log: logger, fileCacheDir: "/tmp/test-cache"} + path1 := r.fileCachePath("https://example.com/file1.toml") + path2 := r.fileCachePath("https://example.com/file2.toml") + require.NotEqual(t, path1, path2) + }) + + t.Run("same url produces same path", func(t *testing.T) { + r := &Registry{log: logger, fileCacheDir: "/tmp/test-cache"} + path1 := r.fileCachePath("https://example.com/same.toml") + path2 := r.fileCachePath("https://example.com/same.toml") + require.Equal(t, path1, path2) + }) +} + +func TestFileCacheRoundTrip(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + tmpDir := t.TempDir() + r := &Registry{log: logger, fileCacheDir: tmpDir} + + testURL := "https://example.com/test.toml" + testData := []byte(`[releases] +version = "1.0.0"`) + + // Save to cache + r.saveToFileCache(testURL, testData) + + // Load from cache + loaded, ok := r.loadFromFileCache(testURL) + require.True(t, ok) + require.Equal(t, testData, loaded) +} + +func TestFileCacheExpiration(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + tmpDir := t.TempDir() + r := &Registry{log: logger, fileCacheDir: tmpDir} + + testURL := "https://example.com/expired.toml" + testData := []byte(`test = "data"`) + + // Save to cache + r.saveToFileCache(testURL, testData) + + // Modify file time to be older than TTL + path := r.fileCachePath(testURL) + oldTime := time.Now().Add(-fileCacheTTL - time.Hour) + err := os.Chtimes(path, oldTime, oldTime) + require.NoError(t, err) + + // Should not load expired cache + _, ok := r.loadFromFileCache(testURL) + require.False(t, ok) +} + +func TestFileCacheNonExistent(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + tmpDir := t.TempDir() + r := &Registry{log: logger, fileCacheDir: tmpDir} + + _, ok := r.loadFromFileCache("https://example.com/nonexistent.toml") + require.False(t, ok) +} + +func TestFilterOPCMsByReleaseVersion(t *testing.T) { + opcms := []OPCMInfo{ + {ReleaseVersion: "1.0.0", Address: common.HexToAddress("0x1")}, + {ReleaseVersion: "1.5.0", Address: common.HexToAddress("0x2")}, + {ReleaseVersion: "1.6.0", Address: common.HexToAddress("0x3")}, + {ReleaseVersion: "2.0.0", Address: common.HexToAddress("0x4")}, + } + + t.Run("empty lastVersion returns all", func(t *testing.T) { + result, err := FilterOPCMsByReleaseVersion(opcms, "") + require.NoError(t, err) + require.Len(t, result, 4) + }) + + t.Run("filter from version 1.0.0", func(t *testing.T) { + result, err := FilterOPCMsByReleaseVersion(opcms, "1.0.0") + require.NoError(t, err) + require.Len(t, result, 3) + require.Equal(t, "1.5.0", result[0].ReleaseVersion) + require.Equal(t, "1.6.0", result[1].ReleaseVersion) + require.Equal(t, "2.0.0", result[2].ReleaseVersion) + }) + + t.Run("filter from version 1.5.0", func(t *testing.T) { + result, err := FilterOPCMsByReleaseVersion(opcms, "1.5.0") + require.NoError(t, err) + require.Len(t, result, 2) + require.Equal(t, "1.6.0", result[0].ReleaseVersion) + require.Equal(t, "2.0.0", result[1].ReleaseVersion) + }) + + t.Run("filter from latest version returns empty", func(t *testing.T) { + result, err := FilterOPCMsByReleaseVersion(opcms, "2.0.0") + require.NoError(t, err) + require.Empty(t, result) + }) + + t.Run("filter from future version returns empty", func(t *testing.T) { + result, err := FilterOPCMsByReleaseVersion(opcms, "99.0.0") + require.NoError(t, err) + require.Empty(t, result) + }) + + t.Run("invalid lastVersion returns error", func(t *testing.T) { + _, err := FilterOPCMsByReleaseVersion(opcms, "invalid") + require.Error(t, err) + require.Contains(t, err.Error(), "invalid lastVersion") + }) +} + +func TestOPCMsFromVersionsSkipsPrereleaseReleaseTags(t *testing.T) { + versions := Versions{ + "op-contracts/v7.0.0-rc.2": { + OPContractsManager: &ContractData{ + Version: "7.1.17", + Address: addrPtr("0x9999999999999999999999999999999999999999"), + }, + }, + "op-contracts/v6.0.0": { + OPContractsManager: &ContractData{ + Version: "6.0.0", + Address: addrPtr("0x6666666666666666666666666666666666666666"), + }, + }, + } + + opcms := opcmsFromVersions(MainnetChainID, versions) + require.Len(t, opcms, 1) + require.Equal(t, common.HexToAddress("0x6666666666666666666666666666666666666666"), opcms[0].Address) + require.Equal(t, "6.0.0", opcms[0].ReleaseVersion) +} + +func TestFilterByLastUsedOPCMVersion(t *testing.T) { + sv6, _ := ParseSemver("6.0.0") + sv61, _ := ParseSemver("6.1.0") + sv7, _ := ParseSemver("7.0.0") + + opcms := []ResolvedOPCM{ + {Address: common.HexToAddress("0x1"), OPCMVersion: sv6}, + {Address: common.HexToAddress("0x2"), OPCMVersion: sv61}, + {Address: common.HexToAddress("0x3"), OPCMVersion: sv7}, + } + + t.Run("empty lastVersion returns all", func(t *testing.T) { + result, err := FilterByLastUsedOPCMVersion(opcms, "") + require.NoError(t, err) + require.Len(t, result, 3) + }) + + t.Run("filter from 6.0.0", func(t *testing.T) { + result, err := FilterByLastUsedOPCMVersion(opcms, "6.0.0") + require.NoError(t, err) + require.Len(t, result, 2) + require.Equal(t, "6.1.0", result[0].OPCMVersion.Raw) + require.Equal(t, "7.0.0", result[1].OPCMVersion.Raw) + }) + + t.Run("filter from 6.1.0", func(t *testing.T) { + result, err := FilterByLastUsedOPCMVersion(opcms, "6.1.0") + require.NoError(t, err) + require.Len(t, result, 1) + require.Equal(t, "7.0.0", result[0].OPCMVersion.Raw) + }) + + t.Run("filter from 7.0.0 returns empty", func(t *testing.T) { + result, err := FilterByLastUsedOPCMVersion(opcms, "7.0.0") + require.NoError(t, err) + require.Empty(t, result) + }) + + t.Run("invalid lastVersion returns error", func(t *testing.T) { + _, err := FilterByLastUsedOPCMVersion(opcms, "invalid") + require.Error(t, err) + require.Contains(t, err.Error(), "invalid lastVersion") + }) +} + +func TestGetResolvedOPCMs(t *testing.T) { + // This test uses a mock version querier to avoid network calls + t.Run("filters out versions below 6.x.x", func(t *testing.T) { + // Create a mock registry with in-memory data + mockVersions := Versions{ + "old-release": { + OPContractsManager: &ContractData{ + Version: "1.0.0", + Address: addrPtr("0x1111111111111111111111111111111111111111"), + }, + }, + "v6-release": { + OPContractsManager: &ContractData{ + Version: "1.6.0", + Address: addrPtr("0x2222222222222222222222222222222222222222"), + }, + }, + } + + // Mock version querier that returns different OPCM versions + mockQuerier := func(addr common.Address) (string, error) { + switch addr.Hex() { + case "0x1111111111111111111111111111111111111111": + return "5.0.0", nil // Should be filtered out (< 6.x.x) + case "0x2222222222222222222222222222222222222222": + return "6.0.0", nil // Should be included + default: + return "", nil + } + } + + // Process the mock data similar to GetResolvedOPCMs + var opcms []OPCMInfo + for _, vc := range mockVersions { + if vc.OPContractsManager != nil && vc.OPContractsManager.Address != nil { + sv, err := ParseSemver(vc.OPContractsManager.Version) + if err != nil || sv.IsPrerelease() { + continue + } + opcms = append(opcms, OPCMInfo{ + ReleaseVersion: vc.OPContractsManager.Version, + Address: common.Address(*vc.OPContractsManager.Address), + }) + } + } + + var resolved []ResolvedOPCM + for _, opcm := range opcms { + opcmVersion, err := mockQuerier(opcm.Address) + if err != nil { + continue + } + sv, err := ParseSemver(opcmVersion) + if err != nil || sv.IsPrerelease() || sv.Major < 6 { + continue + } + resolved = append(resolved, ResolvedOPCM{ + Address: opcm.Address, + OPCMVersion: sv, + }) + } + + require.Len(t, resolved, 1) + require.Equal(t, common.HexToAddress("0x2222222222222222222222222222222222222222"), resolved[0].Address) + }) + + t.Run("filters out prerelease versions", func(t *testing.T) { + mockQuerier := func(addr common.Address) (string, error) { + return "6.0.0-rc.1", nil + } + + opcmVersion, _ := mockQuerier(common.Address{}) + sv, err := ParseSemver(opcmVersion) + require.NoError(t, err) + require.True(t, sv.IsPrerelease()) + }) +} + +func TestNewRegistry(t *testing.T) { + t.Run("with nil logger uses default", func(t *testing.T) { + r := NewRegistry(nil) + require.NotNil(t, r) + require.NotNil(t, r.log) + require.NotNil(t, r.memoryCache) + require.NotNil(t, r.downloader) + }) + + t.Run("with provided logger", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + r := NewRegistry(logger) + require.NotNil(t, r) + require.Equal(t, logger, r.log) + require.NotNil(t, r.memoryCache) + require.NotNil(t, r.downloader) + }) +} + +func TestDefaultCacheDir(t *testing.T) { + // Test with XDG_CACHE_HOME set + t.Run("uses XDG_CACHE_HOME when set", func(t *testing.T) { + origXDG := os.Getenv("XDG_CACHE_HOME") + defer os.Setenv("XDG_CACHE_HOME", origXDG) + + os.Setenv("XDG_CACHE_HOME", "/custom/cache") + dir := defaultCacheDir() + require.Equal(t, filepath.Join("/custom/cache", cacheSubdir), dir) + }) + + t.Run("falls back to home directory", func(t *testing.T) { + origXDG := os.Getenv("XDG_CACHE_HOME") + defer os.Setenv("XDG_CACHE_HOME", origXDG) + + os.Setenv("XDG_CACHE_HOME", "") + dir := defaultCacheDir() + home, err := os.UserHomeDir() + require.NoError(t, err) + require.Equal(t, filepath.Join(home, ".cache", cacheSubdir), dir) + }) +} + +func TestChainIDConstants(t *testing.T) { + require.Equal(t, uint64(1), MainnetChainID) + require.Equal(t, uint64(11155111), SepoliaChainID) +} + +// Helper function to create Address pointer +func addrPtr(hex string) *Address { + addr := Address(common.HexToAddress(hex)) + return &addr +} diff --git a/op-chain-ops/script/bindings_test.go b/op-chain-ops/script/bindings_test.go index 5fbc9f8f2c9..b156e71d4bf 100644 --- a/op-chain-ops/script/bindings_test.go +++ b/op-chain-ops/script/bindings_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum/go-ethereum/common" ) @@ -55,7 +56,7 @@ func TestBindings(t *testing.T) { result := bindings.Adder(42, 0x1337) require.NoError(t, err) require.True(t, result.IsUint64()) - require.Equal(t, uint64(42+0x1337), result.Uint64()) + require.Equal(t, uint64(42+0x1337), bigs.Uint64Strict(result)) } type TestContract struct{} diff --git a/op-chain-ops/script/cheatcodes.go b/op-chain-ops/script/cheatcodes.go index e148d82ea8c..e9c2609080d 100644 --- a/op-chain-ops/script/cheatcodes.go +++ b/op-chain-ops/script/cheatcodes.go @@ -3,7 +3,6 @@ package script import ( "fmt" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" ) @@ -35,14 +34,14 @@ func (c *AccessControlledPrecompile) RequiredGas(input []byte) uint64 { return c.inner.RequiredGas(input) } -func (c *AccessControlledPrecompile) Run(input []byte, blockContext common.Hash) ([]byte, error) { +func (c *AccessControlledPrecompile) Run(input []byte) ([]byte, error) { // call-frame is not open yet, and prank is ignored for cheatcode access-checking. accessor := c.h.SelfAddress() if !c.h.AllowedCheatcodes(accessor) { c.h.log.Error("Cheatcode access denied!", "caller", accessor, "label", c.h.labels[accessor]) return encodeRevert(fmt.Errorf("call by %s to cheatcode precompile is not allowed", accessor)) } - return c.inner.Run(input, blockContext) + return c.inner.Run(input) } func (c *AccessControlledPrecompile) Name() string { diff --git a/op-chain-ops/script/cheatcodes_environment.go b/op-chain-ops/script/cheatcodes_environment.go index 3b23f312519..0fedf3aecf5 100644 --- a/op-chain-ops/script/cheatcodes_environment.go +++ b/op-chain-ops/script/cheatcodes_environment.go @@ -6,6 +6,7 @@ import ( "github.com/holiman/uint256" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" @@ -14,7 +15,7 @@ import ( // Warp implements https://book.getfoundry.sh/cheatcodes/warp func (c *CheatCodesPrecompile) Warp(timestamp *big.Int) { - c.h.env.Context().Time = timestamp.Uint64() + c.h.env.Context().Time = bigs.Uint64Strict(timestamp) } // Roll implements https://book.getfoundry.sh/cheatcodes/roll @@ -66,7 +67,7 @@ func (c *CheatCodesPrecompile) Load(account common.Address, slot [32]byte) [32]b // Etch implements https://book.getfoundry.sh/cheatcodes/etch func (c *CheatCodesPrecompile) Etch(who common.Address, code []byte) { - c.h.state.SetCode(who, bytes.Clone(code)) // important to clone; geth EVM will reuse the calldata memory. + c.h.state.SetCode(who, bytes.Clone(code), tracing.CodeChangeUnspecified) // important to clone; geth EVM will reuse the calldata memory. if len(code) > 0 { // if we're not just zeroing out the account: allow it to access cheatcodes c.h.AllowCheatcodes(who) @@ -221,7 +222,7 @@ func (c *CheatCodesPrecompile) ResumeGasMetering() error { } // TxGasPrice implements https://book.getfoundry.sh/cheatcodes/tx-gas-price -func (c *CheatCodesPrecompile) TxGasPrice(newGasPrice *big.Int) { +func (c *CheatCodesPrecompile) TxGasPrice(newGasPrice *uint256.Int) { c.h.env.TxContext().GasPrice = newGasPrice } diff --git a/op-chain-ops/script/cheatcodes_external.go b/op-chain-ops/script/cheatcodes_external.go index 70b18c04a75..063f63ee3a5 100644 --- a/op-chain-ops/script/cheatcodes_external.go +++ b/op-chain-ops/script/cheatcodes_external.go @@ -5,20 +5,22 @@ import ( "encoding/json" "errors" "fmt" + "maps" "math/big" "path" + "slices" "strconv" "strings" "time" "github.com/BurntSushi/toml" - "golang.org/x/exp/maps" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-service/bigs" ) // Ffi implements https://book.getfoundry.sh/cheatcodes/ffi @@ -93,7 +95,7 @@ func (c *CheatCodesPrecompile) Sleep(ms *big.Int) error { if !ms.IsUint64() { return vm.ErrExecutionReverted } - time.Sleep(time.Duration(ms.Uint64()) * time.Millisecond) + time.Sleep(time.Duration(bigs.Uint64Strict(ms)) * time.Millisecond) return nil } @@ -387,7 +389,7 @@ func lookupKeys(v any, query string) ([]string, error) { if query == "$" || query == "" { switch x := v.(type) { case map[string]any: - return maps.Keys(x), nil + return slices.Collect(maps.Keys(x)), nil default: return nil, fmt.Errorf("JSON value (Type %T) is not an object", x) } @@ -413,7 +415,7 @@ func lookupKeys(v any, query string) ([]string, error) { if trailing != "" { return nil, errors.New("cannot continue query after $ sign") } - return maps.Keys(x), nil + return slices.Collect(maps.Keys(x)), nil } data, ok := x[stringKey] if !ok { diff --git a/op-chain-ops/script/cheatcodes_utilities.go b/op-chain-ops/script/cheatcodes_utilities.go index b2de7e0bd40..bf9c4ab5d9c 100644 --- a/op-chain-ops/script/cheatcodes_utilities.go +++ b/op-chain-ops/script/cheatcodes_utilities.go @@ -11,6 +11,7 @@ import ( hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -248,7 +249,7 @@ func (c *CheatCodesPrecompile) ComputeCreateAddress_74637a7a(deployer common.Add if !nonce.IsUint64() { return common.Address{}, fmt.Errorf("nonce %s too large to fit in regular nonce type", nonce) } - return crypto.CreateAddress(deployer, nonce.Uint64()), nil + return crypto.CreateAddress(deployer, bigs.Uint64Strict(nonce)), nil } // unsupported diff --git a/op-chain-ops/script/check_super_root.go b/op-chain-ops/script/check_super_root.go index 8d1b3c9a9ed..db6e45c1e1a 100644 --- a/op-chain-ops/script/check_super_root.go +++ b/op-chain-ops/script/check_super_root.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -74,28 +75,13 @@ func NewSuperRootMigrator(logger log.Logger, rpcEndpoints []string, targetTimest return migrator, nil } -func NewSuperRootMigratorWithClients(logger log.Logger, clients map[string]*ethclient.Client, targetTimestamp *uint64) (*SuperRootMigrator, error) { - if len(clients) == 0 { - return nil, errors.New("must provide at least one client") - } - migrator := &SuperRootMigrator{ - log: logger, - ethClients: clients, - chainSettings: make(map[string]*ChainSettings), - TargetTimestamp: targetTimestamp, - } - return migrator, nil -} - // Run executes the main logic of the super root migrator within the given context. func (m *SuperRootMigrator) Run(ctx context.Context) (common.Hash, error) { - if m.ethClients == nil { - clients, err := dialClients(ctx, m.rpcEndpoints) - if err != nil { - return common.Hash{}, err - } - m.ethClients = clients + clients, err := dialClients(ctx, m.rpcEndpoints) + if err != nil { + return common.Hash{}, err } + m.ethClients = clients // Use the provided context for all operations if err := m.initClientsAndFetchIDs(ctx); err != nil { return common.Hash{}, fmt.Errorf("failed to initialize clients: %w", err) @@ -222,7 +208,7 @@ func (m *SuperRootMigrator) calculateTargetBlockNumbers(ctx context.Context) err return fmt.Errorf("target timestamp is prior to genesis for endpoint %v", endpoint) } // Compute the target block number - targetBlockNumber := new(big.Int).SetUint64(latestBlock.Number().Uint64() - blocksToLookBack) + targetBlockNumber := new(big.Int).SetUint64(bigs.Uint64Strict(latestBlock.Number()) - blocksToLookBack) // Store the computed values in the chain settings m.chainSettings[endpoint].BlockTime = blockTime @@ -246,7 +232,7 @@ func (m *SuperRootMigrator) calculateOutputRoots(ctx context.Context) error { // Isthmus assumes WithdrawalsHash is present in the header. if targetHeader.WithdrawalsHash == nil { return fmt.Errorf("target block %d (%s) on chain %s (ID: %s) is missing withdrawals hash, required for Isthmus output root calculation", - targetHeader.Number.Uint64(), targetHeader.Hash(), url, settings.ChainID) + bigs.Uint64Strict(targetHeader.Number), targetHeader.Hash(), url, settings.ChainID) } // Construct OutputV0 using StateRoot, WithdrawalsHash (as MessagePasserStorageRoot), and BlockHash diff --git a/op-chain-ops/script/deploy.go b/op-chain-ops/script/deploy.go index 9c39121a1db..b1d1a18b58d 100644 --- a/op-chain-ops/script/deploy.go +++ b/op-chain-ops/script/deploy.go @@ -211,9 +211,15 @@ func (b *forgeScriptBackendImpl) Deploy(artifact *foundry.Artifact, label string b.host.AllowCheatcodes(address) // before constructor execution, give our script cheatcode access b.host.state.MakeExcluded(address) // scripts are persistent across forks - // disable contract size constraints + // disable contract size constraints for script deployment + wasNoMaxCodeSize := b.host.noMaxCodeSize b.host.EnforceMaxCodeSize(false) - defer b.host.EnforceMaxCodeSize(true) + defer func() { + // Only re-enable if it wasn't originally disabled + if !wasNoMaxCodeSize { + b.host.EnforceMaxCodeSize(true) + } + }() // deploy the script deployedAddr, err := b.host.Create(deployer, artifact.Bytecode.Object) diff --git a/op-chain-ops/script/fork.go b/op-chain-ops/script/fork.go index 8954a9984a3..53ae50a2cb6 100644 --- a/op-chain-ops/script/fork.go +++ b/op-chain-ops/script/fork.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" + "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum/go-ethereum/common" ) @@ -35,7 +36,7 @@ func ForkWithBlockNumberU256(num *big.Int) ForkOption { if !num.IsUint64() { return fmt.Errorf("block number %s is too large", num.String()) } - v := num.Uint64() + v := bigs.Uint64Strict(num) cfg.BlockNumber = &v return nil } diff --git a/op-chain-ops/script/forking/db.go b/op-chain-ops/script/forking/db.go index d210e5c5256..c9ba2aa6aa8 100644 --- a/op-chain-ops/script/forking/db.go +++ b/op-chain-ops/script/forking/db.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/triedb" ) @@ -40,6 +39,10 @@ func (f *ForkDB) Snapshot() *snapshot.Tree { return nil } +func (f *ForkDB) Commit(*state.StateUpdate) error { + panic("unimplemented") +} + var _ state.Database = (*ForkDB)(nil) func NewForkDB(source ForkSource) *ForkDB { @@ -96,10 +99,6 @@ func (f *ForkDB) DiskDB() ethdb.KeyValueStore { panic("DiskDB() during active Fork is not supported") } -func (f *ForkDB) PointCache() *utils.PointCache { - panic("PointCache() is not supported") -} - func (f *ForkDB) TrieDB() *triedb.Database { // The TrieDB is unused, but geth does use to check if Verkle is activated. // So we have to create a read-only dummy one, to communicate that verkle really is disabled. diff --git a/op-chain-ops/script/forking/forking_test.go b/op-chain-ops/script/forking/forking_test.go index dd5f1f8518e..d9299e35c3d 100644 --- a/op-chain-ops/script/forking/forking_test.go +++ b/op-chain-ops/script/forking/forking_test.go @@ -259,7 +259,7 @@ func TestForking(t *testing.T) { forkState.SetBalance(alice, uint256.NewInt(100_000), tracing.BalanceChangeUnspecified) forkState.SetState(alice, common.Hash{4}, common.Hash{42}) forkState.SetState(alice, common.Hash{5}, common.Hash{100}) - forkState.SetCode(alice, []byte("hello world")) + forkState.SetCode(alice, []byte("hello world"), tracing.CodeChangeUnspecified) // Check the name name, err = forkState.ForkURLOrAlias(forkC) diff --git a/op-chain-ops/script/forking/reader.go b/op-chain-ops/script/forking/reader.go index ab4b9f5ba6a..86c91adbdbf 100644 --- a/op-chain-ops/script/forking/reader.go +++ b/op-chain-ops/script/forking/reader.go @@ -1,6 +1,8 @@ package forking import ( + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -14,6 +16,11 @@ type forkStateReader struct { var _ state.Reader = (*forkStateReader)(nil) +func (f *forkStateReader) Has(addr common.Address, codeHash common.Hash) bool { + _, err := f.trie.ContractCode(addr, codeHash) + return err != nil +} + func (f *forkStateReader) Account(addr common.Address) (*types.StateAccount, error) { acc, err := f.trie.GetAccount(addr) if err != nil { @@ -31,12 +38,20 @@ func (f *forkStateReader) Storage(addr common.Address, slot common.Hash) (common return common.Hash(v), nil } -func (f *forkStateReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) { - return f.trie.ContractCode(addr, codeHash) +func (f *forkStateReader) Code(addr common.Address, codeHash common.Hash) []byte { + result, err := f.trie.ContractCode(addr, codeHash) + if err != nil { + panic(fmt.Errorf("get contract code for address %q and codeHash %q: %w", addr, codeHash, err)) + } + return result } -func (f *forkStateReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) { - return f.trie.ContractCodeSize(addr, codeHash) +func (f *forkStateReader) CodeSize(addr common.Address, codeHash common.Hash) int { + result, err := f.trie.ContractCodeSize(addr, codeHash) + if err != nil { + panic(fmt.Errorf("get contract code size for address %q and codeHash %q: %w", addr, codeHash, err)) + } + return result } func (f *forkStateReader) Copy() state.Reader { diff --git a/op-chain-ops/script/forking/state.go b/op-chain-ops/script/forking/state.go index 9c49d81c2bb..284f8a8b113 100644 --- a/op-chain-ops/script/forking/state.go +++ b/op-chain-ops/script/forking/state.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" ) @@ -45,6 +44,10 @@ type ForkableState struct { var _ VMStateDB = (*ForkableState)(nil) +func (fst *ForkableState) EmitLogsForBurnAccounts() { + panic("unimplemented") +} + func NewForkableState(base VMStateDB) *ForkableState { return &ForkableState{ selected: base, @@ -60,6 +63,11 @@ func NewForkableState(base VMStateDB) *ForkableState { } } +// IsNewContract implements [VMStateDB]. +func (fst *ForkableState) IsNewContract(addr common.Address) bool { + panic("unimplemented") +} + // ExportDiff exports a state diff. Warning: diffs are like flushed states. // So we flush the state, making all the contents cold, losing transient storage, etc. func (fst *ForkableState) ExportDiff(id ForkID) (*ExportDiff, error) { @@ -278,8 +286,8 @@ func (fst *ForkableState) GetCode(address common.Address) []byte { return fst.stateFor(address).GetCode(address) } -func (fst *ForkableState) SetCode(address common.Address, bytes []byte) []byte { - return fst.stateFor(address).SetCode(address, bytes) +func (fst *ForkableState) SetCode(address common.Address, bytes []byte, reason tracing.CodeChangeReason) []byte { + return fst.stateFor(address).SetCode(address, bytes, reason) } func (fst *ForkableState) GetCodeSize(address common.Address) int { @@ -322,18 +330,14 @@ func (fst *ForkableState) SetTransientState(addr common.Address, key, value comm fst.stateFor(addr).SetTransientState(addr, key, value) } -func (fst *ForkableState) SelfDestruct(address common.Address) uint256.Int { - return fst.stateFor(address).SelfDestruct(address) +func (fst *ForkableState) SelfDestruct(address common.Address) { + fst.stateFor(address).SelfDestruct(address) } func (fst *ForkableState) HasSelfDestructed(address common.Address) bool { return fst.stateFor(address).HasSelfDestructed(address) } -func (fst *ForkableState) SelfDestruct6780(address common.Address) (uint256.Int, bool) { - return fst.stateFor(address).SelfDestruct6780(address) -} - func (fst *ForkableState) Exist(address common.Address) bool { return fst.stateFor(address).Exist(address) } @@ -358,10 +362,6 @@ func (fst *ForkableState) AddSlotToAccessList(addr common.Address, slot common.H fst.stateFor(addr).AddSlotToAccessList(addr, slot) } -func (fst *ForkableState) PointCache() *utils.PointCache { - return fst.selected.PointCache() -} - func (fst *ForkableState) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) { fst.selected.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses) } diff --git a/op-chain-ops/script/precompile.go b/op-chain-ops/script/precompile.go index 2779ac6f96f..0ba9f2e1b3c 100644 --- a/op-chain-ops/script/precompile.go +++ b/op-chain-ops/script/precompile.go @@ -617,7 +617,7 @@ func (p *Precompile[E]) RequiredGas(input []byte) uint64 { // Run implements the vm.PrecompiledContract interface. // This takes the ABI calldata, finds the applicable method by selector, and then runs that method with the data. -func (p *Precompile[E]) Run(input []byte, blockContext common.Hash) ([]byte, error) { +func (p *Precompile[E]) Run(input []byte) ([]byte, error) { if len(input) < 4 { return encodeRevert(fmt.Errorf("expected at least 4 bytes, but got '%x'", input)) } diff --git a/op-chain-ops/script/script.go b/op-chain-ops/script/script.go index 68c072a5688..b62adf88dc2 100644 --- a/op-chain-ops/script/script.go +++ b/op-chain-ops/script/script.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" "github.com/ethereum-optimism/optimism/op-chain-ops/script/forking" "github.com/ethereum-optimism/optimism/op-chain-ops/srcmap" + "github.com/ethereum-optimism/optimism/op-service/bigs" ) // jumpHistory is the amount of successful jumps to track for debugging. @@ -117,6 +118,9 @@ type Host struct { // useCreate2Deployer uses the Create2Deployer for broadcasted // create2 calls. useCreate2Deployer bool + + // noMaxCodeSize disables the maximum contract bytecode size check. + noMaxCodeSize bool } type HostOption func(h *Host) @@ -161,6 +165,15 @@ func WithCreate2Deployer() HostOption { } } +// WithNoMaxCodeSize disables the maximum contract bytecode size check. +// This is useful for development environments where contracts may be compiled +// without optimizations and exceed the standard 24KB limit. +func WithNoMaxCodeSize() HostOption { + return func(h *Host) { + h.noMaxCodeSize = true + } +} + // NewHost creates a Host that can load contracts from the given Artifacts FS, // and with an EVM initialized to the given executionContext. // Optionally src-map loading may be enabled, by providing a non-nil srcFS to read sources from. @@ -228,6 +241,7 @@ func NewHost( GraniteTime: nil, HoloceneTime: nil, JovianTime: nil, + KarstTime: nil, InteropTime: nil, Optimism: nil, } @@ -266,15 +280,13 @@ func NewHost( BlobBaseFee: big.NewInt(0), Random: &executionContext.PrevRandao, } - // Initialize a transaction-context for the EVM to access environment variables. // The transaction context (after embedding inside of the EVM environment) may be mutated later. txContext := vm.TxContext{ Origin: executionContext.Origin, - GasPrice: big.NewInt(0), + GasPrice: uint256.NewInt(0), BlobHashes: executionContext.BlobHashes, - BlobFeeCap: big.NewInt(0), - AccessEvents: state.NewAccessEvents(h.baseState.PointCache()), + AccessEvents: state.NewAccessEvents(), } // Hook up the Host to capture the EVM environment changes @@ -298,6 +310,11 @@ func NewHost( h.env = WrapEVM(vm.NewEVM(blockContext, h.state, h.chainCfg, vmCfg)) h.env.SetTxContext(txContext) + // Apply noMaxCodeSize after EVM is initialized + if h.noMaxCodeSize { + h.EnforceMaxCodeSize(false) + } + return h } @@ -323,7 +340,7 @@ func (h *Host) EnableCheats() error { // Solidity does EXTCODESIZE checks on functions without return-data. // We need to insert some placeholder code to prevent it from aborting calls. // Emulates Forge script: https://github.com/foundry-rs/foundry/blob/224fe9cbf76084c176dabf7d3b2edab5df1ab818/crates/evm/evm/src/executors/mod.rs#L108 - h.state.SetCode(addresses.VMAddr, []byte{0x00}) + h.state.SetCode(addresses.VMAddr, []byte{0x00}, tracing.CodeChangeUnspecified) h.precompiles[addresses.VMAddr] = h.cheatcodes consolePrecompile, err := NewPrecompile[*ConsolePrecompile](&ConsolePrecompile{ @@ -354,9 +371,17 @@ func (h *Host) Call(from common.Address, to common.Address, input []byte, gas ui defer func() { if r := recover(); r != nil { - // Cast to a string to check the error message. If it's not a string it's - // an unexpected panic and we should re-raise it. - rStr, ok := r.(string) + // Try to get the panic message as a string. It could be a plain string + // or an error type (e.g., from errors.New or fmt.Errorf). + var rStr string + var ok bool + if rStr, ok = r.(string); !ok { + // Not a string, try error interface + if rErr, errOk := r.(error); errOk { + rStr = rErr.Error() + ok = true + } + } if !ok || !strings.Contains(strings.ToLower(rStr), "revision id 1") { fmt.Println("panic", rStr) panic(r) @@ -437,7 +462,7 @@ func (h *Host) Create(from common.Address, initCode []byte) (common.Address, err // Note that storage is not removed. func (h *Host) Wipe(addr common.Address) { if h.state.GetCodeSize(addr) > 0 { - h.state.SetCode(addr, nil) + h.state.SetCode(addr, nil, tracing.CodeChangeUnspecified) } h.state.SetNonce(addr, 0, tracing.NonceChangeUnspecified) h.state.SetBalance(addr, uint256.NewInt(0), tracing.BalanceChangeUnspecified) @@ -476,7 +501,7 @@ func (h *Host) ImportAccount(addr common.Address, account types.Account) { } h.state.SetBalance(addr, balance, tracing.BalanceChangeUnspecified) h.state.SetNonce(addr, account.Nonce, tracing.NonceChangeUnspecified) - h.state.SetCode(addr, account.Code) + h.state.SetCode(addr, account.Code, tracing.CodeChangeUnspecified) for key, value := range account.Storage { h.state.SetState(addr, key, value) } @@ -506,7 +531,7 @@ func (h *Host) SetPrecompile(addr common.Address, precompile vm.PrecompiledContr h.log.Debug("adding precompile", "addr", addr) h.precompiles[addr] = precompile // insert non-empty placeholder bytecode, so EXTCODESIZE checks pass - h.state.SetCode(addr, []byte{0}) + h.state.SetCode(addr, []byte{0}, tracing.CodeChangeUnspecified) } // HasPrecompileOverride inspects if there exists an active precompile-override at the given address. @@ -767,7 +792,7 @@ func (h *Host) StateDump() (*foundry.ForgeAllocs, error) { baseState := h.baseState // We have to commit the existing state to the trie, // for all the state-changes to be captured by the trie iterator. - root, err := baseState.Commit(h.env.Context().BlockNumber.Uint64(), true, false) + root, err := baseState.Commit(bigs.Uint64Strict(h.env.Context().BlockNumber), true, false) if err != nil { return nil, fmt.Errorf("failed to commit state: %w", err) } diff --git a/op-chain-ops/script/script_test.go b/op-chain-ops/script/script_test.go index 814bac637f7..8bac8afcf8b 100644 --- a/op-chain-ops/script/script_test.go +++ b/op-chain-ops/script/script_test.go @@ -472,3 +472,57 @@ func TestScriptErrorHandling(t *testing.T) { }) } } + +func TestWithNoMaxCodeSize(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + af := foundry.OpenArtifactsDir("./testdata/test-artifacts") + scriptContext := DefaultContext + deployer := scriptContext.Sender + + // Create init code that deploys a contract with >24KB runtime code + // Init code structure: + // PUSH2 0x6400 (25600 bytes = 25KB) + // PUSH1 0x0c (offset where runtime code starts) + // PUSH1 0x00 (memory destination) + // CODECOPY + // PUSH2 0x6400 (size to return) + // PUSH1 0x00 (memory offset) + // RETURN + runtimeSize := 25 * 1024 // 25KB runtime code + initCode := []byte{ + 0x61, 0x64, 0x00, // PUSH2 0x6400 + 0x60, 0x0c, // PUSH1 0x0c (12 bytes - length of this init code) + 0x60, 0x00, // PUSH1 0x00 + 0x39, // CODECOPY + 0x61, 0x64, 0x00, // PUSH2 0x6400 + 0x60, 0x00, // PUSH1 0x00 + 0xf3, // RETURN + } + // Append runtime code (can be any data, we'll use zeros) + runtimeCode := make([]byte, runtimeSize) + largeBytecode := append(initCode, runtimeCode...) + + t.Run("WithNoMaxCodeSize allows large contracts", func(t *testing.T) { + h := NewHost(logger, af, nil, scriptContext, WithNoMaxCodeSize()) + require.True(t, h.noMaxCodeSize, "noMaxCodeSize flag should be set") + require.True(t, h.env.Config().NoMaxCodeSize, "EVM should have NoMaxCodeSize enabled") + + addr, err := h.Create(deployer, largeBytecode) + require.NoError(t, err, "Should deploy large contract when NoMaxCodeSize is enabled") + require.NotEqual(t, common.Address{}, addr, "Should return valid address") + + // Verify the code was actually deployed + code := h.GetCode(addr) + require.NotEmpty(t, code, "Contract code should be deployed") + }) + + t.Run("Default behavior rejects large contracts", func(t *testing.T) { + h := NewHost(logger, af, nil, scriptContext) + require.False(t, h.noMaxCodeSize, "noMaxCodeSize flag should be false by default") + require.False(t, h.env.Config().NoMaxCodeSize, "EVM should enforce max code size by default") + + _, err := h.Create(deployer, largeBytecode) + require.Error(t, err, "Should reject large contract by default") + require.Contains(t, err.Error(), "max code size", "Error should mention max code size") + }) +} diff --git a/op-chain-ops/script/with.go b/op-chain-ops/script/with.go index 0823bf9b7fb..9bfc9dffd69 100644 --- a/op-chain-ops/script/with.go +++ b/op-chain-ops/script/with.go @@ -44,9 +44,15 @@ func WithScript[B any](h *Host, name string, contract string) (b *B, cleanup fun return nil, nil, fmt.Errorf("failed to make bindings: %w", err) } - // Scripts can be very large + // Scripts can be very large - disable contract size constraints for script deployment + wasNoMaxCodeSize := h.noMaxCodeSize h.EnforceMaxCodeSize(false) - defer h.EnforceMaxCodeSize(true) + defer func() { + // Only re-enable if it wasn't originally disabled + if !wasNoMaxCodeSize { + h.EnforceMaxCodeSize(true) + } + }() // deploy the script contract deployedAddr, err := h.Create(deployer, artifact.Bytecode.Object) if err != nil { diff --git a/op-challenger/flags/flags.go b/op-challenger/flags/flags.go index 7139a32e659..976366023a9 100644 --- a/op-challenger/flags/flags.go +++ b/op-challenger/flags/flags.go @@ -223,15 +223,6 @@ var ( Value: false, Hidden: true, } - CannonKonaExperimentalWitnessEndpointFlag = &cli.BoolFlag{ - Name: "cannon-kona-experimental-witness-endpoint", - Usage: "Enable experimental witness endpoint for Kona interop. " + - "Uses debug_executePayload RPC to collect execution witnesses, " + - "reducing proof generation time by avoiding re-execution. " + - "Requires op-reth or execution client started with " + - "--proofs-history enabled to provide debug_executePayload support.", - EnvVars: prefixEnvVars("CANNON_KONA_EXPERIMENTAL_WITNESS_ENDPOINT"), - } GameWindowFlag = &cli.DurationFlag{ Name: "game-window", Usage: "The time window which the challenger will look for games to progress and claim bonds. " + @@ -295,7 +286,6 @@ var optionalFlags = []cli.Flag{ CannonKonaServerFlag, CannonKonaPreStateFlag, CannonKonaL2CustomFlag, - CannonKonaExperimentalWitnessEndpointFlag, GameWindowFlag, SelectiveClaimResolutionFlag, UnsafeAllowInvalidPrestate, @@ -729,24 +719,23 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), CannonAbsolutePreStateBaseURL: cannonPreStatesURL, CannonKona: vm.Config{ - VmType: gameTypes.CannonKonaGameType, - L1: l1EthRpc, - L1Beacon: l1Beacon, - L2s: l2Rpcs, - L2Experimental: l2Experimental, - VmBin: ctx.String(CannonBinFlag.Name), - Server: ctx.String(CannonKonaServerFlag.Name), - Networks: networks, - L2Custom: ctx.Bool(CannonKonaL2CustomFlag.Name), - RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, gameTypes.CannonKonaGameType), - L1GenesisPath: L1GenesisFlag.String(ctx, gameTypes.CannonKonaGameType), - L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, gameTypes.CannonKonaGameType), - DepsetConfigPath: DepsetConfigFlag.String(ctx, gameTypes.CannonKonaGameType), - SnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), - InfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), - DebugInfo: true, - BinarySnapshots: true, - EnableExperimentalWitnessEndpoint: ctx.Bool(CannonKonaExperimentalWitnessEndpointFlag.Name), + VmType: gameTypes.CannonKonaGameType, + L1: l1EthRpc, + L1Beacon: l1Beacon, + L2s: l2Rpcs, + L2Experimental: l2Experimental, + VmBin: ctx.String(CannonBinFlag.Name), + Server: ctx.String(CannonKonaServerFlag.Name), + Networks: networks, + L2Custom: ctx.Bool(CannonKonaL2CustomFlag.Name), + RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, gameTypes.CannonKonaGameType), + L1GenesisPath: L1GenesisFlag.String(ctx, gameTypes.CannonKonaGameType), + L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, gameTypes.CannonKonaGameType), + DepsetConfigPath: DepsetConfigFlag.String(ctx, gameTypes.CannonKonaGameType), + SnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), + InfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), + DebugInfo: true, + BinarySnapshots: true, }, CannonKonaAbsolutePreState: ctx.String(CannonKonaPreStateFlag.Name), CannonKonaAbsolutePreStateBaseURL: cannonKonaPreStatesURL, diff --git a/op-challenger/game/fault/trace/vm/executor.go b/op-challenger/game/fault/trace/vm/executor.go index 56870f74197..f6908b95bed 100644 --- a/op-challenger/game/fault/trace/vm/executor.go +++ b/op-challenger/game/fault/trace/vm/executor.go @@ -60,13 +60,6 @@ type Config struct { L1GenesisPath string L2GenesisPaths []string DepsetConfigPath string - - // EnableExperimentalWitnessEndpoint enables kona's L2PayloadWitness hint, - // which uses debug_executePayload to collect execution witnesses. - // This can reduce proof generation time by avoiding full block re-derivation - // and re-execution. Requires an execution client with debug_executePayload - // support (e.g., op-reth). - EnableExperimentalWitnessEndpoint bool } func (c *Config) Check() error { diff --git a/op-challenger/game/fault/trace/vm/kona_server_executor.go b/op-challenger/game/fault/trace/vm/kona_server_executor.go index d3c8a51ab40..76da8269754 100644 --- a/op-challenger/game/fault/trace/vm/kona_server_executor.go +++ b/op-challenger/game/fault/trace/vm/kona_server_executor.go @@ -61,9 +61,5 @@ func (s *KonaExecutor) OracleCommand(cfg Config, dataDir string, inputs utils.Lo args = append(args, "--l1-config-path", cfg.L1GenesisPath) } - if cfg.EnableExperimentalWitnessEndpoint { - args = append(args, "--enable-experimental-witness-endpoint") - } - return args, nil } diff --git a/op-challenger/game/fault/trace/vm/kona_server_executor_test.go b/op-challenger/game/fault/trace/vm/kona_server_executor_test.go index 4c3fe86ab99..8894bfab8b2 100644 --- a/op-challenger/game/fault/trace/vm/kona_server_executor_test.go +++ b/op-challenger/game/fault/trace/vm/kona_server_executor_test.go @@ -10,54 +10,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestKonaExecutorWithWitnessEndpoint(t *testing.T) { - t.Parallel() - executor := NewKonaExecutor() - cfg := Config{ - Server: "/path/to/kona", - L1: "http://l1", - L1Beacon: "http://beacon", - L2s: []string{"http://l2"}, - Networks: []string{"op-sepolia"}, - EnableExperimentalWitnessEndpoint: true, - } - inputs := utils.LocalGameInputs{ - L1Head: common.Hash{0x11}, - L2Head: common.Hash{0x22}, - L2OutputRoot: common.Hash{0x33}, - L2Claim: common.Hash{0x44}, - L2SequenceNumber: big.NewInt(100), - } - - args, err := executor.OracleCommand(cfg, "/data", inputs) - require.NoError(t, err) - require.True(t, slices.Contains(args, "--enable-experimental-witness-endpoint")) -} - -func TestKonaExecutorWithoutWitnessEndpoint(t *testing.T) { - t.Parallel() - executor := NewKonaExecutor() - cfg := Config{ - Server: "/path/to/kona", - L1: "http://l1", - L1Beacon: "http://beacon", - L2s: []string{"http://l2"}, - Networks: []string{"op-sepolia"}, - EnableExperimentalWitnessEndpoint: false, - } - inputs := utils.LocalGameInputs{ - L1Head: common.Hash{0x11}, - L2Head: common.Hash{0x22}, - L2OutputRoot: common.Hash{0x33}, - L2Claim: common.Hash{0x44}, - L2SequenceNumber: big.NewInt(100), - } - - args, err := executor.OracleCommand(cfg, "/data", inputs) - require.NoError(t, err) - require.False(t, slices.Contains(args, "--enable-experimental-witness-endpoint")) -} - func TestKonaFillHostCommand(t *testing.T) { dir := "mockdir" cfg := Config{ diff --git a/op-challenger/game/fault/trace/vm/kona_super_server_executor.go b/op-challenger/game/fault/trace/vm/kona_super_server_executor.go index 89db00c4b37..15bc21161fc 100644 --- a/op-challenger/game/fault/trace/vm/kona_super_server_executor.go +++ b/op-challenger/game/fault/trace/vm/kona_super_server_executor.go @@ -54,8 +54,8 @@ func (s *KonaSuperExecutor) OracleCommand(cfg Config, dataDir string, inputs uti args = append(args, "--l1-config-path", cfg.L1GenesisPath) } - if cfg.EnableExperimentalWitnessEndpoint { - args = append(args, "--enable-experimental-witness-endpoint") + if cfg.DepsetConfigPath != "" { + args = append(args, "--depset-cfg", cfg.DepsetConfigPath) } return args, nil diff --git a/op-challenger/game/fault/trace/vm/kona_super_server_executor_test.go b/op-challenger/game/fault/trace/vm/kona_super_server_executor_test.go index 2264b17b5eb..bedfa03982c 100644 --- a/op-challenger/game/fault/trace/vm/kona_super_server_executor_test.go +++ b/op-challenger/game/fault/trace/vm/kona_super_server_executor_test.go @@ -10,15 +10,15 @@ import ( "github.com/stretchr/testify/require" ) -func TestKonaSuperExecutorWithWitnessEndpoint(t *testing.T) { +func TestKonaSuperExecutorWithDepsetConfig(t *testing.T) { t.Parallel() executor := NewKonaSuperExecutor() cfg := Config{ - Server: "/path/to/kona", - L1: "http://l1", - L1Beacon: "http://beacon", - L2s: []string{"http://l2a", "http://l2b"}, - EnableExperimentalWitnessEndpoint: true, + Server: "/path/to/kona", + L1: "http://l1", + L1Beacon: "http://beacon", + L2s: []string{"http://l2a", "http://l2b"}, + DepsetConfigPath: "/path/to/depset.json", } inputs := utils.LocalGameInputs{ L1Head: common.Hash{0x11}, @@ -29,18 +29,19 @@ func TestKonaSuperExecutorWithWitnessEndpoint(t *testing.T) { args, err := executor.OracleCommand(cfg, "/data", inputs) require.NoError(t, err) - require.True(t, slices.Contains(args, "--enable-experimental-witness-endpoint")) + require.True(t, slices.Contains(args, "--depset-cfg")) + idx := slices.Index(args, "--depset-cfg") + require.Equal(t, "/path/to/depset.json", args[idx+1]) } -func TestKonaSuperExecutorWithoutWitnessEndpoint(t *testing.T) { +func TestKonaSuperExecutorWithoutDepsetConfig(t *testing.T) { t.Parallel() executor := NewKonaSuperExecutor() cfg := Config{ - Server: "/path/to/kona", - L1: "http://l1", - L1Beacon: "http://beacon", - L2s: []string{"http://l2a", "http://l2b"}, - EnableExperimentalWitnessEndpoint: false, + Server: "/path/to/kona", + L1: "http://l1", + L1Beacon: "http://beacon", + L2s: []string{"http://l2a", "http://l2b"}, } inputs := utils.LocalGameInputs{ L1Head: common.Hash{0x11}, @@ -51,5 +52,5 @@ func TestKonaSuperExecutorWithoutWitnessEndpoint(t *testing.T) { args, err := executor.OracleCommand(cfg, "/data", inputs) require.NoError(t, err) - require.False(t, slices.Contains(args, "--enable-experimental-witness-endpoint")) + require.False(t, slices.Contains(args, "--depset-cfg")) } diff --git a/op-conductor/conductor/service_test.go b/op-conductor/conductor/service_test.go index 91b5903734a..b88c032a5b4 100644 --- a/op-conductor/conductor/service_test.go +++ b/op-conductor/conductor/service_test.go @@ -66,18 +66,17 @@ func mockConfig(t *testing.T) Config { GasLimit: 30000000, }, }, - BlockTime: 2, - MaxSequencerDrift: 600, - SeqWindowSize: 3600, - ChannelTimeoutBedrock: 300, - L1ChainID: big.NewInt(1), - L2ChainID: big.NewInt(2), - RegolithTime: &now, - CanyonTime: &now, - BatchInboxAddress: [20]byte{1, 2}, - DepositContractAddress: [20]byte{2, 3}, - L1SystemConfigAddress: [20]byte{3, 4}, - ProtocolVersionsAddress: [20]byte{4, 5}, + BlockTime: 2, + MaxSequencerDrift: 600, + SeqWindowSize: 3600, + ChannelTimeoutBedrock: 300, + L1ChainID: big.NewInt(1), + L2ChainID: big.NewInt(2), + RegolithTime: &now, + CanyonTime: &now, + BatchInboxAddress: [20]byte{1, 2}, + DepositContractAddress: [20]byte{2, 3}, + L1SystemConfigAddress: [20]byte{3, 4}, }, RPCEnableProxy: false, } diff --git a/op-core/devfeatures/devfeatures.go b/op-core/devfeatures/devfeatures.go new file mode 100644 index 00000000000..331718851f5 --- /dev/null +++ b/op-core/devfeatures/devfeatures.go @@ -0,0 +1,68 @@ +package devfeatures + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Development feature flag constants. +var ( + // OptimismPortalInteropFlag enables interop features in OptimismPortal2. + OptimismPortalInteropFlag = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001") + + // CannonKonaFlag enables Kona as the default cannon prover. + CannonKonaFlag = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000010") + + // DeployV2DisputeGamesFlag enables deployment of V2 dispute game contracts. + DeployV2DisputeGamesFlag = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000100") + + // L2CMFlag enables L2CM. + L2CMFlag = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000100000") + + // ZKDisputeGameFlag enables the ZK dispute game system. + // TODO(#19432): Use this flag in the OPCM/OPD integration pipeline. + ZKDisputeGameFlag = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000001000000") + + // SuperRootGamesMigrationFlag enables the super root games migration path in OPCM upgrade. + SuperRootGamesMigrationFlag = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000010000000") +) + +// IsDevFeatureEnabled checks if a specific development feature is enabled in a feature bitmap. +// It performs a bitwise AND between the bitmap and flag to determine if the feature +// is set. This follows the same pattern as the Solidity DevFeatures library. +func IsDevFeatureEnabled(bitmap, flag common.Hash) bool { + // L2CM is enabled by default. TODO(#20084): remove with the broader L2CMFlag cleanup. + if hasFlag(flag, L2CMFlag) { + return true + } + // CannonKona is enabled by default. TODO(#20084): remove with the broader CannonKonaFlag cleanup. + if hasFlag(flag, CannonKonaFlag) { + return true + } + b := new(big.Int).SetBytes(bitmap[:]) + f := new(big.Int).SetBytes(flag[:]) + + featuresIsNonZero := f.Cmp(big.NewInt(0)) != 0 + bitmapContainsFeatures := new(big.Int).And(b, f).Cmp(f) == 0 + return featuresIsNonZero && bitmapContainsFeatures +} + +// hasFlag reports whether all bits of flag are set in features. +func hasFlag(features, flag common.Hash) bool { + for i := 0; i < 32; i++ { + if features[i]&flag[i] != flag[i] { + return false + } + } + return true +} + +// EnableDevFeature sets a specific development feature flag in a feature bitmap. +func EnableDevFeature(bitmap, flag common.Hash) common.Hash { + var result common.Hash + for i := 0; i < 32; i++ { + result[i] = bitmap[i] | flag[i] + } + return result +} diff --git a/op-core/devfeatures/devfeatures_test.go b/op-core/devfeatures/devfeatures_test.go new file mode 100644 index 00000000000..5b4359fcd15 --- /dev/null +++ b/op-core/devfeatures/devfeatures_test.go @@ -0,0 +1,139 @@ +package devfeatures + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +var ( + FEATURE_A = common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001") + FEATURE_B = common.HexToHash("0000000000000000000000000000000000000000000000000000000000000100") + FEATURE_C = common.HexToHash("1000000000000000000000000000000000000000000000000000000000000000") + FEATURES_AB = or(FEATURE_A, FEATURE_B) + FEATURES_ABC = or(FEATURE_A, FEATURE_B, FEATURE_C) + FEATURES_AB_INVERTED = not(FEATURES_AB) + EMPTY_FEATURES = [32]byte{} + ALL_FEATURES = common.HexToHash("1111111111111111111111111111111111111111111111111111111111111111") +) + +func TestIsDevFeatureEnabled(t *testing.T) { + t.Run("single feature exact match", func(t *testing.T) { + require.True(t, IsDevFeatureEnabled(FEATURE_A, FEATURE_A)) + require.True(t, IsDevFeatureEnabled(FEATURE_B, FEATURE_B)) + }) + + t.Run("single feature against superset", func(t *testing.T) { + require.True(t, IsDevFeatureEnabled(FEATURES_AB, FEATURE_A)) + require.True(t, IsDevFeatureEnabled(FEATURES_AB, FEATURE_B)) + require.True(t, IsDevFeatureEnabled(FEATURES_ABC, FEATURE_A)) + }) + + t.Run("single feature against all", func(t *testing.T) { + require.True(t, IsDevFeatureEnabled(ALL_FEATURES, FEATURE_A)) + require.True(t, IsDevFeatureEnabled(ALL_FEATURES, FEATURE_B)) + }) + + t.Run("single feature against mismatched bitmap", func(t *testing.T) { + require.False(t, IsDevFeatureEnabled(FEATURE_B, FEATURE_A)) + require.False(t, IsDevFeatureEnabled(FEATURE_A, FEATURE_B)) + require.False(t, IsDevFeatureEnabled(FEATURES_AB_INVERTED, FEATURE_A)) + require.False(t, IsDevFeatureEnabled(FEATURES_AB_INVERTED, FEATURE_B)) + }) + + t.Run("single feature against empty", func(t *testing.T) { + require.False(t, IsDevFeatureEnabled(EMPTY_FEATURES, FEATURE_A)) + require.False(t, IsDevFeatureEnabled(EMPTY_FEATURES, FEATURE_B)) + }) + + t.Run("combined features exact match", func(t *testing.T) { + require.True(t, IsDevFeatureEnabled(FEATURES_AB, FEATURES_AB)) + }) + + t.Run("combined features against superset", func(t *testing.T) { + require.True(t, IsDevFeatureEnabled(ALL_FEATURES, FEATURES_AB)) + require.True(t, IsDevFeatureEnabled(FEATURES_ABC, FEATURES_AB)) + }) + + t.Run("combined features against subset", func(t *testing.T) { + require.False(t, IsDevFeatureEnabled(FEATURE_A, FEATURES_AB)) + require.False(t, IsDevFeatureEnabled(FEATURE_B, FEATURES_AB)) + }) + + t.Run("combined features against mismatched bitmap", func(t *testing.T) { + require.False(t, IsDevFeatureEnabled(FEATURES_AB_INVERTED, FEATURES_AB)) + require.False(t, IsDevFeatureEnabled(EMPTY_FEATURES, FEATURES_AB)) + require.False(t, IsDevFeatureEnabled(FEATURE_C, FEATURES_AB)) + }) + + t.Run("empty vs empty", func(t *testing.T) { + require.False(t, IsDevFeatureEnabled(EMPTY_FEATURES, EMPTY_FEATURES)) + }) + + t.Run("all vs all", func(t *testing.T) { + require.True(t, IsDevFeatureEnabled(ALL_FEATURES, ALL_FEATURES)) + }) + + t.Run("empty against all", func(t *testing.T) { + require.False(t, IsDevFeatureEnabled(ALL_FEATURES, EMPTY_FEATURES)) + }) + + // Hardcoded-on flags share identical override semantics. TODO(#20084): remove with the broader DevFeatures cleanup. + hardcoded := []struct { + name string + flag common.Hash + }{ + {"L2CM", L2CMFlag}, + {"CannonKona", CannonKonaFlag}, + } + + t.Run("all against empty", func(t *testing.T) { + // Strip hardcoded-enabled flags. + require.False(t, IsDevFeatureEnabled(EMPTY_FEATURES, and(ALL_FEATURES, not(or(L2CMFlag, CannonKonaFlag))))) + }) + + for _, c := range hardcoded { + t.Run(c.name+" always enabled regardless of bitmap", func(t *testing.T) { + require.True(t, IsDevFeatureEnabled(EMPTY_FEATURES, c.flag)) + require.True(t, IsDevFeatureEnabled(FEATURE_A, c.flag)) + require.True(t, IsDevFeatureEnabled(c.flag, c.flag)) + }) + + t.Run(c.name+" always enabled when combined with other flags", func(t *testing.T) { + require.True(t, IsDevFeatureEnabled(EMPTY_FEATURES, or(FEATURE_A, c.flag))) + require.True(t, IsDevFeatureEnabled(EMPTY_FEATURES, or(FEATURE_B, c.flag))) + }) + } +} + +func TestEnableDevFeature(t *testing.T) { + result := EnableDevFeature(EMPTY_FEATURES, FEATURE_A) + require.Equal(t, FEATURE_A, result) +} + +func or(values ...[32]byte) [32]byte { + var out [32]byte + for i := 0; i < 32; i++ { + for _, v := range values { + out[i] |= v[i] + } + } + return out +} + +func not(a [32]byte) [32]byte { + var out [32]byte + for i := 0; i < 32; i++ { + out[i] = ^a[i] + } + return out +} + +func and(a, b [32]byte) [32]byte { + var out [32]byte + for i := 0; i < 32; i++ { + out[i] = a[i] & b[i] + } + return out +} diff --git a/op-core/eip1559/eip1559.go b/op-core/eip1559/eip1559.go new file mode 100644 index 00000000000..7fa52801c6f --- /dev/null +++ b/op-core/eip1559/eip1559.go @@ -0,0 +1,163 @@ +package eip1559 + +import ( + "encoding/binary" + "errors" + "fmt" + "math" +) + +const ( + HoloceneExtraDataVersionByte = uint8(0x00) + JovianExtraDataVersionByte = uint8(0x01) +) + +type ForkChecker interface { + IsHolocene(time uint64) bool + IsJovian(time uint64) bool +} + +// DecodeOptimismExtraData decodes the Optimism extra data. +// It uses the config and parent time to determine how to do the decoding. +// The extraData is expected to already be valid. +func DecodeOptimismExtraData(fc ForkChecker, time uint64, extraData []byte) (uint64, uint64, *uint64) { + if fc.IsJovian(time) { + return DecodeJovianExtraData(extraData) + } else if fc.IsHolocene(time) && len(extraData) == 9 { + denominator, elasticity := DecodeHolocene1559Params(extraData[1:]) + return denominator, elasticity, nil + } + return 0, 0, nil +} + +// DecodeHolocene1559Params extracts the Holcene 1559 parameters from the encoded form defined here: +// https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/holocene/exec-engine.md#eip-1559-parameters-in-payloadattributesv3 +// +// Returns 0,0 if the format is invalid, though [ValidateHolocene1559Params] should be used instead of this function for +// validity checking. +func DecodeHolocene1559Params(params []byte) (uint64, uint64) { + if len(params) != 8 { + return 0, 0 + } + denominator := binary.BigEndian.Uint32(params[:4]) + elasticity := binary.BigEndian.Uint32(params[4:]) + return uint64(denominator), uint64(elasticity) +} + +// EncodeHolocene1559Params encodes the eip-1559 parameters into 'PayloadAttributes.EIP1559Params' format. Will panic if +// either value is outside uint32 range. +func EncodeHolocene1559Params(denom, elasticity uint64) []byte { + r := make([]byte, 8) + if denom > math.MaxUint32 || elasticity > math.MaxUint32 { + panic("eip-1559 parameters out of uint32 range") + } + binary.BigEndian.PutUint32(r[:4], uint32(denom)) + binary.BigEndian.PutUint32(r[4:], uint32(elasticity)) + return r +} + +// EncodeHoloceneExtraData encodes the eip-1559 parameters into the header 'ExtraData' format. Will panic if either +// value is outside uint32 range. +func EncodeHoloceneExtraData(denom, elasticity uint64) []byte { + r := make([]byte, 9) + if denom > math.MaxUint32 || elasticity > math.MaxUint32 { + panic("eip-1559 parameters out of uint32 range") + } + // leave version byte 0 + binary.BigEndian.PutUint32(r[1:5], uint32(denom)) + binary.BigEndian.PutUint32(r[5:], uint32(elasticity)) + return r +} + +// ValidateHolocene1559Params checks if the encoded parameters of the payload attributes are valid +// according to the Holocene rules: the encoded denominator and elasticity must both be either +// zero or non-zero. +// Note the difference to the extraData validation, where both values must be non-zero. +func ValidateHolocene1559Params(params []byte) error { + if len(params) != 8 { + return fmt.Errorf("holocene eip-1559 params should be 8 bytes, got %d", len(params)) + } + d, e := DecodeHolocene1559Params(params) + if e != 0 && d == 0 { + return errors.New("holocene params cannot have a 0 denominator unless elasticity is also 0") + } else if e == 0 && d != 0 { + return errors.New("holocene params cannot have a 0 elasticity unless denominator is also 0") + } + return nil +} + +// validateHoloceneExtraDataPart validates the Holocene 8-bytes part of extraData: +// the encoded denominator and elasticity must both be non-zero. +// The passed bytes are not checked for correct length, so the caller must ensure it has 8 bytes. +func validateHoloceneExtraDataPart(extra []byte) error { + d, e := DecodeHolocene1559Params(extra) + if d == 0 { + return errors.New("holocene extraData must encode a non-zero denominator") + } else if e == 0 { + return errors.New("holocene extraData must encode a non-zero elasticity") + } + return nil +} + +// ValidateHoloceneExtraData checks if the header extraData is valid according to the Holocene +// rules: the encoded denominator and elasticity must both be non-zero. +// Note the difference to the payload attributes validation, where both values may also be zero +// (at the same time). +func ValidateHoloceneExtraData(extra []byte) error { + if len(extra) != 9 { + return fmt.Errorf("holocene extraData should be 9 bytes, got %d", len(extra)) + } + if extra[0] != HoloceneExtraDataVersionByte { + return fmt.Errorf("holocene extraData version byte should be %d, got %d", HoloceneExtraDataVersionByte, extra[0]) + } + return validateHoloceneExtraDataPart(extra[1:]) +} + +// DecodeJovianExtraData decodes the extraData parameters from the encoded form defined here: +// https://specs.optimism.io/protocol/jovian/exec-engine.html +// +// Returns 0,0,nil if the format is invalid, and d, e, nil for the Holocene length, to provide best effort behavior for non-MinBaseFee extradata, though ValidateJovianExtraData should be used instead of this function for +// validity checking. +func DecodeJovianExtraData(extra []byte) (uint64, uint64, *uint64) { + // Best effort to decode the extraData for every block in the chain's history, + // including blocks before the minimum base fee feature was enabled. + if len(extra) == 9 { + // This is Holocene extraData + denominator, elasticity := DecodeHolocene1559Params(extra[1:9]) + return denominator, elasticity, nil + } else if len(extra) == 17 { + // Decode extraData when the minimum base fee fork is enabled + denominator, elasticity := DecodeHolocene1559Params(extra[1:9]) + minBaseFee := binary.BigEndian.Uint64(extra[9:]) + return denominator, elasticity, &minBaseFee + } + return 0, 0, nil +} + +// EncodeJovianExtraData encodes the eip-1559 and minBaseFee parameters into the header 'ExtraData' format. +// Will panic if eip-1559 parameters are outside uint32 range. +func EncodeJovianExtraData(denom, elasticity, minBaseFee uint64) []byte { + r := make([]byte, 17) + if denom > math.MaxUint32 || elasticity > math.MaxUint32 { + panic("eip-1559 parameters out of uint32 range") + } + r[0] = JovianExtraDataVersionByte + binary.BigEndian.PutUint32(r[1:5], uint32(denom)) + binary.BigEndian.PutUint32(r[5:9], uint32(elasticity)) + binary.BigEndian.PutUint64(r[9:], minBaseFee) + return r +} + +// ValidateJovianExtraData checks if the header extraData is valid according to the Jovian rules: +// the Holocene rules apply to the 8 bytes encoding the eip-1559 parameters and the minBaseFee can +// be set arbitrarily. +func ValidateJovianExtraData(extra []byte) error { + if len(extra) != 17 { + return fmt.Errorf("Jovian extraData should be 17 bytes, got %d", len(extra)) + } + if extra[0] != JovianExtraDataVersionByte { + return fmt.Errorf("Jovian extraData version byte should be %d, got %d", JovianExtraDataVersionByte, extra[0]) + } + // Note that the encoded minBaseFee can be set arbitrarily, so no additional validation is done. + return validateHoloceneExtraDataPart(extra[1:9]) +} diff --git a/op-core/eip1559/eip1559_test.go b/op-core/eip1559/eip1559_test.go new file mode 100644 index 00000000000..aa6889b392e --- /dev/null +++ b/op-core/eip1559/eip1559_test.go @@ -0,0 +1,99 @@ +package eip1559 + +import ( + "testing" +) + +func TestValidateHolocene1559Params(t *testing.T) { + tests := []struct { + name string + params []byte + expected string + }{ + { + name: "Wrong Length", + params: []byte{0x00, 0x01}, + expected: "holocene eip-1559 params should be 8 bytes, got 2", + }, + { + name: "Zero denominator, non-zero elasticity", + params: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + expected: "holocene params cannot have a 0 denominator unless elasticity is also 0", + }, + { + name: "Zero elasticity, non-zero denominator", + params: []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}, + expected: "holocene params cannot have a 0 elasticity unless denominator is also 0", + }, + { + name: "Both zero (valid)", + params: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + }, + { + name: "Both non-zero (valid)", + params: []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := ValidateHolocene1559Params(tc.params) + if tc.expected == "" && err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + if tc.expected != "" && (err == nil || err.Error() != tc.expected) { + t.Errorf("Expected error: %s, but got: %v", tc.expected, err) + } + }) + } +} + +func TestValidateHoloceneExtraData(t *testing.T) { + tests := []struct { + name string + extra []byte + expected string + }{ + { + name: "Wrong Length", + extra: []byte{0x00, 0x01}, + expected: "holocene extraData should be 9 bytes, got 2", + }, + { + name: "Wrong Version", + extra: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + expected: "holocene extraData version byte should be 0, got 1", + }, + { + name: "Zero denominator, non-zero elasticity", + extra: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + expected: "holocene extraData must encode a non-zero denominator", + }, + { + name: "Zero elasticity, non-zero denominator", + extra: []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}, + expected: "holocene extraData must encode a non-zero elasticity", + }, + { + name: "Both zero (invalid)", + extra: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + expected: "holocene extraData must encode a non-zero denominator", + }, + { + name: "Both non-zero (valid)", + extra: []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := ValidateHoloceneExtraData(tc.extra) + if tc.expected == "" && err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + if tc.expected != "" && (err == nil || err.Error() != tc.expected) { + t.Errorf("Expected error: %s, but got: %v", tc.expected, err) + } + }) + } +} diff --git a/op-core/forks/forks.go b/op-core/forks/forks.go index 6267734cb99..546360ac1c1 100644 --- a/op-core/forks/forks.go +++ b/op-core/forks/forks.go @@ -16,6 +16,7 @@ const ( Holocene Name = "holocene" Isthmus Name = "isthmus" Jovian Name = "jovian" + Karst Name = "karst" Interop Name = "interop" // ADD NEW MAINLINE FORKS TO [All] BELOW! @@ -37,6 +38,7 @@ var All = []Name{ Holocene, Isthmus, Jovian, + Karst, Interop, // ADD NEW MAINLINE FORKS HERE! } @@ -60,16 +62,22 @@ func From(start Name) []Name { panic(fmt.Sprintf("invalid fork: %s", start)) } -var next = func() map[Name]Name { - m := make(map[Name]Name, len(All)) +var next, prev = func() (map[Name]Name, map[Name]Name) { + n := make(map[Name]Name, len(All)) + p := make(map[Name]Name, len(All)) for i, f := range All { if i == len(All)-1 { - m[f] = None - break + n[f] = None + } else { + n[f] = All[i+1] + } + if i == 0 { + p[f] = None + } else { + p[f] = All[i-1] } - m[f] = All[i+1] } - return m + return n, p }() // IsValid returns true if the provided fork is a known fork. @@ -80,3 +88,6 @@ func IsValid(f Name) bool { // Next returns the fork that follows the provided fork, or None if it is the last. func Next(f Name) Name { return next[f] } + +// Prev returns the fork that precedes the provided fork, or None if it is the first. +func Prev(f Name) Name { return prev[f] } diff --git a/op-core/forks/forks_test.go b/op-core/forks/forks_test.go new file mode 100644 index 00000000000..e49b1574f34 --- /dev/null +++ b/op-core/forks/forks_test.go @@ -0,0 +1,77 @@ +package forks + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNext(t *testing.T) { + tests := []struct { + name string + fork Name + expected Name + }{ + {"first fork", Bedrock, Regolith}, + {"middle fork", Ecotone, Fjord}, + {"second-to-last", Karst, Interop}, + {"last fork returns None", Interop, None}, + {"unknown fork returns None", Name("unknown"), None}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.expected, Next(tc.fork)) + }) + } +} + +func TestPrev(t *testing.T) { + tests := []struct { + name string + fork Name + expected Name + }{ + {"first fork returns None", Bedrock, None}, + {"second fork", Regolith, Bedrock}, + {"middle fork", Fjord, Ecotone}, + {"last fork", Interop, Karst}, + {"unknown fork returns None", Name("unknown"), None}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.expected, Prev(tc.fork)) + }) + } +} + +func TestNextPrevInverse(t *testing.T) { + // For every mainline fork that is not the first, Prev(Next(prev)) == prev, + // and for every fork that is not the last, Next(Prev(next)) == next. + // This guards against the two maps drifting out of sync. + for i, f := range All { + if i < len(All)-1 { + require.Equal(t, f, Prev(Next(f)), "Prev(Next(%s)) must equal %s", f, f) + } + if i > 0 { + require.Equal(t, f, Next(Prev(f)), "Next(Prev(%s)) must equal %s", f, f) + } + } +} + +func TestFrom(t *testing.T) { + t.Run("from first returns all", func(t *testing.T) { + require.Equal(t, All, From(Bedrock)) + }) + t.Run("from middle returns tail", func(t *testing.T) { + got := From(Ecotone) + require.Equal(t, Ecotone, got[0]) + require.Equal(t, All[len(All)-1], got[len(got)-1]) + require.Len(t, got, len(All)-4) // Bedrock, Regolith, Canyon, Delta excluded + }) + t.Run("from last returns single", func(t *testing.T) { + require.Equal(t, []Name{Interop}, From(Interop)) + }) + t.Run("unknown fork panics", func(t *testing.T) { + require.Panics(t, func() { From(Name("unknown")) }) + }) +} diff --git a/op-core/interop/depset/depset.go b/op-core/interop/depset/depset.go new file mode 100644 index 00000000000..447185e48da --- /dev/null +++ b/op-core/interop/depset/depset.go @@ -0,0 +1,49 @@ +package depset + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/superchain" +) + +type DependencySetSource interface { + LoadDependencySet() (DependencySet, error) +} + +// DependencySet is an initialized dependency set, ready to answer queries +// of what is and what is not part of the dependency set. +type DependencySet interface { + // Chains returns the list of chains that are part of the dependency set. + Chains() []eth.ChainID + + // HasChain determines if a chain is being tracked for interop purposes. + // See CanExecuteAt and CanInitiateAt to check if a chain may message at a given time. + HasChain(chainID eth.ChainID) bool + + // MessageExpiryWindow returns the message expiry window to use for this dependency set. + MessageExpiryWindow() uint64 +} + +// FromRegistry loads a dependency set from the superchain-registry. +// Returns error of type superchain.ErrUnknownChain if the chain is not available in the superchain registry. +func FromRegistry(chainID eth.ChainID) (DependencySet, error) { + id, ok := chainID.Uint64() + if !ok { + return nil, fmt.Errorf("%w: %v", superchain.ErrUnknownChain, chainID) + } + depSet, err := superchain.GetDepset(id) + if err != nil { + return nil, err + } + chains := make(map[eth.ChainID]*StaticConfigDependency) + for idStr := range depSet { + id, ok := math.ParseUint64(idStr) + if !ok { + return nil, fmt.Errorf("invalid chain ID in dependency set: %s", idStr) + } + chains[eth.ChainIDFromUInt64(id)] = &StaticConfigDependency{} + } + return NewStaticConfigDependencySet(chains) +} diff --git a/op-core/interop/depset/depset_test.go b/op-core/interop/depset/depset_test.go new file mode 100644 index 00000000000..cf764859eda --- /dev/null +++ b/op-core/interop/depset/depset_test.go @@ -0,0 +1,132 @@ +package depset + +import ( + "bytes" + "encoding/json" + "os" + "path" + "testing" + + "github.com/BurntSushi/toml" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/superchain" + "github.com/stretchr/testify/require" +) + +func TestDependencySet(t *testing.T) { + t.Run("JSON serialization", func(t *testing.T) { + testDependencySetSerialization(t, "json", + func(depSet *StaticConfigDependencySet) ([]byte, error) { return json.Marshal(depSet) }, + func(data []byte, depSet *StaticConfigDependencySet) error { return json.Unmarshal(data, depSet) }, + ) + }) + + t.Run("TOML serialization", func(t *testing.T) { + testDependencySetSerialization(t, "toml", + func(depSet *StaticConfigDependencySet) ([]byte, error) { + var buf bytes.Buffer + encoder := toml.NewEncoder(&buf) + if err := encoder.Encode(depSet); err != nil { + return nil, err + } + return buf.Bytes(), nil + }, + func(data []byte, depSet *StaticConfigDependencySet) error { + _, err := toml.Decode(string(data), depSet) + return err + }, + ) + }) + + t.Run("invalid TOML", func(t *testing.T) { + bad := []byte(`dependencies = { bad = 1 }`) + var ds StaticConfigDependencySet + _, err := toml.Decode(string(bad), &ds) + require.Error(t, err) + }) +} + +func testDependencySetSerialization( + t *testing.T, + fileExt string, + marshal func(*StaticConfigDependencySet) ([]byte, error), + unmarshal func([]byte, *StaticConfigDependencySet) error, +) { + d := path.Join(t.TempDir(), "tmp_dep_set."+fileExt) + + depSet, err := NewStaticConfigDependencySet( + map[eth.ChainID]*StaticConfigDependency{ + eth.ChainIDFromUInt64(900): {}, + eth.ChainIDFromUInt64(901): {}, + }) + require.NoError(t, err) + + t.Run("DefaultExpiryWindow", func(t *testing.T) { + data, err := marshal(depSet) + require.NoError(t, err) + + require.NoError(t, os.WriteFile(d, data, 0o644)) + + // For JSON, use the loader. For TOML, unmarshal directly + var result DependencySet + if fileExt == "json" { + loader := &JSONDependencySetLoader{Path: d} + result, err = loader.LoadDependencySet() + require.NoError(t, err) + } else { + fileData, err := os.ReadFile(d) + require.NoError(t, err) + + var newDepSet StaticConfigDependencySet + err = unmarshal(fileData, &newDepSet) + require.NoError(t, err) + result = &newDepSet + } + + chainIDs := result.Chains() + require.ElementsMatch(t, []eth.ChainID{ + eth.ChainIDFromUInt64(900), + eth.ChainIDFromUInt64(901), + }, chainIDs) + + require.Equal(t, MessageExpiryTimeSecondsInterop, result.MessageExpiryWindow()) + }) + + t.Run("CustomExpiryWindow", func(t *testing.T) { + depSet.overrideMessageExpiryWindow = 15 + + data, err := marshal(depSet) + require.NoError(t, err) + require.NoError(t, os.WriteFile(d, data, 0o644)) + + var result DependencySet + if fileExt == "json" { + loader := &JSONDependencySetLoader{Path: d} + result, err = loader.LoadDependencySet() + require.NoError(t, err) + } else { + fileData, err := os.ReadFile(d) + require.NoError(t, err) + + var newDepSet StaticConfigDependencySet + err = unmarshal(fileData, &newDepSet) + require.NoError(t, err) + result = &newDepSet + } + + require.Equal(t, uint64(15), result.MessageExpiryWindow()) + }) + + t.Run("HasChain", func(t *testing.T) { + require.True(t, depSet.HasChain(eth.ChainIDFromUInt64(900))) + require.False(t, depSet.HasChain(eth.ChainIDFromUInt64(902))) + }) +} + +func TestLoadDependencySetFromRegistry(t *testing.T) { + chainID, err := superchain.ChainIDByName("op-mainnet") + require.NoError(t, err) + depSet, err := FromRegistry(eth.ChainIDFromUInt64(chainID)) + require.NoError(t, err) + require.True(t, depSet.HasChain(eth.ChainIDFromUInt64(chainID))) +} diff --git a/op-core/interop/depset/json.go b/op-core/interop/depset/json.go new file mode 100644 index 00000000000..6627312bad3 --- /dev/null +++ b/op-core/interop/depset/json.go @@ -0,0 +1,33 @@ +package depset + +import ( + "encoding/json" + "fmt" + "io" + "os" +) + +// JSONDependencySetLoader loads a dependency set from a file-path. +type JSONDependencySetLoader struct { + Path string +} + +var _ DependencySetSource = (*JSONDependencySetLoader)(nil) + +func (j *JSONDependencySetLoader) LoadDependencySet() (DependencySet, error) { + f, err := os.Open(j.Path) + if err != nil { + return nil, fmt.Errorf("failed to open dependency set: %w", err) + } + defer f.Close() + return ParseJSONDependencySet(f) +} + +func ParseJSONDependencySet(f io.Reader) (DependencySet, error) { + dec := json.NewDecoder(f) + var out StaticConfigDependencySet + if err := dec.Decode(&out); err != nil { + return nil, fmt.Errorf("failed to decode dependency set: %w", err) + } + return &out, nil +} diff --git a/op-core/interop/depset/links.go b/op-core/interop/depset/links.go new file mode 100644 index 00000000000..e43ea9a20da --- /dev/null +++ b/op-core/interop/depset/links.go @@ -0,0 +1,86 @@ +package depset + +import ( + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/safemath" +) + +// ActivationConfig answers per-chain interop activation queries. +// It is satisfied by the rollup config set kept alongside the dependency set. +type ActivationConfig interface { + // IsInterop returns true if the Interop hardfork is active for the given chain at the given timestamp. + // It panics if the chain is not part of the rollup config set. + IsInterop(chainID eth.ChainID, ts uint64) bool + + // IsInteropActivationBlock returns true if the given timestamp is for an Interop activation block. + // It panics if the chain is not part of the rollup config set. + IsInteropActivationBlock(chainID eth.ChainID, ts uint64) bool +} + +type LinkChecker interface { + // CanExecute determines if an executing message is valid w.r.t. chain and timestamp constraints. + // I.e. if the chain may be executing messages at the given timestamp, + // from the given chain at the given initiating timestamp. + // I.e. this does not check a full message, it merely checks some linking constraints. + CanExecute(execInChain eth.ChainID, execInTimestamp uint64, initChainID eth.ChainID, initTimestamp uint64) bool +} + +// LinkerConfig represents the config dependencies that are needed +// to create the default LinkChecker implementation LinkCheckerImpl. +type LinkerConfig interface { + ActivationConfig + DependencySet +} + +// LinkCheckerImpl implements a LinkChecker using the provided config +type LinkCheckerImpl struct { + cfg LinkerConfig +} + +func LinkerFromConfig(cfg LinkerConfig) *LinkCheckerImpl { + return &LinkCheckerImpl{cfg: cfg} +} + +func (lc *LinkCheckerImpl) CanExecute(execInChain eth.ChainID, + execInTimestamp uint64, initChainID eth.ChainID, initTimestamp uint64) bool { + // Check the chains exist in the dependency set. + if !lc.cfg.HasChain(execInChain) { + return false + } + if !lc.cfg.HasChain(initChainID) { + return false + } + // Note: this function deliberately moves some business logic closer to the config. + // What makes a valid/invalid link depends on the interop set, which can change over time. + if !lc.cfg.IsInterop(execInChain, execInTimestamp) { + return false + } + // Note: this does not cover the genesis block of a chain, but that block is empty anyway. + if lc.cfg.IsInteropActivationBlock(execInChain, execInTimestamp) { + return false + } + if !lc.cfg.IsInterop(initChainID, initTimestamp) { + return false + } + // Note: this does not cover the genesis block of a chain, but that block is empty anyway. + if lc.cfg.IsInteropActivationBlock(initChainID, initTimestamp) { + return false + } + if initTimestamp > execInTimestamp { + return false + } + expiresAt := safemath.SaturatingAdd(initTimestamp, lc.cfg.MessageExpiryWindow()) + if expiresAt < execInTimestamp { // expiry check + return false + } + return true +} + +// LinkCheckFn is a function-type that implements LinkChecker, for testing and other special case definitions +type LinkCheckFn func(execInChain eth.ChainID, execInTimestamp uint64, initChainID eth.ChainID, initTimestamp uint64) bool + +func (lFn LinkCheckFn) CanExecute(execInChain eth.ChainID, execInTimestamp uint64, initChainID eth.ChainID, initTimestamp uint64) bool { + return lFn(execInChain, execInTimestamp, initChainID, initTimestamp) +} + +var _ LinkChecker = (LinkCheckFn)(nil) diff --git a/op-core/interop/depset/links_test.go b/op-core/interop/depset/links_test.go new file mode 100644 index 00000000000..9529d2a3281 --- /dev/null +++ b/op-core/interop/depset/links_test.go @@ -0,0 +1,90 @@ +package depset + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type mockLinkCfg struct { + activationTimes map[eth.ChainID]uint64 + window uint64 +} + +func (m *mockLinkCfg) Chains() (out []eth.ChainID) { + for id := range m.activationTimes { + out = append(out, id) + } + sort.Slice(out, func(i, j int) bool { + return out[i].Cmp(out[j]) < 0 + }) + return +} + +func (m *mockLinkCfg) HasChain(chainID eth.ChainID) bool { + _, ok := m.activationTimes[chainID] + return ok +} + +func (m *mockLinkCfg) IsInterop(chainID eth.ChainID, ts uint64) bool { + v, ok := m.activationTimes[chainID] + if !ok { + return false + } + return ts >= v +} + +func (m *mockLinkCfg) IsInteropActivationBlock(chainID eth.ChainID, ts uint64) bool { + v, ok := m.activationTimes[chainID] + if !ok { + return false + } + return ts == v +} + +func (m *mockLinkCfg) MessageExpiryWindow() uint64 { + return m.window +} + +var _ LinkerConfig = (*mockLinkCfg)(nil) + +func TestLinkChecker(t *testing.T) { + chainA := eth.ChainIDFromUInt64(900) + chainB := eth.ChainIDFromUInt64(901) + chainUnknown := eth.ChainIDFromUInt64(700) + + cfg := &mockLinkCfg{ + activationTimes: map[eth.ChainID]uint64{ + chainA: 1000, + chainB: 900, + }, + window: 400, + } + linker := LinkerFromConfig(cfg) + req := require.New(t) + + req.False(linker.CanExecute(chainA, 999, chainB, 950), "cannot exec pre-interop") + req.False(linker.CanExecute(chainA, 1050, chainB, 899), "cannot init pre-interop") + + req.False(linker.CanExecute(chainUnknown, 2050, chainB, 2000), "cannot execute on unknown chain") + req.False(linker.CanExecute(chainA, 2050, chainUnknown, 2000), "cannot initiate on unknown chain") + req.False(linker.CanExecute(chainUnknown, 2050, chainUnknown, 2000), "cannot both be on unknown chain") + + req.False(linker.CanExecute(chainA, 1050, chainB, 1051), "cannot init after exec") + + req.True(linker.CanExecute(chainA, 2000, chainB, 2000), "simple same timestamp") + req.True(linker.CanExecute(chainA, 2050, chainB, 2000), "simple diff timestamp") + req.True(linker.CanExecute(chainA, 2399, chainB, 2000), "near expiry") + req.True(linker.CanExecute(chainA, 2400, chainB, 2000), "at expiry") + req.False(linker.CanExecute(chainA, 2401, chainB, 2000), "expired") + // Technicality: if executing close to the end, and not expiring until after the end, allow it. + req.True(linker.CanExecute(chainA, (^uint64(0))-200, chainB, (^uint64(0))-300), "claimed init msg causes overflow") + + req.True(linker.CanExecute(chainA, 1001, chainA, 1001), "with self") + req.False(linker.CanExecute(chainA, 1001, chainA, 1000), "no init at activation") + req.False(linker.CanExecute(chainA, 1000, chainA, 1001), "no exec at activation") + req.True(linker.CanExecute(chainA, 1001, chainB, 950), "other inits pre-activation of self") +} diff --git a/op-core/interop/depset/static_depset.go b/op-core/interop/depset/static_depset.go new file mode 100644 index 00000000000..9a73c811c58 --- /dev/null +++ b/op-core/interop/depset/static_depset.go @@ -0,0 +1,179 @@ +package depset + +import ( + "bytes" + "encoding/json" + "fmt" + "slices" + "sort" + + "github.com/BurntSushi/toml" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// MessageExpiryTimeSecondsInterop is a post-Interop constant for the minimum age of a message before it can be considered executable. +const MessageExpiryTimeSecondsInterop uint64 = 604800 + +type StaticConfigDependency struct { +} + +// StaticConfigDependencySet statically declares a DependencySet. +// It can be used as a DependencySetSource itself, by simply returning the itself when loading the set. +type StaticConfigDependencySet struct { + // dependency info per chain + dependencies map[eth.ChainID]*StaticConfigDependency + // cached list of chain IDs, sorted by ID value + chainIDs []eth.ChainID + // overrideMessageExpiryWindow is the message expiry window to use for this dependency set + overrideMessageExpiryWindow uint64 +} + +func NewStaticConfigDependencySet(dependencies map[eth.ChainID]*StaticConfigDependency) (*StaticConfigDependencySet, error) { + out := &StaticConfigDependencySet{dependencies: dependencies} + if err := out.hydrate(); err != nil { + return nil, err + } + return out, nil +} + +// NewStaticConfigDependencySetWithMessageExpiryOverride creates a new StaticConfigDependencySet with a message expiry window override. +// To be used only for testing. +func NewStaticConfigDependencySetWithMessageExpiryOverride(dependencies map[eth.ChainID]*StaticConfigDependency, overrideMessageExpiryWindow uint64) (*StaticConfigDependencySet, error) { + out := &StaticConfigDependencySet{dependencies: dependencies, overrideMessageExpiryWindow: overrideMessageExpiryWindow} + if err := out.hydrate(); err != nil { + return nil, err + } + return out, nil +} + +// minStaticConfigDependencySet is a util for JSON/TOML encoding/decoding, +// for just the minimal set of attributes that matter, +// while wrapping the decoding functionality with additional hydration step. +type minStaticConfigDependencySet struct { + Dependencies map[string]*StaticConfigDependency `json:"dependencies" toml:"dependencies"` + OverrideMessageExpiryWindow uint64 `json:"overrideMessageExpiryWindow,omitempty" toml:"override_message_expiry_window,omitempty"` +} + +func (ds *StaticConfigDependencySet) MarshalJSON() ([]byte, error) { + // Convert map keys to strings + stringMap := make(map[string]*StaticConfigDependency) + for id, dep := range ds.dependencies { + stringMap[id.String()] = dep + } + + out := &minStaticConfigDependencySet{ + Dependencies: stringMap, + OverrideMessageExpiryWindow: ds.overrideMessageExpiryWindow, + } + return json.Marshal(out) +} + +func (ds *StaticConfigDependencySet) UnmarshalJSON(data []byte) error { + var v minStaticConfigDependencySet + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + // Convert string keys back to ChainID + ds.dependencies = make(map[eth.ChainID]*StaticConfigDependency) + for idStr, dep := range v.Dependencies { + id, err := eth.ParseDecimalChainID(idStr) + if err != nil { + return fmt.Errorf("invalid chain ID in JSON: %w", err) + } + ds.dependencies[id] = dep + } + + ds.overrideMessageExpiryWindow = v.OverrideMessageExpiryWindow + return ds.hydrate() +} + +func (ds *StaticConfigDependencySet) MarshalTOML() ([]byte, error) { + // Convert map keys (ChainID) to strings so TOML can encode the map. + stringMap := make(map[string]*StaticConfigDependency, len(ds.dependencies)) + for id, dep := range ds.dependencies { + stringMap[id.String()] = dep + } + + payload := &minStaticConfigDependencySet{ + Dependencies: stringMap, + OverrideMessageExpiryWindow: ds.overrideMessageExpiryWindow, + } + + var buf bytes.Buffer + if err := toml.NewEncoder(&buf).Encode(payload); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (ds *StaticConfigDependencySet) UnmarshalTOML(v interface{}) error { + var buf bytes.Buffer + if err := toml.NewEncoder(&buf).Encode(v); err != nil { + return fmt.Errorf("re-encoding TOML: %w", err) + } + + // Decode into the minimal helper struct that has the right tags. + var tmp minStaticConfigDependencySet + if _, err := toml.Decode(buf.String(), &tmp); err != nil { + return fmt.Errorf("decoding into helper struct: %w", err) + } + + // Convert string keys back to ChainID and copy the data. + ds.dependencies = make(map[eth.ChainID]*StaticConfigDependency, len(tmp.Dependencies)) + for idStr, dep := range tmp.Dependencies { + id, err := eth.ParseDecimalChainID(idStr) + if err != nil { + return fmt.Errorf("invalid chain ID %q: %w", idStr, err) + } + ds.dependencies[id] = dep + } + + ds.overrideMessageExpiryWindow = tmp.OverrideMessageExpiryWindow + return ds.hydrate() +} + +// hydrate sets all the cached values, based on the dependencies attribute +func (ds *StaticConfigDependencySet) hydrate() error { + ds.chainIDs = make([]eth.ChainID, 0, len(ds.dependencies)) + for id := range ds.dependencies { + ds.chainIDs = append(ds.chainIDs, id) + } + sort.Slice(ds.chainIDs, func(i, j int) bool { + return ds.chainIDs[i].Cmp(ds.chainIDs[j]) < 0 + }) + return nil +} + +var _ DependencySetSource = (*StaticConfigDependencySet)(nil) + +var _ DependencySet = (*StaticConfigDependencySet)(nil) + +func (ds *StaticConfigDependencySet) LoadDependencySet() (DependencySet, error) { + return ds, nil +} + +func (ds *StaticConfigDependencySet) Chains() []eth.ChainID { + return slices.Clone(ds.chainIDs) +} + +func (ds *StaticConfigDependencySet) HasChain(chainID eth.ChainID) bool { + _, ok := ds.dependencies[chainID] + return ok +} + +func (ds *StaticConfigDependencySet) MessageExpiryWindow() uint64 { + if ds.overrideMessageExpiryWindow == 0 { + return MessageExpiryTimeSecondsInterop + } + return ds.overrideMessageExpiryWindow +} + +// Dependencies returns a deep copy of the dependencies map +func (ds *StaticConfigDependencySet) Dependencies() map[eth.ChainID]*StaticConfigDependency { + copied := make(map[eth.ChainID]*StaticConfigDependency, len(ds.dependencies)) + for chainId := range ds.dependencies { + copied[chainId] = &StaticConfigDependency{} + } + return copied +} diff --git a/op-core/interop/interop.go b/op-core/interop/interop.go new file mode 100644 index 00000000000..b5779a738fd --- /dev/null +++ b/op-core/interop/interop.go @@ -0,0 +1,87 @@ +package interop + +import "errors" + +var ( + // ErrOutOfOrder happens when you try to add data to the DB, + // but it does not actually fit onto the latest data (by being too old or new). + ErrOutOfOrder = errors.New("data out of order") + // ErrDataCorruption happens when the underlying DB has some I/O issue + ErrDataCorruption = errors.New("data corruption") + // ErrNotExact happens when we search the DB, know the data may be there, but is not (e.g. different revision) + ErrNotExact = errors.New("missed data") + // ErrSkipped happens when we try to retrieve data that is not available (pruned) + // It may also happen if we erroneously skip data, that was not considered a conflict, if the DB is corrupted. + ErrSkipped = errors.New("skipped data") + // ErrFuture happens when data is just not yet available + ErrFuture = errors.New("future data") + // ErrInvalidatedRead happens when something was assumed from the DB, but then invalidated due to e.g. a reorg. + ErrInvalidatedRead = errors.New("invalidated read") + // ErrAlreadyInvalidatingRead happens when something is being invalidated, + // and something else attempts to invalidate at the same time. + ErrAlreadyInvalidatingRead = errors.New("already invalidating read") + // ErrRewindFailed happens when we fail to rewind the chain (reorg response). + ErrRewindFailed = errors.New("rewind failed") + // ErrIneffective happens when data is accepted as compatible, but did not change anything. + // This happens when a node is deriving an L2 block we already know of being derived from the given source, + // but without path to skip forward to newer source blocks without doing the known derivation work first. + ErrIneffective = errors.New("ineffective data") + // ErrConflict happens when we know for sure that there is different canonical data + ErrConflict = errors.New("conflicting data") + // ErrAwaitReplacementBlock happens when we know for sure that a replacement block is needed before progress can be made. + ErrAwaitReplacementBlock = errors.New("awaiting replacement block") + // ErrStop can be used in iterators to indicate iteration has to stop + ErrStop = errors.New("iter stop") + // ErrOutOfScope is when data is accessed, but access is not allowed, because of a limited scope. + // E.g. when limiting scope to L2 blocks derived from a specific subset of the L1 chain. + ErrOutOfScope = errors.New("out of scope") + // ErrPreviousToFirst is when you try to get the previous block of the first block + // E.g. when calling PreviousDerivedFrom on the first L1 block in the DB. + ErrPreviousToFirst = errors.New("cannot get parent of first block in the database") + // ErrUnknownChain is when a chain is unknown, not in the dependency set. + ErrUnknownChain = errors.New("unknown chain") + // ErrNoRPCSource happens when a sub-service needs an RPC data source, but is not configured with one. + ErrNoRPCSource = errors.New("no RPC client configured") + // ErrUninitialized happens when a chain database is not initialized yet + ErrUninitialized = errors.New("uninitialized chain database") + // ErrFailsafeEnabled is when failsafe is enabled and the request is rejected + ErrFailsafeEnabled = errors.New("failsafe is enabled, rejecting all CheckAccessList requests") +) + +var genericInvalidParamsErr = -32602 + +// errorCodeMap is based on the interop supervisor spec - https://github.com/ethereum-optimism/specs/blob/28a0fac2428b10f9ee29ee1bfbbe366181cc9ac4/specs/interop/supervisor.md#json-rpc-error-codes +var errorCodeMap = map[error]int{ + ErrOutOfOrder: -320900, + ErrDataCorruption: -321501, + ErrNotExact: -321500, + ErrSkipped: -320500, + ErrFuture: -321401, + ErrIneffective: -320601, + ErrConflict: -320600, + ErrAwaitReplacementBlock: -320901, + ErrStop: -321000, + ErrOutOfScope: -321100, + ErrPreviousToFirst: -321200, + ErrUnknownChain: -320501, + ErrUninitialized: -320400, + + ErrNoRPCSource: genericInvalidParamsErr, + ErrFailsafeEnabled: genericInvalidParamsErr, + ErrInvalidatedRead: genericInvalidParamsErr, + ErrAlreadyInvalidatingRead: genericInvalidParamsErr, + ErrRewindFailed: genericInvalidParamsErr, +} + +// GetErrorCode returns the error code for the given error based on interop supervisor spec +func GetErrorCode(err error) int { + if err == nil { + return 0 + } + for knownErr, code := range errorCodeMap { + if errors.Is(err, knownErr) { + return code + } + } + return genericInvalidParamsErr +} diff --git a/op-core/interop/messages/logs.go b/op-core/interop/messages/logs.go new file mode 100644 index 00000000000..1ede7f53b5b --- /dev/null +++ b/op-core/interop/messages/logs.go @@ -0,0 +1,55 @@ +package messages + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// MessageFromLog returns the executing message decoded from a CrossL2Inbox log, +// or nil if the log is not an executing message. +func MessageFromLog(l *ethTypes.Log) (*Message, error) { + if l.Address != params.InteropCrossL2InboxAddress { + return nil, nil + } + if len(l.Topics) != 2 { // topics: event-id and payload-hash + return nil, nil + } + if l.Topics[0] != ExecutingMessageEventTopic { + return nil, nil + } + var msg Message + if err := msg.DecodeEvent(l.Topics, l.Data); err != nil { + return nil, fmt.Errorf("invalid executing message: %w", err) + } + return &msg, nil +} + +// DecodeExecutingMessageLog returns the ExecutingMessage decoded from a CrossL2Inbox log, +// or nil if the log is not an executing message. +func DecodeExecutingMessageLog(l *ethTypes.Log) (*ExecutingMessage, error) { + msg, err := MessageFromLog(l) + if err != nil || msg == nil { + return nil, err + } + return &ExecutingMessage{ + ChainID: msg.Identifier.ChainID, + BlockNum: msg.Identifier.BlockNumber, + LogIdx: msg.Identifier.LogIndex, + Timestamp: msg.Identifier.Timestamp, + Checksum: msg.Checksum(), + }, nil +} + +// LogToLogHash transforms a log into a hash that represents the log. +// It is the concatenation of the log's address and the hash of the log's payload, +// which is then hashed again. This is the hash that is stored in the log storage. +// The address is hashed into the payload hash to save space in the log storage, +// and because they represent paired data. +func LogToLogHash(l *ethTypes.Log) common.Hash { + payloadHash := crypto.Keccak256Hash(LogToMessagePayload(l)) + return PayloadHashToLogHash(payloadHash, l.Address) +} diff --git a/op-core/interop/messages/logs_test.go b/op-core/interop/messages/logs_test.go new file mode 100644 index 00000000000..201c4e5f188 --- /dev/null +++ b/op-core/interop/messages/logs_test.go @@ -0,0 +1,108 @@ +package messages + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +func TestDecodeExecutingMessageLog(t *testing.T) { + data := ` +{ + "address": "0x4200000000000000000000000000000000000022", + "topics": [ + "0x5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba7", + "0xc3f57e1f0dd62a4f77787d834029bfeaab8894022c47edbe13b044fb658c9190" + ], + "data": "0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa3000000000000000000000000000000000000000000000000000000000000119d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006724d56300000000000000000000000000000000000000000000000000000000000dbc68", + "blockHash": "0x355b82e9db9105fe3e7b7ed1897878ecbba8be7f30f94aca9dc55b6296a624cf", + "blockNumber": "0x13a8", + "transactionHash": "0x6eb22bb67562ac6a8fdbf60d6227c0b1f3f9d1d15ead1b0de055358f4fb9fa69", + "transactionIndex": "0x2", + "logIndex": "0x0", + "removed": false +} +` + var logEvent ethTypes.Log + require.NoError(t, json.Unmarshal([]byte(data), &logEvent)) + + msg, err := DecodeExecutingMessageLog(&logEvent) + require.NoError(t, err) + require.NotNil(t, msg) + + originAddr := common.HexToAddress("0x5fbdb2315678afecb367f032d93f642f64180aa3") + payloadHash := common.HexToHash("0xc3f57e1f0dd62a4f77787d834029bfeaab8894022c47edbe13b044fb658c9190") + logHash := PayloadHashToLogHash(payloadHash, originAddr) + args := ChecksumArgs{ + BlockNumber: uint64(4509), + LogIndex: uint32(0), + Timestamp: uint64(1730467171), + ChainID: eth.ChainIDFromUInt64(900200), + LogHash: logHash, + } + checksum := args.Checksum() + + require.Equal(t, checksum, msg.Checksum) + require.Equal(t, uint64(4509), msg.BlockNum) + require.Equal(t, uint32(0), msg.LogIdx) + require.Equal(t, uint64(1730467171), msg.Timestamp) + require.Equal(t, eth.ChainIDFromUInt64(900200), msg.ChainID) +} + +func TestLogToLogHash(t *testing.T) { + mkLog := func() *ethTypes.Log { + return ðTypes.Log{ + Address: common.Address{0xaa, 0xbb}, + Topics: []common.Hash{ + {0xcc}, + {0xdd}, + }, + Data: []byte{0xee, 0xff, 0x00}, + BlockNumber: 12345, + TxHash: common.Hash{0x11, 0x22, 0x33}, + TxIndex: 4, + BlockHash: common.Hash{0x44, 0x55}, + Index: 8, + Removed: false, + } + } + relevantMods := []func(l *ethTypes.Log){ + func(l *ethTypes.Log) { l.Address = common.Address{0xab, 0xcd} }, + func(l *ethTypes.Log) { l.Topics = append(l.Topics, common.Hash{0x12, 0x34}) }, + func(l *ethTypes.Log) { l.Topics = l.Topics[:len(l.Topics)-1] }, + func(l *ethTypes.Log) { l.Topics[0] = common.Hash{0x12, 0x34} }, + func(l *ethTypes.Log) { l.Data = append(l.Data, 0x56) }, + func(l *ethTypes.Log) { l.Data = l.Data[:len(l.Data)-1] }, + func(l *ethTypes.Log) { l.Data[0] = 0x45 }, + } + irrelevantMods := []func(l *ethTypes.Log){ + func(l *ethTypes.Log) { l.BlockNumber = 987 }, + func(l *ethTypes.Log) { l.TxHash = common.Hash{0xab, 0xcd} }, + func(l *ethTypes.Log) { l.TxIndex = 99 }, + func(l *ethTypes.Log) { l.BlockHash = common.Hash{0xab, 0xcd} }, + func(l *ethTypes.Log) { l.Index = 98 }, + func(l *ethTypes.Log) { l.Removed = true }, + } + refHash := LogToLogHash(mkLog()) + expectedRefHash := common.HexToHash("0x4e1dc08fddeb273275f787762cdfe945cf47bb4e80a1fabbc7a825801e81b73f") + require.Equal(t, expectedRefHash, refHash, "reference hash changed, check that database compatibility is not broken") + + for i, mod := range relevantMods { + l := mkLog() + mod(l) + hash := LogToLogHash(l) + require.NotEqualf(t, refHash, hash, "expected relevant modification %v to affect the hash but it did not", i) + } + for i, mod := range irrelevantMods { + l := mkLog() + mod(l) + hash := LogToLogHash(l) + require.Equal(t, refHash, hash, "expected irrelevant modification %v to not affect the hash but it did", i) + } +} diff --git a/op-core/interop/messages/messages.go b/op-core/interop/messages/messages.go new file mode 100644 index 00000000000..3d76111ab1e --- /dev/null +++ b/op-core/interop/messages/messages.go @@ -0,0 +1,493 @@ +package messages + +import ( + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "math" + + "github.com/holiman/uint256" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +var ( + errLogIndexTooLarge = errors.New("log index too large") + errInvalidParentBlock = errors.New("invalid parent block") +) + +var ExecutingMessageEventTopic = crypto.Keccak256Hash([]byte("ExecutingMessage(bytes32,(address,uint256,uint256,uint256,uint256))")) + +// ContainsQuery contains all the information needed to check a message +// against a chain's database, to determine if it is valid (ie all invariants hold). +type ContainsQuery struct { + Timestamp uint64 + BlockNum uint64 + LogIdx uint32 + Checksum MessageChecksum +} + +type ExecutingMessage struct { + ChainID eth.ChainID + BlockNum uint64 + LogIdx uint32 + Timestamp uint64 + Checksum MessageChecksum +} + +func (s *ExecutingMessage) String() string { + return fmt.Sprintf("ExecMsg(chain: %s, block: %d, log: %d, time: %d, checksum: %s)", + s.ChainID, s.BlockNum, s.LogIdx, s.Timestamp, s.Checksum) +} + +type Message struct { + Identifier Identifier `json:"identifier"` + PayloadHash common.Hash `json:"payloadHash"` +} + +func (m *Message) ToCheckSumArgs() ChecksumArgs { + return ChecksumArgs{ + BlockNumber: m.Identifier.BlockNumber, + LogIndex: m.Identifier.LogIndex, + Timestamp: m.Identifier.Timestamp, + ChainID: m.Identifier.ChainID, + LogHash: PayloadHashToLogHash(m.PayloadHash, m.Identifier.Origin), + } +} + +func (m *Message) Checksum() MessageChecksum { + return m.ToCheckSumArgs().Checksum() +} + +func (m *Message) Access() Access { + return m.ToCheckSumArgs().Access() +} + +func (m *Message) DecodeEvent(topics []common.Hash, data []byte) error { + if len(topics) != 2 { // event hash, indexed payloadHash + return fmt.Errorf("unexpected number of event topics: %d", len(topics)) + } + if topics[0] != ExecutingMessageEventTopic { + return fmt.Errorf("unexpected event topic %q", topics[0]) + } + if len(data) != 32*5 { + return fmt.Errorf("unexpected identifier data length: %d", len(data)) + } + take := func(length uint) []byte { + taken := data[:length] + data = data[length:] + return taken + } + takeZeroes := func(length uint) error { + for _, v := range take(length) { + if v != 0 { + return errors.New("expected zero") + } + } + return nil + } + if err := takeZeroes(12); err != nil { + return fmt.Errorf("invalid address padding: %w", err) + } + m.Identifier.Origin = common.Address(take(20)) + if err := takeZeroes(32 - 8); err != nil { + return fmt.Errorf("invalid block number padding: %w", err) + } + m.Identifier.BlockNumber = binary.BigEndian.Uint64(take(8)) + if err := takeZeroes(32 - 4); err != nil { + return fmt.Errorf("invalid log index padding: %w", err) + } + m.Identifier.LogIndex = binary.BigEndian.Uint32(take(4)) + if err := takeZeroes(32 - 8); err != nil { + return fmt.Errorf("invalid timestamp padding: %w", err) + } + m.Identifier.Timestamp = binary.BigEndian.Uint64(take(8)) + m.Identifier.ChainID = eth.ChainIDFromBytes32([32]byte(take(32))) + m.PayloadHash = topics[1] + return nil +} + +type ChecksumArgs struct { + BlockNumber uint64 + LogIndex uint32 + Timestamp uint64 + ChainID eth.ChainID + LogHash common.Hash +} + +func (args ChecksumArgs) Checksum() MessageChecksum { + idPacked := make([]byte, 12, 32) // 12 zero bytes, as padding to 32 bytes + idPacked = binary.BigEndian.AppendUint64(idPacked, args.BlockNumber) + idPacked = binary.BigEndian.AppendUint64(idPacked, args.Timestamp) + idPacked = binary.BigEndian.AppendUint32(idPacked, args.LogIndex) + idLogHash := crypto.Keccak256Hash(args.LogHash[:], idPacked) + chainID := args.ChainID.Bytes32() + out := crypto.Keccak256Hash(idLogHash[:], chainID[:]) + out[0] = 0x03 // type/version byte + return MessageChecksum(out) +} + +func (args ChecksumArgs) Access() Access { + return Access{ + BlockNumber: args.BlockNumber, + Timestamp: args.Timestamp, + LogIndex: args.LogIndex, + ChainID: args.ChainID, + Checksum: args.Checksum(), + } +} + +func (args ChecksumArgs) Query() ContainsQuery { + return ContainsQuery{ + BlockNum: args.BlockNumber, + Timestamp: args.Timestamp, + LogIdx: args.LogIndex, + Checksum: args.Checksum(), + } +} + +type Identifier struct { + Origin common.Address + BlockNumber uint64 + LogIndex uint32 + Timestamp uint64 + ChainID eth.ChainID // flat, not a pointer, to make Identifier safe as map key +} + +type identifierMarshaling struct { + Origin common.Address `json:"origin"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + LogIndex hexutil.Uint64 `json:"logIndex"` + Timestamp hexutil.Uint64 `json:"timestamp"` + ChainID hexutil.U256 `json:"chainID"` +} + +func (id Identifier) MarshalJSON() ([]byte, error) { + var enc identifierMarshaling + enc.Origin = id.Origin + enc.BlockNumber = hexutil.Uint64(id.BlockNumber) + enc.LogIndex = hexutil.Uint64(id.LogIndex) + enc.Timestamp = hexutil.Uint64(id.Timestamp) + enc.ChainID = (hexutil.U256)(id.ChainID) + return json.Marshal(&enc) +} + +func (id *Identifier) UnmarshalJSON(input []byte) error { + var dec identifierMarshaling + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + id.Origin = dec.Origin + id.BlockNumber = uint64(dec.BlockNumber) + if dec.LogIndex > math.MaxUint32 { + return fmt.Errorf("%w: %d", errLogIndexTooLarge, dec.LogIndex) + } + id.LogIndex = uint32(dec.LogIndex) + id.Timestamp = uint64(dec.Timestamp) + id.ChainID = (eth.ChainID)(dec.ChainID) + return nil +} + +func (id Identifier) ChecksumArgs(msgHash common.Hash) ChecksumArgs { + return ChecksumArgs{ + BlockNumber: id.BlockNumber, + Timestamp: id.Timestamp, + LogIndex: id.LogIndex, + ChainID: id.ChainID, + LogHash: PayloadHashToLogHash(msgHash, id.Origin), + } +} + +type ExecutingDescriptor struct { + // ChainID of the executing message + ChainID eth.ChainID + + // Timestamp is the timestamp of the executing message + Timestamp uint64 + + // Timeout, requests verification to still hold at Timestamp+Timeout (incl.). Defaults to 0. + // I.e. Timestamp is used as lower-bound validity, and Timeout defines the span to the upper-bound. + Timeout uint64 +} + +type executingDescriptorMarshaling struct { + ChainID eth.ChainID `json:"chainID"` + Timestamp hexutil.Uint64 `json:"timestamp"` + Timeout hexutil.Uint64 `json:"timeout,omitempty"` +} + +func (ed ExecutingDescriptor) MarshalJSON() ([]byte, error) { + var enc executingDescriptorMarshaling + enc.ChainID = ed.ChainID + enc.Timestamp = hexutil.Uint64(ed.Timestamp) + enc.Timeout = hexutil.Uint64(ed.Timeout) + return json.Marshal(&enc) +} + +func (ed *ExecutingDescriptor) UnmarshalJSON(input []byte) error { + var dec executingDescriptorMarshaling + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + ed.ChainID = dec.ChainID + ed.Timestamp = uint64(dec.Timestamp) + ed.Timeout = uint64(dec.Timeout) + return nil +} + +type BlockSeal struct { + Hash common.Hash `json:"hash"` + Number uint64 `json:"number"` + Timestamp uint64 `json:"timestamp"` +} + +func (s BlockSeal) String() string { + return fmt.Sprintf("BlockSeal(hash:%s, number:%d, time:%d)", s.Hash, s.Number, s.Timestamp) +} + +func (s BlockSeal) ID() eth.BlockID { + return eth.BlockID{Hash: s.Hash, Number: s.Number} +} + +func (s BlockSeal) MustWithParent(parent eth.BlockID) eth.BlockRef { + ref, err := s.WithParent(parent) + if err != nil { + panic(err) + } + return ref +} + +func (s BlockSeal) WithParent(parent eth.BlockID) (eth.BlockRef, error) { + // prevent parent attachment if the parent is not the previous block, + // and the block is not the genesis block + if s.Number != parent.Number+1 && s.Number != 0 { + return eth.BlockRef{}, fmt.Errorf("%w: parent %s to combine with %s", errInvalidParentBlock, parent, s) + } + return eth.BlockRef{ + Hash: s.Hash, + Number: s.Number, + ParentHash: parent.Hash, + Time: s.Timestamp, + }, nil +} + +// WithZeroParent returns [s] with a zero parent hash. This should only be used +// where a BlockRef is required, but from the calling context it is guaranteed that +// the parent hash is not needed. +func (s BlockSeal) WithZeroParent() eth.BlockRef { + return eth.BlockRef{ + Hash: s.Hash, + Number: s.Number, + Time: s.Timestamp, + } +} + +func (s BlockSeal) ForceWithParent(parent eth.BlockID) eth.BlockRef { + return eth.BlockRef{ + Hash: s.Hash, + Number: s.Number, + ParentHash: parent.Hash, + Time: s.Timestamp, + } +} + +func BlockSealFromRef(ref eth.BlockRef) BlockSeal { + return BlockSeal{ + Hash: ref.Hash, + Number: ref.Number, + Timestamp: ref.Time, + } +} + +// PayloadHashToLogHash converts the payload hash to the log hash +// it is the concatenation of the log's address and the hash of the log's payload, +// which is then hashed again. This is the hash that is stored in the log storage. +// The logHash can then be used to traverse from the executing message +// to the log the referenced initiating message. +func PayloadHashToLogHash(payloadHash common.Hash, addr common.Address) common.Hash { + msg := make([]byte, 0, 2*common.HashLength) + msg = append(msg, addr.Bytes()...) + msg = append(msg, payloadHash.Bytes()...) + return crypto.Keccak256Hash(msg) +} + +// LogToMessagePayload is the data that is hashed to get the payloadHash +// it is the concatenation of the log's topics and data +// the implementation is based on the interop messaging spec +func LogToMessagePayload(l *ethTypes.Log) []byte { + msg := make([]byte, 0) + for _, topic := range l.Topics { + msg = append(msg, topic.Bytes()...) + } + msg = append(msg, l.Data...) + return msg +} + +// MessageChecksum represents a message checksum, as used for access-list checks. +type MessageChecksum common.Hash + +func (mc MessageChecksum) MarshalText() ([]byte, error) { + return common.Hash(mc).MarshalText() +} + +func (mc *MessageChecksum) UnmarshalText(data []byte) error { + return (*common.Hash)(mc).UnmarshalText(data) +} + +func (mc MessageChecksum) String() string { + return common.Hash(mc).String() +} + +// Access represents access to a message, parsed from an access-list +type Access struct { + BlockNumber uint64 + Timestamp uint64 + LogIndex uint32 + ChainID eth.ChainID + Checksum MessageChecksum +} + +func (acc Access) Query() ContainsQuery { + return ContainsQuery{ + Timestamp: acc.Timestamp, + BlockNum: acc.BlockNumber, + LogIdx: acc.LogIndex, + Checksum: acc.Checksum, + } +} + +// lookupEntry encodes a lookup entry for an access-list +func (acc Access) lookupEntry() common.Hash { + var out common.Hash + out[0] = PrefixLookup + binary.BigEndian.PutUint64(out[4:12], (*uint256.Int)(&acc.ChainID).Uint64()) + binary.BigEndian.PutUint64(out[12:20], acc.BlockNumber) + binary.BigEndian.PutUint64(out[20:28], acc.Timestamp) + binary.BigEndian.PutUint32(out[28:32], acc.LogIndex) + return out +} + +// chainIDExtensionEntry encodes a chainID-extension entry for an access-list +func (acc Access) chainIDExtensionEntry() common.Hash { + var out common.Hash + dat := (*uint256.Int)(&acc.ChainID).Bytes32() + out[0] = PrefixChainIDExtension + copy(out[8:32], dat[0:24]) + return out +} + +type accessMarshaling struct { + BlockNumber hexutil.Uint64 `json:"blockNumber"` + Timestamp hexutil.Uint64 `json:"timestamp"` + LogIndex uint32 `json:"logIndex"` + ChainID eth.ChainID `json:"chainID"` + Checksum MessageChecksum `json:"checksum"` +} + +func (a Access) MarshalJSON() ([]byte, error) { + enc := accessMarshaling{ + BlockNumber: hexutil.Uint64(a.BlockNumber), + Timestamp: hexutil.Uint64(a.Timestamp), + LogIndex: a.LogIndex, + ChainID: a.ChainID, + Checksum: a.Checksum, + } + return json.Marshal(&enc) +} + +func (a *Access) UnmarshalJSON(input []byte) error { + var dec accessMarshaling + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + a.BlockNumber = uint64(dec.BlockNumber) + a.Timestamp = uint64(dec.Timestamp) + a.LogIndex = dec.LogIndex + a.ChainID = dec.ChainID + a.Checksum = dec.Checksum + return nil +} + +const ( + PrefixLookup = 1 + PrefixChainIDExtension = 2 + PrefixChecksum = 3 +) + +var ( + errExpectedEntry = errors.New("expected entry") + errMalformedEntry = errors.New("malformed entry") + errUnexpectedEntryType = errors.New("unexpected entry type") +) + +// ParseAccess parses some access-list entries into an Access, and returns the remaining entries. +// This process can be repeated until no entries are left, to parse an access-list. +func ParseAccess(entries []common.Hash) ([]common.Hash, Access, error) { + if len(entries) == 0 { + return nil, Access{}, errExpectedEntry + } + entry := entries[0] + entries = entries[1:] + if typeByte := entry[0]; typeByte != PrefixLookup { + return nil, Access{}, fmt.Errorf("expected lookup, got entry type %d: %w", + typeByte, errUnexpectedEntryType) + } + if ([3]byte)(entry[1:4]) != ([3]byte{}) { + return nil, Access{}, fmt.Errorf("expected zero bytes: %w", errMalformedEntry) + } + var access Access + access.ChainID = eth.ChainIDFromUInt64(binary.BigEndian.Uint64(entry[4:12])) + access.BlockNumber = binary.BigEndian.Uint64(entry[12:20]) + access.Timestamp = binary.BigEndian.Uint64(entry[20:28]) + access.LogIndex = binary.BigEndian.Uint32(entry[28:32]) + + if len(entries) == 0 { + return nil, Access{}, errExpectedEntry + } + entry = entries[0] + entries = entries[1:] + if typeByte := entry[0]; typeByte == PrefixChainIDExtension { + if ([7]byte)(entry[1:8]) != ([7]byte{}) { + return nil, Access{}, fmt.Errorf("expected zero bytes: %w", errMalformedEntry) + } + // The lower 8 bytes is set to the uint64 in the first entry. + // The upper 24 bytes are set with this extension entry. + chIDBytes32 := access.ChainID.Bytes32() + copy(chIDBytes32[0:24], entry[8:32]) + access.ChainID = eth.ChainIDFromBytes32(chIDBytes32) + if len(entries) == 0 { + return nil, Access{}, errExpectedEntry + } + entry = entries[0] + entries = entries[1:] + } + if typeByte := entry[0]; typeByte != PrefixChecksum { + return nil, Access{}, fmt.Errorf("expected checksum, got entry type %d: %w", + typeByte, errUnexpectedEntryType) + } + access.Checksum = MessageChecksum(entry) + return entries, access, nil +} + +func EncodeAccessList(accesses []Access) []common.Hash { + out := make([]common.Hash, 0, len(accesses)*2) + for _, acc := range accesses { + out = append(out, acc.lookupEntry()) + + if !(*uint256.Int)(&acc.ChainID).IsUint64() { + out = append(out, acc.chainIDExtensionEntry()) + } + + if acc.Checksum[0] != PrefixChecksum { + panic("invalid checksum entry") + } + out = append(out, common.Hash(acc.Checksum)) + } + return out +} diff --git a/op-core/interop/messages/messages_test.go b/op-core/interop/messages/messages_test.go new file mode 100644 index 00000000000..245fb6afb54 --- /dev/null +++ b/op-core/interop/messages/messages_test.go @@ -0,0 +1,574 @@ +package messages + +import ( + "encoding/binary" + "encoding/json" + "math/big" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +func FuzzRoundtripIdentifierJSONMarshal(f *testing.F) { + f.Fuzz(func(t *testing.T, origin []byte, blockNumber uint64, logIndex uint32, timestamp uint64, chainID []byte) { + if len(chainID) > 32 { + chainID = chainID[:32] + } + + id := Identifier{ + Origin: common.BytesToAddress(origin), + BlockNumber: blockNumber, + LogIndex: logIndex, + Timestamp: timestamp, + ChainID: eth.ChainIDFromBig(new(big.Int).SetBytes(chainID)), + } + + raw, err := json.Marshal(&id) + require.NoError(t, err) + + var dec Identifier + require.NoError(t, json.Unmarshal(raw, &dec)) + + require.Equal(t, id.Origin, dec.Origin) + require.Equal(t, id.BlockNumber, dec.BlockNumber) + require.Equal(t, id.LogIndex, dec.LogIndex) + require.Equal(t, id.Timestamp, dec.Timestamp) + require.Equal(t, id.ChainID, dec.ChainID) + }) +} + +func FuzzMessage_DecodeEvent(f *testing.F) { + f.Fuzz(func(t *testing.T, validEvTopic bool, numTopics uint8, data []byte) { + if len(data) < 32 { + return + } + if len(data) > 100_000 { + return + } + if validEvTopic { // valid even signature topic implies a topic to be there + numTopics += 1 + } + if numTopics > 4 { // There can be no more than 4 topics per log event + return + } + if int(numTopics)*32 > len(data) { + return + } + var topics []common.Hash + if validEvTopic { + topics = append(topics, ExecutingMessageEventTopic) + } + for i := 0; i < int(numTopics); i++ { + var topic common.Hash + copy(topic[:], data[:]) + data = data[32:] + } + require.NotPanics(t, func() { + var m Message + _ = m.DecodeEvent(topics, data) + }) + }) +} + +func TestInteropMessageFormatEdgeCases(t *testing.T) { + tests := []struct { + name string + log *ethTypes.Log + expectedError string + }{ + { + name: "Empty Topics", + log: ðTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{}, + Data: make([]byte, 32*5), + }, + expectedError: "unexpected number of event topics: 0", + }, + { + name: "Wrong Event Topic", + log: ðTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{ + common.BytesToHash([]byte("wrong topic")), + common.BytesToHash([]byte("payloadHash")), + }, + Data: make([]byte, 32*5), + }, + expectedError: "unexpected event topic", + }, + { + name: "Missing PayloadHash Topic", + log: ðTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{ + common.BytesToHash(ExecutingMessageEventTopic[:]), + }, + Data: make([]byte, 32*5), + }, + expectedError: "unexpected number of event topics: 1", + }, + { + name: "Too Many Topics", + log: ðTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{ + common.BytesToHash(ExecutingMessageEventTopic[:]), + common.BytesToHash([]byte("payloadHash")), + common.BytesToHash([]byte("extra")), + }, + Data: make([]byte, 32*5), + }, + expectedError: "unexpected number of event topics: 3", + }, + { + name: "Data Too Short", + log: ðTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{ + common.BytesToHash(ExecutingMessageEventTopic[:]), + common.BytesToHash([]byte("payloadHash")), + }, + Data: make([]byte, 32*4), // One word too short + }, + expectedError: "unexpected identifier data length: 128", + }, + { + name: "Data Too Long", + log: ðTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{ + common.BytesToHash(ExecutingMessageEventTopic[:]), + common.BytesToHash([]byte("payloadHash")), + }, + Data: make([]byte, 32*6), // One word too long + }, + expectedError: "unexpected identifier data length: 192", + }, + { + name: "Invalid Address Padding", + log: ðTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{ + common.BytesToHash(ExecutingMessageEventTopic[:]), + common.BytesToHash([]byte("payloadHash")), + }, + Data: func() []byte { + data := make([]byte, 32*5) + data[0] = 1 // Add non-zero byte in address padding + return data + }(), + }, + expectedError: "invalid address padding", + }, + { + name: "Invalid Block Number Padding", + log: ðTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{ + common.BytesToHash(ExecutingMessageEventTopic[:]), + common.BytesToHash([]byte("payloadHash")), + }, + Data: func() []byte { + data := make([]byte, 32*5) + data[32+23] = 1 // Add non-zero byte in block number padding + return data + }(), + }, + expectedError: "invalid block number padding", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var msg Message + err := msg.DecodeEvent(tt.log.Topics, tt.log.Data) + if tt.expectedError != "" { + require.Error(t, err) + require.ErrorContains(t, err, tt.expectedError) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestHashing(t *testing.T) { + keccak256 := func(name string, parts ...[]byte) (h common.Hash) { + t.Logf("%s = H(", name) + for _, p := range parts { + t.Logf(" %x,", p) + } + t.Logf(")") + h = crypto.Keccak256Hash(parts...) + t.Logf("%s = %s", name, h) + return h + } + id := Identifier{ + Origin: common.HexToAddress("0xe0e1e2e3e4e5e6e7e8e9f0f1f2f3f4f5f6f7f8f9"), + BlockNumber: 0xa1a2_a3a4_a5a6_a7a8, + LogIndex: 0xb1b2_b3b4, + Timestamp: 0xc1c2_c3c4_c5c6_c7c8, + ChainID: eth.ChainIDFromUInt64(0xd1d2_d3d4_d5d6_d7d8), + } + payloadHash := keccak256("payloadHash", []byte("example payload")) // aka msgHash + logHash := keccak256("logHash", id.Origin[:], payloadHash[:]) + x := PayloadHashToLogHash(payloadHash, id.Origin) + require.Equal(t, logHash, x, "check op-supervisor version of log-hashing matches intermediate value") + + var idPacked []byte + idPacked = append(idPacked, make([]byte, 12)...) + idPacked = binary.BigEndian.AppendUint64(idPacked, id.BlockNumber) + idPacked = binary.BigEndian.AppendUint64(idPacked, id.Timestamp) + idPacked = binary.BigEndian.AppendUint32(idPacked, id.LogIndex) + t.Logf("idPacked: %x", idPacked) + + idLogHash := keccak256("idLogHash", logHash[:], idPacked) + chainID := id.ChainID.Bytes32() + bareChecksum := keccak256("bareChecksum", idLogHash[:], chainID[:]) + + checksum := bareChecksum + checksum[0] = 0x03 + t.Logf("Checksum: %s", checksum) +} + +var ( + testOrigin = common.HexToAddress("0xe0e1e2e3e4e5e6e7e8e9f0f1f2f3f4f5f6f7f8f9") + testBlockNumber = uint64(0xa1a2_a3a4_a5a6_a7a8) + testLogIndex = uint32(0xb1b2_b3b4) + testTimestamp = uint64(0xc1c2_c3c4_c5c6_c7c8) + testChainID = eth.ChainIDFromUInt64(0xd1d2_d3d4_d5d6_d7d8) + testPayload = []byte("example payload") + testMsgHash = common.HexToHash("0x8017559a85b12c04b14a1a425d53486d1015f833714a09bd62f04152a7e2ae9b") + testLogHash = common.HexToHash("0xf9ed05990c887d3f86718aabd7e940faaa75d6a5cd44602e89642586ce85f2aa") + testChecksum = MessageChecksum(common.HexToHash("0x03749e87fd7789575de9906569deb05aaf220dc4cfab3d8abbfd34a2e1d7d357")) + testLookupEntry = common.HexToHash("0x01000000d1d2d3d4d5d6d7d8a1a2a3a4a5a6a7a8c1c2c3c4c5c6c7c8b1b2b3b4") +) + +func TestMessage(t *testing.T) { + msg := Message{ + Identifier: Identifier{ + Origin: testOrigin, + BlockNumber: testBlockNumber, + LogIndex: testLogIndex, + Timestamp: testTimestamp, + ChainID: testChainID, + }, + PayloadHash: testMsgHash, + } + t.Run("checksum", func(t *testing.T) { + require.Equal(t, testChecksum, msg.Checksum()) + }) + t.Run("json roundtrip", func(t *testing.T) { + data, err := json.Marshal(msg) + require.NoError(t, err) + var out Message + require.NoError(t, json.Unmarshal(data, &out)) + require.Equal(t, msg, out) + }) +} + +func TestChecksumArgs(t *testing.T) { + args := ChecksumArgs{ + BlockNumber: testBlockNumber, + LogIndex: testLogIndex, + Timestamp: testTimestamp, + ChainID: testChainID, + LogHash: testLogHash, + } + t.Run("checksum", func(t *testing.T) { + require.Equal(t, testChecksum, args.Checksum()) + }) + t.Run("as query", func(t *testing.T) { + q := args.Query() + require.Equal(t, testBlockNumber, q.BlockNum) + require.Equal(t, testTimestamp, q.Timestamp) + require.Equal(t, testLogIndex, q.LogIdx) + require.Equal(t, testChecksum, q.Checksum) + }) + t.Run("as access", func(t *testing.T) { + acc := args.Access() + require.Equal(t, testBlockNumber, acc.BlockNumber) + require.Equal(t, testTimestamp, acc.Timestamp) + require.Equal(t, testLogIndex, acc.LogIndex) + require.Equal(t, testChainID, acc.ChainID) + require.Equal(t, testChecksum, acc.Checksum) + }) +} + +func TestIdentifier(t *testing.T) { + id := Identifier{ + Origin: testOrigin, + BlockNumber: testBlockNumber, + LogIndex: testLogIndex, + Timestamp: testTimestamp, + ChainID: testChainID, + } + t.Run("json roundtrip", func(t *testing.T) { + data, err := json.Marshal(id) + require.NoError(t, err) + var out Identifier + require.NoError(t, json.Unmarshal(data, &out)) + require.Equal(t, id, out) + }) +} + +func TestPayloadHashToLogHash(t *testing.T) { + logHash := PayloadHashToLogHash(testMsgHash, testOrigin) + require.Equal(t, testLogHash, logHash) +} + +func TestLogToMessagePayload(t *testing.T) { + payload := LogToMessagePayload(ðTypes.Log{ + Data: testPayload, + }) + require.Equal(t, hexutil.Encode(testPayload), hexutil.Encode(payload)) + + t.Run("1 topic", func(t *testing.T) { + v := LogToMessagePayload(ðTypes.Log{ + Data: []byte(`foobar`), + Topics: []common.Hash{ + crypto.Keccak256Hash([]byte(`topic0`)), + }, + }) + expected := make([]byte, 0) + expected = append(expected, crypto.Keccak256([]byte(`topic0`))...) + expected = append(expected, []byte(`foobar`)...) + require.Equal(t, expected, v) + }) + + t.Run("4 topics", func(t *testing.T) { + v := LogToMessagePayload(ðTypes.Log{ + Data: []byte(`foobar`), + Topics: []common.Hash{ + crypto.Keccak256Hash([]byte(`topic0`)), + crypto.Keccak256Hash([]byte(`topic1`)), + crypto.Keccak256Hash([]byte(`topic2`)), + crypto.Keccak256Hash([]byte(`topic3`)), + }, + }) + expected := make([]byte, 0) + expected = append(expected, crypto.Keccak256([]byte(`topic0`))...) + expected = append(expected, crypto.Keccak256([]byte(`topic1`))...) + expected = append(expected, crypto.Keccak256([]byte(`topic2`))...) + expected = append(expected, crypto.Keccak256([]byte(`topic3`))...) + expected = append(expected, []byte(`foobar`)...) + require.Equal(t, expected, v) + }) +} + +func TestAccess(t *testing.T) { + acc := Access{ + BlockNumber: testBlockNumber, + Timestamp: testTimestamp, + LogIndex: testLogIndex, + ChainID: testChainID, + Checksum: MessageChecksum(testChecksum), + } + t.Run("json roundtrip", func(t *testing.T) { + data, err := json.Marshal(acc) + require.NoError(t, err) + var out Access + require.NoError(t, json.Unmarshal(data, &out)) + require.Equal(t, acc, out) + }) +} + +func TestParseAccess(t *testing.T) { + t.Run("empty", func(t *testing.T) { + _, _, err := ParseAccess(nil) + require.ErrorIs(t, err, errExpectedEntry) + }) + t.Run("unexpected 0 type", func(t *testing.T) { + _, _, err := ParseAccess([]common.Hash{ + {0: 0x00}, + }) + require.ErrorIs(t, err, errUnexpectedEntryType) + require.ErrorContains(t, err, "expected lookup") + }) + t.Run("unexpected arbitrary type", func(t *testing.T) { + _, _, err := ParseAccess([]common.Hash{ + {0: 10}, + }) + require.ErrorIs(t, err, errUnexpectedEntryType) + require.ErrorContains(t, err, "expected lookup") + }) + t.Run("unexpected non-zero padding", func(t *testing.T) { + _, _, err := ParseAccess([]common.Hash{ + {0: PrefixLookup, 1: 0x01}, // valid lookup prefix byte, but non-zero value in padding area + }) + require.ErrorIs(t, err, errMalformedEntry) + require.ErrorContains(t, err, "expected zero bytes") + }) + t.Run("incomplete", func(t *testing.T) { + _, _, err := ParseAccess([]common.Hash{ + {0: PrefixLookup}, // valid lookup, but no checksum after + }) + require.ErrorIs(t, err, errExpectedEntry) + }) + t.Run("unexpected 0 type after checksum", func(t *testing.T) { + _, _, err := ParseAccess([]common.Hash{ + {0: PrefixLookup}, + {0: 0}, + }) + require.ErrorIs(t, err, errUnexpectedEntryType) + }) + t.Run("unexpected lookup repeat", func(t *testing.T) { + _, _, err := ParseAccess([]common.Hash{ + {0: PrefixLookup}, + {0: PrefixLookup}, + }) + require.ErrorIs(t, err, errUnexpectedEntryType) + }) + t.Run("unexpected arbitrary type after checksum", func(t *testing.T) { + _, _, err := ParseAccess([]common.Hash{ + {0: PrefixLookup}, + {0: 10}, // unexpected type byte + }) + require.ErrorIs(t, err, errUnexpectedEntryType) + }) + t.Run("valid but zero", func(t *testing.T) { + remaining, acc, err := ParseAccess([]common.Hash{ + {0: PrefixLookup}, // valid lookup entry + {0: PrefixChecksum}, // valid checksum entry + }) + require.NoError(t, err) + require.Equal(t, Access{ + BlockNumber: 0, + Timestamp: 0, + LogIndex: 0, + ChainID: eth.ChainID{}, + Checksum: MessageChecksum{0: PrefixChecksum}, + }, acc) + require.Empty(t, remaining) + }) + t.Run("valid", func(t *testing.T) { + acc := Access{ + BlockNumber: testBlockNumber, + Timestamp: testTimestamp, + LogIndex: testLogIndex, + ChainID: testChainID, + Checksum: MessageChecksum(testChecksum), + } + remaining, parsed, err := ParseAccess([]common.Hash{ + testLookupEntry, + common.Hash(acc.Checksum), + }) + require.NoError(t, err) + require.Equal(t, acc, parsed) + require.Empty(t, remaining) + }) + t.Run("repeat", func(t *testing.T) { + acc := Access{ + BlockNumber: testBlockNumber, + Timestamp: testTimestamp, + LogIndex: testLogIndex, + ChainID: testChainID, + Checksum: MessageChecksum(testChecksum), + } + remaining, parsed, err := ParseAccess([]common.Hash{ + testLookupEntry, + common.Hash(acc.Checksum), + testLookupEntry, + common.Hash(acc.Checksum), + }) + require.NoError(t, err) + require.Equal(t, acc, parsed) + require.Len(t, remaining, 2) + remaining2, parsed2, err := ParseAccess(remaining) + require.NoError(t, err) + require.Equal(t, acc, parsed2) + require.Empty(t, remaining2) + }) + t.Run("with chainID extension", func(t *testing.T) { + acc := Access{ + BlockNumber: testBlockNumber, + Timestamp: testTimestamp, + LogIndex: testLogIndex, + ChainID: eth.ChainIDFromBytes32([32]byte{0: 7, 31: 10}), + Checksum: MessageChecksum(testChecksum), + } + remaining, parsed, err := ParseAccess([]common.Hash{ + acc.lookupEntry(), + acc.chainIDExtensionEntry(), + common.Hash(acc.Checksum), + }) + require.NoError(t, err) + require.Equal(t, acc, parsed) + require.Empty(t, remaining) + }) +} + +func TestEncodeAccessList(t *testing.T) { + acc := Access{ + BlockNumber: testBlockNumber, + Timestamp: testTimestamp, + LogIndex: testLogIndex, + ChainID: testChainID, + Checksum: MessageChecksum(testChecksum), + } + t.Run("valid single", func(t *testing.T) { + accList := EncodeAccessList([]Access{acc}) + require.Len(t, accList, 2) + require.Equal(t, testLookupEntry, accList[0]) + require.Equal(t, common.Hash(testChecksum), accList[1]) + _, result, err := ParseAccess(accList) + require.NoError(t, err) + require.Equal(t, acc, result, "roundtrip") + }) + t.Run("valid repeat", func(t *testing.T) { + accList := EncodeAccessList([]Access{ + acc, + acc, + }) + require.Len(t, accList, 4) + require.Equal(t, testLookupEntry, accList[0]) + require.Equal(t, common.Hash(testChecksum), accList[1]) + require.Equal(t, testLookupEntry, accList[2]) + require.Equal(t, common.Hash(testChecksum), accList[3]) + }) + t.Run("roundtrip", func(t *testing.T) { + accObjects := make([]Access, 0) + rng := rand.New(rand.NewSource(1234)) + randB32 := func() (out [32]byte) { + rng.Read(out[:]) + return + } + // test a big random access-list + count := 200 + for i := 0; i < count; i++ { + chainID := eth.ChainIDFromBytes32(randB32()) + if rng.Intn(5) < 2 { // don't make them all full random bytes32 + chainID = eth.ChainIDFromUInt64(rng.Uint64()) + } + checksum := randB32() + checksum[0] = PrefixChecksum + accObjects = append(accObjects, Access{ + BlockNumber: rng.Uint64(), + Timestamp: rng.Uint64(), + LogIndex: rng.Uint32(), + ChainID: chainID, + Checksum: checksum, + }) + } + list := EncodeAccessList(accObjects) + var result []Access + for i := 0; i < count && len(list) > 0; i++ { + remaining, v, err := ParseAccess(list) + require.NoError(t, err) + result = append(result, v) + list = remaining + } + require.Empty(t, list, "need to exhaust entries, expecting to be done") + require.Equal(t, accObjects, result, "roundtrip of random entries should work") + }) +} diff --git a/op-core/nuts/README.md b/op-core/nuts/README.md new file mode 100644 index 00000000000..633c8aa25e7 --- /dev/null +++ b/op-core/nuts/README.md @@ -0,0 +1,66 @@ +# NUT Bundles + +Network Upgrade Transaction (NUT) bundles define the L2 deposit transactions that activate a hardfork. Each bundle is a JSON file containing ordered transactions (implementation deployments, proxy upgrades, etc.) that the rollup node embeds and executes at the fork activation block. + +## Files + +| File | Purpose | +|------|---------| +| `fork_lock.toml` | Lock file mapping fork names to bundle paths, sha256 hashes, and source commits | +| `bundles/_nut_bundle.json` | Embedded bundle consumed by op-node and kona-node at fork activation | + +## Workflow + +Updating a fork's bundle is a **two-PR flow**: + +### PR 1 — Contracts change + +Change the Solidity source, then regenerate the in-repo snapshots: + +```bash +cd packages/contracts-bedrock +just generate-nut-bundle +``` + +This updates: +- `packages/contracts-bedrock/snapshots/semver-lock.json` (if any predeploy bytecode changed) +- `packages/contracts-bedrock/snapshots/upgrades/current-upgrade-bundle.json` (the candidate bundle) + +Commit these alongside your contracts change. **Merge this PR to `develop` before proceeding.** + +### PR 2 — Snapshot the bundle for a fork + +From a branch based on the updated `develop`: + +```bash +just nut-snapshot-for +``` + +This copies `current-upgrade-bundle.json` to `op-core/nuts/bundles/_nut_bundle.json` and updates `fork_lock.toml` with the sha256 hash and the merge-base commit with `origin/develop`. + +**Why merge-base, not HEAD?** The recorded commit is the [merge-base](https://git-scm.com/docs/git-merge-base) with `develop`. By ensuring that the recorded commit is a recent ancestor of `develop`, we can ensure that the reference will survives a squash-merge and persist in the history of the `develop` branch. This is why PR 1 must be merged first. + +### Verifying a bundle + +```bash +just nut-provenance-verify +``` + +Checks that: +1. The bundle file exists and its sha256 matches the lock +2. Creates a temporary worktree at the recorded commit, regenerates the bundle, and compares byte-for-byte + +Requires `forge` for the provenance check (step 2). + +### CI checks + +- **`check-nut-locks`** — Verifies all bundle hashes match their lock entries, all entries have a commit, and every `*_nut_bundle.json` file has a corresponding lock entry. Runs in CI on every PR. + +## fork_lock.toml schema + +```toml +[] +bundle = "op-core/nuts/bundles/_nut_bundle.json" # repo-relative path +hash = "sha256:" # sha256 of bundle contents +commit = "" # commit that produced the bundle +``` diff --git a/op-core/nuts/bundles.go b/op-core/nuts/bundles.go new file mode 100644 index 00000000000..23231980093 --- /dev/null +++ b/op-core/nuts/bundles.go @@ -0,0 +1,15 @@ +package nuts + +import ( + _ "embed" +) + +// KarstNUTBundleJSON is the embedded Karst NUT bundle. +// +//go:embed bundles/karst_nut_bundle.json +var KarstNUTBundleJSON []byte + +// InteropNUTBundleJSON is the embedded Interop NUT bundle. +// +//go:embed bundles/interop_nut_bundle.json +var InteropNUTBundleJSON []byte diff --git a/op-core/nuts/bundles/interop_nut_bundle.json b/op-core/nuts/bundles/interop_nut_bundle.json new file mode 100644 index 00000000000..61bb614af7b --- /dev/null +++ b/op-core/nuts/bundles/interop_nut_bundle.json @@ -0,0 +1,203 @@ +{ + "metadata": { + "version": "1.0.0" + }, + "transactions": [ + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000048a608060405234801561001057600080fd5b5061046a806100206000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c8063a6ed563e11610076578063bd02d0f51161005b578063bd02d0f51461018e578063ca446dd9146101b8578063e2a4853a1461011557600080fd5b8063a6ed563e1461018e578063abfdcced146101aa57600080fd5b80634e91db08116100a75780634e91db081461011557806354fd4d50146101275780637ae1cfca1461017057600080fd5b80630528afe2146100c357806321f8a721146100d8575b600080fd5b6100d66100d1366004610239565b6101c6565b005b6100eb6100e63660046102ae565b610229565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100d66101233660046102c7565b9055565b6101636040518060400160405280600581526020017f312e322e3200000000000000000000000000000000000000000000000000000081525081565b60405161010c91906102e9565b61017e6100e63660046102ae565b604051901515815260200161010c565b61019c6100e63660046102ae565b60405190815260200161010c565b6100d661012336600461035c565b6100d6610123366004610391565b8060005b81811015610223576102118484838181106101e7576101e76103cf565b90506040020160000135858584818110610203576102036103cf565b905060400201602001359055565b8061021b816103fe565b9150506101ca565b50505050565b6000610233825490565b92915050565b6000806020838503121561024c57600080fd5b823567ffffffffffffffff8082111561026457600080fd5b818501915085601f83011261027857600080fd5b81358181111561028757600080fd5b8660208260061b850101111561029c57600080fd5b60209290920196919550909350505050565b6000602082840312156102c057600080fd5b5035919050565b600080604083850312156102da57600080fd5b50508035926020909101359150565b600060208083528351808285015260005b81811015610316578581018301518582016040015282016102fa565b81811115610328576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561036f57600080fd5b823591506020830135801515811461038657600080fd5b809150509250929050565b600080604083850312156103a457600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461038657600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610456577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b506001019056fea164736f6c634300080f000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 498000, + "intent": "Deploy StorageSetter Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000022d3608060405234801561001057600080fd5b5061001961001e565b6100eb565b600054600160a81b900460ff161561008c5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff600160a01b909104811610156100e9576000805460ff60a01b191660ff60a01b17905560405160ff81527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6121d880620000fb6000396000f3fe6080604052600436106101a15760003560e01c80638cbeeef2116100e1578063c4d66de81161008a578063db505d8011610064578063db505d8014610451578063ddd5a40f1461047e578063e46e245a14610494578063ecc70428146104a957600080fd5b8063c4d66de814610409578063d764ad0b14610429578063dad544e01461043c57600080fd5b8063a7119869116100bb578063a71198691461035e578063b1b1b209146103b9578063b28ade25146103e957600080fd5b80638cbeeef2146102905780639fce812c1461035e578063a4e7f8bd1461038957600080fd5b80633f827a5a1161014e5780635644cfdf116101285780635644cfdf146102fc5780635c975abb146103125780636e296e451461033257806383a740741461034757600080fd5b80633f827a5a146102685780634c1d6a691461029057806354fd4d50146102a657600080fd5b80632f7d39221161017f5780632f7d3922146102035780633dbb202b146102195780633e47158c1461022e57600080fd5b8063028f85f7146101a65780630c568498146101d95780632828d7e8146101ee575b600080fd5b3480156101b257600080fd5b506101bb601081565b60405167ffffffffffffffff90911681526020015b60405180910390f35b3480156101e557600080fd5b506101bb603f81565b3480156101fa57600080fd5b506101bb604081565b34801561020f57600080fd5b506101bb61520881565b61022c610227366004611bb4565b61050e565b005b34801561023a57600080fd5b506102436107a1565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d0565b34801561027457600080fd5b5061027d600181565b60405161ffff90911681526020016101d0565b34801561029c57600080fd5b506101bb619c4081565b3480156102b257600080fd5b506102ef6040518060400160405280600581526020017f322e322e3100000000000000000000000000000000000000000000000000000081525081565b6040516101d09190611c86565b34801561030857600080fd5b506101bb61138881565b34801561031e57600080fd5b5060005b60405190151581526020016101d0565b34801561033e57600080fd5b506102436109ac565b34801561035357600080fd5b506101bb62030d4081565b34801561036a57600080fd5b5060cf5473ffffffffffffffffffffffffffffffffffffffff16610243565b34801561039557600080fd5b506103226103a4366004611c99565b60ce6020526000908152604090205460ff1681565b3480156103c557600080fd5b506103226103d4366004611c99565b60cb6020526000908152604090205460ff1681565b3480156103f557600080fd5b506101bb610404366004611ce1565b610a98565b34801561041557600080fd5b5061022c610424366004611dc1565b610b59565b61022c610437366004611dde565b610d60565b34801561044857600080fd5b50610243611645565b34801561045d57600080fd5b5060cf546102439073ffffffffffffffffffffffffffffffffffffffff1681565b34801561048a57600080fd5b506101bb61010481565b3480156104a057600080fd5b506101bb602881565b3480156104b557600080fd5b5061050060cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016101d0565b60cf54604080516020601f86018190048102820181019092528481526106769273ffffffffffffffffffffffffffffffffffffffff169161056c91908790879081908401838280828437600092019190915250879250610a98915050565b347fd764ad0b000000000000000000000000000000000000000000000000000000006105d860cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b338a34898c8c6040516024016105f49796959493929190611ead565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526116c2565b8373ffffffffffffffffffffffffffffffffffffffff167fcb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52a3385856106fb60cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b8660405161070d959493929190611f0c565b60405180910390a260405134815233907f8ebb2ec2465bdb2a06a66fc37a0963af8a2a6a1479d81d56fdb8cbb98096d5469060200160405180910390a2505060cd80547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808216600101167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b6000806107cc7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107ef57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026108329190611f89565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061088d906060015b604051602081830303815290604052805190602001205490565b146108c4576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091526000906108e690606001610873565b905073ffffffffffffffffffffffffffffffffffffffff81161561097a578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561094f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109739190611fc6565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60cc5460009073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff215301610a7b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f43726f7373446f6d61696e4d657373656e6765723a2078446f6d61696e4d657360448201527f7361676553656e646572206973206e6f7420736574000000000000000000000060648201526084015b60405180910390fd5b5060cc5473ffffffffffffffffffffffffffffffffffffffff1690565b600080603f610aae604063ffffffff8616611fe3565b610ab89190612013565b611388619c40610acb8162030d40612061565b610ad59190612061565b610adf9190612061565b610ae99190612061565b9050600061010467ffffffffffffffff168551610b06919061208d565b9050610b44610b16601083611fe3565b610b209084612061565b67ffffffffffffffff16610b35602884611fe3565b67ffffffffffffffff16611750565b610b5090615208612061565b95945050505050565b6000547501000000000000000000000000000000000000000000900460ff1615808015610ba4575060005460017401000000000000000000000000000000000000000090910460ff16105b80610bd65750303b158015610bd6575060005474010000000000000000000000000000000000000000900460ff166001145b610c62576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610a72565b600080547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790558015610ce857600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1675010000000000000000000000000000000000000000001790555b610cf0611769565b610cf9826117ec565b8015610d5c57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b60f087901c60028110610e1b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f43726f7373446f6d61696e4d657373656e6765723a206f6e6c7920766572736960448201527f6f6e2030206f722031206d657373616765732061726520737570706f7274656460648201527f20617420746869732074696d6500000000000000000000000000000000000000608482015260a401610a72565b8061ffff16600003610f10576000610e6c878986868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508f9250611928915050565b600081815260cb602052604090205490915060ff1615610f0e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f43726f7373446f6d61696e4d657373656e6765723a206c65676163792077697460448201527f6864726177616c20616c72656164792072656c617965640000000000000000006064820152608401610a72565b505b6000610f56898989898989898080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061194792505050565b9050610f9f60cf54337fffffffffffffffffffffffffeeeeffffffffffffffffffffffffffffffffeeef0173ffffffffffffffffffffffffffffffffffffffff90811691161490565b15610fd757853414610fb357610fb36120a5565b600081815260ce602052604090205460ff1615610fd257610fd26120a5565b611129565b341561108b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605060248201527f43726f7373446f6d61696e4d657373656e6765723a2076616c7565206d75737460448201527f206265207a65726f20756e6c657373206d6573736167652069732066726f6d2060648201527f612073797374656d206164647265737300000000000000000000000000000000608482015260a401610a72565b600081815260ce602052604090205460ff16611129576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f43726f7373446f6d61696e4d657373656e6765723a206d65737361676520636160448201527f6e6e6f74206265207265706c61796564000000000000000000000000000000006064820152608401610a72565b6111328761196a565b156111e5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f43726f7373446f6d61696e4d657373656e6765723a2063616e6e6f742073656e60448201527f64206d65737361676520746f20626c6f636b65642073797374656d206164647260648201527f6573730000000000000000000000000000000000000000000000000000000000608482015260a401610a72565b600081815260cb602052604090205460ff1615611284576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f43726f7373446f6d61696e4d657373656e6765723a206d65737361676520686160448201527f7320616c7265616479206265656e2072656c61796564000000000000000000006064820152608401610a72565b6112a585611296611388619c40612061565b67ffffffffffffffff166119bf565b15806112cb575060cc5473ffffffffffffffffffffffffffffffffffffffff1661dead14155b156113e457600081815260ce602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555182917f99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f91a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff32016113dd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f43726f7373446f6d61696e4d657373656e6765723a206661696c656420746f2060448201527f72656c6179206d657373616765000000000000000000000000000000000000006064820152608401610a72565b505061163c565b60cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8a16179055600061147588619c405a61143891906120d4565b8988888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506119dd92505050565b60cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790559050801561152b57600082815260cb602052604090205460ff16156114c8576114c86120a5565b600082815260cb602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183917f4641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133c91a2611638565b600082815260ce602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183917f99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f91a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3201611638576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f43726f7373446f6d61696e4d657373656e6765723a206661696c656420746f2060448201527f72656c6179206d657373616765000000000000000000000000000000000000006064820152608401610a72565b5050505b50505050505050565b600061164f6107a1565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611699573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116bd9190611fc6565b905090565b6040517fc2b3e5ac0000000000000000000000000000000000000000000000000000000081527342000000000000000000000000000000000000169063c2b3e5ac908490611718908890889087906004016120eb565b6000604051808303818588803b15801561173157600080fd5b505af1158015611745573d6000803e3d6000fd5b505050505050505050565b6000818310156117605781611762565b825b9392505050565b336117726107a1565b73ffffffffffffffffffffffffffffffffffffffff16141580156117b357503361179a611645565b73ffffffffffffffffffffffffffffffffffffffff1614155b156117ea576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6000547501000000000000000000000000000000000000000000900460ff16611897576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610a72565b60cc5473ffffffffffffffffffffffffffffffffffffffff166118e15760cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790555b60cf80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6000611936858585856119f5565b805190602001209050949350505050565b6000611957878787878787611a8e565b8051906020012090509695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82163014806119b9575073ffffffffffffffffffffffffffffffffffffffff8216734200000000000000000000000000000000000016145b92915050565b600080603f83619c4001026040850201603f5a021015949350505050565b6000806000835160208501868989f195945050505050565b606084848484604051602401611a0e949392919061212a565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fcbd4ece9000000000000000000000000000000000000000000000000000000001790529050949350505050565b6060868686868686604051602401611aab96959493929190612174565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fd764ad0b0000000000000000000000000000000000000000000000000000000017905290509695505050505050565b73ffffffffffffffffffffffffffffffffffffffff81168114611b4f57600080fd5b50565b60008083601f840112611b6457600080fd5b50813567ffffffffffffffff811115611b7c57600080fd5b602083019150836020828501011115611b9457600080fd5b9250929050565b803563ffffffff81168114611baf57600080fd5b919050565b60008060008060608587031215611bca57600080fd5b8435611bd581611b2d565b9350602085013567ffffffffffffffff811115611bf157600080fd5b611bfd87828801611b52565b9094509250611c10905060408601611b9b565b905092959194509250565b6000815180845260005b81811015611c4157602081850181015186830182015201611c25565b81811115611c53576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006117626020830184611c1b565b600060208284031215611cab57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060408385031215611cf457600080fd5b823567ffffffffffffffff80821115611d0c57600080fd5b818501915085601f830112611d2057600080fd5b813581811115611d3257611d32611cb2565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715611d7857611d78611cb2565b81604052828152886020848701011115611d9157600080fd5b826020860160208301376000602084830101528096505050505050611db860208401611b9b565b90509250929050565b600060208284031215611dd357600080fd5b813561176281611b2d565b600080600080600080600060c0888a031215611df957600080fd5b873596506020880135611e0b81611b2d565b95506040880135611e1b81611b2d565b9450606088013593506080880135925060a088013567ffffffffffffffff811115611e4557600080fd5b611e518a828b01611b52565b989b979a50959850939692959293505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b878152600073ffffffffffffffffffffffffffffffffffffffff808916602084015280881660408401525085606083015263ffffffff8516608083015260c060a0830152611eff60c083018486611e64565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff86168152608060208201526000611f3c608083018688611e64565b905083604083015263ffffffff831660608301529695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611fc157611fc1611f5a565b500290565b600060208284031215611fd857600080fd5b815161176281611b2d565b600067ffffffffffffffff8083168185168183048111821515161561200a5761200a611f5a565b02949350505050565b600067ffffffffffffffff80841680612055577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b92169190910492915050565b600067ffffffffffffffff80831681851680830382111561208457612084611f5a565b01949350505050565b600082198211156120a0576120a0611f5a565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b6000828210156120e6576120e6611f5a565b500390565b73ffffffffffffffffffffffffffffffffffffffff8416815267ffffffffffffffff83166020820152606060408201526000610b506060830184611c1b565b600073ffffffffffffffffffffffffffffffffffffffff8087168352808616602084015250608060408301526121636080830185611c1b565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a08301526121bf60c0830184611c1b565b9897505050505050505056fea164736f6c634300080f000a00000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 3129000, + "intent": "Deploy L2CrossDomainMessenger Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001ec8608060405234801561001057600080fd5b50611ea8806100206000396000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806368d5dca6116100e3578063c59859181161008c578063f45e65d811610066578063f45e65d8146102fc578063f820614014610304578063fe173b971461029357600080fd5b8063c5985918146102ce578063de26c4a1146102d6578063f1c7a58b146102e957600080fd5b8063960e3a23116100bd578063960e3a23146102a1578063b3d72079146102b3578063b54501bc146102bb57600080fd5b806368d5dca6146102765780636ef25c3a146102935780638e98b1061461029957600080fd5b80632e0f2625116101455780634ef6e2241161011f5780634ef6e22414610218578063519b4bd31461022557806354fd4d501461022d57600080fd5b80632e0f2625146101f6578063313ce567146101fe57806349948e0e1461020557600080fd5b806322b90ab31161017657806322b90ab3146101d1578063275aedd2146101db578063291b0383146101ee57600080fd5b80630c18c16214610192578063105d0b81146101ad575b600080fd5b61019a61030c565b6040519081526020015b60405180910390f35b6000546101c1906301000000900460ff1681565b60405190151581526020016101a4565b6101d961042d565b005b61019a6101e93660046118fa565b6105b6565b6101d9610776565b61019a600681565b600661019a565b61019a610213366004611942565b61099e565b6000546101c19060ff1681565b61019a6109db565b6102696040518060400160405280600581526020017f312e362e3000000000000000000000000000000000000000000000000000000081525081565b6040516101a49190611a11565b61027e610a3c565b60405163ffffffff90911681526020016101a4565b4861019a565b6101d9610ac1565b6000546101c190610100900460ff1681565b6101d9610cbb565b6000546101c19062010000900460ff1681565b61027e610ec2565b61019a6102e4366004611942565b610f23565b61019a6102f73660046118fa565b61101d565b61019a6110f1565b61019a6111e4565b6000805460ff16156103a5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f47617350726963654f7261636c653a206f76657268656164282920697320646560448201527f707265636174656400000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610404573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104289190611a84565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e2073657420697345636f746f6e6520666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161039c565b60005460ff1615610589576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a2045636f746f6e6520616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161039c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000805462010000900460ff166105cf57506000919050565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16634d5d9a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610630573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106549190611a9d565b63ffffffff169050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166316d3bc7f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e19190611ac3565b67ffffffffffffffff169050600060039054906101000a900460ff161561072a578061070d8386611b1c565b610718906064611b1c565b6107229190611b59565b949350505050565b610722620f424083860286810485148715177fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01176107699190611b71565b8281019081106000031790565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461083f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973497374686d757320666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161039c565b600054610100900460ff166108d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20497374686d75732063616e206f6e6c7960448201527f2062652061637469766174656420616674657220466a6f726400000000000000606482015260840161039c565b60005462010000900460ff161561096f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a20497374686d757320616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161039c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff1662010000179055565b60008054610100900460ff16156109be576109b882611245565b92915050565b60005460ff16156109d2576109b882611264565b6109b882611308565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16635cf249696040518163ffffffff1660e01b8152600401602060405180830381865afa158015610404573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166368d5dca66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a9d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104289190611a9d565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610b64576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973466a6f726420666c616700606482015260840161039c565b60005460ff16610bf6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20466a6f72642063616e206f6e6c79206260448201527f65206163746976617465642061667465722045636f746f6e6500000000000000606482015260840161039c565b600054610100900460ff1615610c8d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f47617350726963654f7261636c653a20466a6f726420616c726561647920616360448201527f7469766500000000000000000000000000000000000000000000000000000000606482015260840161039c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100179055565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610d6057604080517f08c379a00000000000000000000000000000000000000000000000000000000081526020600482015260248101919091527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e207365742069734a6f7669616e20666c6167606482015260840161039c565b60005462010000900460ff16610df8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f47617350726963654f7261636c653a204a6f7669616e2063616e206f6e6c792060448201527f62652061637469766174656420616674657220497374686d7573000000000000606482015260840161039c565b6000546301000000900460ff1615610e92576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f47617350726963654f7261636c653a204a6f7669616e20616c7265616479206160448201527f6374697665000000000000000000000000000000000000000000000000000000606482015260840161039c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffff166301000000179055565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663c59859186040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a9d573d6000803e3d6000fd5b60008054610100900460ff1615610f6a57620f4240610f55610f448461145c565b51610f50906044611b59565b611779565b610f60906010611b1c565b6109b89190611b71565b6000610f75836117d8565b60005490915060ff1615610f895792915050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fe8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061100c9190611a84565b6110169082611b59565b9392505050565b60008054610100900460ff166110b5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f47617350726963654f7261636c653a206765744c314665655570706572426f7560448201527f6e64206f6e6c7920737570706f72747320466a6f726400000000000000000000606482015260840161039c565b60006110c2836044611b59565b905060006110d160ff83611b71565b6110db9083611b59565b6110e6906010611b59565b905061072281611868565b6000805460ff1615611185576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a207363616c61722829206973206465707260448201527f6563617465640000000000000000000000000000000000000000000000000000606482015260840161039c565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa158015610404573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663f82061406040518163ffffffff1660e01b8152600401602060405180830381865afa158015610404573d6000803e3d6000fd5b60006109b86112538361145c565b5161125f906044611b59565b611868565b600080611270836117d8565b9050600061127c6109db565b611284610ec2565b61128f906010611bac565b63ffffffff1661129f9190611b1c565b905060006112ab6111e4565b6112b3610a3c565b63ffffffff166112c39190611b1c565b905060006112d18284611b59565b6112db9085611b1c565b90506112e96006600a611cf8565b6112f4906010611b1c565b6112fe9082611b71565b9695505050505050565b600080611314836117d8565b9050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa158015611377573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061139b9190611a84565b6113a36109db565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015611402573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114269190611a84565b6114309085611b59565b61143a9190611b1c565b6114449190611b1c565b90506114526006600a611cf8565b6107229082611b71565b60606115eb565b818153600101919050565b600082840393505b838110156110165782810151828201511860001a1590930292600101611476565b825b602082106114e35782516114ae601f83611463565b52602092909201917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090910190602101611499565b81156110165782516114f86001840383611463565b520160010192915050565b60006001830392505b6101078210611544576115368360ff1661153160fd6115318760081c60e00189611463565b611463565b93506101068203915061150c565b600782106115715761156a8360ff16611531600785036115318760081c60e00189611463565b9050611016565b6107228360ff166115318560081c8560051b0187611463565b6115e38282036115c76115b784600081518060001a8160011a60081b178160021a60101b17915050919050565b639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b6180003860405139618000604051016020830180600d8551820103826002015b8181101561171e576000805b50508051604051600082901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b9091189091528401908183039084841061167357506116ae565b600184019350611fff82116116a8578251600081901a600182901a60081b1760029190911a60101b1781036116a857506116ae565b50611617565b8383106116bc57505061171e565b600183039250858311156116da576116d78787888603611497565b96505b6116ee60098501600385016003850161146e565b91506116fb878284611503565b9650506117138461170e8684860161158a565b61158a565b91505080935061160b565b50506117308383848851850103611497565b925050506040519150618000820180820391508183526020830160005b8381101561176557828101518282015260200161174d565b506000920191825250602001604052919050565b60008061178983620cc394611b1c565b6117b3907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd763200611d04565b90506117c36064620f4240611d78565b8112156109b8576110166064620f4240611d78565b80516000908190815b8181101561185b578481815181106117fb576117fb611e34565b01602001517fff000000000000000000000000000000000000000000000000000000000000001660000361183b57611834600484611b59565b9250611849565b611846601084611b59565b92505b8061185381611e63565b9150506117e1565b5061072282610440611b59565b60008061187483611779565b905060006118806111e4565b611888610a3c565b63ffffffff166118989190611b1c565b6118a06109db565b6118a8610ec2565b6118b3906010611bac565b63ffffffff166118c39190611b1c565b6118cd9190611b59565b90506118db60066002611b1c565b6118e690600a611cf8565b6118f08284611b1c565b6107229190611b71565b60006020828403121561190c57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561195457600080fd5b813567ffffffffffffffff8082111561196c57600080fd5b818401915084601f83011261198057600080fd5b81358181111561199257611992611913565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156119d8576119d8611913565b816040528281528760208487010111156119f157600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b81811015611a3e57858101830151858201604001528201611a22565b81811115611a50576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b600060208284031215611a9657600080fd5b5051919050565b600060208284031215611aaf57600080fd5b815163ffffffff8116811461101657600080fd5b600060208284031215611ad557600080fd5b815167ffffffffffffffff8116811461101657600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611b5457611b54611aed565b500290565b60008219821115611b6c57611b6c611aed565b500190565b600082611ba7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600063ffffffff80831681851681830481118215151615611bcf57611bcf611aed565b02949350505050565b600181815b80851115611c3157817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115611c1757611c17611aed565b80851615611c2457918102915b93841c9390800290611bdd565b509250929050565b600082611c48575060016109b8565b81611c55575060006109b8565b8160018114611c6b5760028114611c7557611c91565b60019150506109b8565b60ff841115611c8657611c86611aed565b50506001821b6109b8565b5060208310610133831016604e8410600b8410161715611cb4575081810a6109b8565b611cbe8383611bd8565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115611cf057611cf0611aed565b029392505050565b60006110168383611c39565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03841381151615611d3e57611d3e611aed565b827f8000000000000000000000000000000000000000000000000000000000000000038412811615611d7257611d72611aed565b50500190565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615611db957611db9611aed565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615611df457611df4611aed565b60008712925087820587128484161615611e1057611e10611aed565b87850587128184161615611e2657611e26611aed565b505050929093029392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611e9457611e94611aed565b506001019056fea164736f6c634300080f000a000000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 2762000, + "intent": "Deploy GasPriceOracle Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000002f3d60806040523480156200001157600080fd5b506200001c62000022565b620000e4565b600054610100900460ff16156200008f5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff9081161015620000e2576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b612e4980620000f46000396000f3fe6080604052600436106101485760003560e01c80635c975abb116100c0578063a3a7954811610074578063c89701a211610059578063c89701a214610459578063dad544e014610486578063e11013dd1461049b57600080fd5b8063a3a7954814610426578063c4d66de81461043957600080fd5b806387087623116100a557806387087623146103955780638f601f66146103b5578063927ede2d146103fb57600080fd5b80635c975abb146103795780637f46ddb21461027a57600080fd5b806336c717c1116101175780633e47158c116100fc5780633e47158c146102f8578063540abf731461030d57806354fd4d501461032d57600080fd5b806336c717c11461027a5780633cb747bf146102cb57600080fd5b80630166a07a1461022157806309fc8843146102415780631635f5fd1461025457806332b7006d1461026757600080fd5b3661021c576101556104ae565b6101e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084015b60405180910390fd5b61021a73deaddeaddeaddeaddeaddeaddeaddeaddead000033333462030d40604051806020016040528060008152506104eb565b005b600080fd5b34801561022d57600080fd5b5061021a61023c3660046127ff565b6105c6565b61021a61024f3660046128b0565b610968565b61021a610262366004612903565b610a44565b61021a610275366004612976565b610e96565b34801561028657600080fd5b5060045473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b3480156102d757600080fd5b506003546102a19073ffffffffffffffffffffffffffffffffffffffff1681565b34801561030457600080fd5b506102a1610f75565b34801561031957600080fd5b5061021a6103283660046129ca565b611180565b34801561033957600080fd5b50604080518082018252600681527f312e31332e320000000000000000000000000000000000000000000000000000602082015290516102c29190612ab7565b34801561038557600080fd5b50604051600081526020016102c2565b3480156103a157600080fd5b5061021a6103b0366004612aca565b6111c5565b3480156103c157600080fd5b506103ed6103d0366004612b4d565b600260209081526000928352604080842090915290825290205481565b6040519081526020016102c2565b34801561040757600080fd5b5060035473ffffffffffffffffffffffffffffffffffffffff166102a1565b61021a610434366004612aca565b61129e565b34801561044557600080fd5b5061021a610454366004612b86565b6112e2565b34801561046557600080fd5b506004546102a19073ffffffffffffffffffffffffffffffffffffffff1681565b34801561049257600080fd5b506102a1611493565b61021a6104a9366004612ba3565b611510565b60003233036104bd5750600190565b333b6017036104e557604051602081016040526020600082333c5160e81c62ef010014905090565b50600090565b7fffffffffffffffffffffffff215221522152215221522152215221522153000073ffffffffffffffffffffffffffffffffffffffff87160161053a576105358585858585611559565b6105be565b60008673ffffffffffffffffffffffffffffffffffffffff1663c01e1bd66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610587573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ab9190612c06565b90506105bc87828888888888611723565b505b505050505050565b60035473ffffffffffffffffffffffffffffffffffffffff1633148015610699575060048054600354604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff938416949390921692636e296e459282820192602092908290030181865afa15801561065d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106819190612c06565b73ffffffffffffffffffffffffffffffffffffffff16145b61074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20746865206f7468657220627269646760648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a4016101dd565b61075487611adc565b156108a2576107638787611b3e565b610815576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f5374616e646172644272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433230206c60648201527f6f63616c20746f6b656e00000000000000000000000000000000000000000000608482015260a4016101dd565b6040517f40c10f1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8581166004830152602482018590528816906340c10f1990604401600060405180830381600087803b15801561088557600080fd5b505af1158015610899573d6000803e3d6000fd5b50505050610924565b73ffffffffffffffffffffffffffffffffffffffff8088166000908152600260209081526040808320938a16835292905220546108e0908490612c52565b73ffffffffffffffffffffffffffffffffffffffff8089166000818152600260209081526040808320948c1683529390529190912091909155610924908585611c5e565b6105bc878787878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611d3292505050565b6109706104ae565b6109fc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101dd565b610a3f3333348686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061155992505050565b505050565b60035473ffffffffffffffffffffffffffffffffffffffff1633148015610b17575060048054600354604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff938416949390921692636e296e459282820192602092908290030181865afa158015610adb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aff9190612c06565b73ffffffffffffffffffffffffffffffffffffffff16145b610bc9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20746865206f7468657220627269646760648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a4016101dd565b823414610c58576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f5374616e646172644272696467653a20616d6f756e742073656e7420646f657360448201527f206e6f74206d6174636820616d6f756e7420726571756972656400000000000060648201526084016101dd565b3073ffffffffffffffffffffffffffffffffffffffff851603610cfd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f5374616e646172644272696467653a2063616e6e6f742073656e6420746f207360448201527f656c66000000000000000000000000000000000000000000000000000000000060648201526084016101dd565b60035473ffffffffffffffffffffffffffffffffffffffff90811690851603610da8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f5374616e646172644272696467653a2063616e6e6f742073656e6420746f206d60448201527f657373656e67657200000000000000000000000000000000000000000000000060648201526084016101dd565b610dea85858585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dc092505050565b6000610e07855a8660405180602001604052806000815250611e61565b9050806105be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f5374616e646172644272696467653a20455448207472616e736665722066616960448201527f6c6564000000000000000000000000000000000000000000000000000000000060648201526084016101dd565b610e9e6104ae565b610f2a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101dd565b610f6e853333878787878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506104eb92505050565b5050505050565b600080610fa07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff811615610fc357919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026110069190612c69565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000009190911790611061906060015b604051602081830303815290604052805190602001205490565b14611098576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091526000906110ba90606001611047565b905073ffffffffffffffffffffffffffffffffffffffff81161561114e578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611123573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111479190612c06565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6105bc87873388888888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061172392505050565b6111cd6104ae565b611259576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101dd565b6105be86863333888888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061172392505050565b6105be863387878787878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506104eb92505050565b600054610100900460ff16158080156113025750600054600160ff909116105b8061131c5750303b15801561131c575060005460ff166001145b6113a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016101dd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561140657600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61140e611e79565b61142c73420000000000000000000000000000000000000783611efc565b801561148f57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b600061149d610f75565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156114e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061150b9190612c06565b905090565b6115533385348686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061155992505050565b50505050565b8234146115e8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f5374616e646172644272696467653a206272696467696e6720455448206d757360448201527f7420696e636c7564652073756666696369656e74204554482076616c7565000060648201526084016101dd565b6115f485858584611fe6565b60035460045460405173ffffffffffffffffffffffffffffffffffffffff92831692633dbb202b9287929116907f1635f5fd0000000000000000000000000000000000000000000000000000000090611657908b908b9086908a90602401612ca6565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e086901b90921682526116ea92918890600401612cef565b6000604051808303818588803b15801561170357600080fd5b505af1158015611717573d6000803e3d6000fd5b50505050505050505050565b34156117b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f5374616e646172644272696467653a2063616e6e6f742073656e642076616c7560448201527f650000000000000000000000000000000000000000000000000000000000000060648201526084016101dd565b6117ba87611adc565b15611908576117c98787611b3e565b61187b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f5374616e646172644272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433230206c60648201527f6f63616c20746f6b656e00000000000000000000000000000000000000000000608482015260a4016101dd565b6040517f9dc29fac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015260248201859052881690639dc29fac90604401600060405180830381600087803b1580156118eb57600080fd5b505af11580156118ff573d6000803e3d6000fd5b5050505061199c565b61192a73ffffffffffffffffffffffffffffffffffffffff8816863086612087565b73ffffffffffffffffffffffffffffffffffffffff8088166000908152600260209081526040808320938a1683529290522054611968908490612d34565b73ffffffffffffffffffffffffffffffffffffffff8089166000908152600260209081526040808320938b16835292905220555b6119aa8787878787866120e5565b60035460045460405173ffffffffffffffffffffffffffffffffffffffff92831692633dbb202b9216907f0166a07a0000000000000000000000000000000000000000000000000000000090611a0e908b908d908c908c908c908b90602401612d4c565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e085901b9092168252611aa192918790600401612cef565b600060405180830381600087803b158015611abb57600080fd5b505af1158015611acf573d6000803e3d6000fd5b5050505050505050505050565b6000611b08827f1d1d8b6300000000000000000000000000000000000000000000000000000000612173565b80611b385750611b38827fec4fc8e300000000000000000000000000000000000000000000000000000000612173565b92915050565b6000611b6a837f1d1d8b6300000000000000000000000000000000000000000000000000000000612173565b15611c13578273ffffffffffffffffffffffffffffffffffffffff1663c01e1bd66040518163ffffffff1660e01b8152600401602060405180830381865afa158015611bba573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bde9190612c06565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16149050611b38565b8273ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015611bba573d6000803e3d6000fd5b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610a3f9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612196565b8373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167fb0444523268717a02698be47d0803aa7468c00acbed2f8bd93a0459cde61dd89868686604051611daa93929190612da7565b60405180910390a46105be8686868686866122a2565b8373ffffffffffffffffffffffffffffffffffffffff1673deaddeaddeaddeaddeaddeaddeaddeaddead000073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fb0444523268717a02698be47d0803aa7468c00acbed2f8bd93a0459cde61dd89868686604051611e4d93929190612da7565b60405180910390a46115538484848461232a565b6000806000835160208501868989f195945050505050565b33611e82610f75565b73ffffffffffffffffffffffffffffffffffffffff1614158015611ec3575033611eaa611493565b73ffffffffffffffffffffffffffffffffffffffff1614155b15611efa576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b600054610100900460ff16611f93576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016101dd565b6003805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560048054929093169116179055565b8373ffffffffffffffffffffffffffffffffffffffff1673deaddeaddeaddeaddeaddeaddeaddeaddead000073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f73d170910aba9e6d50b102db522b1dbcd796216f5128b445aa2135272886497e86868660405161207393929190612da7565b60405180910390a461155384848484612397565b60405173ffffffffffffffffffffffffffffffffffffffff808516602483015283166044820152606481018290526115539085907f23b872dd0000000000000000000000000000000000000000000000000000000090608401611cb0565b8373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167f73d170910aba9e6d50b102db522b1dbcd796216f5128b445aa2135272886497e86868660405161215d93929190612da7565b60405180910390a46105be8686868686866123f6565b600061217e8361246e565b801561218f575061218f83836124d2565b9392505050565b60006121f8826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166125a19092919063ffffffff16565b805190915015610a3f57808060200190518101906122169190612de5565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016101dd565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167fd59c65b35445225835c83f50b6ede06a7be047d22e357073e250d9af537518cd86868660405161231a93929190612da7565b60405180910390a4505050505050565b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f31b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83d8484604051612389929190612e07565b60405180910390a350505050565b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f2849b43074093a05396b6f2a937dee8565b15a48a7b3d4bffb732a5017380af58484604051612389929190612e07565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167f7ff126db8024424bbfd9826e8ab82ff59136289ea440b04b39a0df1b03b9cabf86868660405161231a93929190612da7565b600061249a827f01ffc9a7000000000000000000000000000000000000000000000000000000006124d2565b8015611b3857506124cb827fffffffff000000000000000000000000000000000000000000000000000000006124d2565b1592915050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d9150600051905082801561258a575060208210155b80156125965750600081115b979650505050505050565b60606125b084846000856125b8565b949350505050565b60608247101561264a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016101dd565b73ffffffffffffffffffffffffffffffffffffffff85163b6126c8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101dd565b6000808673ffffffffffffffffffffffffffffffffffffffff1685876040516126f19190612e20565b60006040518083038185875af1925050503d806000811461272e576040519150601f19603f3d011682016040523d82523d6000602084013e612733565b606091505b50915091506125968282866060831561274d57508161218f565b82511561275d5782518084602001fd5b816040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101dd9190612ab7565b73ffffffffffffffffffffffffffffffffffffffff811681146127b357600080fd5b50565b60008083601f8401126127c857600080fd5b50813567ffffffffffffffff8111156127e057600080fd5b6020830191508360208285010111156127f857600080fd5b9250929050565b600080600080600080600060c0888a03121561281a57600080fd5b873561282581612791565b9650602088013561283581612791565b9550604088013561284581612791565b9450606088013561285581612791565b93506080880135925060a088013567ffffffffffffffff81111561287857600080fd5b6128848a828b016127b6565b989b979a50959850939692959293505050565b803563ffffffff811681146128ab57600080fd5b919050565b6000806000604084860312156128c557600080fd5b6128ce84612897565b9250602084013567ffffffffffffffff8111156128ea57600080fd5b6128f6868287016127b6565b9497909650939450505050565b60008060008060006080868803121561291b57600080fd5b853561292681612791565b9450602086013561293681612791565b935060408601359250606086013567ffffffffffffffff81111561295957600080fd5b612965888289016127b6565b969995985093965092949392505050565b60008060008060006080868803121561298e57600080fd5b853561299981612791565b9450602086013593506129ae60408701612897565b9250606086013567ffffffffffffffff81111561295957600080fd5b600080600080600080600060c0888a0312156129e557600080fd5b87356129f081612791565b96506020880135612a0081612791565b95506040880135612a1081612791565b945060608801359350612a2560808901612897565b925060a088013567ffffffffffffffff81111561287857600080fd5b60005b83811015612a5c578181015183820152602001612a44565b838111156115535750506000910152565b60008151808452612a85816020860160208601612a41565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061218f6020830184612a6d565b60008060008060008060a08789031215612ae357600080fd5b8635612aee81612791565b95506020870135612afe81612791565b945060408701359350612b1360608801612897565b9250608087013567ffffffffffffffff811115612b2f57600080fd5b612b3b89828a016127b6565b979a9699509497509295939492505050565b60008060408385031215612b6057600080fd5b8235612b6b81612791565b91506020830135612b7b81612791565b809150509250929050565b600060208284031215612b9857600080fd5b813561218f81612791565b60008060008060608587031215612bb957600080fd5b8435612bc481612791565b9350612bd260208601612897565b9250604085013567ffffffffffffffff811115612bee57600080fd5b612bfa878288016127b6565b95989497509550505050565b600060208284031215612c1857600080fd5b815161218f81612791565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015612c6457612c64612c23565b500390565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615612ca157612ca1612c23565b500290565b600073ffffffffffffffffffffffffffffffffffffffff808716835280861660208401525083604083015260806060830152612ce56080830184612a6d565b9695505050505050565b73ffffffffffffffffffffffffffffffffffffffff84168152606060208201526000612d1e6060830185612a6d565b905063ffffffff83166040830152949350505050565b60008219821115612d4757612d47612c23565b500190565b600073ffffffffffffffffffffffffffffffffffffffff80891683528088166020840152808716604084015280861660608401525083608083015260c060a0830152612d9b60c0830184612a6d565b98975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff84168152826020820152606060408201526000612ddc6060830184612a6d565b95945050505050565b600060208284031215612df757600080fd5b8151801515811461218f57600080fd5b8281526040602082015260006125b06040830184612a6d565b60008251612e32818460208701612a41565b919091019291505056fea164736f6c634300080f000a000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 4193000, + "intent": "Deploy L2StandardBridge Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000010956080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610fbf806100d65f395ff3fe6080604052600436106100f2575f3560e01c80638312f14911610087578063d0e12f9011610057578063d0e12f90146102e9578063d3e5792b14610318578063d4ff9218146100fd578063dad544e01461032c575f80fd5b80638312f1491461028257806384411d651461029757806385b5b14d146102ab578063b49dc741146102ca575f80fd5b80633e47158c116100c25780633e47158c146101af57806354fd4d50146101c357806366d003ac1461021857806382356d8a14610244575f80fd5b80630d9019e1146100fd578063307f29621461014d5780633bbed4a01461016e5780633ccfd60b1461018d575f80fd5b366100f957005b5f80fd5b348015610108575f80fd5b5060025473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b348015610158575f80fd5b5061016c610167366004610d6d565b610340565b005b348015610179575f80fd5b5061016c610188366004610daa565b6103e6565b348015610198575f80fd5b506101a161046d565b604051908152602001610144565b3480156101ba575f80fd5b5061012361077b565b3480156101ce575f80fd5b5061020b6040518060400160405280600581526020017f312e362e3100000000000000000000000000000000000000000000000000000081525081565b6040516101449190610dc5565b348015610223575f80fd5b506002546101239073ffffffffffffffffffffffffffffffffffffffff1681565b34801561024f575f80fd5b506002546102759074010000000000000000000000000000000000000000900460ff1681565b6040516101449190610e7e565b34801561028d575f80fd5b506101a160015481565b3480156102a2575f80fd5b506101a15f5481565b3480156102b6575f80fd5b5061016c6102c5366004610e92565b610981565b3480156102d5575f80fd5b5061016c6102e4366004610ea9565b6109c7565b3480156102f4575f80fd5b5060025474010000000000000000000000000000000000000000900460ff16610275565b348015610323575f80fd5b506001546101a1565b348015610337575f80fd5b50610123610be3565b610348610c5d565b600280547401000000000000000000000000000000000000000080820460ff1692849290917fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff909116908360018111156103a4576103a4610e18565b02179055507ff2ec44eb1c3b3acd547b76333eb2c4b27eee311860c57a9fdb04c95f62398fc881836040516103da929190610ee4565b60405180910390a15050565b6103ee610c5d565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f62e69886a5df0ba8ffcacbfc1388754e7abd9bde24b036354c561f1acd4e459391016103da565b5f60015447101561052b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b479050805f8082825461053e9190610f2c565b90915550506002546040805183815273ffffffffffffffffffffffffffffffffffffffff909216602083018190523383830152905190917fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba919081900360600190a16002546040517f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee916105f39185918591339174010000000000000000000000000000000000000000900460ff1690610f3f565b60405180910390a1600160025474010000000000000000000000000000000000000000900460ff16600181111561062c5761062c610e18565b036106d0575f61063c8284610cb5565b9050806106cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610522565b505090565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216600482015262061a806024820152606060448201525f60648201527342000000000000000000000000000000000000169063c2b3e5ac9084906084015f604051808303818588803b158015610760575f80fd5b505af1158015610772573d5f803e3d5ffd5b50505050505090565b5f806107a57fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107c857919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000081525051600261080b9190610f80565b604080513060208201525f918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000009190911790610865906060015b604051602081830303815290604052805190602001205490565b1461089c576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091525f906108bd9060600161084b565b905073ffffffffffffffffffffffffffffffffffffffff81161561094f578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610924573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109489190610f97565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610989610c5d565b600180549082905560408051828152602081018490527f895a067c78583e800418fabf3da26a9496aab2ff3429cebdf7fefa642b2e420391016103da565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015610a115750825b90505f8267ffffffffffffffff166001148015610a2d5750303b155b905081158015610a3b575080155b15610a72576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315610ad35784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b610adb610cc8565b6002805473ffffffffffffffffffffffffffffffffffffffff8a167fffffffffffffffffffffffff000000000000000000000000000000000000000082168117835560018a81558993927fffffffffffffffffffffff000000000000000000000000000000000000000000169091179074010000000000000000000000000000000000000000908490811115610b7357610b73610e18565b02179055508315610bd95784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610bec61077b565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c34573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c589190610f97565b905090565b33610c66610be3565b73ffffffffffffffffffffffffffffffffffffffff1614610cb3576040517f7f12c64b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f610cc1835a84610d49565b9392505050565b33610cd161077b565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d12575033610cf9610be3565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610cb3576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f805f858888f1949350505050565b803560028110610d68575f80fd5b919050565b5f60208284031215610d7d575f80fd5b610cc182610d5a565b73ffffffffffffffffffffffffffffffffffffffff81168114610da7575f80fd5b50565b5f60208284031215610dba575f80fd5b8135610cc181610d86565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60028110610e7a577f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9052565b60208101610e8c8284610e45565b92915050565b5f60208284031215610ea2575f80fd5b5035919050565b5f805f60608486031215610ebb575f80fd5b8335610ec681610d86565b925060208401359150610edb60408501610d5a565b90509250925092565b60408101610ef28285610e45565b610cc16020830184610e45565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115610e8c57610e8c610eff565b84815273ffffffffffffffffffffffffffffffffffffffff84811660208301528316604082015260808101610f776060830184610e45565b95945050505050565b8082028115828204841417610e8c57610e8c610eff565b5f60208284031215610fa7575f80fd5b8151610cc181610d8656fea164736f6c6343000819000a0000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1506000, + "intent": "Deploy SequencerFeeVault Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000002f6e608060405234801561001057600080fd5b5061001961001e565b6100de565b600054610100900460ff161561008a5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff90811610156100dc576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b612e81806100ed6000396000f3fe60806040523480156200001157600080fd5b5060043610620000cd5760003560e01c8063c4d66de8116200007f578063dad544e01162000062578063dad544e014620001e9578063e78cea9214620001f3578063ee9a31a2146200021457600080fd5b8063c4d66de814620001b9578063ce5ac90f14620001d257600080fd5b806354fd4d5011620000b457806354fd4d50146200013f578063896f93d1146200018b5780638cf0629c14620001a257600080fd5b8063316b373914620000d25780633e47158c1462000135575b600080fd5b6200010b620000e3366004620009ac565b60026020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6200010b62000233565b6200017c6040518060400160405280600681526020017f312e31312e30000000000000000000000000000000000000000000000000000081525081565b6040516200012c919062000a41565b6200010b6200019c36600462000b38565b6200044a565b6200010b620001b336600462000bb7565b62000461565b620001d0620001ca366004620009ac565b62000680565b005b6200010b620001e336600462000b38565b6200085c565b6200010b6200086d565b6001546200010b9073ffffffffffffffffffffffffffffffffffffffff1681565b60015473ffffffffffffffffffffffffffffffffffffffff166200010b565b6000806200025f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156200028357919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000815250516002620002c8919062000c50565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179062000324906060015b604051602081830303815290604052805190602001205490565b146200035c576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805130602082015260019181019190915260009062000380906060016200030a565b905073ffffffffffffffffffffffffffffffffffffffff81161562000418578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620003eb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000411919062000cb5565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000620004598484846200085c565b949350505050565b600073ffffffffffffffffffffffffffffffffffffffff85166200050c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4f7074696d69736d4d696e7461626c654552433230466163746f72793a206d7560448201527f73742070726f766964652072656d6f746520746f6b656e20616464726573730060648201526084015b60405180910390fd5b60008585858560405160200162000527949392919062000cd5565b604051602081830303815290604052805190602001209050600081600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1688888888604051620005779062000978565b6200058795949392919062000d2f565b8190604051809103906000f5905080158015620005a8573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff81811660008181526002602052604080822080547fffffffffffffffffffffffff000000000000000000000000000000000000000016948d1694851790555193945090927fceeb8e7d520d7f3b65fc11a262b91066940193b05d4f93df07cfdced0eb551cf9190a360405133815273ffffffffffffffffffffffffffffffffffffffff80891691908316907f52fe89dd5930f343d25650b62fd367bae47088bcddffd2a88350a6ecdd620cdb9060200160405180910390a39695505050505050565b600054610100900460ff1615808015620006a15750600054600160ff909116105b80620006bd5750303b158015620006bd575060005460ff166001145b6200074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a6564000000000000000000000000000000000000606482015260840162000503565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015620007aa57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b620007b4620008ef565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff841617905580156200085857600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b600062000459848484601262000461565b60006200087962000233565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620008c4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620008ea919062000cb5565b905090565b33620008fa62000233565b73ffffffffffffffffffffffffffffffffffffffff16141580156200093e575033620009256200086d565b73ffffffffffffffffffffffffffffffffffffffff1614155b1562000976576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6120e08062000d9583390190565b73ffffffffffffffffffffffffffffffffffffffff81168114620009a957600080fd5b50565b600060208284031215620009bf57600080fd5b8135620009cc8162000986565b9392505050565b6000815180845260005b81811015620009fb57602081850181015186830182015201620009dd565b8181111562000a0e576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000620009cc6020830184620009d3565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011262000a9757600080fd5b813567ffffffffffffffff8082111562000ab55762000ab562000a56565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171562000afe5762000afe62000a56565b8160405283815286602085880101111562000b1857600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121562000b4e57600080fd5b833562000b5b8162000986565b9250602084013567ffffffffffffffff8082111562000b7957600080fd5b62000b878783880162000a85565b9350604086013591508082111562000b9e57600080fd5b5062000bad8682870162000a85565b9150509250925092565b6000806000806080858703121562000bce57600080fd5b843562000bdb8162000986565b9350602085013567ffffffffffffffff8082111562000bf957600080fd5b62000c078883890162000a85565b9450604087013591508082111562000c1e57600080fd5b5062000c2d8782880162000a85565b925050606085013560ff8116811462000c4557600080fd5b939692955090935050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161562000cb0577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b60006020828403121562000cc857600080fd5b8151620009cc8162000986565b73ffffffffffffffffffffffffffffffffffffffff8516815260806020820152600062000d066080830186620009d3565b828103604084015262000d1a8186620009d3565b91505060ff8316606083015295945050505050565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525060a0604083015262000d6a60a0830186620009d3565b828103606084015262000d7e8186620009d3565b91505060ff83166080830152969550505050505056fe6101a06040523480156200001257600080fd5b50604051620020e0380380620020e0833981016040819052620000359162000215565b6040805180820190915260018152603160f81b6020820152839081908185600362000061838262000350565b50600462000070828262000350565b5050825160208085019190912083518483012060e08290526101008190524660a0818152604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81880181905281830187905260608201869052608082019490945230818401528151808203909301835260c0019052805194019390932091935091906080523060c05261012052505050506001600160a01b0394851661014052509390921661016052505060ff16610180526200041c565b80516001600160a01b03811681146200014357600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200017057600080fd5b81516001600160401b03808211156200018d576200018d62000148565b604051601f8301601f19908116603f01168101908282118183101715620001b857620001b862000148565b81604052838152602092508683858801011115620001d557600080fd5b600091505b83821015620001f95785820183015181830184015290820190620001da565b838211156200020b5760008385830101525b9695505050505050565b600080600080600060a086880312156200022e57600080fd5b62000239866200012b565b945062000249602087016200012b565b60408701519094506001600160401b03808211156200026757600080fd5b6200027589838a016200015e565b945060608801519150808211156200028c57600080fd5b506200029b888289016200015e565b925050608086015160ff81168114620002b357600080fd5b809150509295509295909350565b600181811c90821680620002d657607f821691505b602082108103620002f757634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200034b57600081815260208120601f850160051c81016020861015620003265750805b601f850160051c820191505b81811015620003475782815560010162000332565b5050505b505050565b81516001600160401b038111156200036c576200036c62000148565b62000384816200037d8454620002c1565b84620002fd565b602080601f831160018114620003bc5760008415620003a35750858301515b600019600386901b1c1916600185901b17855562000347565b600085815260208120601f198616915b82811015620003ed57888601518255948401946001909101908401620003cc565b50858210156200040c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a05160c05160e0516101005161012051610140516101605161018051611c37620004a960003960006102700152600081816103a70152818161041c0152818161064801526107aa0152600081816101d501526103cd01526000611174015260006111c30152600061119e015260006110f7015260006111210152600061114b0152611c376000f3fe608060405234801561001057600080fd5b50600436106101a35760003560e01c806370a08231116100ee578063ae1f6aaf11610097578063d6c0b2c411610071578063d6c0b2c4146103cb578063dd62ed3e14610404578063e78cea92146103a5578063ee9a31a21461041757600080fd5b8063ae1f6aaf146103a5578063c01e1bd6146103cb578063d505accf146103f157600080fd5b80639dc29fac116100c85780639dc29fac1461036c578063a457c2d71461037f578063a9059cbb1461039257600080fd5b806370a082311461031b5780637ecebe001461035157806395d89b411461036457600080fd5b8063313ce5671161015057806340c10f191161012a57806340c10f19146102b557806354fd4d50146102ca5780636afdd8501461030657600080fd5b8063313ce567146102695780633644e5151461029a57806339509351146102a257600080fd5b8063095ea7b311610181578063095ea7b31461023157806318160ddd1461024457806323b872dd1461025657600080fd5b806301ffc9a7146101a8578063033964be146101d057806306fdde031461021c575b600080fd5b6101bb6101b636600461194b565b61043e565b60405190151581526020015b60405180910390f35b6101f77f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101c7565b61022461052f565b6040516101c7919061198d565b6101bb61023f366004611a29565b6105c1565b6002545b6040519081526020016101c7565b6101bb610264366004611a53565b6105db565b60405160ff7f00000000000000000000000000000000000000000000000000000000000000001681526020016101c7565b6102486105ff565b6101bb6102b0366004611a29565b61060e565b6102c86102c3366004611a29565b610630565b005b6102246040518060400160405280600581526020017f312e342e3100000000000000000000000000000000000000000000000000000081525081565b6e22d473030f116ddee9f6b43ac78ba36101f7565b610248610329366004611a8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b61024861035f366004611a8f565b610758565b610224610783565b6102c861037a366004611a29565b610792565b6101bb61038d366004611a29565b6108a9565b6101bb6103a0366004611a29565b610956565b7f00000000000000000000000000000000000000000000000000000000000000006101f7565b7f00000000000000000000000000000000000000000000000000000000000000006101f7565b6102c86103ff366004611aaa565b610964565b610248610412366004611b1d565b610b23565b6101f77f000000000000000000000000000000000000000000000000000000000000000081565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007f1d1d8b63000000000000000000000000000000000000000000000000000000007fec4fc8e3000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000085168314806104f757507fffffffff00000000000000000000000000000000000000000000000000000000858116908316145b8061052657507fffffffff00000000000000000000000000000000000000000000000000000000858116908216145b95945050505050565b60606003805461053e90611b50565b80601f016020809104026020016040519081016040528092919081815260200182805461056a90611b50565b80156105b75780601f1061058c576101008083540402835291602001916105b7565b820191906000526020600020905b81548152906001019060200180831161059a57829003601f168201915b5050505050905090565b6000336105cf818585610bc4565b60019150505b92915050565b6000336105e9858285610d78565b6105f4858585610e2a565b506001949350505050565b60006106096110dd565b905090565b6000336105cf8185856106218383610b23565b61062b9190611bcc565b610bc4565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146106fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f4f7074696d69736d4d696e7461626c6545524332303a206f6e6c79206272696460448201527f67652063616e206d696e7420616e64206275726e00000000000000000000000060648201526084015b60405180910390fd5b6107048282611211565b8173ffffffffffffffffffffffffffffffffffffffff167f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d41213968858260405161074c91815260200190565b60405180910390a25050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600560205260408120546105d5565b60606004805461053e90611b50565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610857576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f4f7074696d69736d4d696e7461626c6545524332303a206f6e6c79206272696460448201527f67652063616e206d696e7420616e64206275726e00000000000000000000000060648201526084016106f1565b6108618282611331565b8173ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca58260405161074c91815260200190565b600033816108b78286610b23565b905083811015610949576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016106f1565b6105f48286868403610bc4565b6000336105cf818585610e2a565b834211156109ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e6500000060448201526064016106f1565b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886109fd8c611516565b60408051602081019690965273ffffffffffffffffffffffffffffffffffffffff94851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090506000610a658261154b565b90506000610a75828787876115b4565b90508973ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610b0c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e6174757265000060448201526064016106f1565b610b178a8a8a610bc4565b50505050505050505050565b60007fffffffffffffffffffffffffffffffffffdd2b8cfcf0ee922116094bc538745d73ffffffffffffffffffffffffffffffffffffffff831601610b8957507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6105d5565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152600160209081526040808320938616835292905220545b9392505050565b73ffffffffffffffffffffffffffffffffffffffff8316610c66576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff8216610d09576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610d848484610b23565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610e245781811015610e17576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016106f1565b610e248484848403610bc4565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610ecd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff8216610f70576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015611026576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff80851660009081526020819052604080822085850390559185168152908120805484929061106a908490611bcc565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516110d091815260200190565b60405180910390a3610e24565b60003073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614801561114357507f000000000000000000000000000000000000000000000000000000000000000046145b1561116d57507f000000000000000000000000000000000000000000000000000000000000000090565b50604080517f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401527f000000000000000000000000000000000000000000000000000000000000000060608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b73ffffffffffffffffffffffffffffffffffffffff821661128e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016106f1565b80600260008282546112a09190611bcc565b909155505073ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906112da908490611bcc565b909155505060405181815273ffffffffffffffffffffffffffffffffffffffff8316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff82166113d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f730000000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff82166000908152602081905260409020548181101561148a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60448201527f636500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208383039055600280548492906114c6908490611be4565b909155505060405182815260009073ffffffffffffffffffffffffffffffffffffffff8516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610d6b565b73ffffffffffffffffffffffffffffffffffffffff811660009081526005602052604090208054600181018255905b50919050565b60006105d56115586110dd565b836040517f19010000000000000000000000000000000000000000000000000000000000006020820152602281018390526042810182905260009060620160405160208183030381529060405280519060200120905092915050565b60008060006115c5878787876115dc565b915091506115d2816116f4565b5095945050505050565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083111561161357506000905060036116eb565b8460ff16601b1415801561162b57508460ff16601c14155b1561163c57506000905060046116eb565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015611690573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81166116e4576000600192509250506116eb565b9150600090505b94509492505050565b600081600481111561170857611708611bfb565b036117105750565b600181600481111561172457611724611bfb565b0361178b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f45434453413a20696e76616c6964207369676e6174757265000000000000000060448201526064016106f1565b600281600481111561179f5761179f611bfb565b03611806576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e6774680060448201526064016106f1565b600381600481111561181a5761181a611bfb565b036118a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c60448201527f756500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b60048160048111156118bb576118bb611bfb565b03611948576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c60448201527f756500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b50565b60006020828403121561195d57600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610bbd57600080fd5b600060208083528351808285015260005b818110156119ba5785810183015185820160400152820161199e565b818111156119cc576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114611a2457600080fd5b919050565b60008060408385031215611a3c57600080fd5b611a4583611a00565b946020939093013593505050565b600080600060608486031215611a6857600080fd5b611a7184611a00565b9250611a7f60208501611a00565b9150604084013590509250925092565b600060208284031215611aa157600080fd5b610bbd82611a00565b600080600080600080600060e0888a031215611ac557600080fd5b611ace88611a00565b9650611adc60208901611a00565b95506040880135945060608801359350608088013560ff81168114611b0057600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215611b3057600080fd5b611b3983611a00565b9150611b4760208401611a00565b90509250929050565b600181811c90821680611b6457607f821691505b602082108103611545577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115611bdf57611bdf611b9d565b500190565b600082821015611bf657611bf6611b9d565b500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c634300080f000aa164736f6c634300080f000a000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 4193000, + "intent": "Deploy OptimismMintableERC20Factory Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001aaa608060405234801561001057600080fd5b5061001961001e565b6100de565b600054610100900460ff161561008a5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff90811610156100dc576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6119bd806100ed6000396000f3fe608060405234801561001057600080fd5b50600436106100d45760003560e01c80637f46ddb211610081578063c4d66de81161005b578063c4d66de8146101fa578063c89701a21461020d578063dad544e01461022d57600080fd5b80637f46ddb2146101ab578063927ede2d146101c9578063aa557452146101e757600080fd5b806354fd4d50116100b257806354fd4d50146101405780635c975abb14610189578063761f44931461019857600080fd5b80633687011a146100d95780633cb747bf146100ee5780633e47158c14610138575b600080fd5b6100ec6100e73660046115d7565b610235565b005b60015461010e9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b61010e6102e6565b61017c6040518060400160405280600681526020017f312e31302e31000000000000000000000000000000000000000000000000000081525081565b60405161012f91906116c5565b6040516000815260200161012f565b6100ec6101a63660046116d8565b6104f1565b60025473ffffffffffffffffffffffffffffffffffffffff1661010e565b60015473ffffffffffffffffffffffffffffffffffffffff1661010e565b6100ec6101f5366004611770565b610a0f565b6100ec6102083660046117e7565b610acb565b60025461010e9073ffffffffffffffffffffffffffffffffffffffff1681565b61010e610c7c565b61023d610cf9565b6102ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4552433732314272696467653a206163636f756e74206973206e6f742065787460448201527f65726e616c6c79206f776e65640000000000000000000000000000000000000060648201526084015b60405180910390fd5b6102de8686333388888888610d36565b505050505050565b6000806103117fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff81161561033457919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026103779190611804565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000091909117906103d2906060015b604051602081830303815290604052805190602001205490565b14610409576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805130602082015260019181019190915260009061042b906060016103b8565b905073ffffffffffffffffffffffffffffffffffffffff8116156104bf578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610494573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b89190611868565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60015473ffffffffffffffffffffffffffffffffffffffff16331480156105c65750600254600154604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff9384169390921691636e296e45916004808201926020929091908290030181865afa15801561058a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ae9190611868565b73ffffffffffffffffffffffffffffffffffffffff16145b610652576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4552433732314272696467653a2066756e6374696f6e2063616e206f6e6c792060448201527f62652063616c6c65642066726f6d20746865206f74686572206272696467650060648201526084016102c5565b3073ffffffffffffffffffffffffffffffffffffffff8816036106f7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f4c324552433732314272696467653a206c6f63616c20746f6b656e2063616e6e60448201527f6f742062652073656c660000000000000000000000000000000000000000000060648201526084016102c5565b610721877faecafc230000000000000000000000000000000000000000000000000000000061128c565b6107ad576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f4c324552433732314272696467653a206c6f63616c20746f6b656e20696e746560448201527f7266616365206973206e6f7420636f6d706c69616e740000000000000000000060648201526084016102c5565b8673ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061081c9190611868565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146108fc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604b60248201527f4c324552433732314272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433732312060648201527f6c6f63616c20746f6b656e000000000000000000000000000000000000000000608482015260a4016102c5565b6040517fa144819400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301526024820185905288169063a144819490604401600060405180830381600087803b15801561096c57600080fd5b505af1158015610980573d6000803e3d6000fd5b505050508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff167f1f39bf6707b5d608453e0ae4c067b562bcc4c85c0f562ef5d2c774d2e7f131ac878787876040516109fe94939291906118ce565b60405180910390a450505050505050565b73ffffffffffffffffffffffffffffffffffffffff8516610ab2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4552433732314272696467653a206e667420726563697069656e742063616e6e60448201527f6f7420626520616464726573732830290000000000000000000000000000000060648201526084016102c5565b610ac28787338888888888610d36565b50505050505050565b600054610100900460ff1615808015610aeb5750600054600160ff909116105b80610b055750303b158015610b05575060005460ff166001145b610b91576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016102c5565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015610bef57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b610bf76112af565b610c1573420000000000000000000000000000000000000783611332565b8015610c7857600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6000610c866102e6565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cd0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cf49190611868565b905090565b6000323303610d085750600190565b333b601703610d3057604051602081016040526020600082333c5160e81c62ef010014905090565b50600090565b73ffffffffffffffffffffffffffffffffffffffff8716610dd9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603160248201527f4c324552433732314272696467653a2072656d6f746520746f6b656e2063616e60448201527f6e6f74206265206164647265737328302900000000000000000000000000000060648201526084016102c5565b6040517f6352211e0000000000000000000000000000000000000000000000000000000081526004810185905273ffffffffffffffffffffffffffffffffffffffff891690636352211e90602401602060405180830381865afa158015610e44573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e689190611868565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610f22576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4c324552433732314272696467653a205769746864726177616c206973206e6f60448201527f74206265696e6720696e69746961746564206279204e4654206f776e6572000060648201526084016102c5565b60008873ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f6f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f939190611868565b90508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614611050576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f4c324552433732314272696467653a2072656d6f746520746f6b656e20646f6560448201527f73206e6f74206d6174636820676976656e2076616c756500000000000000000060648201526084016102c5565b6040517f9dc29fac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152602482018790528a1690639dc29fac90604401600060405180830381600087803b1580156110c057600080fd5b505af11580156110d4573d6000803e3d6000fd5b505050506000818a89898988886040516024016110f7979695949392919061190e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f761f44930000000000000000000000000000000000000000000000000000000017905260015460025491517f3dbb202b00000000000000000000000000000000000000000000000000000000815292935073ffffffffffffffffffffffffffffffffffffffff90811692633dbb202b926111cc92169085908a9060040161196b565b600060405180830381600087803b1580156111e657600080fd5b505af11580156111fa573d6000803e3d6000fd5b505050508773ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff168b73ffffffffffffffffffffffffffffffffffffffff167fb7460e2a880f256ebef3406116ff3eee0cee51ebccdc2a40698f87ebb2e9c1a58a8a898960405161127894939291906118ce565b60405180910390a450505050505050505050565b60006112978361141c565b80156112a857506112a88383611481565b9392505050565b336112b86102e6565b73ffffffffffffffffffffffffffffffffffffffff16141580156112f95750336112e0610c7c565b73ffffffffffffffffffffffffffffffffffffffff1614155b15611330576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b600054610100900460ff166113c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016102c5565b6001805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560028054929093169116179055565b6000611448827f01ffc9a700000000000000000000000000000000000000000000000000000000611481565b801561147b5750611479827fffffffff00000000000000000000000000000000000000000000000000000000611481565b155b92915050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d91506000519050828015611539575060208210155b80156115455750600081115b979650505050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461157257600080fd5b50565b803563ffffffff8116811461158957600080fd5b919050565b60008083601f8401126115a057600080fd5b50813567ffffffffffffffff8111156115b857600080fd5b6020830191508360208285010111156115d057600080fd5b9250929050565b60008060008060008060a087890312156115f057600080fd5b86356115fb81611550565b9550602087013561160b81611550565b94506040870135935061162060608801611575565b9250608087013567ffffffffffffffff81111561163c57600080fd5b61164889828a0161158e565b979a9699509497509295939492505050565b6000815180845260005b8181101561168057602081850181015186830182015201611664565b81811115611692576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006112a8602083018461165a565b600080600080600080600060c0888a0312156116f357600080fd5b87356116fe81611550565b9650602088013561170e81611550565b9550604088013561171e81611550565b9450606088013561172e81611550565b93506080880135925060a088013567ffffffffffffffff81111561175157600080fd5b61175d8a828b0161158e565b989b979a50959850939692959293505050565b600080600080600080600060c0888a03121561178b57600080fd5b873561179681611550565b965060208801356117a681611550565b955060408801356117b681611550565b9450606088013593506117cb60808901611575565b925060a088013567ffffffffffffffff81111561175157600080fd5b6000602082840312156117f957600080fd5b81356112a881611550565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611863577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b60006020828403121561187a57600080fd5b81516112a881611550565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff85168152836020820152606060408201526000611904606083018486611885565b9695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff808a1683528089166020840152808816604084015280871660608401525084608083015260c060a083015261195e60c083018486611885565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff8416815260606020820152600061199a606083018561165a565b905063ffffffff8316604083015294935050505056fea164736f6c634300080f000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 2367000, + "intent": "Deploy L2ERC721Bridge Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000cad608060405234801561001057600080fd5b50610c8d806100206000396000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80635cf24969116100f9578063c598591811610097578063e591b28211610071578063e591b28214610469578063e81b2c6d14610483578063f82061401461048c578063fe3d57101461049557600080fd5b8063c598591814610408578063d844471514610428578063dad544e01461046157600080fd5b80638381f58a116100d35780638381f58a146103c25780638b239f73146103d65780639e8c4966146103df578063b80777ea146103e857600080fd5b80635cf249691461038957806364ca23ef1461039257806368d5dca6146103a657600080fd5b80634397dfef1161016657806347af267b1161014057806347af267b146102ba5780634d5d9a2a146102dd57806354fd4d501461030e578063550fcdc91461035057600080fd5b80634397dfef14610277578063440a5e201461029f57806346a4d780146102a757600080fd5b806316d3bc7f116101a257806316d3bc7f14610202578063213268491461022f5780633db6be2b146102425780633e47158c1461024a57600080fd5b8063015d8eb9146101c9578063098999be146101de57806309bd5a60146101e6575b600080fd5b6101dc6101d7366004610ae1565b6104c6565b005b6101dc610605565b6101ef60025481565b6040519081526020015b60405180910390f35b6008546102169067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101f9565b60005b60405190151581526020016101f9565b6101dc610618565b610252610642565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101f9565b6040805173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815260126020820152016101f9565b6101dc61084d565b6101dc6102b5366004610b53565b6108a4565b6102326102c8366004610b53565b60096020526000908152604090205460ff1681565b6008546102f99068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101f9565b60408051808201909152600581527f312e392e3000000000000000000000000000000000000000000000000000000060208201525b6040516101f99190610b6c565b60408051808201909152600381527f45544800000000000000000000000000000000000000000000000000000000006020820152610343565b6101ef60015481565b6003546102169067ffffffffffffffff1681565b6003546102f99068010000000000000000900463ffffffff1681565b6000546102169067ffffffffffffffff1681565b6101ef60055481565b6101ef60065481565b6000546102169068010000000000000000900467ffffffffffffffff1681565b6003546102f9906c01000000000000000000000000900463ffffffff1681565b60408051808201909152600581527f45746865720000000000000000000000000000000000000000000000000000006020820152610343565b6102526108b9565b73deaddeaddeaddeaddeaddeaddeaddeaddead0001610252565b6101ef60045481565b6101ef60075481565b6008546104b3906c01000000000000000000000000900461ffff1681565b60405161ffff90911681526020016101f9565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461056d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61060d61084d565b60a43560a01c600855565b61062061084d565b6dffff00000000000000000000000060b03560901c1660a43560a01c17600855565b60008061066d7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff81161561069057919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026106d39190610bdf565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061072e906060015b604051602081830303815290604052805190602001205490565b14610765576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805130602082015260019181019190915260009061078790606001610714565b905073ffffffffffffffffffffffffffffffffffffffff81161561081b578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107f0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108149190610c43565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73deaddeaddeaddeaddeaddeaddeaddeaddead000133811461087757633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b6108ad33610936565b6108b681610a13565b50565b60006108c3610642565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561090d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109319190610c43565b905090565b73ffffffffffffffffffffffffffffffffffffffff811673deaddeaddeaddeaddeaddeaddeaddeaddead000114806109a057506109716108b9565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b806109dd57506109ae610642565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b6108b6576040517fbe9d7ca600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526009602052604090205460ff1615610a5c576040517f4f45326000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526009602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091559051909183917fb876f6594132c89891d2fd198e925e999be741ec809abb58bfe9b966876cc06c9190a350565b803567ffffffffffffffff81168114610adc57600080fd5b919050565b600080600080600080600080610100898b031215610afe57600080fd5b610b0789610ac4565b9750610b1560208a01610ac4565b96506040890135955060608901359450610b3160808a01610ac4565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208284031215610b6557600080fd5b5035919050565b600060208083528351808285015260005b81811015610b9957858101830151858201604001528201610b7d565b81811115610bab576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610c3e577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b600060208284031215610c5557600080fd5b815173ffffffffffffffffffffffffffffffffffffffff81168114610c7957600080fd5b939250505056fea164736f6c634300080f000a00000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1191000, + "intent": "Deploy L1Block Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006e9608060405234801561001057600080fd5b506106c9806100206000396000f3fe6080604052600436106100695760003560e01c806382e3702d1161004357806382e3702d14610120578063c2b3e5ac14610160578063ecc704281461017357600080fd5b80633f827a5a1461009257806344df8e70146100bf57806354fd4d50146100d457600080fd5b3661008d5761008b33620186a0604051806020016040528060008152506101d8565b005b600080fd5b34801561009e57600080fd5b506100a7600181565b60405161ffff90911681526020015b60405180910390f35b3480156100cb57600080fd5b5061008b61039c565b3480156100e057600080fd5b50604080518082018252600581527f312e322e30000000000000000000000000000000000000000000000000000000602082015290516100b691906104c7565b34801561012c57600080fd5b5061015061013b3660046104e1565b60006020819052908152604090205460ff1681565b60405190151581526020016100b6565b61008b61016e366004610529565b6101d8565b34801561017f57600080fd5b506101ca6001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016100b6565b600061026e6040518060c001604052806102326001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b815233602082015273ffffffffffffffffffffffffffffffffffffffff871660408201523460608201526080810186905260a0018490526103d4565b600081815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055905073ffffffffffffffffffffffffffffffffffffffff8416336103096001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b7f02a52367d10742d8032712c1bb8e0144ff1ec5ffda1ed7d70bb05a27449550543487878760405161033e949392919061062d565b60405180910390a45050600180547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8082168301167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b476103a681610421565b60405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b80516020808301516040808501516060860151608087015160a0880151935160009761040497909695910161065d565b604051602081830303815290604052805190602001209050919050565b8060405161042e90610450565b6040518091039082f090508015801561044b573d6000803e3d6000fd5b505050565b6008806106b583390190565b6000815180845260005b8181101561048257602081850181015186830182015201610466565b81811115610494576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104da602083018461045c565b9392505050565b6000602082840312156104f357600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060006060848603121561053e57600080fd5b833573ffffffffffffffffffffffffffffffffffffffff8116811461056257600080fd5b925060208401359150604084013567ffffffffffffffff8082111561058657600080fd5b818601915086601f83011261059a57600080fd5b8135818111156105ac576105ac6104fa565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156105f2576105f26104fa565b8160405282815289602084870101111561060b57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b84815283602082015260806040820152600061064c608083018561045c565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a08301526106a860c083018461045c565b9897505050505050505056fe608060405230fffea164736f6c634300080f000a0000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 694000, + "intent": "Deploy L2ToL1MessagePasser Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000003f8d608060405234801561001057600080fd5b5061001961001e565b6100de565b600154610100900460ff161561008a5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60015460ff90811610156100dc576001805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b613ea0806100ed6000396000f3fe60806040523480156200001157600080fd5b5060043610620000cd5760003560e01c8063d2382242116200007f578063dad544e01162000062578063dad544e014620001d6578063e78cea9214620001e0578063ee9a31a2146200020757600080fd5b8063d238224214620001b5578063d97df65214620001bf57600080fd5b80635572acae11620000b45780635572acae14620001525780637d1d0c5b1462000189578063cd6dc687146200019c57600080fd5b80633e47158c14620000d257806354fd4d501462000106575b600080fd5b620000dc6200022c565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b620001436040518060400160405280600581526020017f312e352e3100000000000000000000000000000000000000000000000000000081525081565b604051620000fd9190620009ad565b6200017862000163366004620009ef565b60006020819052908152604090205460ff1681565b6040519015158152602001620000fd565b6002545b604051908152602001620000fd565b620001b3620001ad36600462000a0f565b62000443565b005b6200018d60025481565b620000dc620001d036600462000b20565b62000628565b620000dc62000826565b600154620000dc9062010000900473ffffffffffffffffffffffffffffffffffffffff1681565b60015462010000900473ffffffffffffffffffffffffffffffffffffffff16620000dc565b600080620002587fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156200027c57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000815250516002620002c1919062000b9f565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000091909117906200031d906060015b604051602081830303815290604052805190602001205490565b1462000355576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051306020820152600191810191909152600090620003799060600162000303565b905073ffffffffffffffffffffffffffffffffffffffff81161562000411578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620003e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200040a919062000c04565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600154610100900460ff16158080156200046157506001805460ff16105b806200047c5750303b1580156200047c57506001805460ff16145b6200050e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084015b60405180910390fd5b600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168117905580156200056c57600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b62000576620008a8565b600180547fffffffffffffffffffff0000000000000000000000000000000000000000ffff166201000073ffffffffffffffffffffffffffffffffffffffff861602179055600282905580156200062357600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1681556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b600073ffffffffffffffffffffffffffffffffffffffff8416620006f6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526044602482018190527f4f7074696d69736d4d696e7461626c65455243373231466163746f72793a204c908201527f3120746f6b656e20616464726573732063616e6e6f742062652061646472657360648201527f7328302900000000000000000000000000000000000000000000000000000000608482015260a40162000505565b60008484846040516020016200070f9392919062000c24565b604051602081830303815290604052805190602001209050600081600160029054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600254888888604051620007619062000931565b6200077195949392919062000c73565b8190604051809103906000f590508015801562000792573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff8181166000818152602081815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905590513381529394509189169290917fe72783bb8e0ca31286b85278da59684dd814df9762a52f0837f89edd1483b299910160405180910390a395945050505050565b6000620008326200022c565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200087d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620008a3919062000c04565b905090565b33620008b36200022c565b73ffffffffffffffffffffffffffffffffffffffff1614158015620008f7575033620008de62000826565b73ffffffffffffffffffffffffffffffffffffffff1614155b156200092f576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6131bf8062000cd583390190565b6000815180845260005b81811015620009675760208185018101518683018201520162000949565b818111156200097a576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000620009c260208301846200093f565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114620009ec57600080fd5b50565b60006020828403121562000a0257600080fd5b8135620009c281620009c9565b6000806040838503121562000a2357600080fd5b823562000a3081620009c9565b946020939093013593505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011262000a7f57600080fd5b813567ffffffffffffffff8082111562000a9d5762000a9d62000a3e565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171562000ae65762000ae662000a3e565b8160405283815286602085880101111562000b0057600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121562000b3657600080fd5b833562000b4381620009c9565b9250602084013567ffffffffffffffff8082111562000b6157600080fd5b62000b6f8783880162000a6d565b9350604086013591508082111562000b8657600080fd5b5062000b958682870162000a6d565b9150509250925092565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161562000bff577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b60006020828403121562000c1757600080fd5b8151620009c281620009c9565b73ffffffffffffffffffffffffffffffffffffffff8416815260606020820152600062000c5560608301856200093f565b828103604084015262000c6981856200093f565b9695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff808816835286602084015280861660408401525060a0606083015262000cb460a08301856200093f565b828103608084015262000cc881856200093f565b9897505050505050505056fe60e06040523480156200001157600080fd5b50604051620031bf380380620031bf83398101604081905262000034916200062d565b8181600062000044838262000756565b50600162000053828262000756565b5050506001600160a01b038516620000d85760405162461bcd60e51b815260206004820152603360248201527f4f7074696d69736d4d696e7461626c654552433732313a20627269646765206360448201527f616e6e6f7420626520616464726573732830290000000000000000000000000060648201526084015b60405180910390fd5b83600003620001505760405162461bcd60e51b815260206004820152603660248201527f4f7074696d69736d4d696e7461626c654552433732313a2072656d6f7465206360448201527f6861696e2069642063616e6e6f74206265207a65726f000000000000000000006064820152608401620000cf565b6001600160a01b038316620001ce5760405162461bcd60e51b815260206004820152603960248201527f4f7074696d69736d4d696e7461626c654552433732313a2072656d6f7465207460448201527f6f6b656e2063616e6e6f742062652061646472657373283029000000000000006064820152608401620000cf565b60808490526001600160a01b0383811660a081905290861660c0526200020290601462000256602090811b62000eed17901c565b62000218856200041660201b620011301760201c565b6040516020016200022b92919062000822565b604051602081830303815290604052600a90816200024a919062000756565b50505050505062000993565b6060600062000267836002620008ac565b62000274906002620008ce565b6001600160401b038111156200028e576200028e62000553565b6040519080825280601f01601f191660200182016040528015620002b9576020820181803683370190505b509050600360fc1b81600081518110620002d757620002d7620008e9565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110620003095762000309620008e9565b60200101906001600160f81b031916908160001a90535060006200032f846002620008ac565b6200033c906001620008ce565b90505b6001811115620003be576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110620003745762000374620008e9565b1a60f81b8282815181106200038d576200038d620008e9565b60200101906001600160f81b031916908160001a90535060049490941c93620003b681620008ff565b90506200033f565b5083156200040f5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401620000cf565b9392505050565b6060816000036200043e5750506040805180820190915260018152600360fc1b602082015290565b8160005b81156200046e5780620004558162000919565b9150620004669050600a836200094b565b915062000442565b6000816001600160401b038111156200048b576200048b62000553565b6040519080825280601f01601f191660200182016040528015620004b6576020820181803683370190505b5090505b84156200052e57620004ce60018362000962565b9150620004dd600a866200097c565b620004ea906030620008ce565b60f81b818381518110620005025762000502620008e9565b60200101906001600160f81b031916908160001a90535062000526600a866200094b565b9450620004ba565b949350505050565b80516001600160a01b03811681146200054e57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620005865781810151838201526020016200056c565b8381111562000596576000848401525b50505050565b600082601f830112620005ae57600080fd5b81516001600160401b0380821115620005cb57620005cb62000553565b604051601f8301601f19908116603f01168101908282118183101715620005f657620005f662000553565b816040528381528660208588010111156200061057600080fd5b6200062384602083016020890162000569565b9695505050505050565b600080600080600060a086880312156200064657600080fd5b620006518662000536565b945060208601519350620006686040870162000536565b60608701519093506001600160401b03808211156200068657600080fd5b6200069489838a016200059c565b93506080880151915080821115620006ab57600080fd5b50620006ba888289016200059c565b9150509295509295909350565b600181811c90821680620006dc57607f821691505b602082108103620006fd57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200075157600081815260208120601f850160051c810160208610156200072c5750805b601f850160051c820191505b818110156200074d5782815560010162000738565b5050505b505050565b81516001600160401b0381111562000772576200077262000553565b6200078a81620007838454620006c7565b8462000703565b602080601f831160018114620007c25760008415620007a95750858301515b600019600386901b1c1916600185901b1785556200074d565b600085815260208120601f198616915b82811015620007f357888601518255948401946001909101908401620007d2565b5085821015620008125787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6832ba3432b932bab69d60b91b8152600083516200084881600985016020880162000569565b600160fe1b60099184019182015283516200086b81600a84016020880162000569565b712f746f6b656e5552493f75696e743235363d60701b600a9290910191820152601c01949350505050565b634e487b7160e01b600052601160045260246000fd5b6000816000190483118215151615620008c957620008c962000896565b500290565b60008219821115620008e457620008e462000896565b500190565b634e487b7160e01b600052603260045260246000fd5b60008162000911576200091162000896565b506000190190565b6000600182016200092e576200092e62000896565b5060010190565b634e487b7160e01b600052601260045260246000fd5b6000826200095d576200095d62000935565b500490565b60008282101562000977576200097762000896565b500390565b6000826200098e576200098e62000935565b500690565b60805160a05160c0516127d9620009e6600039600081816103e20152818161047a01528181610b210152610c430152600081816101e001526103bc015260008181610329015261040801526127d96000f3fe608060405234801561001057600080fd5b50600436106101ae5760003560e01c80637d1d0c5b116100ee578063c87b56dd11610097578063e78cea9211610071578063e78cea92146103e0578063e951819614610406578063e985e9c51461042c578063ee9a31a21461047557600080fd5b8063c87b56dd1461039f578063d547cfb7146103b2578063d6c0b2c4146103ba57600080fd5b8063a1448194116100c8578063a144819414610366578063a22cb46514610379578063b88d4fde1461038c57600080fd5b80637d1d0c5b1461032457806395d89b411461034b5780639dc29fac1461035357600080fd5b806323b872dd1161015b5780634f6ccce7116101355780634f6ccce7146102af57806354fd4d50146102c25780636352211e146102fe57806370a082311461031157600080fd5b806323b872dd146102765780632f745c591461028957806342842e0e1461029c57600080fd5b8063081812fc1161018c578063081812fc1461023c578063095ea7b31461024f57806318160ddd1461026457600080fd5b806301ffc9a7146101b3578063033964be146101db57806306fdde0314610227575b600080fd5b6101c66101c1366004612226565b61049c565b60405190151581526020015b60405180910390f35b6102027f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d2565b61022f6104fa565b6040516101d291906122b9565b61020261024a3660046122cc565b61058c565b61026261025d36600461230e565b6105c0565b005b6008545b6040519081526020016101d2565b610262610284366004612338565b610751565b61026861029736600461230e565b6107f2565b6102626102aa366004612338565b6108c1565b6102686102bd3660046122cc565b6108dc565b61022f6040518060400160405280600581526020017f312e332e3200000000000000000000000000000000000000000000000000000081525081565b61020261030c3660046122cc565b61099a565b61026861031f366004612374565b610a2c565b6102687f000000000000000000000000000000000000000000000000000000000000000081565b61022f610afa565b61026261036136600461230e565b610b09565b61026261037436600461230e565b610c2b565b61026261038736600461238f565b610d42565b61026261039a3660046123fa565b610d51565b61022f6103ad3660046122cc565b610df9565b61022f610e5f565b7f0000000000000000000000000000000000000000000000000000000000000000610202565b7f0000000000000000000000000000000000000000000000000000000000000000610202565b7f0000000000000000000000000000000000000000000000000000000000000000610268565b6101c661043a3660046124f4565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260056020908152604080832093909416825291909152205460ff1690565b6102027f000000000000000000000000000000000000000000000000000000000000000081565b60007faecafc23000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000083168114806104f357506104f38361126d565b9392505050565b60606000805461050990612527565b80601f016020809104026020016040519081016040528092919081815260200182805461053590612527565b80156105825780601f1061055757610100808354040283529160200191610582565b820191906000526020600020905b81548152906001019060200180831161056557829003601f168201915b5050505050905090565b6000610597826112c3565b5060009081526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b60006105cb8261099a565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361068d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560448201527f720000000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614806106b657506106b6813361043a565b610742576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206e6f7220617070726f76656420666f7220616c6c00006064820152608401610684565b61074c8383611351565b505050565b61075b33826113f1565b6107e7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560448201527f72206e6f7220617070726f7665640000000000000000000000000000000000006064820152608401610684565b61074c8383836114b0565b60006107fd83610a2c565b821061088b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f455243373231456e756d657261626c653a206f776e657220696e646578206f7560448201527f74206f6620626f756e64730000000000000000000000000000000000000000006064820152608401610684565b5073ffffffffffffffffffffffffffffffffffffffff919091166000908152600660209081526040808320938352929052205490565b61074c83838360405180602001604052806000815250610d51565b60006108e760085490565b8210610975576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602c60248201527f455243373231456e756d657261626c653a20676c6f62616c20696e646578206f60448201527f7574206f6620626f756e647300000000000000000000000000000000000000006064820152608401610684565b600882815481106109885761098861257a565b90600052602060002001549050919050565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff1680610a26576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4552433732313a20696e76616c696420746f6b656e20494400000000000000006044820152606401610684565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff8216610ad1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f74206120766160448201527f6c6964206f776e657200000000000000000000000000000000000000000000006064820152608401610684565b5073ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205490565b60606001805461050990612527565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610bce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4f7074696d69736d4d696e7461626c654552433732313a206f6e6c792062726960448201527f6467652063616e2063616c6c20746869732066756e6374696f6e0000000000006064820152608401610684565b610bd781611722565b8173ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca582604051610c1f91815260200190565b60405180910390a25050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610cf0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4f7074696d69736d4d696e7461626c654552433732313a206f6e6c792062726960448201527f6467652063616e2063616c6c20746869732066756e6374696f6e0000000000006064820152608401610684565b610cfa82826117fb565b8173ffffffffffffffffffffffffffffffffffffffff167f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d412139688582604051610c1f91815260200190565b610d4d338383611815565b5050565b610d5b33836113f1565b610de7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560448201527f72206e6f7220617070726f7665640000000000000000000000000000000000006064820152608401610684565b610df384848484611942565b50505050565b6060610e04826112c3565b6000610e0e6119e5565b90506000815111610e2e57604051806020016040528060008152506104f3565b80610e3884611130565b604051602001610e499291906125a9565b6040516020818303038152906040529392505050565b600a8054610e6c90612527565b80601f0160208091040260200160405190810160405280929190818152602001828054610e9890612527565b8015610ee55780601f10610eba57610100808354040283529160200191610ee5565b820191906000526020600020905b815481529060010190602001808311610ec857829003601f168201915b505050505081565b60606000610efc836002612607565b610f07906002612644565b67ffffffffffffffff811115610f1f57610f1f6123cb565b6040519080825280601f01601f191660200182016040528015610f49576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110610f8057610f8061257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110610fe357610fe361257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600061101f846002612607565b61102a906001612644565b90505b60018111156110c7577f303132333435363738396162636465660000000000000000000000000000000085600f166010811061106b5761106b61257a565b1a60f81b8282815181106110815761108161257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060049490941c936110c08161265c565b905061102d565b5083156104f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610684565b60608160000361117357505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b811561119d578061118781612691565b91506111969050600a836126f8565b9150611177565b60008167ffffffffffffffff8111156111b8576111b86123cb565b6040519080825280601f01601f1916602001820160405280156111e2576020820181803683370190505b5090505b8415611265576111f760018361270c565b9150611204600a86612723565b61120f906030612644565b60f81b8183815181106112245761122461257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535061125e600a866126f8565b94506111e6565b949350505050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f780e9d63000000000000000000000000000000000000000000000000000000001480610a265750610a26826119f4565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1661134e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4552433732313a20696e76616c696420746f6b656e20494400000000000000006044820152606401610684565b50565b600081815260046020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff841690811790915581906113ab8261099a565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000806113fd8361099a565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16148061146b575073ffffffffffffffffffffffffffffffffffffffff80821660009081526005602090815260408083209388168352929052205460ff165b8061126557508373ffffffffffffffffffffffffffffffffffffffff166114918461058c565b73ffffffffffffffffffffffffffffffffffffffff1614949350505050565b8273ffffffffffffffffffffffffffffffffffffffff166114d08261099a565b73ffffffffffffffffffffffffffffffffffffffff1614611573576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060448201527f6f776e65720000000000000000000000000000000000000000000000000000006064820152608401610684565b73ffffffffffffffffffffffffffffffffffffffff8216611615576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f2061646460448201527f72657373000000000000000000000000000000000000000000000000000000006064820152608401610684565b611620838383611ad7565b61162b600082611351565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080546001929061166190849061270c565b909155505073ffffffffffffffffffffffffffffffffffffffff8216600090815260036020526040812080546001929061169c908490612644565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff86811691821790925591518493918716917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b600061172d8261099a565b905061173b81600084611ad7565b611746600083611351565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260036020526040812080546001929061177c90849061270c565b909155505060008281526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555183919073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b610d4d828260405180602001604052806000815250611bdd565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036118aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c6572000000000000006044820152606401610684565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526005602090815260408083209487168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b61194d8484846114b0565b61195984848484611c80565b610df3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b6060600a805461050990612527565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f80ac58cd000000000000000000000000000000000000000000000000000000001480611a8757507fffffffff0000000000000000000000000000000000000000000000000000000082167f5b5e139f00000000000000000000000000000000000000000000000000000000145b80610a2657507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000831614610a26565b73ffffffffffffffffffffffffffffffffffffffff8316611b3f57611b3a81600880546000838152600960205260408120829055600182018355919091527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30155565b611b7c565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614611b7c57611b7c8382611e73565b73ffffffffffffffffffffffffffffffffffffffff8216611ba05761074c81611f2a565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161461074c5761074c8282611fd9565b611be7838361202a565b611bf46000848484611c80565b61074c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b600073ffffffffffffffffffffffffffffffffffffffff84163b15611e68576040517f150b7a0200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063150b7a0290611cf7903390899088908890600401612737565b6020604051808303816000875af1925050508015611d50575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252611d4d91810190612780565b60015b611e1d573d808015611d7e576040519150601f19603f3d011682016040523d82523d6000602084013e611d83565b606091505b508051600003611e15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b805181602001fd5b7fffffffff00000000000000000000000000000000000000000000000000000000167f150b7a0200000000000000000000000000000000000000000000000000000000149050611265565b506001949350505050565b60006001611e8084610a2c565b611e8a919061270c565b600083815260076020526040902054909150808214611eea5773ffffffffffffffffffffffffffffffffffffffff841660009081526006602090815260408083208584528252808320548484528184208190558352600790915290208190555b50600091825260076020908152604080842084905573ffffffffffffffffffffffffffffffffffffffff9094168352600681528383209183525290812055565b600854600090611f3c9060019061270c565b60008381526009602052604081205460088054939450909284908110611f6457611f6461257a565b906000526020600020015490508060088381548110611f8557611f8561257a565b6000918252602080832090910192909255828152600990915260408082208490558582528120556008805480611fbd57611fbd61279d565b6001900381819060005260206000200160009055905550505050565b6000611fe483610a2c565b73ffffffffffffffffffffffffffffffffffffffff9093166000908152600660209081526040808320868452825280832085905593825260079052919091209190915550565b73ffffffffffffffffffffffffffffffffffffffff82166120a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f20616464726573736044820152606401610684565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1615612133576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e746564000000006044820152606401610684565b61213f60008383611ad7565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600360205260408120805460019290612175908490612644565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff861690811790915590518392907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461134e57600080fd5b60006020828403121561223857600080fd5b81356104f3816121f8565b60005b8381101561225e578181015183820152602001612246565b83811115610df35750506000910152565b60008151808452612287816020860160208601612243565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104f3602083018461226f565b6000602082840312156122de57600080fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461230957600080fd5b919050565b6000806040838503121561232157600080fd5b61232a836122e5565b946020939093013593505050565b60008060006060848603121561234d57600080fd5b612356846122e5565b9250612364602085016122e5565b9150604084013590509250925092565b60006020828403121561238657600080fd5b6104f3826122e5565b600080604083850312156123a257600080fd5b6123ab836122e5565b9150602083013580151581146123c057600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000806080858703121561241057600080fd5b612419856122e5565b9350612427602086016122e5565b925060408501359150606085013567ffffffffffffffff8082111561244b57600080fd5b818701915087601f83011261245f57600080fd5b813581811115612471576124716123cb565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156124b7576124b76123cb565b816040528281528a60208487010111156124d057600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b6000806040838503121561250757600080fd5b612510836122e5565b915061251e602084016122e5565b90509250929050565b600181811c9082168061253b57607f821691505b602082108103612574577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600083516125bb818460208801612243565b8351908301906125cf818360208801612243565b01949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561263f5761263f6125d8565b500290565b60008219821115612657576126576125d8565b500190565b60008161266b5761266b6125d8565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036126c2576126c26125d8565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082612707576127076126c9565b500490565b60008282101561271e5761271e6125d8565b500390565b600082612732576127326126c9565b500690565b600073ffffffffffffffffffffffffffffffffffffffff808716835280861660208401525083604083015260806060830152612776608083018461226f565b9695505050505050565b60006020828403121561279257600080fd5b81516104f3816121f8565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c634300080f000aa164736f6c634300080f000a00000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 5661000, + "intent": "Deploy OptimismMintableERC721Factory Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001c9d608060405234801561001057600080fd5b50600061001c3361002b565b6100258161002b565b5061007b565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b611c138061008a6000396000f3fe6080604052600436106101445760003560e01c80637eff275e116100c057806399a88ec411610074578063b794726211610059578063b7947262146103c8578063f2fde38b14610403578063f3b7dead1461042357600080fd5b806399a88ec4146103885780639b2ea4bd146103a857600080fd5b80638d52d4a0116100a55780638d52d4a01461032a5780638da5cb5b1461034a5780639623609d1461037557600080fd5b80637eff275e146102ea578063860f7cda1461030a57600080fd5b80633ab76e9f116101175780636bd9f516116100fc5780636bd9f51614610278578063715018a6146102b55780637c36f37e146102ca57600080fd5b80633ab76e9f1461020257806354fd4d501461022f57600080fd5b80630652b57a1461014957806307c8f7b01461016b578063204e1c7a1461018b578063238181ae146101d5575b600080fd5b34801561015557600080fd5b50610169610164366004611490565b610443565b005b34801561017757600080fd5b506101696101863660046114ad565b610492565b34801561019757600080fd5b506101ab6101a6366004611490565b6104e4565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b3480156101e157600080fd5b506101f56101f0366004611490565b61070a565b6040516101cc9190611545565b34801561020e57600080fd5b506003546101ab9073ffffffffffffffffffffffffffffffffffffffff1681565b34801561023b57600080fd5b506101f56040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b34801561028457600080fd5b506102a8610293366004611490565b60016020526000908152604090205460ff1681565b6040516101cc9190611587565b3480156102c157600080fd5b506101696107a4565b3480156102d657600080fd5b506101696102e5366004611490565b6107b8565b3480156102f657600080fd5b506101696103053660046115c8565b6109b0565b34801561031657600080fd5b50610169610325366004611723565b610b63565b34801561033657600080fd5b50610169610345366004611773565b610b9a565b34801561035657600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166101ab565b6101696103833660046117a5565b610c0e565b34801561039457600080fd5b506101696103a33660046115c8565b610e25565b3480156103b457600080fd5b506101696103c336600461181b565b6110b5565b3480156103d457600080fd5b5060035474010000000000000000000000000000000000000000900460ff1660405190151581526020016101cc565b34801561040f57600080fd5b5061016961041e366004611490565b61114b565b34801561042f57600080fd5b506101ab61043e366004611490565b611202565b61044b611378565b600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b61049a611378565b6003805491151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff909216919091179055565b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604081205460ff168181600281111561052057610520611558565b0361059b578273ffffffffffffffffffffffffffffffffffffffff16635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105949190611862565b9392505050565b60018160028111156105af576105af611558565b036105ff578273ffffffffffffffffffffffffffffffffffffffff1663aaf10f426040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b600281600281111561061357610613611558565b0361069d5760035473ffffffffffffffffffffffffffffffffffffffff8481166000908152600260205260409081902090517fbf40fac1000000000000000000000000000000000000000000000000000000008152919092169163bf40fac19161068091906004016118cc565b602060405180830381865afa158015610570573d6000803e3d6000fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f50726f787941646d696e3a20756e6b6e6f776e2070726f78792074797065000060448201526064015b60405180910390fd5b50919050565b600260205260009081526040902080546107239061187f565b80601f016020809104026020016040519081016040528092919081815260200182805461074f9061187f565b801561079c5780601f106107715761010080835404028352916020019161079c565b820191906000526020600020905b81548152906001019060200180831161077f57829003601f168201915b505050505081565b6107ac611378565b6107b660006113f9565b565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610805576040517fcde661e700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff163b60000361086e576040517fe1e56d9d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016106fb565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fd55ec697000000000000000000000000000000000000000000000000000000001790529051600091829173ffffffffffffffffffffffffffffffffffffffff8516916108ec91611975565b600060405180830381855af49150503d8060008114610927576040519150601f19603f3d011682016040523d82523d6000602084013e61092c565b606091505b50915091508161096a57806040517f1c0a89cc0000000000000000000000000000000000000000000000000000000081526004016106fb9190611545565b60405173ffffffffffffffffffffffffffffffffffffffff8416907f14e22d69ea30aab5b2220164345b33bdb5125e9c77a7d5fe12e23a1c691bd13990600090a2505050565b6109b8611378565b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604081205460ff16908160028111156109f4576109f4611558565b03610a80576040517f8f28397000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152841690638f283970906024015b600060405180830381600087803b158015610a6357600080fd5b505af1158015610a77573d6000803e3d6000fd5b50505050505050565b6001816002811115610a9457610a94611558565b03610aed576040517f13af403500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83811660048301528416906313af403590602401610a49565b6002816002811115610b0157610b01611558565b0361069d576003546040517ff2fde38b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301529091169063f2fde38b90602401610a49565b505050565b610b6b611378565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600260205260409020610b5e82826119d7565b610ba2611378565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160208190526040909120805483927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0090911690836002811115610c0557610c05611558565b02179055505050565b610c16611378565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081205460ff1690816002811115610c5257610c52611558565b03610d18576040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851690634f1ef286903490610cad9087908790600401611af1565b60006040518083038185885af1158015610ccb573d6000803e3d6000fd5b50505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610d129190810190611b28565b50610e1f565b610d228484610e25565b60008473ffffffffffffffffffffffffffffffffffffffff163484604051610d4a9190611975565b60006040518083038185875af1925050503d8060008114610d87576040519150601f19603f3d011682016040523d82523d6000602084013e610d8c565b606091505b5050905080610e1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f50726f787941646d696e3a2063616c6c20746f2070726f78792061667465722060448201527f75706772616465206661696c656400000000000000000000000000000000000060648201526084016106fb565b505b50505050565b610e2d611378565b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604081205460ff1690816002811115610e6957610e69611558565b03610ec2576040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152841690633659cfe690602401610a49565b6001816002811115610ed657610ed6611558565b03610f55576040517f9b0b0fda0000000000000000000000000000000000000000000000000000000081527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc600482015273ffffffffffffffffffffffffffffffffffffffff8381166024830152841690639b0b0fda90604401610a49565b6002816002811115610f6957610f69611558565b036110ad5773ffffffffffffffffffffffffffffffffffffffff831660009081526002602052604081208054610f9e9061187f565b80601f0160208091040260200160405190810160405280929190818152602001828054610fca9061187f565b80156110175780601f10610fec57610100808354040283529160200191611017565b820191906000526020600020905b815481529060010190602001808311610ffa57829003601f168201915b50506003546040517f9b2ea4bd00000000000000000000000000000000000000000000000000000000815294955073ffffffffffffffffffffffffffffffffffffffff1693639b2ea4bd935061107592508591508790600401611b9f565b600060405180830381600087803b15801561108f57600080fd5b505af11580156110a3573d6000803e3d6000fd5b5050505050505050565b610b5e611bd7565b6110bd611378565b6003546040517f9b2ea4bd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911690639b2ea4bd906111159085908590600401611b9f565b600060405180830381600087803b15801561112f57600080fd5b505af1158015611143573d6000803e3d6000fd5b505050505050565b611153611378565b73ffffffffffffffffffffffffffffffffffffffff81166111f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016106fb565b6111ff816113f9565b50565b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604081205460ff168181600281111561123e5761123e611558565b0361128e578273ffffffffffffffffffffffffffffffffffffffff1663f851a4406040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b60018160028111156112a2576112a2611558565b036112f2578273ffffffffffffffffffffffffffffffffffffffff1663893d20e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b600281600281111561130657611306611558565b0361069d57600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b60005473ffffffffffffffffffffffffffffffffffffffff1633146107b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106fb565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff811681146111ff57600080fd5b6000602082840312156114a257600080fd5b81356105948161146e565b6000602082840312156114bf57600080fd5b8135801515811461059457600080fd5b60005b838110156114ea5781810151838201526020016114d2565b83811115610e1f5750506000910152565b600081518084526115138160208601602086016114cf565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061059460208301846114fb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60208101600383106115c2577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b600080604083850312156115db57600080fd5b82356115e68161146e565b915060208301356115f68161146e565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561167757611677611601565b604052919050565b600067ffffffffffffffff82111561169957611699611601565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b60006116d86116d38461167f565b611630565b90508281528383830111156116ec57600080fd5b828260208301376000602084830101529392505050565b600082601f83011261171457600080fd5b610594838335602085016116c5565b6000806040838503121561173657600080fd5b82356117418161146e565b9150602083013567ffffffffffffffff81111561175d57600080fd5b61176985828601611703565b9150509250929050565b6000806040838503121561178657600080fd5b82356117918161146e565b91506020830135600381106115f657600080fd5b6000806000606084860312156117ba57600080fd5b83356117c58161146e565b925060208401356117d58161146e565b9150604084013567ffffffffffffffff8111156117f157600080fd5b8401601f8101861361180257600080fd5b611811868235602084016116c5565b9150509250925092565b6000806040838503121561182e57600080fd5b823567ffffffffffffffff81111561184557600080fd5b61185185828601611703565b92505060208301356115f68161146e565b60006020828403121561187457600080fd5b81516105948161146e565b600181811c9082168061189357607f821691505b602082108103610704577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006020808352600084546118e08161187f565b80848701526040600180841660008114611901576001811461193957611967565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008516838a01528284151560051b8a01019550611967565b896000528660002060005b8581101561195f5781548b8201860152908301908801611944565b8a0184019650505b509398975050505050505050565b600082516119878184602087016114cf565b9190910192915050565b601f821115610b5e57600081815260208120601f850160051c810160208610156119b85750805b601f850160051c820191505b81811015611143578281556001016119c4565b815167ffffffffffffffff8111156119f1576119f1611601565b611a05816119ff845461187f565b84611991565b602080601f831160018114611a585760008415611a225750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555611143565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015611aa557888601518255948401946001909101908401611a86565b5085821015611ae157878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b73ffffffffffffffffffffffffffffffffffffffff83168152604060208201526000611b2060408301846114fb565b949350505050565b600060208284031215611b3a57600080fd5b815167ffffffffffffffff811115611b5157600080fd5b8201601f81018413611b6257600080fd5b8051611b706116d38261167f565b818152856020838501011115611b8557600080fd5b611b968260208301602086016114cf565b95945050505050565b604081526000611bb260408301856114fb565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fdfea164736f6c634300080f000a000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 2541000, + "intent": "Deploy L2ProxyAdmin Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000108a6080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610fb4806100d65f395ff3fe6080604052600436106100e7575f3560e01c806382356d8a11610087578063b49dc74111610057578063b49dc741146102bf578063d0e12f90146102de578063d3e5792b1461030d578063dad544e014610321575f80fd5b806382356d8a146102395780638312f1491461027757806384411d651461028c57806385b5b14d146102a0575f80fd5b80633ccfd60b116100c25780633ccfd60b146101825780633e47158c146101a457806354fd4d50146101b857806366d003ac1461020d575f80fd5b80630d9019e1146100f2578063307f2962146101425780633bbed4a014610163575f80fd5b366100ee57005b5f80fd5b3480156100fd575f80fd5b5060025473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561014d575f80fd5b5061016161015c366004610d62565b610335565b005b34801561016e575f80fd5b5061016161017d366004610d9f565b6103db565b34801561018d575f80fd5b50610196610462565b604051908152602001610139565b3480156101af575f80fd5b50610118610770565b3480156101c3575f80fd5b506102006040518060400160405280600581526020017f312e362e3100000000000000000000000000000000000000000000000000000081525081565b6040516101399190610dba565b348015610218575f80fd5b506002546101189073ffffffffffffffffffffffffffffffffffffffff1681565b348015610244575f80fd5b5060025461026a9074010000000000000000000000000000000000000000900460ff1681565b6040516101399190610e73565b348015610282575f80fd5b5061019660015481565b348015610297575f80fd5b506101965f5481565b3480156102ab575f80fd5b506101616102ba366004610e87565b610976565b3480156102ca575f80fd5b506101616102d9366004610e9e565b6109bc565b3480156102e9575f80fd5b5060025474010000000000000000000000000000000000000000900460ff1661026a565b348015610318575f80fd5b50600154610196565b34801561032c575f80fd5b50610118610bd8565b61033d610c52565b600280547401000000000000000000000000000000000000000080820460ff1692849290917fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff9091169083600181111561039957610399610e0d565b02179055507ff2ec44eb1c3b3acd547b76333eb2c4b27eee311860c57a9fdb04c95f62398fc881836040516103cf929190610ed9565b60405180910390a15050565b6103e3610c52565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f62e69886a5df0ba8ffcacbfc1388754e7abd9bde24b036354c561f1acd4e459391016103cf565b5f600154471015610520576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b479050805f808282546105339190610f21565b90915550506002546040805183815273ffffffffffffffffffffffffffffffffffffffff909216602083018190523383830152905190917fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba919081900360600190a16002546040517f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee916105e89185918591339174010000000000000000000000000000000000000000900460ff1690610f34565b60405180910390a1600160025474010000000000000000000000000000000000000000900460ff16600181111561062157610621610e0d565b036106c5575f6106318284610caa565b9050806106c0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610517565b505090565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216600482015262061a806024820152606060448201525f60648201527342000000000000000000000000000000000000169063c2b3e5ac9084906084015f604051808303818588803b158015610755575f80fd5b505af1158015610767573d5f803e3d5ffd5b50505050505090565b5f8061079a7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107bd57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026108009190610f75565b604080513060208201525f918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061085a906060015b604051602081830303815290604052805190602001205490565b14610891576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091525f906108b290606001610840565b905073ffffffffffffffffffffffffffffffffffffffff811615610944578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610919573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061093d9190610f8c565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61097e610c52565b600180549082905560408051828152602081018490527f895a067c78583e800418fabf3da26a9496aab2ff3429cebdf7fefa642b2e420391016103cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015610a065750825b90505f8267ffffffffffffffff166001148015610a225750303b155b905081158015610a30575080155b15610a67576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315610ac85784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b610ad0610cbd565b6002805473ffffffffffffffffffffffffffffffffffffffff8a167fffffffffffffffffffffffff000000000000000000000000000000000000000082168117835560018a81558993927fffffffffffffffffffffff000000000000000000000000000000000000000000169091179074010000000000000000000000000000000000000000908490811115610b6857610b68610e0d565b02179055508315610bce5784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610be1610770565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c29573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c4d9190610f8c565b905090565b33610c5b610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614610ca8576040517f7f12c64b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f610cb6835a84610d3e565b9392505050565b33610cc6610770565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d07575033610cee610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610ca8576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f805f858888f1949350505050565b803560028110610d5d575f80fd5b919050565b5f60208284031215610d72575f80fd5b610cb682610d4f565b73ffffffffffffffffffffffffffffffffffffffff81168114610d9c575f80fd5b50565b5f60208284031215610daf575f80fd5b8135610cb681610d7b565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60028110610e6f577f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9052565b60208101610e818284610e3a565b92915050565b5f60208284031215610e97575f80fd5b5035919050565b5f805f60608486031215610eb0575f80fd5b8335610ebb81610d7b565b925060208401359150610ed060408501610d4f565b90509250925092565b60408101610ee78285610e3a565b610cb66020830184610e3a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115610e8157610e81610ef4565b84815273ffffffffffffffffffffffffffffffffffffffff84811660208301528316604082015260808101610f6c6060830184610e3a565b95945050505050565b8082028115828204841417610e8157610e81610ef4565b5f60208284031215610f9c575f80fd5b8151610cb681610d7b56fea164736f6c6343000819000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1503000, + "intent": "Deploy BaseFeeVault Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000108a6080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610fb4806100d65f395ff3fe6080604052600436106100e7575f3560e01c806382356d8a11610087578063b49dc74111610057578063b49dc741146102bf578063d0e12f90146102de578063d3e5792b1461030d578063dad544e014610321575f80fd5b806382356d8a146102395780638312f1491461027757806384411d651461028c57806385b5b14d146102a0575f80fd5b80633ccfd60b116100c25780633ccfd60b146101825780633e47158c146101a457806354fd4d50146101b857806366d003ac1461020d575f80fd5b80630d9019e1146100f2578063307f2962146101425780633bbed4a014610163575f80fd5b366100ee57005b5f80fd5b3480156100fd575f80fd5b5060025473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561014d575f80fd5b5061016161015c366004610d62565b610335565b005b34801561016e575f80fd5b5061016161017d366004610d9f565b6103db565b34801561018d575f80fd5b50610196610462565b604051908152602001610139565b3480156101af575f80fd5b50610118610770565b3480156101c3575f80fd5b506102006040518060400160405280600581526020017f312e362e3100000000000000000000000000000000000000000000000000000081525081565b6040516101399190610dba565b348015610218575f80fd5b506002546101189073ffffffffffffffffffffffffffffffffffffffff1681565b348015610244575f80fd5b5060025461026a9074010000000000000000000000000000000000000000900460ff1681565b6040516101399190610e73565b348015610282575f80fd5b5061019660015481565b348015610297575f80fd5b506101965f5481565b3480156102ab575f80fd5b506101616102ba366004610e87565b610976565b3480156102ca575f80fd5b506101616102d9366004610e9e565b6109bc565b3480156102e9575f80fd5b5060025474010000000000000000000000000000000000000000900460ff1661026a565b348015610318575f80fd5b50600154610196565b34801561032c575f80fd5b50610118610bd8565b61033d610c52565b600280547401000000000000000000000000000000000000000080820460ff1692849290917fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff9091169083600181111561039957610399610e0d565b02179055507ff2ec44eb1c3b3acd547b76333eb2c4b27eee311860c57a9fdb04c95f62398fc881836040516103cf929190610ed9565b60405180910390a15050565b6103e3610c52565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f62e69886a5df0ba8ffcacbfc1388754e7abd9bde24b036354c561f1acd4e459391016103cf565b5f600154471015610520576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b479050805f808282546105339190610f21565b90915550506002546040805183815273ffffffffffffffffffffffffffffffffffffffff909216602083018190523383830152905190917fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba919081900360600190a16002546040517f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee916105e89185918591339174010000000000000000000000000000000000000000900460ff1690610f34565b60405180910390a1600160025474010000000000000000000000000000000000000000900460ff16600181111561062157610621610e0d565b036106c5575f6106318284610caa565b9050806106c0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610517565b505090565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216600482015262061a806024820152606060448201525f60648201527342000000000000000000000000000000000000169063c2b3e5ac9084906084015f604051808303818588803b158015610755575f80fd5b505af1158015610767573d5f803e3d5ffd5b50505050505090565b5f8061079a7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107bd57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026108009190610f75565b604080513060208201525f918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061085a906060015b604051602081830303815290604052805190602001205490565b14610891576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091525f906108b290606001610840565b905073ffffffffffffffffffffffffffffffffffffffff811615610944578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610919573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061093d9190610f8c565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61097e610c52565b600180549082905560408051828152602081018490527f895a067c78583e800418fabf3da26a9496aab2ff3429cebdf7fefa642b2e420391016103cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015610a065750825b90505f8267ffffffffffffffff166001148015610a225750303b155b905081158015610a30575080155b15610a67576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315610ac85784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b610ad0610cbd565b6002805473ffffffffffffffffffffffffffffffffffffffff8a167fffffffffffffffffffffffff000000000000000000000000000000000000000082168117835560018a81558993927fffffffffffffffffffffff000000000000000000000000000000000000000000169091179074010000000000000000000000000000000000000000908490811115610b6857610b68610e0d565b02179055508315610bce5784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610be1610770565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c29573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c4d9190610f8c565b905090565b33610c5b610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614610ca8576040517f7f12c64b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f610cb6835a84610d3e565b9392505050565b33610cc6610770565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d07575033610cee610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610ca8576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f805f858888f1949350505050565b803560028110610d5d575f80fd5b919050565b5f60208284031215610d72575f80fd5b610cb682610d4f565b73ffffffffffffffffffffffffffffffffffffffff81168114610d9c575f80fd5b50565b5f60208284031215610daf575f80fd5b8135610cb681610d7b565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60028110610e6f577f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9052565b60208101610e818284610e3a565b92915050565b5f60208284031215610e97575f80fd5b5035919050565b5f805f60608486031215610eb0575f80fd5b8335610ebb81610d7b565b925060208401359150610ed060408501610d4f565b90509250925092565b60408101610ee78285610e3a565b610cb66020830184610e3a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115610e8157610e81610ef4565b84815273ffffffffffffffffffffffffffffffffffffffff84811660208301528316604082015260808101610f6c6060830184610e3a565b95945050505050565b8082028115828204841417610e8157610e81610ef4565b5f60208284031215610f9c575f80fd5b8151610cb681610d7b56fea164736f6c6343000819000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 260550, + "intent": "Deploy L1FeeVault Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000108a6080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610fb4806100d65f395ff3fe6080604052600436106100e7575f3560e01c806382356d8a11610087578063b49dc74111610057578063b49dc741146102bf578063d0e12f90146102de578063d3e5792b1461030d578063dad544e014610321575f80fd5b806382356d8a146102395780638312f1491461027757806384411d651461028c57806385b5b14d146102a0575f80fd5b80633ccfd60b116100c25780633ccfd60b146101825780633e47158c146101a457806354fd4d50146101b857806366d003ac1461020d575f80fd5b80630d9019e1146100f2578063307f2962146101425780633bbed4a014610163575f80fd5b366100ee57005b5f80fd5b3480156100fd575f80fd5b5060025473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561014d575f80fd5b5061016161015c366004610d62565b610335565b005b34801561016e575f80fd5b5061016161017d366004610d9f565b6103db565b34801561018d575f80fd5b50610196610462565b604051908152602001610139565b3480156101af575f80fd5b50610118610770565b3480156101c3575f80fd5b506102006040518060400160405280600581526020017f312e312e3100000000000000000000000000000000000000000000000000000081525081565b6040516101399190610dba565b348015610218575f80fd5b506002546101189073ffffffffffffffffffffffffffffffffffffffff1681565b348015610244575f80fd5b5060025461026a9074010000000000000000000000000000000000000000900460ff1681565b6040516101399190610e73565b348015610282575f80fd5b5061019660015481565b348015610297575f80fd5b506101965f5481565b3480156102ab575f80fd5b506101616102ba366004610e87565b610976565b3480156102ca575f80fd5b506101616102d9366004610e9e565b6109bc565b3480156102e9575f80fd5b5060025474010000000000000000000000000000000000000000900460ff1661026a565b348015610318575f80fd5b50600154610196565b34801561032c575f80fd5b50610118610bd8565b61033d610c52565b600280547401000000000000000000000000000000000000000080820460ff1692849290917fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff9091169083600181111561039957610399610e0d565b02179055507ff2ec44eb1c3b3acd547b76333eb2c4b27eee311860c57a9fdb04c95f62398fc881836040516103cf929190610ed9565b60405180910390a15050565b6103e3610c52565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f62e69886a5df0ba8ffcacbfc1388754e7abd9bde24b036354c561f1acd4e459391016103cf565b5f600154471015610520576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b479050805f808282546105339190610f21565b90915550506002546040805183815273ffffffffffffffffffffffffffffffffffffffff909216602083018190523383830152905190917fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba919081900360600190a16002546040517f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee916105e89185918591339174010000000000000000000000000000000000000000900460ff1690610f34565b60405180910390a1600160025474010000000000000000000000000000000000000000900460ff16600181111561062157610621610e0d565b036106c5575f6106318284610caa565b9050806106c0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610517565b505090565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216600482015262061a806024820152606060448201525f60648201527342000000000000000000000000000000000000169063c2b3e5ac9084906084015f604051808303818588803b158015610755575f80fd5b505af1158015610767573d5f803e3d5ffd5b50505050505090565b5f8061079a7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107bd57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026108009190610f75565b604080513060208201525f918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061085a906060015b604051602081830303815290604052805190602001205490565b14610891576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091525f906108b290606001610840565b905073ffffffffffffffffffffffffffffffffffffffff811615610944578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610919573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061093d9190610f8c565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61097e610c52565b600180549082905560408051828152602081018490527f895a067c78583e800418fabf3da26a9496aab2ff3429cebdf7fefa642b2e420391016103cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015610a065750825b90505f8267ffffffffffffffff166001148015610a225750303b155b905081158015610a30575080155b15610a67576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315610ac85784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b610ad0610cbd565b6002805473ffffffffffffffffffffffffffffffffffffffff8a167fffffffffffffffffffffffff000000000000000000000000000000000000000082168117835560018a81558993927fffffffffffffffffffffff000000000000000000000000000000000000000000169091179074010000000000000000000000000000000000000000908490811115610b6857610b68610e0d565b02179055508315610bce5784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610be1610770565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c29573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c4d9190610f8c565b905090565b33610c5b610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614610ca8576040517f7f12c64b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f610cb6835a84610d3e565b9392505050565b33610cc6610770565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d07575033610cee610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610ca8576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f805f858888f1949350505050565b803560028110610d5d575f80fd5b919050565b5f60208284031215610d72575f80fd5b610cb682610d4f565b73ffffffffffffffffffffffffffffffffffffffff81168114610d9c575f80fd5b50565b5f60208284031215610daf575f80fd5b8135610cb681610d7b565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60028110610e6f577f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9052565b60208101610e818284610e3a565b92915050565b5f60208284031215610e97575f80fd5b5035919050565b5f805f60608486031215610eb0575f80fd5b8335610ebb81610d7b565b925060208401359150610ed060408501610d4f565b90509250925092565b60408101610ee78285610e3a565b610cb66020830184610e3a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115610e8157610e81610ef4565b84815273ffffffffffffffffffffffffffffffffffffffff84811660208301528316604082015260808101610f6c6060830184610e3a565b95945050505050565b8082028115828204841417610e8157610e81610ef4565b5f60208284031215610f9c575f80fd5b8151610cb681610d7b56fea164736f6c6343000819000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1504000, + "intent": "Deploy OperatorFeeVault Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000081e608060405234801561001057600080fd5b506107fe806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d501461004657806360d7a27814610098578063a2ea7c6e146100b9575b600080fd5b6100826040518060400160405280600c81526020017f312e332e312d626574612e32000000000000000000000000000000000000000081525081565b60405161008f9190610473565b60405180910390f35b6100ab6100a636600461048d565b6100d9565b60405190815260200161008f565b6100cc6100c736600461053f565b61029d565b60405161008f9190610558565b60008060405180608001604052806000801b81526020018573ffffffffffffffffffffffffffffffffffffffff168152602001841515815260200187878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052509390945250929350915061015b9050826103c5565b600081815260208190526040902054909150156101a4576040517f23369fa600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80825260008181526020818152604091829020845181559084015160018201805493860151151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00000000000000000000000000000000000000000090941673ffffffffffffffffffffffffffffffffffffffff9092169190911792909217909155606083015183919060028201906102409082610682565b509050503373ffffffffffffffffffffffffffffffffffffffff16817fd0b86852e21f9e5fa4bc3b0cff9757ffe243d50c4b43968a42202153d651ea5e8460405161028b9190610558565b60405180910390a39695505050505050565b604080516080810182526000808252602082018190529181019190915260608082015260008281526020818152604091829020825160808101845281548152600182015473ffffffffffffffffffffffffffffffffffffffff8116938201939093527401000000000000000000000000000000000000000090920460ff1615159282019290925260028201805491929160608401919061033c906105e0565b80601f0160208091040260200160405190810160405280929190818152602001828054610368906105e0565b80156103b55780601f1061038a576101008083540402835291602001916103b5565b820191906000526020600020905b81548152906001019060200180831161039857829003601f168201915b5050505050815250509050919050565b60008160600151826020015183604001516040516020016103e89392919061079c565b604051602081830303815290604052805190602001209050919050565b60005b83811015610420578181015183820152602001610408565b50506000910152565b60008151808452610441816020860160208601610405565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104866020830184610429565b9392505050565b600080600080606085870312156104a357600080fd5b843567ffffffffffffffff808211156104bb57600080fd5b818701915087601f8301126104cf57600080fd5b8135818111156104de57600080fd5b8860208285010111156104f057600080fd5b6020928301965094505085013573ffffffffffffffffffffffffffffffffffffffff8116811461051f57600080fd5b91506040850135801515811461053457600080fd5b939692955090935050565b60006020828403121561055157600080fd5b5035919050565b602081528151602082015273ffffffffffffffffffffffffffffffffffffffff6020830151166040820152604082015115156060820152600060608301516080808401526105a960a0840182610429565b949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600181811c908216806105f457607f821691505b60208210810361062d577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f82111561067d57600081815260208120601f850160051c8101602086101561065a5750805b601f850160051c820191505b8181101561067957828155600101610666565b5050505b505050565b815167ffffffffffffffff81111561069c5761069c6105b1565b6106b0816106aa84546105e0565b84610633565b602080601f83116001811461070357600084156106cd5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610679565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561075057888601518255948401946001909101908401610731565b508582101561078c57878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b600084516107ae818460208901610405565b60609490941b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000169190930190815290151560f81b60148201526015019291505056fea164736f6c6343000813000a0000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 805000, + "intent": "Deploy SchemaRegistry Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000046c961016060405234801561001157600080fd5b50604080518082018252600381526245415360e81b60208083019182528351808501855260058152640312e332e360dc1b908201529151812060e08190527f6a08c3e203132c561752255a4d52ffae85bb9c5d33cb3291520dea1b843563896101008190524660a081815286517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f818801819052818901959095526060810193909352608080840192909252308382018190528751808503909201825260c093840190975280519501949094209093529290915261012091909152516101405260805160a05160c05160e05161010051610120516101405161457e61014b600039600061073701526000612784015260006127d3015260006127ae01526000612707015260006127310152600061275b015261457e6000f3fe60806040526004361061018b5760003560e01c806395411525116100d6578063d45c44351161007f578063ed24911d11610059578063ed24911d146104fd578063f10b5cc814610512578063f17325e71461054157600080fd5b8063d45c443514610467578063e30bb5631461049e578063e71ff365146104dd57600080fd5b8063b469318d116100b0578063b469318d146103ba578063b83010d314610414578063cf190f341461044757600080fd5b80639541152514610367578063a3112a641461037a578063a6d4dbc7146103a757600080fd5b806344adc90e116101385780634d003070116101125780634d003070146102de57806354fd4d50146102fe57806379f7573a1461034757600080fd5b806344adc90e1461029857806346926267146102b85780634cb7e9e5146102cb57600080fd5b806317d7de7c1161016957806317d7de7c146102205780632d0335ab146102425780633c0427151461028557600080fd5b80630eabf6601461019057806312b11a17146101a557806313893f61146101e7575b600080fd5b6101a361019e3660046134c8565b610554565b005b3480156101b157600080fd5b507ffeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d9880765b6040519081526020015b60405180910390f35b3480156101f357600080fd5b506102076102023660046134c8565b6106eb565b60405167ffffffffffffffff90911681526020016101de565b34801561022c57600080fd5b50610235610730565b6040516101de9190613578565b34801561024e57600080fd5b506101d461025d3660046135bd565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6101d46102933660046135da565b610760565b6102ab6102a63660046134c8565b610863565b6040516101de9190613615565b6101a36102c6366004613659565b6109e4565b6101a36102d93660046134c8565b610a68565b3480156102ea57600080fd5b506102076102f9366004613671565b610b4b565b34801561030a57600080fd5b506102356040518060400160405280600c81526020017f312e342e312d626574612e33000000000000000000000000000000000000000081525081565b34801561035357600080fd5b506101a3610362366004613671565b610b58565b6102ab6103753660046134c8565b610bef565b34801561038657600080fd5b5061039a610395366004613671565b610e62565b6040516101de9190613771565b6101a36103b5366004613784565b611025565b3480156103c657600080fd5b506102076103d5366004613797565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152603460209081526040808320938352929052205467ffffffffffffffff1690565b34801561042057600080fd5b507fb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e756101d4565b34801561045357600080fd5b50610207610462366004613671565b6110ca565b34801561047357600080fd5b50610207610482366004613671565b60009081526033602052604090205467ffffffffffffffff1690565b3480156104aa57600080fd5b506104cd6104b9366004613671565b600090815260326020526040902054151590565b60405190151581526020016101de565b3480156104e957600080fd5b506102076104f83660046134c8565b6110d8565b34801561050957600080fd5b506101d4611110565b34801561051e57600080fd5b5060405173420000000000000000000000000000000000002081526020016101de565b6101d461054f3660046137c3565b61111a565b348160005b818110156106e4577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82018114600086868481811061059a5761059a6137fe565b90506020028101906105ac919061382d565b6105b590613ac3565b60208101518051919250908015806105d257508260400151518114155b15610609576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b818110156106ad576106a56040518060a001604052808660000151815260200185848151811061063e5761063e6137fe565b6020026020010151815260200186604001518481518110610661576106616137fe565b60200260200101518152602001866060015173ffffffffffffffffffffffffffffffffffffffff168152602001866080015167ffffffffffffffff168152506111d8565b60010161060c565b506106c383600001518385606001518a886113e9565b6106cd9088613bed565b9650505050506106dd8160010190565b9050610559565b5050505050565b60004282825b818110156107245761071c3387878481811061070f5761070f6137fe565b9050602002013585611a18565b6001016106f1565b50909150505b92915050565b606061075b7f0000000000000000000000000000000000000000000000000000000000000000611b17565b905090565b600061077361076e83613d22565b611ca5565b604080516001808252818301909252600091816020015b6040805160c081018252600080825260208083018290529282018190526060808301829052608083015260a082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161078a5790505090506107f86020840184613d9d565b61080190613dd1565b81600081518110610814576108146137fe565b602090810291909101015261083d83358261083560c0870160a088016135bd565b346001611e2f565b60200151600081518110610853576108536137fe565b6020026020010151915050919050565b60608160008167ffffffffffffffff8111156108815761088161386b565b6040519080825280602002602001820160405280156108b457816020015b606081526020019060019003908161089f5790505b509050600034815b848110156109ce577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85018114368989848181106108fc576108fc6137fe565b905060200281019061090e9190613ddd565b905061091d6020820182613e11565b9050600003610958576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061097d823561096c6020850185613e11565b61097591613e79565b338887611e2f565b805190915061098c9086613bed565b945080602001518785815181106109a5576109a56137fe565b6020026020010181905250806020015151860195505050506109c78160010190565b90506108bc565b506109d98383612541565b979650505050505050565b604080516001808252818301909252600091816020015b60408051808201909152600080825260208201528152602001906001900390816109fb579050509050610a3636839003830160208401613eed565b81600081518110610a4957610a496137fe565b6020908102919091010152610a63823582333460016113e9565b505050565b348160005b818110156106e4577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201811436868684818110610aad57610aad6137fe565b9050602002810190610abf9190613ddd565b9050610b2c8135610ad36020840184613f09565b808060200260200160405190810160405280939291908181526020016000905b82821015610b1f57610b1060408302860136819003810190613eed565b81526020019060010190610af3565b50505050503388866113e9565b610b369086613bed565b94505050610b448160010190565b9050610a6d565b60004261072a838261262b565b33600090815260208190526040902054808211610ba1576040517f756688fe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152602081815260409182902084905581518381529081018490527f57b09af877df9068fd60a69d7b21f5576b8b38955812d6ae4ac52942f1e38fb7910160405180910390a15050565b60608160008167ffffffffffffffff811115610c0d57610c0d61386b565b604051908082528060200260200182016040528015610c4057816020015b6060815260200190600190039081610c2b5790505b509050600034815b848110156109ce577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8501811436898984818110610c8857610c886137fe565b9050602002810190610c9a919061382d565b9050366000610cac6020840184613e11565b909250905080801580610ccd5750610cc76040850185613f71565b90508114155b15610d04576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b81811015610de557610ddd6040518060a0016040528087600001358152602001868685818110610d3957610d396137fe565b9050602002810190610d4b9190613d9d565b610d5490613dd1565b8152602001610d666040890189613f71565b85818110610d7657610d766137fe565b905060600201803603810190610d8c9190613fd8565b8152602001610da16080890160608a016135bd565b73ffffffffffffffffffffffffffffffffffffffff168152602001610dcc60a0890160808a01613ff4565b67ffffffffffffffff169052611ca5565b600101610d07565b506000610e0e8535610df78587613e79565b610e076080890160608a016135bd565b8b8a611e2f565b8051909150610e1d9089613bed565b975080602001518a8881518110610e3657610e366137fe565b602002602001018190525080602001515189019850505050505050610e5b8160010190565b9050610c48565b604080516101408101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082019290925261012081019190915260008281526032602090815260409182902082516101408101845281548152600182015492810192909252600281015467ffffffffffffffff808216948401949094526801000000000000000081048416606084015270010000000000000000000000000000000090049092166080820152600382015460a0820152600482015473ffffffffffffffffffffffffffffffffffffffff90811660c0830152600583015490811660e083015274010000000000000000000000000000000000000000900460ff16151561010082015260068201805491929161012084019190610f9c9061400f565b80601f0160208091040260200160405190810160405280929190818152602001828054610fc89061400f565b80156110155780601f10610fea57610100808354040283529160200191611015565b820191906000526020600020905b815481529060010190602001808311610ff857829003601f168201915b5050505050815250509050919050565b61103c6110373683900383018361405c565b6111d8565b604080516001808252818301909252600091816020015b604080518082019091526000808252602082015281526020019060019003908161105357905050905061108e36839003830160208401613eed565b816000815181106110a1576110a16137fe565b6020908102919091010152610a638235826110c260e0860160c087016135bd565b3460016113e9565b60004261072a338483611a18565b60004282825b81811015610724576111088686838181106110fb576110fb6137fe565b905060200201358461262b565b6001016110de565b600061075b6126ed565b604080516001808252818301909252600091829190816020015b6040805160c081018252600080825260208083018290529282018190526060808301829052608083015260a082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816111345790505090506111a26020840184613d9d565b6111ab90613dd1565b816000815181106111be576111be6137fe565b602090810291909101015261083d83358233346001611e2f565b608081015167ffffffffffffffff161580159061120c57504267ffffffffffffffff16816080015167ffffffffffffffff16105b15611243576040517f1ab7da6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808201516040808401516060850151855184518587015173ffffffffffffffffffffffffffffffffffffffff84166000908152978890529487208054969794969495611337957fb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e7595949392886112ba836140ca565b909155506080808c015160408051602081019990995273ffffffffffffffffffffffffffffffffffffffff9097169688019690965260608701949094529285019190915260a084015260c083015267ffffffffffffffff1660e0820152610100015b60405160208183030381529060405280519060200120612821565b90506113ad84606001518284602001518560400151866000015160405160200161139993929190928352602083019190915260f81b7fff0000000000000000000000000000000000000000000000000000000000000016604082015260410190565b604051602081830303815290604052612834565b6113e3576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050565b6040517fa2ea7c6e0000000000000000000000000000000000000000000000000000000081526004810186905260009081907342000000000000000000000000000000000000209063a2ea7c6e90602401600060405180830381865afa158015611457573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261149d9190810190614102565b80519091506114d8576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b855160008167ffffffffffffffff8111156114f5576114f561386b565b60405190808252806020026020018201604052801561159457816020015b60408051610140810182526000808252602080830182905292820181905260608083018290526080830182905260a0830182905260c0830182905260e0830182905261010083019190915261012082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816115135790505b50905060008267ffffffffffffffff8111156115b2576115b261386b565b6040519080825280602002602001820160405280156115db578160200160208202803683370190505b50905060005b838110156119fa5760008a82815181106115fd576115fd6137fe565b6020908102919091018101518051600090815260329092526040909120805491925090611656576040517fc5723b5100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8c816001015414611693576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600581015473ffffffffffffffffffffffffffffffffffffffff8c81169116146116e9576040517f4ca8886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600581015474010000000000000000000000000000000000000000900460ff1661173f576040517f157bd4c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002810154700100000000000000000000000000000000900467ffffffffffffffff1615611799576040517f905e710700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b426002820180547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff811670010000000000000000000000000000000067ffffffffffffffff948516810291821793849055604080516101408101825287548152600188015460208201529386169286169290921791830191909152680100000000000000008304841660608301529091049091166080820152600382015460a0820152600482015473ffffffffffffffffffffffffffffffffffffffff90811660c0830152600583015490811660e083015274010000000000000000000000000000000000000000900460ff16151561010082015260068201805483916101208401916118a59061400f565b80601f01602080910402602001604051908101604052809291908181526020018280546118d19061400f565b801561191e5780601f106118f35761010080835404028352916020019161191e565b820191906000526020600020905b81548152906001019060200180831161190157829003601f168201915b505050505081525050858481518110611939576119396137fe565b6020026020010181905250816020015184848151811061195b5761195b6137fe565b6020026020010181815250508c8b73ffffffffffffffffffffffffffffffffffffffff16868581518110611991576119916137fe565b602002602001015160c0015173ffffffffffffffffffffffffffffffffffffffff167ff930a6e2523c9cc298691873087a740550b8fc85a0680830414c148ed927f61585600001516040516119e891815260200190565b60405180910390a450506001016115e1565b50611a0a84838360018b8b612a03565b9a9950505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff83166000908152603460209081526040808320858452918290529091205467ffffffffffffffff1615611a8c576040517fec9d6eeb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526020829052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff861690811790915590519091859173ffffffffffffffffffffffffffffffffffffffff8816917f92a1f7a41a7c585a8b09e25b195e225b1d43248daca46b0faf9e0792777a222991a450505050565b604080516020808252818301909252606091600091906020820181803683370190505090506000805b6020811015611be2576000858260208110611b5d57611b5d6137fe565b1a60f81b90507fff000000000000000000000000000000000000000000000000000000000000008116600003611b935750611be2565b80848481518110611ba657611ba66137fe565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053505060019182019101611b40565b5060008167ffffffffffffffff811115611bfe57611bfe61386b565b6040519080825280601f01601f191660200182016040528015611c28576020820181803683370190505b50905060005b82811015611c9c57838181518110611c4857611c486137fe565b602001015160f81c60f81b828281518110611c6557611c656137fe565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600101611c2e565b50949350505050565b608081015167ffffffffffffffff1615801590611cd957504267ffffffffffffffff16816080015167ffffffffffffffff16105b15611d10576040517f1ab7da6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808201516040808401516060808601518651855186880151868801519488015160808901518051908b012060a08a015173ffffffffffffffffffffffffffffffffffffffff871660009081529b8c9052988b2080549a9b989a9899611337997ffeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d988076999493928c611da0836140ca565b919050558e6080015160405160200161131c9b9a999897969594939291909a8b5273ffffffffffffffffffffffffffffffffffffffff998a1660208c015260408b019890985295909716606089015267ffffffffffffffff938416608089015291151560a088015260c087015260e0860152610100850193909352610120840152166101408201526101600190565b60408051808201909152600081526060602082015284516040805180820190915260008152606060208201528167ffffffffffffffff811115611e7457611e7461386b565b604051908082528060200260200182016040528015611e9d578160200160208202803683370190505b5060208201526040517fa2ea7c6e000000000000000000000000000000000000000000000000000000008152600481018990526000907342000000000000000000000000000000000000209063a2ea7c6e90602401600060405180830381865afa158015611f0f573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052611f559190810190614102565b8051909150611f90576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008367ffffffffffffffff811115611fab57611fab61386b565b60405190808252806020026020018201604052801561204a57816020015b60408051610140810182526000808252602080830182905292820181905260608083018290526080830182905260a0830182905260c0830182905260e0830182905261010083019190915261012082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff909201910181611fc95790505b50905060008467ffffffffffffffff8111156120685761206861386b565b604051908082528060200260200182016040528015612091578160200160208202803683370190505b50905060005b858110156125205760008b82815181106120b3576120b36137fe565b60200260200101519050600067ffffffffffffffff16816020015167ffffffffffffffff16141580156120fe57504267ffffffffffffffff16816020015167ffffffffffffffff1611155b15612135576040517f08e8b93700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8460400151158015612148575080604001515b1561217f576040517f157bd4c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006040518061014001604052806000801b81526020018f81526020016121a34290565b67ffffffffffffffff168152602001836020015167ffffffffffffffff168152602001600067ffffffffffffffff16815260200183606001518152602001836000015173ffffffffffffffffffffffffffffffffffffffff1681526020018d73ffffffffffffffffffffffffffffffffffffffff16815260200183604001511515815260200183608001518152509050600080600090505b6122458382612df4565b600081815260326020526040902054909250156122645760010161223b565b81835260008281526032602090815260409182902085518155908501516001820155908401516002820180546060870151608088015167ffffffffffffffff908116700100000000000000000000000000000000027fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff92821668010000000000000000027fffffffffffffffffffffffffffffffff000000000000000000000000000000009094169190951617919091171691909117905560a0840151600382015560c084015160048201805473ffffffffffffffffffffffffffffffffffffffff9283167fffffffffffffffffffffffff000000000000000000000000000000000000000090911617905560e0850151600583018054610100880151151574010000000000000000000000000000000000000000027fffffffffffffffffffffff000000000000000000000000000000000000000000909116929093169190911791909117905561012084015184919060068201906123e49082614228565b50505060608401511561243b57606084015160009081526032602052604090205461243b576040517fc5723b5100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8287868151811061244e5761244e6137fe565b60200260200101819052508360a00151868681518110612470576124706137fe565b6020026020010181815250508189602001518681518110612493576124936137fe565b6020026020010181815250508f8e73ffffffffffffffffffffffffffffffffffffffff16856000015173ffffffffffffffffffffffffffffffffffffffff167f8bf46bf4cfd674fa735a3d63ec1c9ad4153f033c290341f3a588b75685141b358560405161250391815260200190565b60405180910390a4505050506125198160010190565b9050612097565b5061253083838360008c8c612a03565b845250919998505050505050505050565b606060008267ffffffffffffffff81111561255e5761255e61386b565b604051908082528060200260200182016040528015612587578160200160208202803683370190505b508451909150600090815b818110156126205760008782815181106125ae576125ae6137fe565b6020026020010151905060008151905060005b8181101561260c578281815181106125db576125db6137fe565b60200260200101518787815181106125f5576125f56137fe565b6020908102919091010152600195860195016125c1565b5050506126198160010190565b9050612592565b509195945050505050565b60008281526033602052604090205467ffffffffffffffff161561267b576040517f2e26794600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526033602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff85169081179091559051909184917f5aafceeb1c7ad58e4a84898bdee37c02c0fc46e7d24e6b60e8209449f183459f9190a35050565b60003073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614801561275357507f000000000000000000000000000000000000000000000000000000000000000046145b1561277d57507f000000000000000000000000000000000000000000000000000000000000000090565b50604080517f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401527f000000000000000000000000000000000000000000000000000000000000000060608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b600061072a61282e6126ed565b83612e53565b60008060006128438585612e95565b9092509050600081600481111561285c5761285c614342565b14801561289457508573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16145b156128a4576001925050506129fc565b6000808773ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b88886040516024016128d9929190614371565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516129629190614392565b600060405180830381855afa9150503d806000811461299d576040519150601f19603f3d011682016040523d82523d6000602084013e6129a2565b606091505b50915091508180156129b5575080516020145b80156129f5575080517f1626ba7e00000000000000000000000000000000000000000000000000000000906129f390830160209081019084016143a4565b145b9450505050505b9392505050565b84516000906001819003612a5b57612a538888600081518110612a2857612a286137fe565b602002602001015188600081518110612a4357612a436137fe565b6020026020010151888888612eda565b915050612dea565b602088015173ffffffffffffffffffffffffffffffffffffffff8116612afc5760005b82811015612ae157878181518110612a9857612a986137fe565b6020026020010151600014612ad9576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612a7e565b508315612af157612af1856131f9565b600092505050612dea565b6000808273ffffffffffffffffffffffffffffffffffffffff1663ce46e0466040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6e91906143bd565b905060005b84811015612c2b5760008a8281518110612b8f57612b8f6137fe565b6020026020010151905080600003612ba75750612c23565b82612bde576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b88811115612c18576040517f1101129400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b978890039792909201915b600101612b73565b508715612d06576040517f88e5b2d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416906388e5b2d9908490612c88908e908e906004016143da565b60206040518083038185885af1158015612ca6573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612ccb91906143bd565b612d01576040517fbf2f3a8b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612dd5565b6040517f91db0b7e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416906391db0b7e908490612d5c908e908e906004016143da565b60206040518083038185885af1158015612d7a573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612d9f91906143bd565b612dd5576040517fe8bee83900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8515612de457612de4876131f9565b50925050505b9695505050505050565b60208083015160c084015160e0850151604080870151606088015161010089015160a08a01516101208b01519451600099612e3599989796918c9101614493565b60405160208183030381529060405280519060200120905092915050565b6040517f190100000000000000000000000000000000000000000000000000000000000060208201526022810183905260428101829052600090606201612e35565b6000808251604103612ecb5760208301516040840151606085015160001a612ebf8782858561320c565b94509450505050612ed3565b506000905060025b9250929050565b602086015160009073ffffffffffffffffffffffffffffffffffffffff8116612f4e578515612f35576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8215612f4457612f44846131f9565b6000915050612dea565b8515613039578073ffffffffffffffffffffffffffffffffffffffff1663ce46e0466040518163ffffffff1660e01b8152600401602060405180830381865afa158015612f9f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fc391906143bd565b612ff9576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83861115613033576040517f1101129400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85840393505b8415613111576040517fe49617e100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063e49617e1908890613093908b90600401613771565b60206040518083038185885af11580156130b1573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906130d691906143bd565b61310c576040517fccf3bb2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6131de565b6040517fe60c350500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063e60c3505908890613165908b90600401613771565b60206040518083038185885af1158015613183573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906131a891906143bd565b6131de576040517fbd8ba84d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82156131ed576131ed846131f9565b50939695505050505050565b8015613209576132093382613324565b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115613243575060009050600361331b565b8460ff16601b1415801561325b57508460ff16601c14155b1561326c575060009050600461331b565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa1580156132c0573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81166133145760006001925092505061331b565b9150600090505b94509492505050565b80471015613393576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064015b60405180910390fd5b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d80600081146133ed576040519150601f19603f3d011682016040523d82523d6000602084013e6133f2565b606091505b5050905080610a63576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d61792068617665207265766572746564000000000000606482015260840161338a565b60008083601f84011261349557600080fd5b50813567ffffffffffffffff8111156134ad57600080fd5b6020830191508360208260051b8501011115612ed357600080fd5b600080602083850312156134db57600080fd5b823567ffffffffffffffff8111156134f257600080fd5b6134fe85828601613483565b90969095509350505050565b60005b8381101561352557818101518382015260200161350d565b50506000910152565b6000815180845261354681602086016020860161350a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006129fc602083018461352e565b73ffffffffffffffffffffffffffffffffffffffff8116811461320957600080fd5b80356135b88161358b565b919050565b6000602082840312156135cf57600080fd5b81356129fc8161358b565b6000602082840312156135ec57600080fd5b813567ffffffffffffffff81111561360357600080fd5b820160e081850312156129fc57600080fd5b6020808252825182820181905260009190848201906040850190845b8181101561364d57835183529284019291840191600101613631565b50909695505050505050565b60006060828403121561366b57600080fd5b50919050565b60006020828403121561368357600080fd5b5035919050565b6000610140825184526020830151602085015260408301516136b8604086018267ffffffffffffffff169052565b5060608301516136d4606086018267ffffffffffffffff169052565b5060808301516136f0608086018267ffffffffffffffff169052565b5060a083015160a085015260c083015161372260c086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060e083015161374a60e086018273ffffffffffffffffffffffffffffffffffffffff169052565b506101008381015115159085015261012080840151818601839052612dea8387018261352e565b6020815260006129fc602083018461368a565b6000610100828403121561366b57600080fd5b600080604083850312156137aa57600080fd5b82356137b58161358b565b946020939093013593505050565b6000602082840312156137d557600080fd5b813567ffffffffffffffff8111156137ec57600080fd5b8201604081850312156129fc57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6183360301811261386157600080fd5b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405160a0810167ffffffffffffffff811182821017156138bd576138bd61386b565b60405290565b60405160c0810167ffffffffffffffff811182821017156138bd576138bd61386b565b6040516080810167ffffffffffffffff811182821017156138bd576138bd61386b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156139505761395061386b565b604052919050565b600067ffffffffffffffff8211156139725761397261386b565b5060051b60200190565b60006040828403121561398e57600080fd5b6040516040810181811067ffffffffffffffff821117156139b1576139b161386b565b604052823581526020928301359281019290925250919050565b6000606082840312156139dd57600080fd5b6040516060810181811067ffffffffffffffff82111715613a0057613a0061386b565b604052905080823560ff81168114613a1757600080fd5b8082525060208301356020820152604083013560408201525092915050565b600082601f830112613a4757600080fd5b81356020613a5c613a5783613958565b613909565b82815260609283028501820192828201919087851115613a7b57600080fd5b8387015b85811015613a9e57613a9189826139cb565b8452928401928101613a7f565b5090979650505050505050565b803567ffffffffffffffff811681146135b857600080fd5b600060a08236031215613ad557600080fd5b613add61389a565b8235815260208084013567ffffffffffffffff80821115613afd57600080fd5b9085019036601f830112613b1057600080fd5b8135613b1e613a5782613958565b81815260069190911b83018401908481019036831115613b3d57600080fd5b938501935b82851015613b6657613b54368661397c565b82528582019150604085019450613b42565b80868801525050506040860135925080831115613b8257600080fd5b5050613b9036828601613a36565b604083015250613ba2606084016135ad565b6060820152613bb360808401613aab565b608082015292915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561072a5761072a613bbe565b801515811461320957600080fd5b600067ffffffffffffffff821115613c2857613c2861386b565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600060c08284031215613c6657600080fd5b613c6e6138c3565b90508135613c7b8161358b565b81526020613c8a838201613aab565b818301526040830135613c9c81613c00565b604083015260608381013590830152608083013567ffffffffffffffff811115613cc557600080fd5b8301601f81018513613cd657600080fd5b8035613ce4613a5782613c0e565b8181528684838501011115613cf857600080fd5b818484018583013760008483830101528060808601525050505060a082013560a082015292915050565b600060e08236031215613d3457600080fd5b613d3c61389a565b82358152602083013567ffffffffffffffff811115613d5a57600080fd5b613d6636828601613c54565b602083015250613d7936604085016139cb565b604082015260a0830135613d8c8161358b565b6060820152613bb360c08401613aab565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4183360301811261386157600080fd5b600061072a3683613c54565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc183360301811261386157600080fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613e4657600080fd5b83018035915067ffffffffffffffff821115613e6157600080fd5b6020019150600581901b3603821315612ed357600080fd5b6000613e87613a5784613958565b80848252602080830192508560051b850136811115613ea557600080fd5b855b81811015613ee157803567ffffffffffffffff811115613ec75760008081fd5b613ed336828a01613c54565b865250938201938201613ea7565b50919695505050505050565b600060408284031215613eff57600080fd5b6129fc838361397c565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613f3e57600080fd5b83018035915067ffffffffffffffff821115613f5957600080fd5b6020019150600681901b3603821315612ed357600080fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613fa657600080fd5b83018035915067ffffffffffffffff821115613fc157600080fd5b6020019150606081023603821315612ed357600080fd5b600060608284031215613fea57600080fd5b6129fc83836139cb565b60006020828403121561400657600080fd5b6129fc82613aab565b600181811c9082168061402357607f821691505b60208210810361366b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000610100828403121561406f57600080fd5b61407761389a565b82358152614088846020850161397c565b602082015261409a84606085016139cb565b604082015260c08301356140ad8161358b565b60608201526140be60e08401613aab565b60808201529392505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036140fb576140fb613bbe565b5060010190565b6000602080838503121561411557600080fd5b825167ffffffffffffffff8082111561412d57600080fd5b908401906080828703121561414157600080fd5b6141496138e6565b825181528383015161415a8161358b565b81850152604083015161416c81613c00565b604082015260608301518281111561418357600080fd5b80840193505086601f84011261419857600080fd5b825191506141a8613a5783613c0e565b82815287858486010111156141bc57600080fd5b6141cb8386830187870161350a565b60608201529695505050505050565b601f821115610a6357600081815260208120601f850160051c810160208610156142015750805b601f850160051c820191505b818110156142205782815560010161420d565b505050505050565b815167ffffffffffffffff8111156142425761424261386b565b61425681614250845461400f565b846141da565b602080601f8311600181146142a957600084156142735750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555614220565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156142f6578886015182559484019460019091019084016142d7565b508582101561433257878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b82815260406020820152600061438a604083018461352e565b949350505050565b6000825161386181846020870161350a565b6000602082840312156143b657600080fd5b5051919050565b6000602082840312156143cf57600080fd5b81516129fc81613c00565b6000604082016040835280855180835260608501915060608160051b8601019250602080880160005b8381101561444f577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa088870301855261443d86835161368a565b95509382019390820190600101614403565b50508584038187015286518085528782019482019350915060005b828110156144865784518452938101939281019260010161446a565b5091979650505050505050565b89815260007fffffffffffffffffffffffffffffffffffffffff000000000000000000000000808b60601b166020840152808a60601b166034840152507fffffffffffffffff000000000000000000000000000000000000000000000000808960c01b166048840152808860c01b1660508401525085151560f81b6058830152846059830152835161452c81607985016020880161350a565b80830190507fffffffff000000000000000000000000000000000000000000000000000000008460e01b166079820152607d81019150509a995050505050505050505056fea164736f6c6343000813000a0000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 6251000, + "intent": "Deploy EAS Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000005d8608060405234801561001057600080fd5b506105b8806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d5014610046578063cdcb760a14610098578063e0145f5c146100d0575b600080fd5b6100826040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161008f91906103f7565b60405180910390f35b6100ab6100a6366004610440565b6100ea565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161008f565b734e59b44847b379578588920ca78fbf26c0b4956c6100ab565b8051602080830191909120604080517fff00000000000000000000000000000000000000000000000000000000000000818501527f4e59b44847b379578588920ca78fbf26c0b4956c000000000000000000000000602182015260358101869052605580820184905282518083039091018152607590910190915280519201919091206000919073ffffffffffffffffffffffffffffffffffffffff81163b156101d85760405173ffffffffffffffffffffffffffffffffffffffff8216907ffbe57d889a7f75a4e0c7da304cd158fcaddc4b925cdd9f4cfb115c0f9e48009b90600090a291506103779050565b600080734e59b44847b379578588920ca78fbf26c0b4956c73ffffffffffffffffffffffffffffffffffffffff168787604051602001610219929190610519565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290526102519161053f565b6000604051808303816000865af19150503d806000811461028e576040519150601f19603f3d011682016040523d82523d6000602084013e610293565b606091505b5091509150806102a29061055b565b60601c94508115806102e057508273ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b1561032257806040517fcb0fc6f700000000000000000000000000000000000000000000000000000000815260040161031991906103f7565b60405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff167f9b7318127ed899f286ea9ddd7925ed8ad24a682b6a825c3b5b3d88a3f00bc1d28860405161036a91815260200190565b60405180910390a2505050505b92915050565b60005b83811015610398578181015183820152602001610380565b838111156103a7576000848401525b50505050565b600081518084526103c581602086016020860161037d565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061040a60208301846103ad565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806040838503121561045357600080fd5b82359150602083013567ffffffffffffffff8082111561047257600080fd5b818501915085601f83011261048657600080fd5b81358181111561049857610498610411565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104de576104de610411565b816040528281528860208487010111156104f757600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b8281526000825161053181602085016020870161037d565b919091016020019392505050565b6000825161055181846020870161037d565b9190910192915050565b6000815160208301517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000808216935060148310156105a35780818460140360031b1b83161693505b50505091905056fea164736f6c634300080f000a0000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 580000, + "intent": "Deploy ConditionalDeployer Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002a9608060405234801561001057600080fd5b50610289806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80631bec9d65146100515780631d8a4e921461006657806354fd4d501461009b57806378ecabce146100e4575b600080fd5b61006461005f3660046101f0565b610107565b005b7fc8bc8f9195cfb2d040744aac63412d02ffc186ea9bd519039edc4666ee9032bc546040519081526020015b60405180910390f35b6100d76040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100929190610209565b6100f76100f23660046101f0565b610178565b6040519015158152602001610092565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610154576040517fee37fa8500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fc8bc8f9195cfb2d040744aac63412d02ffc186ea9bd519039edc4666ee9032bc55565b60006101ab6101a57fc8bc8f9195cfb2d040744aac63412d02ffc186ea9bd519039edc4666ee9032bc5490565b836101b1565b92915050565b600062100000808316036101c7575060016101ab565b6010808316036101d9575060016101ab565b81158015906101e9575081831682145b9392505050565b60006020828403121561020257600080fd5b5035919050565b600060208083528351808285015260005b818110156102365785810183015185820160400152820161021a565b81811115610248576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a0000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 328329, + "intent": "Deploy L2DevFeatureFlags Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000069e6080604052348015600e575f80fd5b506106828061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c8063331b637f1461004357806354fd4d5014610069578063ab4d6f75146100b2575b5f80fd5b610056610051366004610512565b6100c7565b6040519081526020015b60405180910390f35b6100a56040518060400160405280600581526020017f312e302e3200000000000000000000000000000000000000000000000000000081525081565b604051610060919061053b565b6100c56100c036600461058e565b61039e565b005b5f67ffffffffffffffff801683602001511115610110576040517fd1f79e8200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604083015163ffffffff1015610152576040517f94338eba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606083015167ffffffffffffffff1015610198576040517f596a19a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82516040515f916101dd91859060200160609290921b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000168252601482015260340190565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00181528282528051602091820120878201516060890151898501515f9487018590527fffffffffffffffff00000000000000000000000000000000000000000000000060c084811b8216602c8a015283901b1660348801527fffffffff0000000000000000000000000000000000000000000000000000000060e082901b16603c88015292965090949093919291016040516020818303038152906040526102ac906105bc565b90505f85826040516020016102cb929190918252602082015260400190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828252805160209182012060808d01519184018190529183015291505f90606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0300000000000000000000000000000000000000000000000000000000000000179a9950505050505050505050565b5f6103b76103b136859003850185610601565b836100c7565b90505f6103c38261043b565b509050806103fd576040517fe3c0081600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b827f5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba78560405161042d9190610622565b60405180910390a250505050565b5f805a835491505a6103e891031115939092509050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610475575f80fd5b919050565b5f60a0828403121561048a575f80fd5b60405160a0810181811067ffffffffffffffff821117156104d2577f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6040529050806104e183610452565b8152602083013560208201526040830135604082015260608301356060820152608083013560808201525092915050565b5f8060c08385031215610523575f80fd5b61052d848461047a565b9460a0939093013593505050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b5f8082840360c08112156105a0575f80fd5b60a08112156105ad575f80fd5b50919360a08501359350915050565b805160208083015191908110156105fb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b5f60a08284031215610611575f80fd5b61061b838361047a565b9392505050565b60a0810173ffffffffffffffffffffffffffffffffffffffff61064484610452565b168252602083013560208301526040830135604083015260608301356060830152608083013560808301529291505056fea164736f6c6343000819000a0000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 668000, + "intent": "Deploy CrossL2Inbox Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000011836080604052348015600e575f80fd5b506111678061001c5f395ff3fe6080604052600436106100b8575f3560e01c80637056f41f11610071578063b1b1b2091161004c578063b1b1b20914610228578063bc294d7d14610266578063ecc7042814610291575f80fd5b80637056f41f146101b65780637936cbee146101d55780638d1d298f14610215575f80fd5b806352617f3c116100a157806352617f3c1461011c57806354fd4d50146101425780636b0c3c5e14610197575f80fd5b806324794462146100bc57806338ffde18146100e3575b5f80fd5b3480156100c7575f80fd5b506100d06102c5565b6040519081526020015b60405180910390f35b3480156100ee575f80fd5b506100f7610344565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100da565b348015610127575f80fd5b5061012f5f81565b60405161ffff90911681526020016100da565b34801561014d575f80fd5b5061018a6040518060400160405280600581526020017f312e332e3100000000000000000000000000000000000000000000000000000081525081565b6040516100da9190610c7e565b3480156101a2575f80fd5b506100d06101b1366004610d00565b6103c3565b3480156101c1575f80fd5b506100d06101d0366004610d77565b6104ae565b3480156101e0575f80fd5b506101e96106ba565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152016100da565b61018a610223366004610dcf565b61075e565b348015610233575f80fd5b50610256610242366004610e25565b5f6020819052908152604090205460ff1681565b60405190151581526020016100da565b348015610271575f80fd5b506100d0610280366004610e25565b60026020525f908152604090205481565b34801561029c575f80fd5b506001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff166100d0565b5f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c61031e576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b507f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75c90565b5f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c61039d576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b507fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35c90565b5f610407874688888888888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610ae192505050565b5f878152600260205260409020549091508114610450576040517f6eca2e4b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b858473ffffffffffffffffffffffffffffffffffffffff16887f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f32088878760405161049c93929190610e3c565b60405180910390a49695505050505050565b5f4685036104e8576040517f8ed9a95d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fffffffffffffffffffffffffbdffffffffffffffffffffffffffffffffffffdd73ffffffffffffffffffffffffffffffffffffffff851601610557576040517f4faa250900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f6105816001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b90506105c6864683338989898080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610ae192505050565b5f828152600260205260408120829055600180549294507dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff909216919061060a83610ea5565b91906101000a8154817dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02191690837dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16021790555050808573ffffffffffffffffffffffffffffffffffffffff16877f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3203388886040516106a993929190610e3c565b60405180910390a450949350505050565b5f807ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c610714576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50507fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35c907f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75c90565b60607ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c156107b9576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60017ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5d7342000000000000000000000000000000000000236107ff6020860186610f06565b73ffffffffffffffffffffffffffffffffffffffff161461084c576040517f7987c15700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002273ffffffffffffffffffffffffffffffffffffffff1663ab4d6f7585858560405161088d929190610f21565b6040519081900381207fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1682526108cc9291600401610f30565b5f604051808303815f87803b1580156108e3575f80fd5b505af11580156108f5573d5f803e3d5ffd5b505050505f805f805f6109088888610b1f565b9450945094509450945046851461094b576040517f31ac221100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60808901355f61095f878387878a88610ae1565b5f8181526020819052604090205490915060ff16156109aa576040517f9ca9480b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f81815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790556109ea8285610be8565b5f8673ffffffffffffffffffffffffffffffffffffffff163485604051610a119190610f89565b5f6040518083038185875af1925050503d805f8114610a4b576040519150601f19603f3d011682016040523d82523d5f602084013e610a50565b606091505b509950905080610a6257885189602001fd5b8186847fc270d73e26d2d39dee7ef92093555927e344e243415547ecc350b2b5385b68a28c80519060200120604051610a9d91815260200190565b60405180910390a4610aaf5f80610be8565b50505050505050505f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5d9392505050565b5f868686868686604051602001610afd96959493929190610f9f565b6040516020818303038152906040528051906020012090509695505050505050565b5f808080606081610b33602082898b610ff5565b810190610b409190610e25565b90507f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3208114610b9b576040517fdf1eb58600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ba960806020898b610ff5565b810190610bb6919061101c565b91975095509350610bca876080818b610ff5565b810190610bd7919061107e565b969995985093965092949392505050565b817f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75d807fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35d5050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f610c906020830184610c32565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610cb8575f80fd5b50565b5f8083601f840112610ccb575f80fd5b50813567ffffffffffffffff811115610ce2575f80fd5b602083019150836020828501011115610cf9575f80fd5b9250929050565b5f805f805f8060a08789031215610d15575f80fd5b86359550602087013594506040870135610d2e81610c97565b93506060870135610d3e81610c97565b9250608087013567ffffffffffffffff811115610d59575f80fd5b610d6589828a01610cbb565b979a9699509497509295939492505050565b5f805f8060608587031215610d8a575f80fd5b843593506020850135610d9c81610c97565b9250604085013567ffffffffffffffff811115610db7575f80fd5b610dc387828801610cbb565b95989497509550505050565b5f805f83850360c0811215610de2575f80fd5b60a0811215610def575f80fd5b5083925060a084013567ffffffffffffffff811115610e0c575f80fd5b610e1886828701610cbb565b9497909650939450505050565b5f60208284031215610e35575f80fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff8416815260406020820152816040820152818360608301375f818301606090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016010192915050565b5f7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808316818103610efc577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b6001019392505050565b5f60208284031215610f16575f80fd5b8135610c9081610c97565b818382375f9101908152919050565b60c081018335610f3f81610c97565b73ffffffffffffffffffffffffffffffffffffffff1682526020848101359083015260408085013590830152606080850135908301526080938401359382019390935260a0015290565b5f82518060208501845e5f920191825250919050565b8681528560208201528460408201525f73ffffffffffffffffffffffffffffffffffffffff808616606084015280851660808401525060c060a0830152610fe960c0830184610c32565b98975050505050505050565b5f8085851115611003575f80fd5b8386111561100f575f80fd5b5050820193919092039150565b5f805f6060848603121561102e575f80fd5b83359250602084013561104081610c97565b929592945050506040919091013590565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f806040838503121561108f575f80fd5b823561109a81610c97565b9150602083013567ffffffffffffffff808211156110b6575f80fd5b818501915085601f8301126110c9575f80fd5b8135818111156110db576110db611051565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561112157611121611051565b81604052828152886020848701011115611139575f80fd5b826020860160208301375f602084830101528095505050505050925092905056fea164736f6c6343000819000a0000000000000000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1611000, + "intent": "Deploy L2ToL2CrossDomainMessenger Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000007ab608060405234801561001057600080fd5b5061078b806100206000396000f3fe6080604052600436106100345760003560e01c80634f0edcc91461003957806354fd4d501461005b57806364a197f3146100ba575b600080fd5b34801561004557600080fd5b506100596100543660046105ae565b6100db565b005b34801561006757600080fd5b506100a46040518060400160405280600581526020017f312e302e3100000000000000000000000000000000000000000000000000000081525081565b6040516100b1919061065a565b60405180910390f35b6100cd6100c8366004610674565b610340565b6040519081526020016100b1565b3373420000000000000000000000000000000000002314610128576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008073420000000000000000000000000000000000002373ffffffffffffffffffffffffffffffffffffffff16637936cbee6040518163ffffffff1660e01b81526004016040805180830381865afa158015610189573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ad91906106a0565b909250905073ffffffffffffffffffffffffffffffffffffffff82163014610201576040517fbc22e2aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa0712d68000000000000000000000000000000000000000000000000000000008152600481018490527342000000000000000000000000000000000000259063a0712d6890602401600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b50505050828460405161028d9061057d565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156102c7573d6000803e3d6000fd5b50508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d3598584604051610331929190918252602082015260400190565b60405180910390a35050505050565b600073ffffffffffffffffffffffffffffffffffffffff831661038f576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002573ffffffffffffffffffffffffffffffffffffffff166344df8e70346040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103eb57600080fd5b505af11580156103ff573d6000803e3d6000fd5b50506040805133602482015273ffffffffffffffffffffffffffffffffffffffff881660448201523460648083019190915282518083039091018152608490910182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f4f0edcc90000000000000000000000000000000000000000000000000000000017905290517f7056f41f0000000000000000000000000000000000000000000000000000000081527342000000000000000000000000000000000000239450637056f41f93506104de9250869130916004016106ce565b6020604051808303816000875af11580156104fd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610521919061070c565b604080513481526020810185905291925073ffffffffffffffffffffffffffffffffffffffff85169133917fed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402910160405180910390a392915050565b60598061072683390190565b73ffffffffffffffffffffffffffffffffffffffff811681146105ab57600080fd5b50565b6000806000606084860312156105c357600080fd5b83356105ce81610589565b925060208401356105de81610589565b929592945050506040919091013590565b6000815180845260005b81811015610615576020818501810151868301820152016105f9565b81811115610627576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061066d60208301846105ef565b9392505050565b6000806040838503121561068757600080fd5b823561069281610589565b946020939093013593505050565b600080604083850312156106b357600080fd5b82516106be81610589565b6020939093015192949293505050565b83815273ffffffffffffffffffffffffffffffffffffffff8316602082015260606040820152600061070360608301846105ef565b95945050505050565b60006020828403121561071e57600080fd5b505191905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 757000, + "intent": "Deploy SuperchainETHBridge Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003b3608060405234801561001057600080fd5b50610393806100206000396000f3fe60806040526004361061003f5760003560e01c806344df8e701461004457806354fd4d501461004e578063a0712d68146100ad578063b60d4288146100cd575b600080fd5b61004c6100d5565b005b34801561005a57600080fd5b506100976040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b6040516100a491906102a1565b60405180910390f35b3480156100b957600080fd5b5061004c6100c8366004610314565b61015a565b61004c610229565b3373420000000000000000000000000000000000002414610122576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907f875e07afd7ce17c6531b1a6b7b34829dcd8b7e6639448afbd6a8e29fa1422b82906020015b60405180910390a2565b33734200000000000000000000000000000000000024146101a7576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80336040516101b590610295565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156101ef573d6000803e3d6000fd5b505060405181815233907f85719716ac5bd2744ae7ed3d16702129383049b97123b506320e7a5826ebbbba9060200160405180910390a250565b34600003610263576040517f2c5211c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907fbb9e497a5b82d1a37f9496dd70c6efb97ba0d98c66c3422d05010105d063359890602001610150565b60598061032e83390190565b600060208083528351808285015260005b818110156102ce578581018301518582016040015282016102b2565b818111156102e0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561032657600080fd5b503591905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a00000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 423000, + "intent": "Deploy ETHLiquidity Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000010b1608060405234801561001057600080fd5b50611091806100206000396000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80635cf24969116100f9578063c598591811610097578063e591b28211610071578063e591b28214610434578063e81b2c6d1461044e578063f820614014610457578063fe3d57101461046057600080fd5b8063c598591814610404578063d844471514610424578063dad544e01461042c57600080fd5b80638381f58a116100d35780638381f58a146103be5780638b239f73146103d25780639e8c4966146103db578063b80777ea146103e457600080fd5b80635cf249691461038557806364ca23ef1461038e57806368d5dca6146103a257600080fd5b80634397dfef1161016657806347af267b1161014057806347af267b146103145780634d5d9a2a1461033757806354fd4d5014610368578063550fcdc91461037d57600080fd5b80634397dfef146102c3578063440a5e20146102f957806346a4d7801461030157600080fd5b806316d3bc7f116101a257806316d3bc7f14610202578063213268491461022f5780633db6be2b1461028e5780633e47158c1461029657600080fd5b8063015d8eb9146101c9578063098999be146101de57806309bd5a60146101e6575b600080fd5b6101dc6101d7366004610d9c565b610491565b005b6101dc6105d1565b6101ef60025481565b6040519081526020015b60405180910390f35b6008546102169067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101f9565b7f435553544f4d5f4741535f544f4b454e0000000000000000000000000000000060005260096020527f4ad9936a67aeb1898ef7b848aecdf71a1f8999fbf63ff2f5b5691cb14bedfe4d5460ff165b60405190151581526020016101f9565b6101dc6105e4565b61029e61060e565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101f9565b6102cb610819565b6040805173ffffffffffffffffffffffffffffffffffffffff909316835260ff9091166020830152016101f9565b6101dc610880565b6101dc61030f366004610e0e565b6108d7565b61027e610322366004610e0e565b60096020526000908152604090205460ff1681565b6008546103539068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101f9565b6103706108ec565b6040516101f99190610e57565b61037061094c565b6101ef60015481565b6003546102169067ffffffffffffffff1681565b6003546103539068010000000000000000900463ffffffff1681565b6000546102169067ffffffffffffffff1681565b6101ef60055481565b6101ef60065481565b6000546102169068010000000000000000900467ffffffffffffffff1681565b600354610353906c01000000000000000000000000900463ffffffff1681565b610370610a88565b61029e610b79565b73deaddeaddeaddeaddeaddeaddeaddeaddead000161029e565b6101ef60045481565b6101ef60075481565b60085461047e906c01000000000000000000000000900461ffff1681565b60405161ffff90911681526020016101f9565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610539576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c756573000000000060648201526084015b60405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b6105d9610880565b60a43560a01c600855565b6105ec610880565b6dffff00000000000000000000000060b03560901c1660a43560a01c17600855565b6000806106397fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff81161561065c57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000081525051600261069f9190610ea8565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000091909117906106fa906060015b604051602081830303815290604052805190602001205490565b14610731576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051306020820152600191810191909152600090610753906060016106e0565b905073ffffffffffffffffffffffffffffffffffffffff8116156107e7578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107e09190610f0c565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4c31426c6f636b4347543a20646570726563617465640000000000000000000060448201526000908190606401610530565b73deaddeaddeaddeaddeaddeaddeaddeaddead00013381146108aa57633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b6108e033610bf1565b6108e981610cce565b50565b606061092860408051808201909152600581527f312e392e30000000000000000000000000000000000000000000000000000000602082015290565b6040516020016109389190610f49565b604051602081830303815290604052905090565b60606109a27f435553544f4d5f4741535f544f4b454e0000000000000000000000000000000060005260096020527f4ad9936a67aeb1898ef7b848aecdf71a1f8999fbf63ff2f5b5691cb14bedfe4d5460ff1690565b6109de575060408051808201909152600381527f4554480000000000000000000000000000000000000000000000000000000000602082015290565b73420000000000000000000000000000000000002a73ffffffffffffffffffffffffffffffffffffffff1663550fcdc96040518163ffffffff1660e01b8152600401600060405180830381865afa158015610a3d573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a839190810190610fb9565b905090565b6060610ade7f435553544f4d5f4741535f544f4b454e0000000000000000000000000000000060005260096020527f4ad9936a67aeb1898ef7b848aecdf71a1f8999fbf63ff2f5b5691cb14bedfe4d5460ff1690565b610b1a575060408051808201909152600581527f4574686572000000000000000000000000000000000000000000000000000000602082015290565b73420000000000000000000000000000000000002a73ffffffffffffffffffffffffffffffffffffffff1663d84447156040518163ffffffff1660e01b8152600401600060405180830381865afa158015610a3d573d6000803e3d6000fd5b6000610b8361060e565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bcd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a839190610f0c565b73ffffffffffffffffffffffffffffffffffffffff811673deaddeaddeaddeaddeaddeaddeaddeaddead00011480610c5b5750610c2c610b79565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b80610c985750610c6961060e565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b6108e9576040517fbe9d7ca600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526009602052604090205460ff1615610d17576040517f4f45326000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526009602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091559051909183917fb876f6594132c89891d2fd198e925e999be741ec809abb58bfe9b966876cc06c9190a350565b803567ffffffffffffffff81168114610d9757600080fd5b919050565b600080600080600080600080610100898b031215610db957600080fd5b610dc289610d7f565b9750610dd060208a01610d7f565b96506040890135955060608901359450610dec60808a01610d7f565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208284031215610e2057600080fd5b5035919050565b60005b83811015610e42578181015183820152602001610e2a565b83811115610e51576000848401525b50505050565b6020815260008251806020840152610e76816040850160208701610e27565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610f07577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b600060208284031215610f1e57600080fd5b815173ffffffffffffffffffffffffffffffffffffffff81168114610f4257600080fd5b9392505050565b60008251610f5b818460208701610e27565b7f2b637573746f6d2d6761732d746f6b656e2e3100000000000000000000000000920191825250601301919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600060208284031215610fcb57600080fd5b815167ffffffffffffffff80821115610fe357600080fd5b818401915084601f830112610ff757600080fd5b81518181111561100957611009610f8a565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561104f5761104f610f8a565b8160405282815287602084870101111561106857600080fd5b611079836020830160208801610e27565b97965050505050505056fea164736f6c634300080f000a000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1568000, + "intent": "Deploy L1BlockCGT Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000862608060405234801561001057600080fd5b50610842806100206000396000f3fe6080604052600436106100695760003560e01c806382e3702d1161004357806382e3702d146100f6578063c2b3e5ac14610136578063ecc704281461014957600080fd5b80633f827a5a1461009257806344df8e70146100bf57806354fd4d50146100d457600080fd5b3661008d5761008b33620186a0604051806020016040528060008152506101ae565b005b600080fd5b34801561009e57600080fd5b506100a7600181565b60405161ffff90911681526020015b60405180910390f35b3480156100cb57600080fd5b5061008b610284565b3480156100e057600080fd5b506100e96102bc565b6040516100b691906105dd565b34801561010257600080fd5b506101266101113660046105f7565b60006020819052908152604090205460ff1681565b60405190151581526020016100b6565b61008b61014436600461063f565b6101ae565b34801561015557600080fd5b506101a06001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016100b6565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663213268496040518163ffffffff1660e01b8152600401602060405180830381865afa15801561020d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102319190610743565b801561023d5750600034115b15610274576040517fcdfaa11100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61027f83838361031c565b505050565b4761028e816104e0565b60405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b60606102f860408051808201909152600581527f312e322e30000000000000000000000000000000000000000000000000000000602082015290565b6040516020016103089190610765565b604051602081830303815290604052905090565b60006103b26040518060c001604052806103766001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b815233602082015273ffffffffffffffffffffffffffffffffffffffff871660408201523460608201526080810186905260a00184905261050a565b600081815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055905073ffffffffffffffffffffffffffffffffffffffff84163361044d6001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b7f02a52367d10742d8032712c1bb8e0144ff1ec5ffda1ed7d70bb05a27449550543487878760405161048294939291906107a6565b60405180910390a45050600180547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8082168301167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b806040516104ed90610557565b6040518091039082f090508015801561027f573d6000803e3d6000fd5b80516020808301516040808501516060860151608087015160a0880151935160009761053a9790969591016107d6565b604051602081830303815290604052805190602001209050919050565b60088061082e83390190565b60005b8381101561057e578181015183820152602001610566565b8381111561058d576000848401525b50505050565b600081518084526105ab816020860160208601610563565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006105f06020830184610593565b9392505050565b60006020828403121561060957600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060006060848603121561065457600080fd5b833573ffffffffffffffffffffffffffffffffffffffff8116811461067857600080fd5b925060208401359150604084013567ffffffffffffffff8082111561069c57600080fd5b818601915086601f8301126106b057600080fd5b8135818111156106c2576106c2610610565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561070857610708610610565b8160405282815289602084870101111561072157600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b60006020828403121561075557600080fd5b815180151581146105f057600080fd5b60008251610777818460208701610563565b7f2b637573746f6d2d6761732d746f6b656e000000000000000000000000000000920191825250601101919050565b8481528360208201526080604082015260006107c56080830185610593565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261082160c0830184610593565b9897505050505050505056fe608060405230fffea164736f6c634300080f000a000000000000000000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 827000, + "intent": "Deploy L2ToL1MessagePasserCGT Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000146a608060405234801561001057600080fd5b5061001961001e565b6100de565b600054610100900460ff161561008a5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff90811610156100dc576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b61137d806100ed6000396000f3fe6080604052600436106100dd5760003560e01c80638da5cb5b1161007f578063d844471511610059578063d844471514610256578063dad544e01461026b578063f2fde38b14610280578063f46eccc4146102a057600080fd5b80638da5cb5b146101eb5780639065714714610216578063c6f69fbb1461023657600080fd5b806344df8e70116100bb57806344df8e701461016357806354fd4d501461016b578063550fcdc9146101c1578063715018a6146101d657600080fd5b80630c984832146100e25780633e47158c1461010457806340c10f1914610143575b600080fd5b3480156100ee57600080fd5b506101026100fd366004610ec7565b6102e0565b005b34801561011057600080fd5b5061011961035f565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561014f57600080fd5b5061010261015e366004610eeb565b61056a565b6101026106cc565b34801561017757600080fd5b506101b46040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b60405161013a9190610f17565b3480156101cd57600080fd5b506101b46107c2565b3480156101e257600080fd5b50610102610850565b3480156101f757600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff16610119565b34801561022257600080fd5b50610102610231366004611064565b610864565b34801561024257600080fd5b50610102610251366004610ec7565b610a29565b34801561026257600080fd5b506101b4610aa5565b34801561027757600080fd5b50610119610ab2565b34801561028c57600080fd5b5061010261029b366004610ec7565b610b2f565b3480156102ac57600080fd5b506102d06102bb366004610ec7565b60656020526000908152604090205460ff1681565b604051901515815260200161013a565b6102e8610be6565b73ffffffffffffffffffffffffffffffffffffffff811660008181526065602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055517f83b05b6735acd4b85e3bded8e72c851d1a87718f81e3c8e6f0c9d9a2baa88e469190a250565b60008061038a7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156103ad57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026103f091906110da565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061044b906060015b604051602081830303815290604052805190602001205490565b14610482576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091526000906104a490606001610431565b905073ffffffffffffffffffffffffffffffffffffffff811615610538578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561050d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610531919061113e565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3360009081526065602052604090205460ff166105b3576040517f5fbc4ede00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f2e1a7d4d0000000000000000000000000000000000000000000000000000000081526004810182905273420000000000000000000000000000000000002990632e1a7d4d90602401600060405180830381600087803b15801561061957600080fd5b505af115801561062d573d6000803e3d6000fd5b50505050808260405161063f90610e99565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f0905080158015610679573d6000803e3d6000fd5b505060405181815273ffffffffffffffffffffffffffffffffffffffff83169033907fec89d80a36947288037745287dde87d62cd8c141d5323130b3d26d97d84004c79060200160405180910390a35050565b3360009081526065602052604090205460ff16610715576040517f5fbc4ede00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002973ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561077157600080fd5b505af1158015610785573d6000803e3d6000fd5b50506040513481523393507f875e07afd7ce17c6531b1a6b7b34829dcd8b7e6639448afbd6a8e29fa1422b829250602001905060405180910390a2565b606780546107cf9061115b565b80601f01602080910402602001604051908101604052809291908181526020018280546107fb9061115b565b80156108485780601f1061081d57610100808354040283529160200191610848565b820191906000526020600020905b81548152906001019060200180831161082b57829003601f168201915b505050505081565b610858610be6565b6108626000610c67565b565b600054610100900460ff16158080156108845750600054600160ff909116105b8061089e5750303b15801561089e575060005460ff166001145b61092f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084015b60405180910390fd5b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561098d57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b610995610cde565b61099d610d5f565b6109a684610c67565b60666109b284826111fd565b5060676109bf83826111fd565b508015610a2357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b610a31610be6565b73ffffffffffffffffffffffffffffffffffffffff811660008181526065602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517fdf6bf03dfab5b4ccec3ba95544b98d7ecc9d4b9293d8673e86cb6edb5ac0cb629190a250565b606680546107cf9061115b565b6000610abc61035f565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2a919061113e565b905090565b610b37610be6565b73ffffffffffffffffffffffffffffffffffffffff8116610bda576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610926565b610be381610c67565b50565b60335473ffffffffffffffffffffffffffffffffffffffff163314610862576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610926565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b33610ce761035f565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d28575033610d0f610ab2565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610862576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600054610100900460ff16610df6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610926565b610862600054610100900460ff16610e90576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610926565b61086233610c67565b60598061131883390190565b73ffffffffffffffffffffffffffffffffffffffff81168114610be357600080fd5b600060208284031215610ed957600080fd5b8135610ee481610ea5565b9392505050565b60008060408385031215610efe57600080fd5b8235610f0981610ea5565b946020939093013593505050565b600060208083528351808285015260005b81811015610f4457858101830151858201604001528201610f28565b81811115610f56576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f830112610fca57600080fd5b813567ffffffffffffffff80821115610fe557610fe5610f8a565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561102b5761102b610f8a565b8160405283815286602085880101111561104457600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561107957600080fd5b833561108481610ea5565b9250602084013567ffffffffffffffff808211156110a157600080fd5b6110ad87838801610fb9565b935060408601359150808211156110c357600080fd5b506110d086828701610fb9565b9150509250925092565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611139577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b60006020828403121561115057600080fd5b8151610ee481610ea5565b600181811c9082168061116f57607f821691505b6020821081036111a8577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156111f857600081815260208120601f850160051c810160208610156111d55750805b601f850160051c820191505b818110156111f4578281556001016111e1565b5050505b505050565b815167ffffffffffffffff81111561121757611217610f8a565b61122b81611225845461115b565b846111ae565b602080601f83116001811461127e57600084156112485750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556111f4565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156112cb578886015182559484019460019091019084016112ac565b508582101561130757878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b0190555056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1870000, + "intent": "Deploy LiquidityController Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000036d608060405234801561001057600080fd5b5061034d806100206000396000f3fe6080604052600436106100345760003560e01c80632e1a7d4d1461003957806354fd4d501461005b578063d0e30db0146100ba575b600080fd5b34801561004557600080fd5b5061005961005436600461025b565b6100c2565b005b34801561006757600080fd5b506100a46040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100b19190610274565b60405180910390f35b6100596101cb565b3373420000000000000000000000000000000000002a1461010f576040517f565369fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b47811115610149576040517f7b7f21e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80336040516101579061024f565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f0905080158015610191573d6000803e3d6000fd5b505060405181815233907fb1cce8684b4ffa8667b4577654e61ee3480d661ee9c27522ac80e211f6bd4d259060200160405180910390a250565b3373420000000000000000000000000000000000002a14610218576040517f565369fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907f7ff07ce9a287649537e4b012e45cf012d90228b12e2b56bb03515a6b5436fcdf9060200160405180910390a2565b6059806102e883390190565b60006020828403121561026d57600080fd5b5035919050565b600060208083528351808285015260005b818110156102a157858101830151858201604001528201610285565b818111156102b3576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a00000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 392000, + "intent": "Deploy NativeAssetLiquidity Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000003e026103e06040523480156200001257600080fd5b5060405162003ac238038062003ac28339810160408190526200003591620001c7565b30608090815281516001600160a01b0390811660a09081526020840151821660e09081526040850151831660c0908152606086015184166101009081529486015184166101209081529286015184166101409081529086015184166101609081529186015184166101809081529486015184166101a09081529286015184166101c09081529086015184166101e09081529186015184166102009081529486015184166102209081529286015184166102409081529086015184166102609081529186015184166102809081529486015184166102a09081529286015184166102c09081529086015184166102e0908152918601518416610300908152948601518416610320908152928601518416610340528501518316610360528401518216610380529183015181166103a052910151166103c052620003e6565b60405161034081016001600160401b0381118282101715620001a457634e487b7160e01b600052604160045260246000fd5b60405290565b80516001600160a01b0381168114620001c257600080fd5b919050565b60006103408284031215620001db57600080fd5b620001e562000172565b620001f083620001aa565b81526200020060208401620001aa565b60208201526200021360408401620001aa565b60408201526200022660608401620001aa565b60608201526200023960808401620001aa565b60808201526200024c60a08401620001aa565b60a08201526200025f60c08401620001aa565b60c08201526200027260e08401620001aa565b60e082015261010062000287818501620001aa565b908201526101206200029b848201620001aa565b90820152610140620002af848201620001aa565b90820152610160620002c3848201620001aa565b90820152610180620002d7848201620001aa565b908201526101a0620002eb848201620001aa565b908201526101c0620002ff848201620001aa565b908201526101e062000313848201620001aa565b9082015261020062000327848201620001aa565b908201526102206200033b848201620001aa565b908201526102406200034f848201620001aa565b9082015261026062000363848201620001aa565b9082015261028062000377848201620001aa565b908201526102a06200038b848201620001aa565b908201526102c06200039f848201620001aa565b908201526102e0620003b3848201620001aa565b90820152610300620003c7848201620001aa565b90820152610320620003db848201620001aa565b908201529392505050565b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051610320516103405161036051610380516103a0516103c0516134b262000610600039600081816104af01526116e101526000818161048601526118e701526000818161045d0152611207015260008181610434015261172a01526000818161040b015261182d0152600081816103e201526117ef0152600081816103b901526117b1015260008181610390015261177301526000818161036701526118a901526000818161033e015261186b01526000818161031501526115200152600081816102ec01526114a00152600081816102c3015261142101526000818161029a01526116a30152600081816102710152611107015260008181610248015261166501526000818161021f015261163f0152600081816101f601526115f70152600081816101ce01526115d10152600081816101a60152610fe301526000818161017e015261106f015260008181610156015261130401526000818161012e0152610edd01526000818160df0152610dd7015260008181610107015261158901526000818160ba01528181610df901528181610eff015281816110050152818161109101528181611129015281816112290152818161132601528181611443015281816114c201526115420152600061050201526134b26000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d5014610046578063615f64fe14610098578063d55ec697146104e1575b600080fd5b6100826040518060400160405280600581526020017f312e392e3000000000000000000000000000000000000000000000000000000081525081565b60405161008f9190612ed2565b60405180910390f35b604080516103408101825273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811682527f0000000000000000000000000000000000000000000000000000000000000000811660208301527f00000000000000000000000000000000000000000000000000000000000000008116828401527f0000000000000000000000000000000000000000000000000000000000000000811660608301527f0000000000000000000000000000000000000000000000000000000000000000811660808301527f0000000000000000000000000000000000000000000000000000000000000000811660a08301527f0000000000000000000000000000000000000000000000000000000000000000811660c08301527f0000000000000000000000000000000000000000000000000000000000000000811660e08301527f000000000000000000000000000000000000000000000000000000000000000081166101008301527f000000000000000000000000000000000000000000000000000000000000000081166101208301527f000000000000000000000000000000000000000000000000000000000000000081166101408301527f000000000000000000000000000000000000000000000000000000000000000081166101608301527f000000000000000000000000000000000000000000000000000000000000000081166101808301527f000000000000000000000000000000000000000000000000000000000000000081166101a08301527f000000000000000000000000000000000000000000000000000000000000000081166101c08301527f000000000000000000000000000000000000000000000000000000000000000081166101e08301527f000000000000000000000000000000000000000000000000000000000000000081166102008301527f000000000000000000000000000000000000000000000000000000000000000081166102208301527f000000000000000000000000000000000000000000000000000000000000000081166102408301527f000000000000000000000000000000000000000000000000000000000000000081166102608301527f000000000000000000000000000000000000000000000000000000000000000081166102808301527f000000000000000000000000000000000000000000000000000000000000000081166102a08301527f000000000000000000000000000000000000000000000000000000000000000081166102c08301527f000000000000000000000000000000000000000000000000000000000000000081166102e08301527f000000000000000000000000000000000000000000000000000000000000000081166103008301527f000000000000000000000000000000000000000000000000000000000000000016610320820152905161008f9190612ee5565b6104e96104eb565b005b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016300361055a576040517fada337cf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610564610572565b905061056f81610d98565b50565b61057a612d31565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663213268496040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105fd9190613126565b15156101408201526040517f47af267b0000000000000000000000000000000000000000000000000000000081527f494e5445524f50000000000000000000000000000000000000000000000000006004820152734200000000000000000000000000000000000015906347af267b90602401602060405180830381865afa9250505080156106c7575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526106c491810190613126565b60015b6106d85760006101608201526106e1565b15156101608201525b80610160015180156106fa57506106f8600161190b565b155b15610731576040517fa27dcc8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604051806020016040528073420000000000000000000000000000000000000773ffffffffffffffffffffffffffffffffffffffff1663db505d806040518163ffffffff1660e01b8152600401602060405180830381865afa15801561079b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107bf919061316a565b73ffffffffffffffffffffffffffffffffffffffff16905281526040805160208082018084527fc89701a20000000000000000000000000000000000000000000000000000000090529151909182917342000000000000000000000000000000000000109163c89701a29160248086019291908187030181865afa15801561084b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086f919061316a565b73ffffffffffffffffffffffffffffffffffffffff168152508160200181905250604051806020016040528073420000000000000000000000000000000000001473ffffffffffffffffffffffffffffffffffffffff1663c89701a26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108fa573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061091e919061316a565b73ffffffffffffffffffffffffffffffffffffffff168152508160400181905250604051806020016040528073420000000000000000000000000000000000001273ffffffffffffffffffffffffffffffffffffffff1663e78cea926040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109cd919061316a565b73ffffffffffffffffffffffffffffffffffffffff1690526060820152604080518082018083527fe78cea92000000000000000000000000000000000000000000000000000000009052905181907342000000000000000000000000000000000000179063e78cea92906044808501916020918187030181865afa158015610a59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a7d919061316a565b73ffffffffffffffffffffffffffffffffffffffff16815260200173420000000000000000000000000000000000001773ffffffffffffffffffffffffffffffffffffffff1663d23822426040518163ffffffff1660e01b8152600401602060405180830381865afa158015610af7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1b9190613187565b90526080820152610b3f734200000000000000000000000000000000000011611a6e565b60a0820152610b61734200000000000000000000000000000000000019611a6e565b60c0820152610b8373420000000000000000000000000000000000001a611a6e565b60e0820152610ba573420000000000000000000000000000000000001b611a6e565b61010082015261014081015115610d9557600073420000000000000000000000000000000000002a905060405180606001604052808273ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c25573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c49919061316a565b73ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1663d84447156040518163ffffffff1660e01b8152600401600060405180830381865afa158015610caf573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610cf59190810190613261565b81526020018273ffffffffffffffffffffffffffffffffffffffff1663550fcdc96040518163ffffffff1660e01b8152600401600060405180830381865afa158015610d45573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610d8b9190810190613261565b9052610120830152505b90565b80515160405173ffffffffffffffffffffffffffffffffffffffff9091166024820152610e9b90734200000000000000000000000000000000000007907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090604401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc4d66de80000000000000000000000000000000000000000000000000000000017905260006014611c4f565b60208101515160405173ffffffffffffffffffffffffffffffffffffffff9091166024820152610fa190734200000000000000000000000000000000000010907f0000000000000000000000000000000000000000000000000000000000000000907f0000000000000000000000000000000000000000000000000000000000000000906044015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc4d66de800000000000000000000000000000000000000000000000000000000179052600080611c4f565b60408082015151905173ffffffffffffffffffffffffffffffffffffffff909116602482015261102d90734200000000000000000000000000000000000014907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090604401610f23565b60608101515160405173ffffffffffffffffffffffffffffffffffffffff90911660248201526110b990734200000000000000000000000000000000000012907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090604401610f23565b6080810151805160209091015160405173ffffffffffffffffffffffffffffffffffffffff909216602483015260448201526111cb90734200000000000000000000000000000000000017907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fcd6dc6870000000000000000000000000000000000000000000000000000000017905260016000611c4f565b806101400151156112d4576101208101518051602082015160409283015192516112d49373420000000000000000000000000000000000002a937f0000000000000000000000000000000000000000000000000000000000000000937f00000000000000000000000000000000000000000000000000000000000000009361125693906024016132b2565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f9065714700000000000000000000000000000000000000000000000000000000179052600080611c4f565b60a08101518051602082015160409283015192516113f193734200000000000000000000000000000000000011937f0000000000000000000000000000000000000000000000000000000000000000937f000000000000000000000000000000000000000000000000000000000000000093611353939060240161332c565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fb49dc741000000000000000000000000000000000000000000000000000000001790527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006000611c4f565b60c081015180516020820151604092830151925161147093734200000000000000000000000000000000000019937f0000000000000000000000000000000000000000000000000000000000000000937f000000000000000000000000000000000000000000000000000000000000000093611353939060240161332c565b60e08101518051602082015160409283015192516114ef9373420000000000000000000000000000000000001a937f0000000000000000000000000000000000000000000000000000000000000000937f000000000000000000000000000000000000000000000000000000000000000093611353939060240161332c565b61010081015180516020820151604092830151925161156f9373420000000000000000000000000000000000001b937f0000000000000000000000000000000000000000000000000000000000000000937f000000000000000000000000000000000000000000000000000000000000000093611353939060240161332c565b6115ad73420000000000000000000000000000000000000f7f000000000000000000000000000000000000000000000000000000000000000061247d565b61161b7342000000000000000000000000000000000000158261014001516115f5577f000000000000000000000000000000000000000000000000000000000000000061247d565b7f000000000000000000000000000000000000000000000000000000000000000061247d565b611689734200000000000000000000000000000000000016826101400151611663577f000000000000000000000000000000000000000000000000000000000000000061247d565b7f000000000000000000000000000000000000000000000000000000000000000061247d565b6116c77342000000000000000000000000000000000000187f000000000000000000000000000000000000000000000000000000000000000061247d565b61170573420000000000000000000000000000000000002d7f000000000000000000000000000000000000000000000000000000000000000061247d565b8061014001511561174e5761174e7342000000000000000000000000000000000000297f000000000000000000000000000000000000000000000000000000000000000061247d565b80610160015115611851576117977342000000000000000000000000000000000000227f000000000000000000000000000000000000000000000000000000000000000061247d565b6117d57342000000000000000000000000000000000000237f000000000000000000000000000000000000000000000000000000000000000061247d565b6118137342000000000000000000000000000000000000247f000000000000000000000000000000000000000000000000000000000000000061247d565b6118517342000000000000000000000000000000000000257f000000000000000000000000000000000000000000000000000000000000000061247d565b61188f7342000000000000000000000000000000000000207f000000000000000000000000000000000000000000000000000000000000000061247d565b6118cd7342000000000000000000000000000000000000217f000000000000000000000000000000000000000000000000000000000000000061247d565b61056f73420000000000000000000000000000000000002c7f000000000000000000000000000000000000000000000000000000000000000061247d565b6040517f204e1c7a00000000000000000000000000000000000000000000000000000000815273420000000000000000000000000000000000002d600482015260009081907342000000000000000000000000000000000000189063204e1c7a90602401602060405180830381865afa15801561198c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119b0919061316a565b90508073ffffffffffffffffffffffffffffffffffffffff163b6000036119da5750600092915050565b6040517f78ecabce0000000000000000000000000000000000000000000000000000000081526004810184905273420000000000000000000000000000000000002d906378ecabce90602401602060405180830381865afa158015611a43573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a679190613126565b9392505050565b611a8f60408051606081018252600080825260208201819052909182015290565b60008273ffffffffffffffffffffffffffffffffffffffff1663d0e12f906040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611b16575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252611b1391810190613395565b60015b611b2257506000611b25565b90505b600083905060405180606001604052808273ffffffffffffffffffffffffffffffffffffffff16630d9019e16040518163ffffffff1660e01b8152600401602060405180830381865afa158015611b80573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ba4919061316a565b73ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1663d3e5792b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611c0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c2e9190613187565b8152602001836001811115611c4557611c456132fd565b9052949350505050565b611c58866127b9565b611cab576040517fc3fe4a6600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff871660048201526024015b60405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff163b600003611d14576040517f1d38e98500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152602401611ca2565b8473ffffffffffffffffffffffffffffffffffffffff163b600003611d7d576040517f1d38e98500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff86166004820152602401611ca2565b6040517f204e1c7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff871660048201526000907342000000000000000000000000000000000000189063204e1c7a90602401602060405180830381865afa158015611dfe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e22919061316a565b905073ffffffffffffffffffffffffffffffffffffffff81163b15801590611f6f5750611f6f8773ffffffffffffffffffffffffffffffffffffffff166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa158015611e93573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052611ed99190810190613261565b8773ffffffffffffffffffffffffffffffffffffffff166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa158015611f24573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052611f6a9190810190613261565b612802565b15611fbe576040517ff8ce5d1600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff88166004820152602401611ca2565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152881690633659cfe690602401600060405180830381600087803b15801561202757600080fd5b505af115801561203b573d6000803e3d6000fd5b507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00925050508381148015612072575060ff831615155b156120a9576040517f10415a2900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80841461223d576040517fa6ed563e0000000000000000000000000000000000000000000000000000000081526004810185905260009073ffffffffffffffffffffffffffffffffffffffff8a169063a6ed563e90602401602060405180830381865afa15801561211e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121429190613187565b905061214f8460016133e5565b61215d9060ff16600861340a565b81901c60ff161561219a576040517fc996d78400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006121aa60ff8616600861340a565b6040517f4e91db080000000000000000000000000000000000000000000000000000000081526004810188905260ff90911b198381166024830152915073ffffffffffffffffffffffffffffffffffffffff8b1690634e91db0890604401600060405180830381600087803b15801561222257600080fd5b505af1158015612236573d6000803e3d6000fd5b5050505050505b6040517fa6ed563e0000000000000000000000000000000000000000000000000000000081526004810182905260009073ffffffffffffffffffffffffffffffffffffffff8a169063a6ed563e90602401602060405180830381865afa1580156122ab573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906122cf9190613187565b905060ff604082901c1615612310576040517fc996d78400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f4e91db08000000000000000000000000000000000000000000000000000000008152600481018390527fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000008216602482015273ffffffffffffffffffffffffffffffffffffffff8a1690634e91db0890604401600060405180830381600087803b1580156123a057600080fd5b505af11580156123b4573d6000803e3d6000fd5b50506040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8c169250634f1ef286915061240c908b908a90600401613447565b6000604051808303816000875af115801561242b573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526124719190810190613261565b50505050505050505050565b8073ffffffffffffffffffffffffffffffffffffffff163b6000036124e6576040517f1d38e98500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401611ca2565b6124ef826127b9565b61253d576040517fc3fe4a6600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401611ca2565b6040517f204e1c7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526000907342000000000000000000000000000000000000189063204e1c7a90602401602060405180830381865afa1580156125be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125e2919061316a565b905073ffffffffffffffffffffffffffffffffffffffff81163b158015906126e457506126e48373ffffffffffffffffffffffffffffffffffffffff166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa158015612653573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526126999190810190613261565b8373ffffffffffffffffffffffffffffffffffffffff166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa158015611f24573d6000803e3d6000fd5b15612733576040517ff8ce5d1600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84166004820152602401611ca2565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152841690633659cfe690602401600060405180830381600087803b15801561279c57600080fd5b505af11580156127b0573d6000803e3d6000fd5b50505050505050565b60007208400000000000000000000000000000000000600b83901c721fffffffffffffffffffffffffffffffffffff161480156127fc57506127fa82612828565b155b92915050565b600061280e8383612890565b158015611a67575061282083836128e0565b159392505050565b600073ffffffffffffffffffffffffffffffffffffffff821673420000000000000000000000000000000000004214806127fc575073ffffffffffffffffffffffffffffffffffffffff82167342000000000000000000000000000000000000061492915050565b60008061289c84612955565b905060006128a984612955565b805183519192501480156128c4575080602001518260200151145b80156128d7575080604001518260400151145b95945050505050565b6000806128ec84612955565b905060006128f984612955565b80518351919250118061291f57508051825114801561291f575080602001518260200151105b806128d757508051825114801561293d575080602001518260200151145b80156128d75750604090810151910151109392505050565b61297960405180606001604052806000815260200160008152602001600081525090565b60006129ba836040518060400160405280600181526020017f2e00000000000000000000000000000000000000000000000000000000000000815250612b21565b90506003815110156129f8576040517f9eda858c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612a5382600281518110612a1057612a10613476565b60200260200101516040518060400160405280600181526020017f2d00000000000000000000000000000000000000000000000000000000000000815250612b21565b90506000612ab082600081518110612a6d57612a6d613476565b60200260200101516040518060400160405280600181526020017f2b00000000000000000000000000000000000000000000000000000000000000815250612b21565b90506040518060600160405280612ae085600081518110612ad357612ad3613476565b6020026020010151612bcc565b8152602001612afb85600181518110612ad357612ad3613476565b8152602001612b1683600081518110612ad357612ad3613476565b905295945050505050565b60606000612b2f8484612c44565b9050601f1960208201600183510160051b81018651838201526001845101845260005b825160608452818114612b975760405182820380825286601f8201165b8b850181015183820152870180612b6f5750600082820160200152603f018616810160405284525b875160209490940193019050818310612b5257505050508091508251612bc557602081019150600281510382525b5092915050565b80516000907f1999999999999999999999999999999999999999999999999999999999999999825b600181019050603060ff82870151160382851185600a028281019650600983118188108317171586029550505050828110612bf457505080612c3e5763101827966000526004601cfd5b50919050565b606082518251818111612d29576020850194506020840193506020604051019250846001828488010301600060208410612c7d57508286205b601f841660200360031b87515b8951818118831c612cdf578315612cbd5783878c2014612cbd5760018b019a50848b10612cb75750612cee565b50612c8a565b858b038952998601996020909801978615612cdf57848b10612cb75750612cee565b5060018a019950838a10612c8a575b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08189030160051c8152602090970190525050505b505092915050565b604080516101a08101825260006101808201818152825282516020808201855282825280840191909152835180820185528281528385015283518082018552828152606084015283518085019094528184528301529060808201908152602001612db260408051606081018252600080825260208201819052909182015290565b8152602001612dd860408051606081018252600080825260208201819052909182015290565b8152602001612dfe60408051606081018252600080825260208201819052909182015290565b8152602001612e2460408051606081018252600080825260208201819052909182015290565b8152604080516060808201835260008252602082810182905292820152910190815260006020820181905260409091015290565b60005b83811015612e73578181015183820152602001612e5b565b83811115612e82576000848401525b50505050565b60008151808452612ea0816020860160208601612e58565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000611a676020830184612e88565b815173ffffffffffffffffffffffffffffffffffffffff16815261034081016020830151612f2b602084018273ffffffffffffffffffffffffffffffffffffffff169052565b506040830151612f53604084018273ffffffffffffffffffffffffffffffffffffffff169052565b506060830151612f7b606084018273ffffffffffffffffffffffffffffffffffffffff169052565b506080830151612fa3608084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060a0830151612fcb60a084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c0830151612ff360c084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060e083015161301b60e084018273ffffffffffffffffffffffffffffffffffffffff169052565b506101008381015173ffffffffffffffffffffffffffffffffffffffff90811691840191909152610120808501518216908401526101408085015182169084015261016080850151821690840152610180808501518216908401526101a0808501518216908401526101c0808501518216908401526101e08085015182169084015261020080850151821690840152610220808501518216908401526102408085015182169084015261026080850151821690840152610280808501518216908401526102a0808501518216908401526102c0808501518216908401526102e08085015182169084015261030080850151821690840152610320808501519182168185015290612d29565b60006020828403121561313857600080fd5b81518015158114611a6757600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461056f57600080fd5b60006020828403121561317c57600080fd5b8151611a6781613148565b60006020828403121561319957600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600067ffffffffffffffff808411156131ea576131ea6131a0565b604051601f85017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908282118183101715613230576132306131a0565b8160405280935085815286868601111561324957600080fd5b613257866020830187612e58565b5050509392505050565b60006020828403121561327357600080fd5b815167ffffffffffffffff81111561328a57600080fd5b8201601f8101841361329b57600080fd5b6132aa848251602084016131cf565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff841681526060602082015260006132e16060830185612e88565b82810360408401526132f38185612e88565b9695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff84168152602081018390526060810160028310613387577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b826040830152949350505050565b6000602082840312156133a757600080fd5b815160028110611a6757600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060ff821660ff84168060ff03821115613402576134026133b6565b019392505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615613442576134426133b6565b500290565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260006132aa6040830184612e88565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fdfea164736f6c634300080f000a0000000000000000000000002a5a3eabb9fd571a3af0299eebdf8eaafe29a914000000000000000000000000250af3f400cf8aac8d410c90f1ba3968dd87df96000000000000000000000000547d0fba434877d7237d511cf87fabe2ee26b152000000000000000000000000ae9ed42f43a3ee45c3a9def8ae6b48cbb58ed1a1000000000000000000000000b178cdaa8336f25624a63c049edb5af7ca36c2da000000000000000000000000af87f2fd347acb94656f9f715b4f2409b98e75b9000000000000000000000000716ead0cf3e7ff86a02d4f8cb41a6d14922fa8330000000000000000000000006a97c5d55a21265326150efe12fc30fb21cbff56000000000000000000000000a0734858ba5085ff6db493021a0f8c54605c2cda00000000000000000000000027e51b2254433a3284d9ba73ea551c397db2a124000000000000000000000000a0f4ffff79a0a3e039fcbef738751efba8e84f96000000000000000000000000f43862b9d814bb4504158ceccb0b74b31265e4ee000000000000000000000000893c2ceeb71d38514daf67728d3ff1b213fc4b5f000000000000000000000000f7bed7215eef1003fac426682cf2edeb958569f7000000000000000000000000f7bed7215eef1003fac426682cf2edeb958569f7000000000000000000000000eddf416c7159387cc6df3015700f79cfb891137300000000000000000000000070de55bc0bfbc52c5d0cca1da5816c2428886a34000000000000000000000000bec660b456b84a081e90af29be43385bda5bf7b600000000000000000000000093a8a7a9c98cb998d88dba3373a6c7f8ee2e8a460000000000000000000000009c6f280ffa7499eaa5d9c2d081f6f2c333432601000000000000000000000000784f1fae11f1c3a9c413423fe1b370a3636b8d560000000000000000000000002f76618143d9d2731c56778192d3893864b423d7000000000000000000000000dda87ef358082ab3f4ba8982290c671efdc4d1590000000000000000000000003e6a107f4e87af774001b4e42a0d9dabf277c288000000000000000000000000906835344844979ffd3a752eaa23728d513db00b000000000000000000000000d20fb1c96a32e2fbaca22736ff870c991de9ada3000000000000000000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 4944000, + "intent": "Deploy L2ContractsManager Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0x7c36f37e000000000000000000000000f66098e0ebf8e181b9b54504ada4ef8e2d92b149", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 2115000, + "intent": "L2ProxyAdmin Upgrade Predeploys", + "to": "0x4200000000000000000000000000000000000018" + } + ] +} \ No newline at end of file diff --git a/op-core/nuts/bundles/karst_nut_bundle.json b/op-core/nuts/bundles/karst_nut_bundle.json new file mode 100644 index 00000000000..7e18d0ea725 --- /dev/null +++ b/op-core/nuts/bundles/karst_nut_bundle.json @@ -0,0 +1,224 @@ +{ + "metadata": { + "version": "1.0.0" + }, + "transactions": [ + { + "data": "0x9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4608060405234801561001057600080fd5b506105b8806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d5014610046578063cdcb760a14610098578063e0145f5c146100d0575b600080fd5b6100826040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161008f91906103f7565b60405180910390f35b6100ab6100a6366004610440565b6100ea565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161008f565b734e59b44847b379578588920ca78fbf26c0b4956c6100ab565b8051602080830191909120604080517fff00000000000000000000000000000000000000000000000000000000000000818501527f4e59b44847b379578588920ca78fbf26c0b4956c000000000000000000000000602182015260358101869052605580820184905282518083039091018152607590910190915280519201919091206000919073ffffffffffffffffffffffffffffffffffffffff81163b156101d85760405173ffffffffffffffffffffffffffffffffffffffff8216907ffbe57d889a7f75a4e0c7da304cd158fcaddc4b925cdd9f4cfb115c0f9e48009b90600090a291506103779050565b600080734e59b44847b379578588920ca78fbf26c0b4956c73ffffffffffffffffffffffffffffffffffffffff168787604051602001610219929190610519565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290526102519161053f565b6000604051808303816000865af19150503d806000811461028e576040519150601f19603f3d011682016040523d82523d6000602084013e610293565b606091505b5091509150806102a29061055b565b60601c94508115806102e057508273ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b1561032257806040517fcb0fc6f700000000000000000000000000000000000000000000000000000000815260040161031991906103f7565b60405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff167f9b7318127ed899f286ea9ddd7925ed8ad24a682b6a825c3b5b3d88a3f00bc1d28860405161036a91815260200190565b60405180910390a2505050505b92915050565b60005b83811015610398578181015183820152602001610380565b838111156103a7576000848401525b50505050565b600081518084526103c581602086016020860161037d565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061040a60208301846103ad565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806040838503121561045357600080fd5b82359150602083013567ffffffffffffffff8082111561047257600080fd5b818501915085601f83011261048657600080fd5b81358181111561049857610498610411565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104de576104de610411565b816040528281528860208487010111156104f757600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b8281526000825161053181602085016020870161037d565b919091016020019392505050565b6000825161055181846020870161037d565b9190910192915050565b6000815160208301517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000808216935060148310156105a35780818460140360031b1b83161693505b50505091905056fea164736f6c634300080f000a", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 580000, + "intent": "ConditionalDeployer Deployment", + "to": "0x4e59b44847b379578588920cA78FbF26c0B4956C" + }, + { + "data": "0x3659cfe6000000000000000000000000906835344844979ffd3a752eaa23728d513db00b", + "from": "0x0000000000000000000000000000000000000000", + "gasLimit": 77000, + "intent": "Upgrade ConditionalDeployer Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000048a608060405234801561001057600080fd5b5061046a806100206000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c8063a6ed563e11610076578063bd02d0f51161005b578063bd02d0f51461018e578063ca446dd9146101b8578063e2a4853a1461011557600080fd5b8063a6ed563e1461018e578063abfdcced146101aa57600080fd5b80634e91db08116100a75780634e91db081461011557806354fd4d50146101275780637ae1cfca1461017057600080fd5b80630528afe2146100c357806321f8a721146100d8575b600080fd5b6100d66100d1366004610239565b6101c6565b005b6100eb6100e63660046102ae565b610229565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6100d66101233660046102c7565b9055565b6101636040518060400160405280600581526020017f312e322e3200000000000000000000000000000000000000000000000000000081525081565b60405161010c91906102e9565b61017e6100e63660046102ae565b604051901515815260200161010c565b61019c6100e63660046102ae565b60405190815260200161010c565b6100d661012336600461035c565b6100d6610123366004610391565b8060005b81811015610223576102118484838181106101e7576101e76103cf565b90506040020160000135858584818110610203576102036103cf565b905060400201602001359055565b8061021b816103fe565b9150506101ca565b50505050565b6000610233825490565b92915050565b6000806020838503121561024c57600080fd5b823567ffffffffffffffff8082111561026457600080fd5b818501915085601f83011261027857600080fd5b81358181111561028757600080fd5b8660208260061b850101111561029c57600080fd5b60209290920196919550909350505050565b6000602082840312156102c057600080fd5b5035919050565b600080604083850312156102da57600080fd5b50508035926020909101359150565b600060208083528351808285015260005b81811015610316578581018301518582016040015282016102fa565b81811115610328576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000806040838503121561036f57600080fd5b823591506020830135801515811461038657600080fd5b809150509250929050565b600080604083850312156103a457600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811461038657600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610456577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b506001019056fea164736f6c634300080f000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 498000, + "intent": "Deploy StorageSetter Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000022d3608060405234801561001057600080fd5b5061001961001e565b6100eb565b600054600160a81b900460ff161561008c5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff600160a01b909104811610156100e9576000805460ff60a01b191660ff60a01b17905560405160ff81527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6121d880620000fb6000396000f3fe6080604052600436106101a15760003560e01c80638cbeeef2116100e1578063c4d66de81161008a578063db505d8011610064578063db505d8014610451578063ddd5a40f1461047e578063e46e245a14610494578063ecc70428146104a957600080fd5b8063c4d66de814610409578063d764ad0b14610429578063dad544e01461043c57600080fd5b8063a7119869116100bb578063a71198691461035e578063b1b1b209146103b9578063b28ade25146103e957600080fd5b80638cbeeef2146102905780639fce812c1461035e578063a4e7f8bd1461038957600080fd5b80633f827a5a1161014e5780635644cfdf116101285780635644cfdf146102fc5780635c975abb146103125780636e296e451461033257806383a740741461034757600080fd5b80633f827a5a146102685780634c1d6a691461029057806354fd4d50146102a657600080fd5b80632f7d39221161017f5780632f7d3922146102035780633dbb202b146102195780633e47158c1461022e57600080fd5b8063028f85f7146101a65780630c568498146101d95780632828d7e8146101ee575b600080fd5b3480156101b257600080fd5b506101bb601081565b60405167ffffffffffffffff90911681526020015b60405180910390f35b3480156101e557600080fd5b506101bb603f81565b3480156101fa57600080fd5b506101bb604081565b34801561020f57600080fd5b506101bb61520881565b61022c610227366004611bb4565b61050e565b005b34801561023a57600080fd5b506102436107a1565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d0565b34801561027457600080fd5b5061027d600181565b60405161ffff90911681526020016101d0565b34801561029c57600080fd5b506101bb619c4081565b3480156102b257600080fd5b506102ef6040518060400160405280600581526020017f322e322e3100000000000000000000000000000000000000000000000000000081525081565b6040516101d09190611c86565b34801561030857600080fd5b506101bb61138881565b34801561031e57600080fd5b5060005b60405190151581526020016101d0565b34801561033e57600080fd5b506102436109ac565b34801561035357600080fd5b506101bb62030d4081565b34801561036a57600080fd5b5060cf5473ffffffffffffffffffffffffffffffffffffffff16610243565b34801561039557600080fd5b506103226103a4366004611c99565b60ce6020526000908152604090205460ff1681565b3480156103c557600080fd5b506103226103d4366004611c99565b60cb6020526000908152604090205460ff1681565b3480156103f557600080fd5b506101bb610404366004611ce1565b610a98565b34801561041557600080fd5b5061022c610424366004611dc1565b610b59565b61022c610437366004611dde565b610d60565b34801561044857600080fd5b50610243611645565b34801561045d57600080fd5b5060cf546102439073ffffffffffffffffffffffffffffffffffffffff1681565b34801561048a57600080fd5b506101bb61010481565b3480156104a057600080fd5b506101bb602881565b3480156104b557600080fd5b5061050060cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016101d0565b60cf54604080516020601f86018190048102820181019092528481526106769273ffffffffffffffffffffffffffffffffffffffff169161056c91908790879081908401838280828437600092019190915250879250610a98915050565b347fd764ad0b000000000000000000000000000000000000000000000000000000006105d860cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b338a34898c8c6040516024016105f49796959493929190611ead565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526116c2565b8373ffffffffffffffffffffffffffffffffffffffff167fcb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52a3385856106fb60cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b8660405161070d959493929190611f0c565b60405180910390a260405134815233907f8ebb2ec2465bdb2a06a66fc37a0963af8a2a6a1479d81d56fdb8cbb98096d5469060200160405180910390a2505060cd80547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808216600101167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b6000806107cc7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107ef57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026108329190611f89565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061088d906060015b604051602081830303815290604052805190602001205490565b146108c4576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091526000906108e690606001610873565b905073ffffffffffffffffffffffffffffffffffffffff81161561097a578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561094f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109739190611fc6565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60cc5460009073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff215301610a7b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f43726f7373446f6d61696e4d657373656e6765723a2078446f6d61696e4d657360448201527f7361676553656e646572206973206e6f7420736574000000000000000000000060648201526084015b60405180910390fd5b5060cc5473ffffffffffffffffffffffffffffffffffffffff1690565b600080603f610aae604063ffffffff8616611fe3565b610ab89190612013565b611388619c40610acb8162030d40612061565b610ad59190612061565b610adf9190612061565b610ae99190612061565b9050600061010467ffffffffffffffff168551610b06919061208d565b9050610b44610b16601083611fe3565b610b209084612061565b67ffffffffffffffff16610b35602884611fe3565b67ffffffffffffffff16611750565b610b5090615208612061565b95945050505050565b6000547501000000000000000000000000000000000000000000900460ff1615808015610ba4575060005460017401000000000000000000000000000000000000000090910460ff16105b80610bd65750303b158015610bd6575060005474010000000000000000000000000000000000000000900460ff166001145b610c62576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610a72565b600080547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790558015610ce857600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1675010000000000000000000000000000000000000000001790555b610cf0611769565b610cf9826117ec565b8015610d5c57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b60f087901c60028110610e1b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f43726f7373446f6d61696e4d657373656e6765723a206f6e6c7920766572736960448201527f6f6e2030206f722031206d657373616765732061726520737570706f7274656460648201527f20617420746869732074696d6500000000000000000000000000000000000000608482015260a401610a72565b8061ffff16600003610f10576000610e6c878986868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508f9250611928915050565b600081815260cb602052604090205490915060ff1615610f0e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f43726f7373446f6d61696e4d657373656e6765723a206c65676163792077697460448201527f6864726177616c20616c72656164792072656c617965640000000000000000006064820152608401610a72565b505b6000610f56898989898989898080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061194792505050565b9050610f9f60cf54337fffffffffffffffffffffffffeeeeffffffffffffffffffffffffffffffffeeef0173ffffffffffffffffffffffffffffffffffffffff90811691161490565b15610fd757853414610fb357610fb36120a5565b600081815260ce602052604090205460ff1615610fd257610fd26120a5565b611129565b341561108b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605060248201527f43726f7373446f6d61696e4d657373656e6765723a2076616c7565206d75737460448201527f206265207a65726f20756e6c657373206d6573736167652069732066726f6d2060648201527f612073797374656d206164647265737300000000000000000000000000000000608482015260a401610a72565b600081815260ce602052604090205460ff16611129576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f43726f7373446f6d61696e4d657373656e6765723a206d65737361676520636160448201527f6e6e6f74206265207265706c61796564000000000000000000000000000000006064820152608401610a72565b6111328761196a565b156111e5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f43726f7373446f6d61696e4d657373656e6765723a2063616e6e6f742073656e60448201527f64206d65737361676520746f20626c6f636b65642073797374656d206164647260648201527f6573730000000000000000000000000000000000000000000000000000000000608482015260a401610a72565b600081815260cb602052604090205460ff1615611284576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f43726f7373446f6d61696e4d657373656e6765723a206d65737361676520686160448201527f7320616c7265616479206265656e2072656c61796564000000000000000000006064820152608401610a72565b6112a585611296611388619c40612061565b67ffffffffffffffff166119bf565b15806112cb575060cc5473ffffffffffffffffffffffffffffffffffffffff1661dead14155b156113e457600081815260ce602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555182917f99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f91a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff32016113dd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f43726f7373446f6d61696e4d657373656e6765723a206661696c656420746f2060448201527f72656c6179206d657373616765000000000000000000000000000000000000006064820152608401610a72565b505061163c565b60cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8a16179055600061147588619c405a61143891906120d4565b8988888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506119dd92505050565b60cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790559050801561152b57600082815260cb602052604090205460ff16156114c8576114c86120a5565b600082815260cb602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183917f4641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133c91a2611638565b600082815260ce602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183917f99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f91a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3201611638576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f43726f7373446f6d61696e4d657373656e6765723a206661696c656420746f2060448201527f72656c6179206d657373616765000000000000000000000000000000000000006064820152608401610a72565b5050505b50505050505050565b600061164f6107a1565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611699573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116bd9190611fc6565b905090565b6040517fc2b3e5ac0000000000000000000000000000000000000000000000000000000081527342000000000000000000000000000000000000169063c2b3e5ac908490611718908890889087906004016120eb565b6000604051808303818588803b15801561173157600080fd5b505af1158015611745573d6000803e3d6000fd5b505050505050505050565b6000818310156117605781611762565b825b9392505050565b336117726107a1565b73ffffffffffffffffffffffffffffffffffffffff16141580156117b357503361179a611645565b73ffffffffffffffffffffffffffffffffffffffff1614155b156117ea576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6000547501000000000000000000000000000000000000000000900460ff16611897576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610a72565b60cc5473ffffffffffffffffffffffffffffffffffffffff166118e15760cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790555b60cf80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6000611936858585856119f5565b805190602001209050949350505050565b6000611957878787878787611a8e565b8051906020012090509695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82163014806119b9575073ffffffffffffffffffffffffffffffffffffffff8216734200000000000000000000000000000000000016145b92915050565b600080603f83619c4001026040850201603f5a021015949350505050565b6000806000835160208501868989f195945050505050565b606084848484604051602401611a0e949392919061212a565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fcbd4ece9000000000000000000000000000000000000000000000000000000001790529050949350505050565b6060868686868686604051602401611aab96959493929190612174565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fd764ad0b0000000000000000000000000000000000000000000000000000000017905290509695505050505050565b73ffffffffffffffffffffffffffffffffffffffff81168114611b4f57600080fd5b50565b60008083601f840112611b6457600080fd5b50813567ffffffffffffffff811115611b7c57600080fd5b602083019150836020828501011115611b9457600080fd5b9250929050565b803563ffffffff81168114611baf57600080fd5b919050565b60008060008060608587031215611bca57600080fd5b8435611bd581611b2d565b9350602085013567ffffffffffffffff811115611bf157600080fd5b611bfd87828801611b52565b9094509250611c10905060408601611b9b565b905092959194509250565b6000815180845260005b81811015611c4157602081850181015186830182015201611c25565b81811115611c53576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006117626020830184611c1b565b600060208284031215611cab57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060408385031215611cf457600080fd5b823567ffffffffffffffff80821115611d0c57600080fd5b818501915085601f830112611d2057600080fd5b813581811115611d3257611d32611cb2565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715611d7857611d78611cb2565b81604052828152886020848701011115611d9157600080fd5b826020860160208301376000602084830101528096505050505050611db860208401611b9b565b90509250929050565b600060208284031215611dd357600080fd5b813561176281611b2d565b600080600080600080600060c0888a031215611df957600080fd5b873596506020880135611e0b81611b2d565b95506040880135611e1b81611b2d565b9450606088013593506080880135925060a088013567ffffffffffffffff811115611e4557600080fd5b611e518a828b01611b52565b989b979a50959850939692959293505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b878152600073ffffffffffffffffffffffffffffffffffffffff808916602084015280881660408401525085606083015263ffffffff8516608083015260c060a0830152611eff60c083018486611e64565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff86168152608060208201526000611f3c608083018688611e64565b905083604083015263ffffffff831660608301529695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611fc157611fc1611f5a565b500290565b600060208284031215611fd857600080fd5b815161176281611b2d565b600067ffffffffffffffff8083168185168183048111821515161561200a5761200a611f5a565b02949350505050565b600067ffffffffffffffff80841680612055577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b92169190910492915050565b600067ffffffffffffffff80831681851680830382111561208457612084611f5a565b01949350505050565b600082198211156120a0576120a0611f5a565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b6000828210156120e6576120e6611f5a565b500390565b73ffffffffffffffffffffffffffffffffffffffff8416815267ffffffffffffffff83166020820152606060408201526000610b506060830184611c1b565b600073ffffffffffffffffffffffffffffffffffffffff8087168352808616602084015250608060408301526121636080830185611c1b565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a08301526121bf60c0830184611c1b565b9897505050505050505056fea164736f6c634300080f000a00000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 3129000, + "intent": "Deploy L2CrossDomainMessenger Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001ec8608060405234801561001057600080fd5b50611ea8806100206000396000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806368d5dca6116100e3578063c59859181161008c578063f45e65d811610066578063f45e65d8146102fc578063f820614014610304578063fe173b971461029357600080fd5b8063c5985918146102ce578063de26c4a1146102d6578063f1c7a58b146102e957600080fd5b8063960e3a23116100bd578063960e3a23146102a1578063b3d72079146102b3578063b54501bc146102bb57600080fd5b806368d5dca6146102765780636ef25c3a146102935780638e98b1061461029957600080fd5b80632e0f2625116101455780634ef6e2241161011f5780634ef6e22414610218578063519b4bd31461022557806354fd4d501461022d57600080fd5b80632e0f2625146101f6578063313ce567146101fe57806349948e0e1461020557600080fd5b806322b90ab31161017657806322b90ab3146101d1578063275aedd2146101db578063291b0383146101ee57600080fd5b80630c18c16214610192578063105d0b81146101ad575b600080fd5b61019a61030c565b6040519081526020015b60405180910390f35b6000546101c1906301000000900460ff1681565b60405190151581526020016101a4565b6101d961042d565b005b61019a6101e93660046118fa565b6105b6565b6101d9610776565b61019a600681565b600661019a565b61019a610213366004611942565b61099e565b6000546101c19060ff1681565b61019a6109db565b6102696040518060400160405280600581526020017f312e362e3000000000000000000000000000000000000000000000000000000081525081565b6040516101a49190611a11565b61027e610a3c565b60405163ffffffff90911681526020016101a4565b4861019a565b6101d9610ac1565b6000546101c190610100900460ff1681565b6101d9610cbb565b6000546101c19062010000900460ff1681565b61027e610ec2565b61019a6102e4366004611942565b610f23565b61019a6102f73660046118fa565b61101d565b61019a6110f1565b61019a6111e4565b6000805460ff16156103a5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f47617350726963654f7261636c653a206f76657268656164282920697320646560448201527f707265636174656400000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610404573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104289190611a84565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e2073657420697345636f746f6e6520666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161039c565b60005460ff1615610589576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a2045636f746f6e6520616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161039c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000805462010000900460ff166105cf57506000919050565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16634d5d9a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610630573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106549190611a9d565b63ffffffff169050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166316d3bc7f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106e19190611ac3565b67ffffffffffffffff169050600060039054906101000a900460ff161561072a578061070d8386611b1c565b610718906064611b1c565b6107229190611b59565b949350505050565b610722620f424083860286810485148715177fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01176107699190611b71565b8281019081106000031790565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461083f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973497374686d757320666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161039c565b600054610100900460ff166108d6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20497374686d75732063616e206f6e6c7960448201527f2062652061637469766174656420616674657220466a6f726400000000000000606482015260840161039c565b60005462010000900460ff161561096f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a20497374686d757320616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161039c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff1662010000179055565b60008054610100900460ff16156109be576109b882611245565b92915050565b60005460ff16156109d2576109b882611264565b6109b882611308565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16635cf249696040518163ffffffff1660e01b8152600401602060405180830381865afa158015610404573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166368d5dca66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a9d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104289190611a9d565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610b64576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973466a6f726420666c616700606482015260840161039c565b60005460ff16610bf6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20466a6f72642063616e206f6e6c79206260448201527f65206163746976617465642061667465722045636f746f6e6500000000000000606482015260840161039c565b600054610100900460ff1615610c8d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f47617350726963654f7261636c653a20466a6f726420616c726561647920616360448201527f7469766500000000000000000000000000000000000000000000000000000000606482015260840161039c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100179055565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610d6057604080517f08c379a00000000000000000000000000000000000000000000000000000000081526020600482015260248101919091527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e207365742069734a6f7669616e20666c6167606482015260840161039c565b60005462010000900460ff16610df8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f47617350726963654f7261636c653a204a6f7669616e2063616e206f6e6c792060448201527f62652061637469766174656420616674657220497374686d7573000000000000606482015260840161039c565b6000546301000000900460ff1615610e92576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f47617350726963654f7261636c653a204a6f7669616e20616c7265616479206160448201527f6374697665000000000000000000000000000000000000000000000000000000606482015260840161039c565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffff166301000000179055565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663c59859186040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a9d573d6000803e3d6000fd5b60008054610100900460ff1615610f6a57620f4240610f55610f448461145c565b51610f50906044611b59565b611779565b610f60906010611b1c565b6109b89190611b71565b6000610f75836117d8565b60005490915060ff1615610f895792915050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fe8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061100c9190611a84565b6110169082611b59565b9392505050565b60008054610100900460ff166110b5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f47617350726963654f7261636c653a206765744c314665655570706572426f7560448201527f6e64206f6e6c7920737570706f72747320466a6f726400000000000000000000606482015260840161039c565b60006110c2836044611b59565b905060006110d160ff83611b71565b6110db9083611b59565b6110e6906010611b59565b905061072281611868565b6000805460ff1615611185576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a207363616c61722829206973206465707260448201527f6563617465640000000000000000000000000000000000000000000000000000606482015260840161039c565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa158015610404573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663f82061406040518163ffffffff1660e01b8152600401602060405180830381865afa158015610404573d6000803e3d6000fd5b60006109b86112538361145c565b5161125f906044611b59565b611868565b600080611270836117d8565b9050600061127c6109db565b611284610ec2565b61128f906010611bac565b63ffffffff1661129f9190611b1c565b905060006112ab6111e4565b6112b3610a3c565b63ffffffff166112c39190611b1c565b905060006112d18284611b59565b6112db9085611b1c565b90506112e96006600a611cf8565b6112f4906010611b1c565b6112fe9082611b71565b9695505050505050565b600080611314836117d8565b9050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa158015611377573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061139b9190611a84565b6113a36109db565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015611402573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114269190611a84565b6114309085611b59565b61143a9190611b1c565b6114449190611b1c565b90506114526006600a611cf8565b6107229082611b71565b60606115eb565b818153600101919050565b600082840393505b838110156110165782810151828201511860001a1590930292600101611476565b825b602082106114e35782516114ae601f83611463565b52602092909201917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090910190602101611499565b81156110165782516114f86001840383611463565b520160010192915050565b60006001830392505b6101078210611544576115368360ff1661153160fd6115318760081c60e00189611463565b611463565b93506101068203915061150c565b600782106115715761156a8360ff16611531600785036115318760081c60e00189611463565b9050611016565b6107228360ff166115318560081c8560051b0187611463565b6115e38282036115c76115b784600081518060001a8160011a60081b178160021a60101b17915050919050565b639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b6180003860405139618000604051016020830180600d8551820103826002015b8181101561171e576000805b50508051604051600082901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b9091189091528401908183039084841061167357506116ae565b600184019350611fff82116116a8578251600081901a600182901a60081b1760029190911a60101b1781036116a857506116ae565b50611617565b8383106116bc57505061171e565b600183039250858311156116da576116d78787888603611497565b96505b6116ee60098501600385016003850161146e565b91506116fb878284611503565b9650506117138461170e8684860161158a565b61158a565b91505080935061160b565b50506117308383848851850103611497565b925050506040519150618000820180820391508183526020830160005b8381101561176557828101518282015260200161174d565b506000920191825250602001604052919050565b60008061178983620cc394611b1c565b6117b3907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd763200611d04565b90506117c36064620f4240611d78565b8112156109b8576110166064620f4240611d78565b80516000908190815b8181101561185b578481815181106117fb576117fb611e34565b01602001517fff000000000000000000000000000000000000000000000000000000000000001660000361183b57611834600484611b59565b9250611849565b611846601084611b59565b92505b8061185381611e63565b9150506117e1565b5061072282610440611b59565b60008061187483611779565b905060006118806111e4565b611888610a3c565b63ffffffff166118989190611b1c565b6118a06109db565b6118a8610ec2565b6118b3906010611bac565b63ffffffff166118c39190611b1c565b6118cd9190611b59565b90506118db60066002611b1c565b6118e690600a611cf8565b6118f08284611b1c565b6107229190611b71565b60006020828403121561190c57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561195457600080fd5b813567ffffffffffffffff8082111561196c57600080fd5b818401915084601f83011261198057600080fd5b81358181111561199257611992611913565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156119d8576119d8611913565b816040528281528760208487010111156119f157600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b81811015611a3e57858101830151858201604001528201611a22565b81811115611a50576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b600060208284031215611a9657600080fd5b5051919050565b600060208284031215611aaf57600080fd5b815163ffffffff8116811461101657600080fd5b600060208284031215611ad557600080fd5b815167ffffffffffffffff8116811461101657600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611b5457611b54611aed565b500290565b60008219821115611b6c57611b6c611aed565b500190565b600082611ba7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600063ffffffff80831681851681830481118215151615611bcf57611bcf611aed565b02949350505050565b600181815b80851115611c3157817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115611c1757611c17611aed565b80851615611c2457918102915b93841c9390800290611bdd565b509250929050565b600082611c48575060016109b8565b81611c55575060006109b8565b8160018114611c6b5760028114611c7557611c91565b60019150506109b8565b60ff841115611c8657611c86611aed565b50506001821b6109b8565b5060208310610133831016604e8410600b8410161715611cb4575081810a6109b8565b611cbe8383611bd8565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115611cf057611cf0611aed565b029392505050565b60006110168383611c39565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03841381151615611d3e57611d3e611aed565b827f8000000000000000000000000000000000000000000000000000000000000000038412811615611d7257611d72611aed565b50500190565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615611db957611db9611aed565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615611df457611df4611aed565b60008712925087820587128484161615611e1057611e10611aed565b87850587128184161615611e2657611e26611aed565b505050929093029392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611e9457611e94611aed565b506001019056fea164736f6c634300080f000a000000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 2762000, + "intent": "Deploy GasPriceOracle Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000002f3d60806040523480156200001157600080fd5b506200001c62000022565b620000e4565b600054610100900460ff16156200008f5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff9081161015620000e2576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b612e4980620000f46000396000f3fe6080604052600436106101485760003560e01c80635c975abb116100c0578063a3a7954811610074578063c89701a211610059578063c89701a214610459578063dad544e014610486578063e11013dd1461049b57600080fd5b8063a3a7954814610426578063c4d66de81461043957600080fd5b806387087623116100a557806387087623146103955780638f601f66146103b5578063927ede2d146103fb57600080fd5b80635c975abb146103795780637f46ddb21461027a57600080fd5b806336c717c1116101175780633e47158c116100fc5780633e47158c146102f8578063540abf731461030d57806354fd4d501461032d57600080fd5b806336c717c11461027a5780633cb747bf146102cb57600080fd5b80630166a07a1461022157806309fc8843146102415780631635f5fd1461025457806332b7006d1461026757600080fd5b3661021c576101556104ae565b6101e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084015b60405180910390fd5b61021a73deaddeaddeaddeaddeaddeaddeaddeaddead000033333462030d40604051806020016040528060008152506104eb565b005b600080fd5b34801561022d57600080fd5b5061021a61023c3660046127ff565b6105c6565b61021a61024f3660046128b0565b610968565b61021a610262366004612903565b610a44565b61021a610275366004612976565b610e96565b34801561028657600080fd5b5060045473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b3480156102d757600080fd5b506003546102a19073ffffffffffffffffffffffffffffffffffffffff1681565b34801561030457600080fd5b506102a1610f75565b34801561031957600080fd5b5061021a6103283660046129ca565b611180565b34801561033957600080fd5b50604080518082018252600681527f312e31332e320000000000000000000000000000000000000000000000000000602082015290516102c29190612ab7565b34801561038557600080fd5b50604051600081526020016102c2565b3480156103a157600080fd5b5061021a6103b0366004612aca565b6111c5565b3480156103c157600080fd5b506103ed6103d0366004612b4d565b600260209081526000928352604080842090915290825290205481565b6040519081526020016102c2565b34801561040757600080fd5b5060035473ffffffffffffffffffffffffffffffffffffffff166102a1565b61021a610434366004612aca565b61129e565b34801561044557600080fd5b5061021a610454366004612b86565b6112e2565b34801561046557600080fd5b506004546102a19073ffffffffffffffffffffffffffffffffffffffff1681565b34801561049257600080fd5b506102a1611493565b61021a6104a9366004612ba3565b611510565b60003233036104bd5750600190565b333b6017036104e557604051602081016040526020600082333c5160e81c62ef010014905090565b50600090565b7fffffffffffffffffffffffff215221522152215221522152215221522153000073ffffffffffffffffffffffffffffffffffffffff87160161053a576105358585858585611559565b6105be565b60008673ffffffffffffffffffffffffffffffffffffffff1663c01e1bd66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610587573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ab9190612c06565b90506105bc87828888888888611723565b505b505050505050565b60035473ffffffffffffffffffffffffffffffffffffffff1633148015610699575060048054600354604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff938416949390921692636e296e459282820192602092908290030181865afa15801561065d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106819190612c06565b73ffffffffffffffffffffffffffffffffffffffff16145b61074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20746865206f7468657220627269646760648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a4016101dd565b61075487611adc565b156108a2576107638787611b3e565b610815576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f5374616e646172644272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433230206c60648201527f6f63616c20746f6b656e00000000000000000000000000000000000000000000608482015260a4016101dd565b6040517f40c10f1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8581166004830152602482018590528816906340c10f1990604401600060405180830381600087803b15801561088557600080fd5b505af1158015610899573d6000803e3d6000fd5b50505050610924565b73ffffffffffffffffffffffffffffffffffffffff8088166000908152600260209081526040808320938a16835292905220546108e0908490612c52565b73ffffffffffffffffffffffffffffffffffffffff8089166000818152600260209081526040808320948c1683529390529190912091909155610924908585611c5e565b6105bc878787878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611d3292505050565b6109706104ae565b6109fc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101dd565b610a3f3333348686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061155992505050565b505050565b60035473ffffffffffffffffffffffffffffffffffffffff1633148015610b17575060048054600354604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff938416949390921692636e296e459282820192602092908290030181865afa158015610adb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aff9190612c06565b73ffffffffffffffffffffffffffffffffffffffff16145b610bc9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20746865206f7468657220627269646760648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a4016101dd565b823414610c58576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f5374616e646172644272696467653a20616d6f756e742073656e7420646f657360448201527f206e6f74206d6174636820616d6f756e7420726571756972656400000000000060648201526084016101dd565b3073ffffffffffffffffffffffffffffffffffffffff851603610cfd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f5374616e646172644272696467653a2063616e6e6f742073656e6420746f207360448201527f656c66000000000000000000000000000000000000000000000000000000000060648201526084016101dd565b60035473ffffffffffffffffffffffffffffffffffffffff90811690851603610da8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f5374616e646172644272696467653a2063616e6e6f742073656e6420746f206d60448201527f657373656e67657200000000000000000000000000000000000000000000000060648201526084016101dd565b610dea85858585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611dc092505050565b6000610e07855a8660405180602001604052806000815250611e61565b9050806105be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f5374616e646172644272696467653a20455448207472616e736665722066616960448201527f6c6564000000000000000000000000000000000000000000000000000000000060648201526084016101dd565b610e9e6104ae565b610f2a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101dd565b610f6e853333878787878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506104eb92505050565b5050505050565b600080610fa07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff811615610fc357919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026110069190612c69565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000009190911790611061906060015b604051602081830303815290604052805190602001205490565b14611098576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091526000906110ba90606001611047565b905073ffffffffffffffffffffffffffffffffffffffff81161561114e578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611123573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111479190612c06565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6105bc87873388888888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061172392505050565b6111cd6104ae565b611259576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101dd565b6105be86863333888888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061172392505050565b6105be863387878787878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506104eb92505050565b600054610100900460ff16158080156113025750600054600160ff909116105b8061131c5750303b15801561131c575060005460ff166001145b6113a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016101dd565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561140657600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b61140e611e79565b61142c73420000000000000000000000000000000000000783611efc565b801561148f57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b600061149d610f75565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156114e7573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061150b9190612c06565b905090565b6115533385348686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061155992505050565b50505050565b8234146115e8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f5374616e646172644272696467653a206272696467696e6720455448206d757360448201527f7420696e636c7564652073756666696369656e74204554482076616c7565000060648201526084016101dd565b6115f485858584611fe6565b60035460045460405173ffffffffffffffffffffffffffffffffffffffff92831692633dbb202b9287929116907f1635f5fd0000000000000000000000000000000000000000000000000000000090611657908b908b9086908a90602401612ca6565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e086901b90921682526116ea92918890600401612cef565b6000604051808303818588803b15801561170357600080fd5b505af1158015611717573d6000803e3d6000fd5b50505050505050505050565b34156117b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f5374616e646172644272696467653a2063616e6e6f742073656e642076616c7560448201527f650000000000000000000000000000000000000000000000000000000000000060648201526084016101dd565b6117ba87611adc565b15611908576117c98787611b3e565b61187b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f5374616e646172644272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433230206c60648201527f6f63616c20746f6b656e00000000000000000000000000000000000000000000608482015260a4016101dd565b6040517f9dc29fac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015260248201859052881690639dc29fac90604401600060405180830381600087803b1580156118eb57600080fd5b505af11580156118ff573d6000803e3d6000fd5b5050505061199c565b61192a73ffffffffffffffffffffffffffffffffffffffff8816863086612087565b73ffffffffffffffffffffffffffffffffffffffff8088166000908152600260209081526040808320938a1683529290522054611968908490612d34565b73ffffffffffffffffffffffffffffffffffffffff8089166000908152600260209081526040808320938b16835292905220555b6119aa8787878787866120e5565b60035460045460405173ffffffffffffffffffffffffffffffffffffffff92831692633dbb202b9216907f0166a07a0000000000000000000000000000000000000000000000000000000090611a0e908b908d908c908c908c908b90602401612d4c565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e085901b9092168252611aa192918790600401612cef565b600060405180830381600087803b158015611abb57600080fd5b505af1158015611acf573d6000803e3d6000fd5b5050505050505050505050565b6000611b08827f1d1d8b6300000000000000000000000000000000000000000000000000000000612173565b80611b385750611b38827fec4fc8e300000000000000000000000000000000000000000000000000000000612173565b92915050565b6000611b6a837f1d1d8b6300000000000000000000000000000000000000000000000000000000612173565b15611c13578273ffffffffffffffffffffffffffffffffffffffff1663c01e1bd66040518163ffffffff1660e01b8152600401602060405180830381865afa158015611bba573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bde9190612c06565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16149050611b38565b8273ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015611bba573d6000803e3d6000fd5b60405173ffffffffffffffffffffffffffffffffffffffff8316602482015260448101829052610a3f9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152612196565b8373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167fb0444523268717a02698be47d0803aa7468c00acbed2f8bd93a0459cde61dd89868686604051611daa93929190612da7565b60405180910390a46105be8686868686866122a2565b8373ffffffffffffffffffffffffffffffffffffffff1673deaddeaddeaddeaddeaddeaddeaddeaddead000073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fb0444523268717a02698be47d0803aa7468c00acbed2f8bd93a0459cde61dd89868686604051611e4d93929190612da7565b60405180910390a46115538484848461232a565b6000806000835160208501868989f195945050505050565b33611e82610f75565b73ffffffffffffffffffffffffffffffffffffffff1614158015611ec3575033611eaa611493565b73ffffffffffffffffffffffffffffffffffffffff1614155b15611efa576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b600054610100900460ff16611f93576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016101dd565b6003805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560048054929093169116179055565b8373ffffffffffffffffffffffffffffffffffffffff1673deaddeaddeaddeaddeaddeaddeaddeaddead000073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f73d170910aba9e6d50b102db522b1dbcd796216f5128b445aa2135272886497e86868660405161207393929190612da7565b60405180910390a461155384848484612397565b60405173ffffffffffffffffffffffffffffffffffffffff808516602483015283166044820152606481018290526115539085907f23b872dd0000000000000000000000000000000000000000000000000000000090608401611cb0565b8373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167f73d170910aba9e6d50b102db522b1dbcd796216f5128b445aa2135272886497e86868660405161215d93929190612da7565b60405180910390a46105be8686868686866123f6565b600061217e8361246e565b801561218f575061218f83836124d2565b9392505050565b60006121f8826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166125a19092919063ffffffff16565b805190915015610a3f57808060200190518101906122169190612de5565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016101dd565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167fd59c65b35445225835c83f50b6ede06a7be047d22e357073e250d9af537518cd86868660405161231a93929190612da7565b60405180910390a4505050505050565b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f31b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83d8484604051612389929190612e07565b60405180910390a350505050565b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f2849b43074093a05396b6f2a937dee8565b15a48a7b3d4bffb732a5017380af58484604051612389929190612e07565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167f7ff126db8024424bbfd9826e8ab82ff59136289ea440b04b39a0df1b03b9cabf86868660405161231a93929190612da7565b600061249a827f01ffc9a7000000000000000000000000000000000000000000000000000000006124d2565b8015611b3857506124cb827fffffffff000000000000000000000000000000000000000000000000000000006124d2565b1592915050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d9150600051905082801561258a575060208210155b80156125965750600081115b979650505050505050565b60606125b084846000856125b8565b949350505050565b60608247101561264a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016101dd565b73ffffffffffffffffffffffffffffffffffffffff85163b6126c8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101dd565b6000808673ffffffffffffffffffffffffffffffffffffffff1685876040516126f19190612e20565b60006040518083038185875af1925050503d806000811461272e576040519150601f19603f3d011682016040523d82523d6000602084013e612733565b606091505b50915091506125968282866060831561274d57508161218f565b82511561275d5782518084602001fd5b816040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101dd9190612ab7565b73ffffffffffffffffffffffffffffffffffffffff811681146127b357600080fd5b50565b60008083601f8401126127c857600080fd5b50813567ffffffffffffffff8111156127e057600080fd5b6020830191508360208285010111156127f857600080fd5b9250929050565b600080600080600080600060c0888a03121561281a57600080fd5b873561282581612791565b9650602088013561283581612791565b9550604088013561284581612791565b9450606088013561285581612791565b93506080880135925060a088013567ffffffffffffffff81111561287857600080fd5b6128848a828b016127b6565b989b979a50959850939692959293505050565b803563ffffffff811681146128ab57600080fd5b919050565b6000806000604084860312156128c557600080fd5b6128ce84612897565b9250602084013567ffffffffffffffff8111156128ea57600080fd5b6128f6868287016127b6565b9497909650939450505050565b60008060008060006080868803121561291b57600080fd5b853561292681612791565b9450602086013561293681612791565b935060408601359250606086013567ffffffffffffffff81111561295957600080fd5b612965888289016127b6565b969995985093965092949392505050565b60008060008060006080868803121561298e57600080fd5b853561299981612791565b9450602086013593506129ae60408701612897565b9250606086013567ffffffffffffffff81111561295957600080fd5b600080600080600080600060c0888a0312156129e557600080fd5b87356129f081612791565b96506020880135612a0081612791565b95506040880135612a1081612791565b945060608801359350612a2560808901612897565b925060a088013567ffffffffffffffff81111561287857600080fd5b60005b83811015612a5c578181015183820152602001612a44565b838111156115535750506000910152565b60008151808452612a85816020860160208601612a41565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061218f6020830184612a6d565b60008060008060008060a08789031215612ae357600080fd5b8635612aee81612791565b95506020870135612afe81612791565b945060408701359350612b1360608801612897565b9250608087013567ffffffffffffffff811115612b2f57600080fd5b612b3b89828a016127b6565b979a9699509497509295939492505050565b60008060408385031215612b6057600080fd5b8235612b6b81612791565b91506020830135612b7b81612791565b809150509250929050565b600060208284031215612b9857600080fd5b813561218f81612791565b60008060008060608587031215612bb957600080fd5b8435612bc481612791565b9350612bd260208601612897565b9250604085013567ffffffffffffffff811115612bee57600080fd5b612bfa878288016127b6565b95989497509550505050565b600060208284031215612c1857600080fd5b815161218f81612791565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015612c6457612c64612c23565b500390565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615612ca157612ca1612c23565b500290565b600073ffffffffffffffffffffffffffffffffffffffff808716835280861660208401525083604083015260806060830152612ce56080830184612a6d565b9695505050505050565b73ffffffffffffffffffffffffffffffffffffffff84168152606060208201526000612d1e6060830185612a6d565b905063ffffffff83166040830152949350505050565b60008219821115612d4757612d47612c23565b500190565b600073ffffffffffffffffffffffffffffffffffffffff80891683528088166020840152808716604084015280861660608401525083608083015260c060a0830152612d9b60c0830184612a6d565b98975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff84168152826020820152606060408201526000612ddc6060830184612a6d565b95945050505050565b600060208284031215612df757600080fd5b8151801515811461218f57600080fd5b8281526040602082015260006125b06040830184612a6d565b60008251612e32818460208701612a41565b919091019291505056fea164736f6c634300080f000a000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 4193000, + "intent": "Deploy L2StandardBridge Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000010956080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610fbf806100d65f395ff3fe6080604052600436106100f2575f3560e01c80638312f14911610087578063d0e12f9011610057578063d0e12f90146102e9578063d3e5792b14610318578063d4ff9218146100fd578063dad544e01461032c575f80fd5b80638312f1491461028257806384411d651461029757806385b5b14d146102ab578063b49dc741146102ca575f80fd5b80633e47158c116100c25780633e47158c146101af57806354fd4d50146101c357806366d003ac1461021857806382356d8a14610244575f80fd5b80630d9019e1146100fd578063307f29621461014d5780633bbed4a01461016e5780633ccfd60b1461018d575f80fd5b366100f957005b5f80fd5b348015610108575f80fd5b5060025473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b348015610158575f80fd5b5061016c610167366004610d6d565b610340565b005b348015610179575f80fd5b5061016c610188366004610daa565b6103e6565b348015610198575f80fd5b506101a161046d565b604051908152602001610144565b3480156101ba575f80fd5b5061012361077b565b3480156101ce575f80fd5b5061020b6040518060400160405280600581526020017f312e362e3100000000000000000000000000000000000000000000000000000081525081565b6040516101449190610dc5565b348015610223575f80fd5b506002546101239073ffffffffffffffffffffffffffffffffffffffff1681565b34801561024f575f80fd5b506002546102759074010000000000000000000000000000000000000000900460ff1681565b6040516101449190610e7e565b34801561028d575f80fd5b506101a160015481565b3480156102a2575f80fd5b506101a15f5481565b3480156102b6575f80fd5b5061016c6102c5366004610e92565b610981565b3480156102d5575f80fd5b5061016c6102e4366004610ea9565b6109c7565b3480156102f4575f80fd5b5060025474010000000000000000000000000000000000000000900460ff16610275565b348015610323575f80fd5b506001546101a1565b348015610337575f80fd5b50610123610be3565b610348610c5d565b600280547401000000000000000000000000000000000000000080820460ff1692849290917fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff909116908360018111156103a4576103a4610e18565b02179055507ff2ec44eb1c3b3acd547b76333eb2c4b27eee311860c57a9fdb04c95f62398fc881836040516103da929190610ee4565b60405180910390a15050565b6103ee610c5d565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f62e69886a5df0ba8ffcacbfc1388754e7abd9bde24b036354c561f1acd4e459391016103da565b5f60015447101561052b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b479050805f8082825461053e9190610f2c565b90915550506002546040805183815273ffffffffffffffffffffffffffffffffffffffff909216602083018190523383830152905190917fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba919081900360600190a16002546040517f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee916105f39185918591339174010000000000000000000000000000000000000000900460ff1690610f3f565b60405180910390a1600160025474010000000000000000000000000000000000000000900460ff16600181111561062c5761062c610e18565b036106d0575f61063c8284610cb5565b9050806106cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610522565b505090565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216600482015262061a806024820152606060448201525f60648201527342000000000000000000000000000000000000169063c2b3e5ac9084906084015f604051808303818588803b158015610760575f80fd5b505af1158015610772573d5f803e3d5ffd5b50505050505090565b5f806107a57fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107c857919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000081525051600261080b9190610f80565b604080513060208201525f918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000009190911790610865906060015b604051602081830303815290604052805190602001205490565b1461089c576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091525f906108bd9060600161084b565b905073ffffffffffffffffffffffffffffffffffffffff81161561094f578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610924573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109489190610f97565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610989610c5d565b600180549082905560408051828152602081018490527f895a067c78583e800418fabf3da26a9496aab2ff3429cebdf7fefa642b2e420391016103da565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015610a115750825b90505f8267ffffffffffffffff166001148015610a2d5750303b155b905081158015610a3b575080155b15610a72576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315610ad35784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b610adb610cc8565b6002805473ffffffffffffffffffffffffffffffffffffffff8a167fffffffffffffffffffffffff000000000000000000000000000000000000000082168117835560018a81558993927fffffffffffffffffffffff000000000000000000000000000000000000000000169091179074010000000000000000000000000000000000000000908490811115610b7357610b73610e18565b02179055508315610bd95784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610bec61077b565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c34573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c589190610f97565b905090565b33610c66610be3565b73ffffffffffffffffffffffffffffffffffffffff1614610cb3576040517f7f12c64b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f610cc1835a84610d49565b9392505050565b33610cd161077b565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d12575033610cf9610be3565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610cb3576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f805f858888f1949350505050565b803560028110610d68575f80fd5b919050565b5f60208284031215610d7d575f80fd5b610cc182610d5a565b73ffffffffffffffffffffffffffffffffffffffff81168114610da7575f80fd5b50565b5f60208284031215610dba575f80fd5b8135610cc181610d86565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60028110610e7a577f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9052565b60208101610e8c8284610e45565b92915050565b5f60208284031215610ea2575f80fd5b5035919050565b5f805f60608486031215610ebb575f80fd5b8335610ec681610d86565b925060208401359150610edb60408501610d5a565b90509250925092565b60408101610ef28285610e45565b610cc16020830184610e45565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115610e8c57610e8c610eff565b84815273ffffffffffffffffffffffffffffffffffffffff84811660208301528316604082015260808101610f776060830184610e45565b95945050505050565b8082028115828204841417610e8c57610e8c610eff565b5f60208284031215610fa7575f80fd5b8151610cc181610d8656fea164736f6c6343000819000a0000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1506000, + "intent": "Deploy SequencerFeeVault Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000002f6e608060405234801561001057600080fd5b5061001961001e565b6100de565b600054610100900460ff161561008a5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff90811610156100dc576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b612e81806100ed6000396000f3fe60806040523480156200001157600080fd5b5060043610620000cd5760003560e01c8063c4d66de8116200007f578063dad544e01162000062578063dad544e014620001e9578063e78cea9214620001f3578063ee9a31a2146200021457600080fd5b8063c4d66de814620001b9578063ce5ac90f14620001d257600080fd5b806354fd4d5011620000b457806354fd4d50146200013f578063896f93d1146200018b5780638cf0629c14620001a257600080fd5b8063316b373914620000d25780633e47158c1462000135575b600080fd5b6200010b620000e3366004620009ac565b60026020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6200010b62000233565b6200017c6040518060400160405280600681526020017f312e31312e30000000000000000000000000000000000000000000000000000081525081565b6040516200012c919062000a41565b6200010b6200019c36600462000b38565b6200044a565b6200010b620001b336600462000bb7565b62000461565b620001d0620001ca366004620009ac565b62000680565b005b6200010b620001e336600462000b38565b6200085c565b6200010b6200086d565b6001546200010b9073ffffffffffffffffffffffffffffffffffffffff1681565b60015473ffffffffffffffffffffffffffffffffffffffff166200010b565b6000806200025f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156200028357919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000815250516002620002c8919062000c50565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179062000324906060015b604051602081830303815290604052805190602001205490565b146200035c576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805130602082015260019181019190915260009062000380906060016200030a565b905073ffffffffffffffffffffffffffffffffffffffff81161562000418578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620003eb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000411919062000cb5565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000620004598484846200085c565b949350505050565b600073ffffffffffffffffffffffffffffffffffffffff85166200050c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4f7074696d69736d4d696e7461626c654552433230466163746f72793a206d7560448201527f73742070726f766964652072656d6f746520746f6b656e20616464726573730060648201526084015b60405180910390fd5b60008585858560405160200162000527949392919062000cd5565b604051602081830303815290604052805190602001209050600081600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1688888888604051620005779062000978565b6200058795949392919062000d2f565b8190604051809103906000f5905080158015620005a8573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff81811660008181526002602052604080822080547fffffffffffffffffffffffff000000000000000000000000000000000000000016948d1694851790555193945090927fceeb8e7d520d7f3b65fc11a262b91066940193b05d4f93df07cfdced0eb551cf9190a360405133815273ffffffffffffffffffffffffffffffffffffffff80891691908316907f52fe89dd5930f343d25650b62fd367bae47088bcddffd2a88350a6ecdd620cdb9060200160405180910390a39695505050505050565b600054610100900460ff1615808015620006a15750600054600160ff909116105b80620006bd5750303b158015620006bd575060005460ff166001145b6200074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a6564000000000000000000000000000000000000606482015260840162000503565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015620007aa57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b620007b4620008ef565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff841617905580156200085857600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b600062000459848484601262000461565b60006200087962000233565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620008c4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620008ea919062000cb5565b905090565b33620008fa62000233565b73ffffffffffffffffffffffffffffffffffffffff16141580156200093e575033620009256200086d565b73ffffffffffffffffffffffffffffffffffffffff1614155b1562000976576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6120e08062000d9583390190565b73ffffffffffffffffffffffffffffffffffffffff81168114620009a957600080fd5b50565b600060208284031215620009bf57600080fd5b8135620009cc8162000986565b9392505050565b6000815180845260005b81811015620009fb57602081850181015186830182015201620009dd565b8181111562000a0e576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000620009cc6020830184620009d3565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011262000a9757600080fd5b813567ffffffffffffffff8082111562000ab55762000ab562000a56565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171562000afe5762000afe62000a56565b8160405283815286602085880101111562000b1857600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121562000b4e57600080fd5b833562000b5b8162000986565b9250602084013567ffffffffffffffff8082111562000b7957600080fd5b62000b878783880162000a85565b9350604086013591508082111562000b9e57600080fd5b5062000bad8682870162000a85565b9150509250925092565b6000806000806080858703121562000bce57600080fd5b843562000bdb8162000986565b9350602085013567ffffffffffffffff8082111562000bf957600080fd5b62000c078883890162000a85565b9450604087013591508082111562000c1e57600080fd5b5062000c2d8782880162000a85565b925050606085013560ff8116811462000c4557600080fd5b939692955090935050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161562000cb0577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b60006020828403121562000cc857600080fd5b8151620009cc8162000986565b73ffffffffffffffffffffffffffffffffffffffff8516815260806020820152600062000d066080830186620009d3565b828103604084015262000d1a8186620009d3565b91505060ff8316606083015295945050505050565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525060a0604083015262000d6a60a0830186620009d3565b828103606084015262000d7e8186620009d3565b91505060ff83166080830152969550505050505056fe6101a06040523480156200001257600080fd5b50604051620020e0380380620020e0833981016040819052620000359162000215565b6040805180820190915260018152603160f81b6020820152839081908185600362000061838262000350565b50600462000070828262000350565b5050825160208085019190912083518483012060e08290526101008190524660a0818152604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81880181905281830187905260608201869052608082019490945230818401528151808203909301835260c0019052805194019390932091935091906080523060c05261012052505050506001600160a01b0394851661014052509390921661016052505060ff16610180526200041c565b80516001600160a01b03811681146200014357600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200017057600080fd5b81516001600160401b03808211156200018d576200018d62000148565b604051601f8301601f19908116603f01168101908282118183101715620001b857620001b862000148565b81604052838152602092508683858801011115620001d557600080fd5b600091505b83821015620001f95785820183015181830184015290820190620001da565b838211156200020b5760008385830101525b9695505050505050565b600080600080600060a086880312156200022e57600080fd5b62000239866200012b565b945062000249602087016200012b565b60408701519094506001600160401b03808211156200026757600080fd5b6200027589838a016200015e565b945060608801519150808211156200028c57600080fd5b506200029b888289016200015e565b925050608086015160ff81168114620002b357600080fd5b809150509295509295909350565b600181811c90821680620002d657607f821691505b602082108103620002f757634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200034b57600081815260208120601f850160051c81016020861015620003265750805b601f850160051c820191505b81811015620003475782815560010162000332565b5050505b505050565b81516001600160401b038111156200036c576200036c62000148565b62000384816200037d8454620002c1565b84620002fd565b602080601f831160018114620003bc5760008415620003a35750858301515b600019600386901b1c1916600185901b17855562000347565b600085815260208120601f198616915b82811015620003ed57888601518255948401946001909101908401620003cc565b50858210156200040c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a05160c05160e0516101005161012051610140516101605161018051611c37620004a960003960006102700152600081816103a70152818161041c0152818161064801526107aa0152600081816101d501526103cd01526000611174015260006111c30152600061119e015260006110f7015260006111210152600061114b0152611c376000f3fe608060405234801561001057600080fd5b50600436106101a35760003560e01c806370a08231116100ee578063ae1f6aaf11610097578063d6c0b2c411610071578063d6c0b2c4146103cb578063dd62ed3e14610404578063e78cea92146103a5578063ee9a31a21461041757600080fd5b8063ae1f6aaf146103a5578063c01e1bd6146103cb578063d505accf146103f157600080fd5b80639dc29fac116100c85780639dc29fac1461036c578063a457c2d71461037f578063a9059cbb1461039257600080fd5b806370a082311461031b5780637ecebe001461035157806395d89b411461036457600080fd5b8063313ce5671161015057806340c10f191161012a57806340c10f19146102b557806354fd4d50146102ca5780636afdd8501461030657600080fd5b8063313ce567146102695780633644e5151461029a57806339509351146102a257600080fd5b8063095ea7b311610181578063095ea7b31461023157806318160ddd1461024457806323b872dd1461025657600080fd5b806301ffc9a7146101a8578063033964be146101d057806306fdde031461021c575b600080fd5b6101bb6101b636600461194b565b61043e565b60405190151581526020015b60405180910390f35b6101f77f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101c7565b61022461052f565b6040516101c7919061198d565b6101bb61023f366004611a29565b6105c1565b6002545b6040519081526020016101c7565b6101bb610264366004611a53565b6105db565b60405160ff7f00000000000000000000000000000000000000000000000000000000000000001681526020016101c7565b6102486105ff565b6101bb6102b0366004611a29565b61060e565b6102c86102c3366004611a29565b610630565b005b6102246040518060400160405280600581526020017f312e342e3100000000000000000000000000000000000000000000000000000081525081565b6e22d473030f116ddee9f6b43ac78ba36101f7565b610248610329366004611a8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b61024861035f366004611a8f565b610758565b610224610783565b6102c861037a366004611a29565b610792565b6101bb61038d366004611a29565b6108a9565b6101bb6103a0366004611a29565b610956565b7f00000000000000000000000000000000000000000000000000000000000000006101f7565b7f00000000000000000000000000000000000000000000000000000000000000006101f7565b6102c86103ff366004611aaa565b610964565b610248610412366004611b1d565b610b23565b6101f77f000000000000000000000000000000000000000000000000000000000000000081565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007f1d1d8b63000000000000000000000000000000000000000000000000000000007fec4fc8e3000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000085168314806104f757507fffffffff00000000000000000000000000000000000000000000000000000000858116908316145b8061052657507fffffffff00000000000000000000000000000000000000000000000000000000858116908216145b95945050505050565b60606003805461053e90611b50565b80601f016020809104026020016040519081016040528092919081815260200182805461056a90611b50565b80156105b75780601f1061058c576101008083540402835291602001916105b7565b820191906000526020600020905b81548152906001019060200180831161059a57829003601f168201915b5050505050905090565b6000336105cf818585610bc4565b60019150505b92915050565b6000336105e9858285610d78565b6105f4858585610e2a565b506001949350505050565b60006106096110dd565b905090565b6000336105cf8185856106218383610b23565b61062b9190611bcc565b610bc4565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146106fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f4f7074696d69736d4d696e7461626c6545524332303a206f6e6c79206272696460448201527f67652063616e206d696e7420616e64206275726e00000000000000000000000060648201526084015b60405180910390fd5b6107048282611211565b8173ffffffffffffffffffffffffffffffffffffffff167f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d41213968858260405161074c91815260200190565b60405180910390a25050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600560205260408120546105d5565b60606004805461053e90611b50565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610857576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f4f7074696d69736d4d696e7461626c6545524332303a206f6e6c79206272696460448201527f67652063616e206d696e7420616e64206275726e00000000000000000000000060648201526084016106f1565b6108618282611331565b8173ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca58260405161074c91815260200190565b600033816108b78286610b23565b905083811015610949576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016106f1565b6105f48286868403610bc4565b6000336105cf818585610e2a565b834211156109ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e6500000060448201526064016106f1565b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886109fd8c611516565b60408051602081019690965273ffffffffffffffffffffffffffffffffffffffff94851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090506000610a658261154b565b90506000610a75828787876115b4565b90508973ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610b0c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e6174757265000060448201526064016106f1565b610b178a8a8a610bc4565b50505050505050505050565b60007fffffffffffffffffffffffffffffffffffdd2b8cfcf0ee922116094bc538745d73ffffffffffffffffffffffffffffffffffffffff831601610b8957507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6105d5565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152600160209081526040808320938616835292905220545b9392505050565b73ffffffffffffffffffffffffffffffffffffffff8316610c66576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff8216610d09576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610d848484610b23565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610e245781811015610e17576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016106f1565b610e248484848403610bc4565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610ecd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff8216610f70576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015611026576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff80851660009081526020819052604080822085850390559185168152908120805484929061106a908490611bcc565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516110d091815260200190565b60405180910390a3610e24565b60003073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614801561114357507f000000000000000000000000000000000000000000000000000000000000000046145b1561116d57507f000000000000000000000000000000000000000000000000000000000000000090565b50604080517f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401527f000000000000000000000000000000000000000000000000000000000000000060608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b73ffffffffffffffffffffffffffffffffffffffff821661128e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016106f1565b80600260008282546112a09190611bcc565b909155505073ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906112da908490611bcc565b909155505060405181815273ffffffffffffffffffffffffffffffffffffffff8316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff82166113d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f730000000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff82166000908152602081905260409020548181101561148a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60448201527f636500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208383039055600280548492906114c6908490611be4565b909155505060405182815260009073ffffffffffffffffffffffffffffffffffffffff8516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610d6b565b73ffffffffffffffffffffffffffffffffffffffff811660009081526005602052604090208054600181018255905b50919050565b60006105d56115586110dd565b836040517f19010000000000000000000000000000000000000000000000000000000000006020820152602281018390526042810182905260009060620160405160208183030381529060405280519060200120905092915050565b60008060006115c5878787876115dc565b915091506115d2816116f4565b5095945050505050565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083111561161357506000905060036116eb565b8460ff16601b1415801561162b57508460ff16601c14155b1561163c57506000905060046116eb565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015611690573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81166116e4576000600192509250506116eb565b9150600090505b94509492505050565b600081600481111561170857611708611bfb565b036117105750565b600181600481111561172457611724611bfb565b0361178b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f45434453413a20696e76616c6964207369676e6174757265000000000000000060448201526064016106f1565b600281600481111561179f5761179f611bfb565b03611806576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e6774680060448201526064016106f1565b600381600481111561181a5761181a611bfb565b036118a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c60448201527f756500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b60048160048111156118bb576118bb611bfb565b03611948576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c60448201527f756500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b50565b60006020828403121561195d57600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610bbd57600080fd5b600060208083528351808285015260005b818110156119ba5785810183015185820160400152820161199e565b818111156119cc576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114611a2457600080fd5b919050565b60008060408385031215611a3c57600080fd5b611a4583611a00565b946020939093013593505050565b600080600060608486031215611a6857600080fd5b611a7184611a00565b9250611a7f60208501611a00565b9150604084013590509250925092565b600060208284031215611aa157600080fd5b610bbd82611a00565b600080600080600080600060e0888a031215611ac557600080fd5b611ace88611a00565b9650611adc60208901611a00565b95506040880135945060608801359350608088013560ff81168114611b0057600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215611b3057600080fd5b611b3983611a00565b9150611b4760208401611a00565b90509250929050565b600181811c90821680611b6457607f821691505b602082108103611545577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115611bdf57611bdf611b9d565b500190565b600082821015611bf657611bf6611b9d565b500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c634300080f000aa164736f6c634300080f000a000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 4193000, + "intent": "Deploy OptimismMintableERC20Factory Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001aaa608060405234801561001057600080fd5b5061001961001e565b6100de565b600054610100900460ff161561008a5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff90811610156100dc576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b6119bd806100ed6000396000f3fe608060405234801561001057600080fd5b50600436106100d45760003560e01c80637f46ddb211610081578063c4d66de81161005b578063c4d66de8146101fa578063c89701a21461020d578063dad544e01461022d57600080fd5b80637f46ddb2146101ab578063927ede2d146101c9578063aa557452146101e757600080fd5b806354fd4d50116100b257806354fd4d50146101405780635c975abb14610189578063761f44931461019857600080fd5b80633687011a146100d95780633cb747bf146100ee5780633e47158c14610138575b600080fd5b6100ec6100e73660046115d7565b610235565b005b60015461010e9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b61010e6102e6565b61017c6040518060400160405280600681526020017f312e31302e31000000000000000000000000000000000000000000000000000081525081565b60405161012f91906116c5565b6040516000815260200161012f565b6100ec6101a63660046116d8565b6104f1565b60025473ffffffffffffffffffffffffffffffffffffffff1661010e565b60015473ffffffffffffffffffffffffffffffffffffffff1661010e565b6100ec6101f5366004611770565b610a0f565b6100ec6102083660046117e7565b610acb565b60025461010e9073ffffffffffffffffffffffffffffffffffffffff1681565b61010e610c7c565b61023d610cf9565b6102ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4552433732314272696467653a206163636f756e74206973206e6f742065787460448201527f65726e616c6c79206f776e65640000000000000000000000000000000000000060648201526084015b60405180910390fd5b6102de8686333388888888610d36565b505050505050565b6000806103117fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff81161561033457919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026103779190611804565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000091909117906103d2906060015b604051602081830303815290604052805190602001205490565b14610409576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805130602082015260019181019190915260009061042b906060016103b8565b905073ffffffffffffffffffffffffffffffffffffffff8116156104bf578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610494573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b89190611868565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60015473ffffffffffffffffffffffffffffffffffffffff16331480156105c65750600254600154604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff9384169390921691636e296e45916004808201926020929091908290030181865afa15801561058a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ae9190611868565b73ffffffffffffffffffffffffffffffffffffffff16145b610652576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4552433732314272696467653a2066756e6374696f6e2063616e206f6e6c792060448201527f62652063616c6c65642066726f6d20746865206f74686572206272696467650060648201526084016102c5565b3073ffffffffffffffffffffffffffffffffffffffff8816036106f7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f4c324552433732314272696467653a206c6f63616c20746f6b656e2063616e6e60448201527f6f742062652073656c660000000000000000000000000000000000000000000060648201526084016102c5565b610721877faecafc230000000000000000000000000000000000000000000000000000000061128c565b6107ad576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f4c324552433732314272696467653a206c6f63616c20746f6b656e20696e746560448201527f7266616365206973206e6f7420636f6d706c69616e740000000000000000000060648201526084016102c5565b8673ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061081c9190611868565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146108fc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604b60248201527f4c324552433732314272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433732312060648201527f6c6f63616c20746f6b656e000000000000000000000000000000000000000000608482015260a4016102c5565b6040517fa144819400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301526024820185905288169063a144819490604401600060405180830381600087803b15801561096c57600080fd5b505af1158015610980573d6000803e3d6000fd5b505050508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff167f1f39bf6707b5d608453e0ae4c067b562bcc4c85c0f562ef5d2c774d2e7f131ac878787876040516109fe94939291906118ce565b60405180910390a450505050505050565b73ffffffffffffffffffffffffffffffffffffffff8516610ab2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4552433732314272696467653a206e667420726563697069656e742063616e6e60448201527f6f7420626520616464726573732830290000000000000000000000000000000060648201526084016102c5565b610ac28787338888888888610d36565b50505050505050565b600054610100900460ff1615808015610aeb5750600054600160ff909116105b80610b055750303b158015610b05575060005460ff166001145b610b91576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016102c5565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558015610bef57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b610bf76112af565b610c1573420000000000000000000000000000000000000783611332565b8015610c7857600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6000610c866102e6565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cd0573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cf49190611868565b905090565b6000323303610d085750600190565b333b601703610d3057604051602081016040526020600082333c5160e81c62ef010014905090565b50600090565b73ffffffffffffffffffffffffffffffffffffffff8716610dd9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603160248201527f4c324552433732314272696467653a2072656d6f746520746f6b656e2063616e60448201527f6e6f74206265206164647265737328302900000000000000000000000000000060648201526084016102c5565b6040517f6352211e0000000000000000000000000000000000000000000000000000000081526004810185905273ffffffffffffffffffffffffffffffffffffffff891690636352211e90602401602060405180830381865afa158015610e44573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e689190611868565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610f22576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4c324552433732314272696467653a205769746864726177616c206973206e6f60448201527f74206265696e6720696e69746961746564206279204e4654206f776e6572000060648201526084016102c5565b60008873ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f6f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f939190611868565b90508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614611050576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f4c324552433732314272696467653a2072656d6f746520746f6b656e20646f6560448201527f73206e6f74206d6174636820676976656e2076616c756500000000000000000060648201526084016102c5565b6040517f9dc29fac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152602482018790528a1690639dc29fac90604401600060405180830381600087803b1580156110c057600080fd5b505af11580156110d4573d6000803e3d6000fd5b505050506000818a89898988886040516024016110f7979695949392919061190e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f761f44930000000000000000000000000000000000000000000000000000000017905260015460025491517f3dbb202b00000000000000000000000000000000000000000000000000000000815292935073ffffffffffffffffffffffffffffffffffffffff90811692633dbb202b926111cc92169085908a9060040161196b565b600060405180830381600087803b1580156111e657600080fd5b505af11580156111fa573d6000803e3d6000fd5b505050508773ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff168b73ffffffffffffffffffffffffffffffffffffffff167fb7460e2a880f256ebef3406116ff3eee0cee51ebccdc2a40698f87ebb2e9c1a58a8a898960405161127894939291906118ce565b60405180910390a450505050505050505050565b60006112978361141c565b80156112a857506112a88383611481565b9392505050565b336112b86102e6565b73ffffffffffffffffffffffffffffffffffffffff16141580156112f95750336112e0610c7c565b73ffffffffffffffffffffffffffffffffffffffff1614155b15611330576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b600054610100900460ff166113c9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016102c5565b6001805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560028054929093169116179055565b6000611448827f01ffc9a700000000000000000000000000000000000000000000000000000000611481565b801561147b5750611479827fffffffff00000000000000000000000000000000000000000000000000000000611481565b155b92915050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d91506000519050828015611539575060208210155b80156115455750600081115b979650505050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461157257600080fd5b50565b803563ffffffff8116811461158957600080fd5b919050565b60008083601f8401126115a057600080fd5b50813567ffffffffffffffff8111156115b857600080fd5b6020830191508360208285010111156115d057600080fd5b9250929050565b60008060008060008060a087890312156115f057600080fd5b86356115fb81611550565b9550602087013561160b81611550565b94506040870135935061162060608801611575565b9250608087013567ffffffffffffffff81111561163c57600080fd5b61164889828a0161158e565b979a9699509497509295939492505050565b6000815180845260005b8181101561168057602081850181015186830182015201611664565b81811115611692576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006112a8602083018461165a565b600080600080600080600060c0888a0312156116f357600080fd5b87356116fe81611550565b9650602088013561170e81611550565b9550604088013561171e81611550565b9450606088013561172e81611550565b93506080880135925060a088013567ffffffffffffffff81111561175157600080fd5b61175d8a828b0161158e565b989b979a50959850939692959293505050565b600080600080600080600060c0888a03121561178b57600080fd5b873561179681611550565b965060208801356117a681611550565b955060408801356117b681611550565b9450606088013593506117cb60808901611575565b925060a088013567ffffffffffffffff81111561175157600080fd5b6000602082840312156117f957600080fd5b81356112a881611550565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611863577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b60006020828403121561187a57600080fd5b81516112a881611550565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff85168152836020820152606060408201526000611904606083018486611885565b9695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff808a1683528089166020840152808816604084015280871660608401525084608083015260c060a083015261195e60c083018486611885565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff8416815260606020820152600061199a606083018561165a565b905063ffffffff8316604083015294935050505056fea164736f6c634300080f000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 2367000, + "intent": "Deploy L2ERC721Bridge Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000cad608060405234801561001057600080fd5b50610c8d806100206000396000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80635cf24969116100f9578063c598591811610097578063e591b28211610071578063e591b28214610469578063e81b2c6d14610483578063f82061401461048c578063fe3d57101461049557600080fd5b8063c598591814610408578063d844471514610428578063dad544e01461046157600080fd5b80638381f58a116100d35780638381f58a146103c25780638b239f73146103d65780639e8c4966146103df578063b80777ea146103e857600080fd5b80635cf249691461038957806364ca23ef1461039257806368d5dca6146103a657600080fd5b80634397dfef1161016657806347af267b1161014057806347af267b146102ba5780634d5d9a2a146102dd57806354fd4d501461030e578063550fcdc91461035057600080fd5b80634397dfef14610277578063440a5e201461029f57806346a4d780146102a757600080fd5b806316d3bc7f116101a257806316d3bc7f14610202578063213268491461022f5780633db6be2b146102425780633e47158c1461024a57600080fd5b8063015d8eb9146101c9578063098999be146101de57806309bd5a60146101e6575b600080fd5b6101dc6101d7366004610ae1565b6104c6565b005b6101dc610605565b6101ef60025481565b6040519081526020015b60405180910390f35b6008546102169067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101f9565b60005b60405190151581526020016101f9565b6101dc610618565b610252610642565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101f9565b6040805173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815260126020820152016101f9565b6101dc61084d565b6101dc6102b5366004610b53565b6108a4565b6102326102c8366004610b53565b60096020526000908152604090205460ff1681565b6008546102f99068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101f9565b60408051808201909152600581527f312e392e3000000000000000000000000000000000000000000000000000000060208201525b6040516101f99190610b6c565b60408051808201909152600381527f45544800000000000000000000000000000000000000000000000000000000006020820152610343565b6101ef60015481565b6003546102169067ffffffffffffffff1681565b6003546102f99068010000000000000000900463ffffffff1681565b6000546102169067ffffffffffffffff1681565b6101ef60055481565b6101ef60065481565b6000546102169068010000000000000000900467ffffffffffffffff1681565b6003546102f9906c01000000000000000000000000900463ffffffff1681565b60408051808201909152600581527f45746865720000000000000000000000000000000000000000000000000000006020820152610343565b6102526108b9565b73deaddeaddeaddeaddeaddeaddeaddeaddead0001610252565b6101ef60045481565b6101ef60075481565b6008546104b3906c01000000000000000000000000900461ffff1681565b60405161ffff90911681526020016101f9565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461056d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61060d61084d565b60a43560a01c600855565b61062061084d565b6dffff00000000000000000000000060b03560901c1660a43560a01c17600855565b60008061066d7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff81161561069057919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026106d39190610bdf565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061072e906060015b604051602081830303815290604052805190602001205490565b14610765576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805130602082015260019181019190915260009061078790606001610714565b905073ffffffffffffffffffffffffffffffffffffffff81161561081b578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107f0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108149190610c43565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73deaddeaddeaddeaddeaddeaddeaddeaddead000133811461087757633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b6108ad33610936565b6108b681610a13565b50565b60006108c3610642565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561090d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109319190610c43565b905090565b73ffffffffffffffffffffffffffffffffffffffff811673deaddeaddeaddeaddeaddeaddeaddeaddead000114806109a057506109716108b9565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b806109dd57506109ae610642565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b6108b6576040517fbe9d7ca600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526009602052604090205460ff1615610a5c576040517f4f45326000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526009602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091559051909183917fb876f6594132c89891d2fd198e925e999be741ec809abb58bfe9b966876cc06c9190a350565b803567ffffffffffffffff81168114610adc57600080fd5b919050565b600080600080600080600080610100898b031215610afe57600080fd5b610b0789610ac4565b9750610b1560208a01610ac4565b96506040890135955060608901359450610b3160808a01610ac4565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208284031215610b6557600080fd5b5035919050565b600060208083528351808285015260005b81811015610b9957858101830151858201604001528201610b7d565b81811115610bab576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610c3e577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b600060208284031215610c5557600080fd5b815173ffffffffffffffffffffffffffffffffffffffff81168114610c7957600080fd5b939250505056fea164736f6c634300080f000a00000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1191000, + "intent": "Deploy L1Block Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006e9608060405234801561001057600080fd5b506106c9806100206000396000f3fe6080604052600436106100695760003560e01c806382e3702d1161004357806382e3702d14610120578063c2b3e5ac14610160578063ecc704281461017357600080fd5b80633f827a5a1461009257806344df8e70146100bf57806354fd4d50146100d457600080fd5b3661008d5761008b33620186a0604051806020016040528060008152506101d8565b005b600080fd5b34801561009e57600080fd5b506100a7600181565b60405161ffff90911681526020015b60405180910390f35b3480156100cb57600080fd5b5061008b61039c565b3480156100e057600080fd5b50604080518082018252600581527f312e322e30000000000000000000000000000000000000000000000000000000602082015290516100b691906104c7565b34801561012c57600080fd5b5061015061013b3660046104e1565b60006020819052908152604090205460ff1681565b60405190151581526020016100b6565b61008b61016e366004610529565b6101d8565b34801561017f57600080fd5b506101ca6001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016100b6565b600061026e6040518060c001604052806102326001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b815233602082015273ffffffffffffffffffffffffffffffffffffffff871660408201523460608201526080810186905260a0018490526103d4565b600081815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055905073ffffffffffffffffffffffffffffffffffffffff8416336103096001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b7f02a52367d10742d8032712c1bb8e0144ff1ec5ffda1ed7d70bb05a27449550543487878760405161033e949392919061062d565b60405180910390a45050600180547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8082168301167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b476103a681610421565b60405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b80516020808301516040808501516060860151608087015160a0880151935160009761040497909695910161065d565b604051602081830303815290604052805190602001209050919050565b8060405161042e90610450565b6040518091039082f090508015801561044b573d6000803e3d6000fd5b505050565b6008806106b583390190565b6000815180845260005b8181101561048257602081850181015186830182015201610466565b81811115610494576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104da602083018461045c565b9392505050565b6000602082840312156104f357600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060006060848603121561053e57600080fd5b833573ffffffffffffffffffffffffffffffffffffffff8116811461056257600080fd5b925060208401359150604084013567ffffffffffffffff8082111561058657600080fd5b818601915086601f83011261059a57600080fd5b8135818111156105ac576105ac6104fa565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156105f2576105f26104fa565b8160405282815289602084870101111561060b57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b84815283602082015260806040820152600061064c608083018561045c565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a08301526106a860c083018461045c565b9897505050505050505056fe608060405230fffea164736f6c634300080f000a0000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 694000, + "intent": "Deploy L2ToL1MessagePasser Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000003f8d608060405234801561001057600080fd5b5061001961001e565b6100de565b600154610100900460ff161561008a5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60015460ff90811610156100dc576001805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b613ea0806100ed6000396000f3fe60806040523480156200001157600080fd5b5060043610620000cd5760003560e01c8063d2382242116200007f578063dad544e01162000062578063dad544e014620001d6578063e78cea9214620001e0578063ee9a31a2146200020757600080fd5b8063d238224214620001b5578063d97df65214620001bf57600080fd5b80635572acae11620000b45780635572acae14620001525780637d1d0c5b1462000189578063cd6dc687146200019c57600080fd5b80633e47158c14620000d257806354fd4d501462000106575b600080fd5b620000dc6200022c565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b620001436040518060400160405280600581526020017f312e352e3100000000000000000000000000000000000000000000000000000081525081565b604051620000fd9190620009ad565b6200017862000163366004620009ef565b60006020819052908152604090205460ff1681565b6040519015158152602001620000fd565b6002545b604051908152602001620000fd565b620001b3620001ad36600462000a0f565b62000443565b005b6200018d60025481565b620000dc620001d036600462000b20565b62000628565b620000dc62000826565b600154620000dc9062010000900473ffffffffffffffffffffffffffffffffffffffff1681565b60015462010000900473ffffffffffffffffffffffffffffffffffffffff16620000dc565b600080620002587fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156200027c57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000815250516002620002c1919062000b9f565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000091909117906200031d906060015b604051602081830303815290604052805190602001205490565b1462000355576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051306020820152600191810191909152600090620003799060600162000303565b905073ffffffffffffffffffffffffffffffffffffffff81161562000411578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015620003e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200040a919062000c04565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600154610100900460ff16158080156200046157506001805460ff16105b806200047c5750303b1580156200047c57506001805460ff16145b6200050e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084015b60405180910390fd5b600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168117905580156200056c57600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b62000576620008a8565b600180547fffffffffffffffffffff0000000000000000000000000000000000000000ffff166201000073ffffffffffffffffffffffffffffffffffffffff861602179055600282905580156200062357600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1681556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b600073ffffffffffffffffffffffffffffffffffffffff8416620006f6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526044602482018190527f4f7074696d69736d4d696e7461626c65455243373231466163746f72793a204c908201527f3120746f6b656e20616464726573732063616e6e6f742062652061646472657360648201527f7328302900000000000000000000000000000000000000000000000000000000608482015260a40162000505565b60008484846040516020016200070f9392919062000c24565b604051602081830303815290604052805190602001209050600081600160029054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600254888888604051620007619062000931565b6200077195949392919062000c73565b8190604051809103906000f590508015801562000792573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff8181166000818152602081815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905590513381529394509189169290917fe72783bb8e0ca31286b85278da59684dd814df9762a52f0837f89edd1483b299910160405180910390a395945050505050565b6000620008326200022c565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200087d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620008a3919062000c04565b905090565b33620008b36200022c565b73ffffffffffffffffffffffffffffffffffffffff1614158015620008f7575033620008de62000826565b73ffffffffffffffffffffffffffffffffffffffff1614155b156200092f576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6131bf8062000cd583390190565b6000815180845260005b81811015620009675760208185018101518683018201520162000949565b818111156200097a576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000620009c260208301846200093f565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114620009ec57600080fd5b50565b60006020828403121562000a0257600080fd5b8135620009c281620009c9565b6000806040838503121562000a2357600080fd5b823562000a3081620009c9565b946020939093013593505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011262000a7f57600080fd5b813567ffffffffffffffff8082111562000a9d5762000a9d62000a3e565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171562000ae65762000ae662000a3e565b8160405283815286602085880101111562000b0057600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121562000b3657600080fd5b833562000b4381620009c9565b9250602084013567ffffffffffffffff8082111562000b6157600080fd5b62000b6f8783880162000a6d565b9350604086013591508082111562000b8657600080fd5b5062000b958682870162000a6d565b9150509250925092565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161562000bff577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b60006020828403121562000c1757600080fd5b8151620009c281620009c9565b73ffffffffffffffffffffffffffffffffffffffff8416815260606020820152600062000c5560608301856200093f565b828103604084015262000c6981856200093f565b9695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff808816835286602084015280861660408401525060a0606083015262000cb460a08301856200093f565b828103608084015262000cc881856200093f565b9897505050505050505056fe60e06040523480156200001157600080fd5b50604051620031bf380380620031bf83398101604081905262000034916200062d565b8181600062000044838262000756565b50600162000053828262000756565b5050506001600160a01b038516620000d85760405162461bcd60e51b815260206004820152603360248201527f4f7074696d69736d4d696e7461626c654552433732313a20627269646765206360448201527f616e6e6f7420626520616464726573732830290000000000000000000000000060648201526084015b60405180910390fd5b83600003620001505760405162461bcd60e51b815260206004820152603660248201527f4f7074696d69736d4d696e7461626c654552433732313a2072656d6f7465206360448201527f6861696e2069642063616e6e6f74206265207a65726f000000000000000000006064820152608401620000cf565b6001600160a01b038316620001ce5760405162461bcd60e51b815260206004820152603960248201527f4f7074696d69736d4d696e7461626c654552433732313a2072656d6f7465207460448201527f6f6b656e2063616e6e6f742062652061646472657373283029000000000000006064820152608401620000cf565b60808490526001600160a01b0383811660a081905290861660c0526200020290601462000256602090811b62000eed17901c565b62000218856200041660201b620011301760201c565b6040516020016200022b92919062000822565b604051602081830303815290604052600a90816200024a919062000756565b50505050505062000993565b6060600062000267836002620008ac565b62000274906002620008ce565b6001600160401b038111156200028e576200028e62000553565b6040519080825280601f01601f191660200182016040528015620002b9576020820181803683370190505b509050600360fc1b81600081518110620002d757620002d7620008e9565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110620003095762000309620008e9565b60200101906001600160f81b031916908160001a90535060006200032f846002620008ac565b6200033c906001620008ce565b90505b6001811115620003be576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110620003745762000374620008e9565b1a60f81b8282815181106200038d576200038d620008e9565b60200101906001600160f81b031916908160001a90535060049490941c93620003b681620008ff565b90506200033f565b5083156200040f5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401620000cf565b9392505050565b6060816000036200043e5750506040805180820190915260018152600360fc1b602082015290565b8160005b81156200046e5780620004558162000919565b9150620004669050600a836200094b565b915062000442565b6000816001600160401b038111156200048b576200048b62000553565b6040519080825280601f01601f191660200182016040528015620004b6576020820181803683370190505b5090505b84156200052e57620004ce60018362000962565b9150620004dd600a866200097c565b620004ea906030620008ce565b60f81b818381518110620005025762000502620008e9565b60200101906001600160f81b031916908160001a90535062000526600a866200094b565b9450620004ba565b949350505050565b80516001600160a01b03811681146200054e57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620005865781810151838201526020016200056c565b8381111562000596576000848401525b50505050565b600082601f830112620005ae57600080fd5b81516001600160401b0380821115620005cb57620005cb62000553565b604051601f8301601f19908116603f01168101908282118183101715620005f657620005f662000553565b816040528381528660208588010111156200061057600080fd5b6200062384602083016020890162000569565b9695505050505050565b600080600080600060a086880312156200064657600080fd5b620006518662000536565b945060208601519350620006686040870162000536565b60608701519093506001600160401b03808211156200068657600080fd5b6200069489838a016200059c565b93506080880151915080821115620006ab57600080fd5b50620006ba888289016200059c565b9150509295509295909350565b600181811c90821680620006dc57607f821691505b602082108103620006fd57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200075157600081815260208120601f850160051c810160208610156200072c5750805b601f850160051c820191505b818110156200074d5782815560010162000738565b5050505b505050565b81516001600160401b0381111562000772576200077262000553565b6200078a81620007838454620006c7565b8462000703565b602080601f831160018114620007c25760008415620007a95750858301515b600019600386901b1c1916600185901b1785556200074d565b600085815260208120601f198616915b82811015620007f357888601518255948401946001909101908401620007d2565b5085821015620008125787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6832ba3432b932bab69d60b91b8152600083516200084881600985016020880162000569565b600160fe1b60099184019182015283516200086b81600a84016020880162000569565b712f746f6b656e5552493f75696e743235363d60701b600a9290910191820152601c01949350505050565b634e487b7160e01b600052601160045260246000fd5b6000816000190483118215151615620008c957620008c962000896565b500290565b60008219821115620008e457620008e462000896565b500190565b634e487b7160e01b600052603260045260246000fd5b60008162000911576200091162000896565b506000190190565b6000600182016200092e576200092e62000896565b5060010190565b634e487b7160e01b600052601260045260246000fd5b6000826200095d576200095d62000935565b500490565b60008282101562000977576200097762000896565b500390565b6000826200098e576200098e62000935565b500690565b60805160a05160c0516127d9620009e6600039600081816103e20152818161047a01528181610b210152610c430152600081816101e001526103bc015260008181610329015261040801526127d96000f3fe608060405234801561001057600080fd5b50600436106101ae5760003560e01c80637d1d0c5b116100ee578063c87b56dd11610097578063e78cea9211610071578063e78cea92146103e0578063e951819614610406578063e985e9c51461042c578063ee9a31a21461047557600080fd5b8063c87b56dd1461039f578063d547cfb7146103b2578063d6c0b2c4146103ba57600080fd5b8063a1448194116100c8578063a144819414610366578063a22cb46514610379578063b88d4fde1461038c57600080fd5b80637d1d0c5b1461032457806395d89b411461034b5780639dc29fac1461035357600080fd5b806323b872dd1161015b5780634f6ccce7116101355780634f6ccce7146102af57806354fd4d50146102c25780636352211e146102fe57806370a082311461031157600080fd5b806323b872dd146102765780632f745c591461028957806342842e0e1461029c57600080fd5b8063081812fc1161018c578063081812fc1461023c578063095ea7b31461024f57806318160ddd1461026457600080fd5b806301ffc9a7146101b3578063033964be146101db57806306fdde0314610227575b600080fd5b6101c66101c1366004612226565b61049c565b60405190151581526020015b60405180910390f35b6102027f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d2565b61022f6104fa565b6040516101d291906122b9565b61020261024a3660046122cc565b61058c565b61026261025d36600461230e565b6105c0565b005b6008545b6040519081526020016101d2565b610262610284366004612338565b610751565b61026861029736600461230e565b6107f2565b6102626102aa366004612338565b6108c1565b6102686102bd3660046122cc565b6108dc565b61022f6040518060400160405280600581526020017f312e332e3200000000000000000000000000000000000000000000000000000081525081565b61020261030c3660046122cc565b61099a565b61026861031f366004612374565b610a2c565b6102687f000000000000000000000000000000000000000000000000000000000000000081565b61022f610afa565b61026261036136600461230e565b610b09565b61026261037436600461230e565b610c2b565b61026261038736600461238f565b610d42565b61026261039a3660046123fa565b610d51565b61022f6103ad3660046122cc565b610df9565b61022f610e5f565b7f0000000000000000000000000000000000000000000000000000000000000000610202565b7f0000000000000000000000000000000000000000000000000000000000000000610202565b7f0000000000000000000000000000000000000000000000000000000000000000610268565b6101c661043a3660046124f4565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260056020908152604080832093909416825291909152205460ff1690565b6102027f000000000000000000000000000000000000000000000000000000000000000081565b60007faecafc23000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000083168114806104f357506104f38361126d565b9392505050565b60606000805461050990612527565b80601f016020809104026020016040519081016040528092919081815260200182805461053590612527565b80156105825780601f1061055757610100808354040283529160200191610582565b820191906000526020600020905b81548152906001019060200180831161056557829003601f168201915b5050505050905090565b6000610597826112c3565b5060009081526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b60006105cb8261099a565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361068d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560448201527f720000000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614806106b657506106b6813361043a565b610742576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206e6f7220617070726f76656420666f7220616c6c00006064820152608401610684565b61074c8383611351565b505050565b61075b33826113f1565b6107e7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560448201527f72206e6f7220617070726f7665640000000000000000000000000000000000006064820152608401610684565b61074c8383836114b0565b60006107fd83610a2c565b821061088b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f455243373231456e756d657261626c653a206f776e657220696e646578206f7560448201527f74206f6620626f756e64730000000000000000000000000000000000000000006064820152608401610684565b5073ffffffffffffffffffffffffffffffffffffffff919091166000908152600660209081526040808320938352929052205490565b61074c83838360405180602001604052806000815250610d51565b60006108e760085490565b8210610975576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602c60248201527f455243373231456e756d657261626c653a20676c6f62616c20696e646578206f60448201527f7574206f6620626f756e647300000000000000000000000000000000000000006064820152608401610684565b600882815481106109885761098861257a565b90600052602060002001549050919050565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff1680610a26576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4552433732313a20696e76616c696420746f6b656e20494400000000000000006044820152606401610684565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff8216610ad1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f74206120766160448201527f6c6964206f776e657200000000000000000000000000000000000000000000006064820152608401610684565b5073ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205490565b60606001805461050990612527565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610bce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4f7074696d69736d4d696e7461626c654552433732313a206f6e6c792062726960448201527f6467652063616e2063616c6c20746869732066756e6374696f6e0000000000006064820152608401610684565b610bd781611722565b8173ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca582604051610c1f91815260200190565b60405180910390a25050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610cf0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4f7074696d69736d4d696e7461626c654552433732313a206f6e6c792062726960448201527f6467652063616e2063616c6c20746869732066756e6374696f6e0000000000006064820152608401610684565b610cfa82826117fb565b8173ffffffffffffffffffffffffffffffffffffffff167f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d412139688582604051610c1f91815260200190565b610d4d338383611815565b5050565b610d5b33836113f1565b610de7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560448201527f72206e6f7220617070726f7665640000000000000000000000000000000000006064820152608401610684565b610df384848484611942565b50505050565b6060610e04826112c3565b6000610e0e6119e5565b90506000815111610e2e57604051806020016040528060008152506104f3565b80610e3884611130565b604051602001610e499291906125a9565b6040516020818303038152906040529392505050565b600a8054610e6c90612527565b80601f0160208091040260200160405190810160405280929190818152602001828054610e9890612527565b8015610ee55780601f10610eba57610100808354040283529160200191610ee5565b820191906000526020600020905b815481529060010190602001808311610ec857829003601f168201915b505050505081565b60606000610efc836002612607565b610f07906002612644565b67ffffffffffffffff811115610f1f57610f1f6123cb565b6040519080825280601f01601f191660200182016040528015610f49576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110610f8057610f8061257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110610fe357610fe361257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600061101f846002612607565b61102a906001612644565b90505b60018111156110c7577f303132333435363738396162636465660000000000000000000000000000000085600f166010811061106b5761106b61257a565b1a60f81b8282815181106110815761108161257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060049490941c936110c08161265c565b905061102d565b5083156104f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610684565b60608160000361117357505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b811561119d578061118781612691565b91506111969050600a836126f8565b9150611177565b60008167ffffffffffffffff8111156111b8576111b86123cb565b6040519080825280601f01601f1916602001820160405280156111e2576020820181803683370190505b5090505b8415611265576111f760018361270c565b9150611204600a86612723565b61120f906030612644565b60f81b8183815181106112245761122461257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535061125e600a866126f8565b94506111e6565b949350505050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f780e9d63000000000000000000000000000000000000000000000000000000001480610a265750610a26826119f4565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1661134e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4552433732313a20696e76616c696420746f6b656e20494400000000000000006044820152606401610684565b50565b600081815260046020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff841690811790915581906113ab8261099a565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000806113fd8361099a565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16148061146b575073ffffffffffffffffffffffffffffffffffffffff80821660009081526005602090815260408083209388168352929052205460ff165b8061126557508373ffffffffffffffffffffffffffffffffffffffff166114918461058c565b73ffffffffffffffffffffffffffffffffffffffff1614949350505050565b8273ffffffffffffffffffffffffffffffffffffffff166114d08261099a565b73ffffffffffffffffffffffffffffffffffffffff1614611573576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060448201527f6f776e65720000000000000000000000000000000000000000000000000000006064820152608401610684565b73ffffffffffffffffffffffffffffffffffffffff8216611615576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f2061646460448201527f72657373000000000000000000000000000000000000000000000000000000006064820152608401610684565b611620838383611ad7565b61162b600082611351565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080546001929061166190849061270c565b909155505073ffffffffffffffffffffffffffffffffffffffff8216600090815260036020526040812080546001929061169c908490612644565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff86811691821790925591518493918716917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b600061172d8261099a565b905061173b81600084611ad7565b611746600083611351565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260036020526040812080546001929061177c90849061270c565b909155505060008281526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555183919073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b610d4d828260405180602001604052806000815250611bdd565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036118aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c6572000000000000006044820152606401610684565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526005602090815260408083209487168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b61194d8484846114b0565b61195984848484611c80565b610df3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b6060600a805461050990612527565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f80ac58cd000000000000000000000000000000000000000000000000000000001480611a8757507fffffffff0000000000000000000000000000000000000000000000000000000082167f5b5e139f00000000000000000000000000000000000000000000000000000000145b80610a2657507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000831614610a26565b73ffffffffffffffffffffffffffffffffffffffff8316611b3f57611b3a81600880546000838152600960205260408120829055600182018355919091527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30155565b611b7c565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614611b7c57611b7c8382611e73565b73ffffffffffffffffffffffffffffffffffffffff8216611ba05761074c81611f2a565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161461074c5761074c8282611fd9565b611be7838361202a565b611bf46000848484611c80565b61074c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b600073ffffffffffffffffffffffffffffffffffffffff84163b15611e68576040517f150b7a0200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063150b7a0290611cf7903390899088908890600401612737565b6020604051808303816000875af1925050508015611d50575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252611d4d91810190612780565b60015b611e1d573d808015611d7e576040519150601f19603f3d011682016040523d82523d6000602084013e611d83565b606091505b508051600003611e15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b805181602001fd5b7fffffffff00000000000000000000000000000000000000000000000000000000167f150b7a0200000000000000000000000000000000000000000000000000000000149050611265565b506001949350505050565b60006001611e8084610a2c565b611e8a919061270c565b600083815260076020526040902054909150808214611eea5773ffffffffffffffffffffffffffffffffffffffff841660009081526006602090815260408083208584528252808320548484528184208190558352600790915290208190555b50600091825260076020908152604080842084905573ffffffffffffffffffffffffffffffffffffffff9094168352600681528383209183525290812055565b600854600090611f3c9060019061270c565b60008381526009602052604081205460088054939450909284908110611f6457611f6461257a565b906000526020600020015490508060088381548110611f8557611f8561257a565b6000918252602080832090910192909255828152600990915260408082208490558582528120556008805480611fbd57611fbd61279d565b6001900381819060005260206000200160009055905550505050565b6000611fe483610a2c565b73ffffffffffffffffffffffffffffffffffffffff9093166000908152600660209081526040808320868452825280832085905593825260079052919091209190915550565b73ffffffffffffffffffffffffffffffffffffffff82166120a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f20616464726573736044820152606401610684565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1615612133576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e746564000000006044820152606401610684565b61213f60008383611ad7565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600360205260408120805460019290612175908490612644565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff861690811790915590518392907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461134e57600080fd5b60006020828403121561223857600080fd5b81356104f3816121f8565b60005b8381101561225e578181015183820152602001612246565b83811115610df35750506000910152565b60008151808452612287816020860160208601612243565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104f3602083018461226f565b6000602082840312156122de57600080fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461230957600080fd5b919050565b6000806040838503121561232157600080fd5b61232a836122e5565b946020939093013593505050565b60008060006060848603121561234d57600080fd5b612356846122e5565b9250612364602085016122e5565b9150604084013590509250925092565b60006020828403121561238657600080fd5b6104f3826122e5565b600080604083850312156123a257600080fd5b6123ab836122e5565b9150602083013580151581146123c057600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000806080858703121561241057600080fd5b612419856122e5565b9350612427602086016122e5565b925060408501359150606085013567ffffffffffffffff8082111561244b57600080fd5b818701915087601f83011261245f57600080fd5b813581811115612471576124716123cb565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156124b7576124b76123cb565b816040528281528a60208487010111156124d057600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b6000806040838503121561250757600080fd5b612510836122e5565b915061251e602084016122e5565b90509250929050565b600181811c9082168061253b57607f821691505b602082108103612574577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600083516125bb818460208801612243565b8351908301906125cf818360208801612243565b01949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561263f5761263f6125d8565b500290565b60008219821115612657576126576125d8565b500190565b60008161266b5761266b6125d8565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036126c2576126c26125d8565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082612707576127076126c9565b500490565b60008282101561271e5761271e6125d8565b500390565b600082612732576127326126c9565b500690565b600073ffffffffffffffffffffffffffffffffffffffff808716835280861660208401525083604083015260806060830152612776608083018461226f565b9695505050505050565b60006020828403121561279257600080fd5b81516104f3816121f8565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c634300080f000aa164736f6c634300080f000a00000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 5661000, + "intent": "Deploy OptimismMintableERC721Factory Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001c9d608060405234801561001057600080fd5b50600061001c3361002b565b6100258161002b565b5061007b565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b611c138061008a6000396000f3fe6080604052600436106101445760003560e01c80637eff275e116100c057806399a88ec411610074578063b794726211610059578063b7947262146103c8578063f2fde38b14610403578063f3b7dead1461042357600080fd5b806399a88ec4146103885780639b2ea4bd146103a857600080fd5b80638d52d4a0116100a55780638d52d4a01461032a5780638da5cb5b1461034a5780639623609d1461037557600080fd5b80637eff275e146102ea578063860f7cda1461030a57600080fd5b80633ab76e9f116101175780636bd9f516116100fc5780636bd9f51614610278578063715018a6146102b55780637c36f37e146102ca57600080fd5b80633ab76e9f1461020257806354fd4d501461022f57600080fd5b80630652b57a1461014957806307c8f7b01461016b578063204e1c7a1461018b578063238181ae146101d5575b600080fd5b34801561015557600080fd5b50610169610164366004611490565b610443565b005b34801561017757600080fd5b506101696101863660046114ad565b610492565b34801561019757600080fd5b506101ab6101a6366004611490565b6104e4565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b3480156101e157600080fd5b506101f56101f0366004611490565b61070a565b6040516101cc9190611545565b34801561020e57600080fd5b506003546101ab9073ffffffffffffffffffffffffffffffffffffffff1681565b34801561023b57600080fd5b506101f56040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b34801561028457600080fd5b506102a8610293366004611490565b60016020526000908152604090205460ff1681565b6040516101cc9190611587565b3480156102c157600080fd5b506101696107a4565b3480156102d657600080fd5b506101696102e5366004611490565b6107b8565b3480156102f657600080fd5b506101696103053660046115c8565b6109b0565b34801561031657600080fd5b50610169610325366004611723565b610b63565b34801561033657600080fd5b50610169610345366004611773565b610b9a565b34801561035657600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff166101ab565b6101696103833660046117a5565b610c0e565b34801561039457600080fd5b506101696103a33660046115c8565b610e25565b3480156103b457600080fd5b506101696103c336600461181b565b6110b5565b3480156103d457600080fd5b5060035474010000000000000000000000000000000000000000900460ff1660405190151581526020016101cc565b34801561040f57600080fd5b5061016961041e366004611490565b61114b565b34801561042f57600080fd5b506101ab61043e366004611490565b611202565b61044b611378565b600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b61049a611378565b6003805491151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff909216919091179055565b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604081205460ff168181600281111561052057610520611558565b0361059b578273ffffffffffffffffffffffffffffffffffffffff16635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105949190611862565b9392505050565b60018160028111156105af576105af611558565b036105ff578273ffffffffffffffffffffffffffffffffffffffff1663aaf10f426040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b600281600281111561061357610613611558565b0361069d5760035473ffffffffffffffffffffffffffffffffffffffff8481166000908152600260205260409081902090517fbf40fac1000000000000000000000000000000000000000000000000000000008152919092169163bf40fac19161068091906004016118cc565b602060405180830381865afa158015610570573d6000803e3d6000fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f50726f787941646d696e3a20756e6b6e6f776e2070726f78792074797065000060448201526064015b60405180910390fd5b50919050565b600260205260009081526040902080546107239061187f565b80601f016020809104026020016040519081016040528092919081815260200182805461074f9061187f565b801561079c5780601f106107715761010080835404028352916020019161079c565b820191906000526020600020905b81548152906001019060200180831161077f57829003601f168201915b505050505081565b6107ac611378565b6107b660006113f9565b565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610805576040517fcde661e700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff163b60000361086e576040517fe1e56d9d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821660048201526024016106fb565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fd55ec697000000000000000000000000000000000000000000000000000000001790529051600091829173ffffffffffffffffffffffffffffffffffffffff8516916108ec91611975565b600060405180830381855af49150503d8060008114610927576040519150601f19603f3d011682016040523d82523d6000602084013e61092c565b606091505b50915091508161096a57806040517f1c0a89cc0000000000000000000000000000000000000000000000000000000081526004016106fb9190611545565b60405173ffffffffffffffffffffffffffffffffffffffff8416907f14e22d69ea30aab5b2220164345b33bdb5125e9c77a7d5fe12e23a1c691bd13990600090a2505050565b6109b8611378565b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604081205460ff16908160028111156109f4576109f4611558565b03610a80576040517f8f28397000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152841690638f283970906024015b600060405180830381600087803b158015610a6357600080fd5b505af1158015610a77573d6000803e3d6000fd5b50505050505050565b6001816002811115610a9457610a94611558565b03610aed576040517f13af403500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83811660048301528416906313af403590602401610a49565b6002816002811115610b0157610b01611558565b0361069d576003546040517ff2fde38b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301529091169063f2fde38b90602401610a49565b505050565b610b6b611378565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600260205260409020610b5e82826119d7565b610ba2611378565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160208190526040909120805483927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0090911690836002811115610c0557610c05611558565b02179055505050565b610c16611378565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081205460ff1690816002811115610c5257610c52611558565b03610d18576040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851690634f1ef286903490610cad9087908790600401611af1565b60006040518083038185885af1158015610ccb573d6000803e3d6000fd5b50505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610d129190810190611b28565b50610e1f565b610d228484610e25565b60008473ffffffffffffffffffffffffffffffffffffffff163484604051610d4a9190611975565b60006040518083038185875af1925050503d8060008114610d87576040519150601f19603f3d011682016040523d82523d6000602084013e610d8c565b606091505b5050905080610e1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f50726f787941646d696e3a2063616c6c20746f2070726f78792061667465722060448201527f75706772616465206661696c656400000000000000000000000000000000000060648201526084016106fb565b505b50505050565b610e2d611378565b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604081205460ff1690816002811115610e6957610e69611558565b03610ec2576040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152841690633659cfe690602401610a49565b6001816002811115610ed657610ed6611558565b03610f55576040517f9b0b0fda0000000000000000000000000000000000000000000000000000000081527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc600482015273ffffffffffffffffffffffffffffffffffffffff8381166024830152841690639b0b0fda90604401610a49565b6002816002811115610f6957610f69611558565b036110ad5773ffffffffffffffffffffffffffffffffffffffff831660009081526002602052604081208054610f9e9061187f565b80601f0160208091040260200160405190810160405280929190818152602001828054610fca9061187f565b80156110175780601f10610fec57610100808354040283529160200191611017565b820191906000526020600020905b815481529060010190602001808311610ffa57829003601f168201915b50506003546040517f9b2ea4bd00000000000000000000000000000000000000000000000000000000815294955073ffffffffffffffffffffffffffffffffffffffff1693639b2ea4bd935061107592508591508790600401611b9f565b600060405180830381600087803b15801561108f57600080fd5b505af11580156110a3573d6000803e3d6000fd5b5050505050505050565b610b5e611bd7565b6110bd611378565b6003546040517f9b2ea4bd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911690639b2ea4bd906111159085908590600401611b9f565b600060405180830381600087803b15801561112f57600080fd5b505af1158015611143573d6000803e3d6000fd5b505050505050565b611153611378565b73ffffffffffffffffffffffffffffffffffffffff81166111f6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016106fb565b6111ff816113f9565b50565b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604081205460ff168181600281111561123e5761123e611558565b0361128e578273ffffffffffffffffffffffffffffffffffffffff1663f851a4406040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b60018160028111156112a2576112a2611558565b036112f2578273ffffffffffffffffffffffffffffffffffffffff1663893d20e86040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b600281600281111561130657611306611558565b0361069d57600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610570573d6000803e3d6000fd5b60005473ffffffffffffffffffffffffffffffffffffffff1633146107b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106fb565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff811681146111ff57600080fd5b6000602082840312156114a257600080fd5b81356105948161146e565b6000602082840312156114bf57600080fd5b8135801515811461059457600080fd5b60005b838110156114ea5781810151838201526020016114d2565b83811115610e1f5750506000910152565b600081518084526115138160208601602086016114cf565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061059460208301846114fb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60208101600383106115c2577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b600080604083850312156115db57600080fd5b82356115e68161146e565b915060208301356115f68161146e565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561167757611677611601565b604052919050565b600067ffffffffffffffff82111561169957611699611601565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b60006116d86116d38461167f565b611630565b90508281528383830111156116ec57600080fd5b828260208301376000602084830101529392505050565b600082601f83011261171457600080fd5b610594838335602085016116c5565b6000806040838503121561173657600080fd5b82356117418161146e565b9150602083013567ffffffffffffffff81111561175d57600080fd5b61176985828601611703565b9150509250929050565b6000806040838503121561178657600080fd5b82356117918161146e565b91506020830135600381106115f657600080fd5b6000806000606084860312156117ba57600080fd5b83356117c58161146e565b925060208401356117d58161146e565b9150604084013567ffffffffffffffff8111156117f157600080fd5b8401601f8101861361180257600080fd5b611811868235602084016116c5565b9150509250925092565b6000806040838503121561182e57600080fd5b823567ffffffffffffffff81111561184557600080fd5b61185185828601611703565b92505060208301356115f68161146e565b60006020828403121561187457600080fd5b81516105948161146e565b600181811c9082168061189357607f821691505b602082108103610704577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006020808352600084546118e08161187f565b80848701526040600180841660008114611901576001811461193957611967565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008516838a01528284151560051b8a01019550611967565b896000528660002060005b8581101561195f5781548b8201860152908301908801611944565b8a0184019650505b509398975050505050505050565b600082516119878184602087016114cf565b9190910192915050565b601f821115610b5e57600081815260208120601f850160051c810160208610156119b85750805b601f850160051c820191505b81811015611143578281556001016119c4565b815167ffffffffffffffff8111156119f1576119f1611601565b611a05816119ff845461187f565b84611991565b602080601f831160018114611a585760008415611a225750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555611143565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015611aa557888601518255948401946001909101908401611a86565b5085821015611ae157878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b73ffffffffffffffffffffffffffffffffffffffff83168152604060208201526000611b2060408301846114fb565b949350505050565b600060208284031215611b3a57600080fd5b815167ffffffffffffffff811115611b5157600080fd5b8201601f81018413611b6257600080fd5b8051611b706116d38261167f565b818152856020838501011115611b8557600080fd5b611b968260208301602086016114cf565b95945050505050565b604081526000611bb260408301856114fb565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fdfea164736f6c634300080f000a000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 2541000, + "intent": "Deploy L2ProxyAdmin Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000108a6080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610fb4806100d65f395ff3fe6080604052600436106100e7575f3560e01c806382356d8a11610087578063b49dc74111610057578063b49dc741146102bf578063d0e12f90146102de578063d3e5792b1461030d578063dad544e014610321575f80fd5b806382356d8a146102395780638312f1491461027757806384411d651461028c57806385b5b14d146102a0575f80fd5b80633ccfd60b116100c25780633ccfd60b146101825780633e47158c146101a457806354fd4d50146101b857806366d003ac1461020d575f80fd5b80630d9019e1146100f2578063307f2962146101425780633bbed4a014610163575f80fd5b366100ee57005b5f80fd5b3480156100fd575f80fd5b5060025473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561014d575f80fd5b5061016161015c366004610d62565b610335565b005b34801561016e575f80fd5b5061016161017d366004610d9f565b6103db565b34801561018d575f80fd5b50610196610462565b604051908152602001610139565b3480156101af575f80fd5b50610118610770565b3480156101c3575f80fd5b506102006040518060400160405280600581526020017f312e362e3100000000000000000000000000000000000000000000000000000081525081565b6040516101399190610dba565b348015610218575f80fd5b506002546101189073ffffffffffffffffffffffffffffffffffffffff1681565b348015610244575f80fd5b5060025461026a9074010000000000000000000000000000000000000000900460ff1681565b6040516101399190610e73565b348015610282575f80fd5b5061019660015481565b348015610297575f80fd5b506101965f5481565b3480156102ab575f80fd5b506101616102ba366004610e87565b610976565b3480156102ca575f80fd5b506101616102d9366004610e9e565b6109bc565b3480156102e9575f80fd5b5060025474010000000000000000000000000000000000000000900460ff1661026a565b348015610318575f80fd5b50600154610196565b34801561032c575f80fd5b50610118610bd8565b61033d610c52565b600280547401000000000000000000000000000000000000000080820460ff1692849290917fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff9091169083600181111561039957610399610e0d565b02179055507ff2ec44eb1c3b3acd547b76333eb2c4b27eee311860c57a9fdb04c95f62398fc881836040516103cf929190610ed9565b60405180910390a15050565b6103e3610c52565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f62e69886a5df0ba8ffcacbfc1388754e7abd9bde24b036354c561f1acd4e459391016103cf565b5f600154471015610520576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b479050805f808282546105339190610f21565b90915550506002546040805183815273ffffffffffffffffffffffffffffffffffffffff909216602083018190523383830152905190917fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba919081900360600190a16002546040517f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee916105e89185918591339174010000000000000000000000000000000000000000900460ff1690610f34565b60405180910390a1600160025474010000000000000000000000000000000000000000900460ff16600181111561062157610621610e0d565b036106c5575f6106318284610caa565b9050806106c0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610517565b505090565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216600482015262061a806024820152606060448201525f60648201527342000000000000000000000000000000000000169063c2b3e5ac9084906084015f604051808303818588803b158015610755575f80fd5b505af1158015610767573d5f803e3d5ffd5b50505050505090565b5f8061079a7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107bd57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026108009190610f75565b604080513060208201525f918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061085a906060015b604051602081830303815290604052805190602001205490565b14610891576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091525f906108b290606001610840565b905073ffffffffffffffffffffffffffffffffffffffff811615610944578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610919573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061093d9190610f8c565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61097e610c52565b600180549082905560408051828152602081018490527f895a067c78583e800418fabf3da26a9496aab2ff3429cebdf7fefa642b2e420391016103cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015610a065750825b90505f8267ffffffffffffffff166001148015610a225750303b155b905081158015610a30575080155b15610a67576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315610ac85784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b610ad0610cbd565b6002805473ffffffffffffffffffffffffffffffffffffffff8a167fffffffffffffffffffffffff000000000000000000000000000000000000000082168117835560018a81558993927fffffffffffffffffffffff000000000000000000000000000000000000000000169091179074010000000000000000000000000000000000000000908490811115610b6857610b68610e0d565b02179055508315610bce5784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610be1610770565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c29573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c4d9190610f8c565b905090565b33610c5b610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614610ca8576040517f7f12c64b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f610cb6835a84610d3e565b9392505050565b33610cc6610770565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d07575033610cee610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610ca8576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f805f858888f1949350505050565b803560028110610d5d575f80fd5b919050565b5f60208284031215610d72575f80fd5b610cb682610d4f565b73ffffffffffffffffffffffffffffffffffffffff81168114610d9c575f80fd5b50565b5f60208284031215610daf575f80fd5b8135610cb681610d7b565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60028110610e6f577f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9052565b60208101610e818284610e3a565b92915050565b5f60208284031215610e97575f80fd5b5035919050565b5f805f60608486031215610eb0575f80fd5b8335610ebb81610d7b565b925060208401359150610ed060408501610d4f565b90509250925092565b60408101610ee78285610e3a565b610cb66020830184610e3a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115610e8157610e81610ef4565b84815273ffffffffffffffffffffffffffffffffffffffff84811660208301528316604082015260808101610f6c6060830184610e3a565b95945050505050565b8082028115828204841417610e8157610e81610ef4565b5f60208284031215610f9c575f80fd5b8151610cb681610d7b56fea164736f6c6343000819000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1503000, + "intent": "Deploy BaseFeeVault Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000108a6080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610fb4806100d65f395ff3fe6080604052600436106100e7575f3560e01c806382356d8a11610087578063b49dc74111610057578063b49dc741146102bf578063d0e12f90146102de578063d3e5792b1461030d578063dad544e014610321575f80fd5b806382356d8a146102395780638312f1491461027757806384411d651461028c57806385b5b14d146102a0575f80fd5b80633ccfd60b116100c25780633ccfd60b146101825780633e47158c146101a457806354fd4d50146101b857806366d003ac1461020d575f80fd5b80630d9019e1146100f2578063307f2962146101425780633bbed4a014610163575f80fd5b366100ee57005b5f80fd5b3480156100fd575f80fd5b5060025473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561014d575f80fd5b5061016161015c366004610d62565b610335565b005b34801561016e575f80fd5b5061016161017d366004610d9f565b6103db565b34801561018d575f80fd5b50610196610462565b604051908152602001610139565b3480156101af575f80fd5b50610118610770565b3480156101c3575f80fd5b506102006040518060400160405280600581526020017f312e362e3100000000000000000000000000000000000000000000000000000081525081565b6040516101399190610dba565b348015610218575f80fd5b506002546101189073ffffffffffffffffffffffffffffffffffffffff1681565b348015610244575f80fd5b5060025461026a9074010000000000000000000000000000000000000000900460ff1681565b6040516101399190610e73565b348015610282575f80fd5b5061019660015481565b348015610297575f80fd5b506101965f5481565b3480156102ab575f80fd5b506101616102ba366004610e87565b610976565b3480156102ca575f80fd5b506101616102d9366004610e9e565b6109bc565b3480156102e9575f80fd5b5060025474010000000000000000000000000000000000000000900460ff1661026a565b348015610318575f80fd5b50600154610196565b34801561032c575f80fd5b50610118610bd8565b61033d610c52565b600280547401000000000000000000000000000000000000000080820460ff1692849290917fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff9091169083600181111561039957610399610e0d565b02179055507ff2ec44eb1c3b3acd547b76333eb2c4b27eee311860c57a9fdb04c95f62398fc881836040516103cf929190610ed9565b60405180910390a15050565b6103e3610c52565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f62e69886a5df0ba8ffcacbfc1388754e7abd9bde24b036354c561f1acd4e459391016103cf565b5f600154471015610520576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b479050805f808282546105339190610f21565b90915550506002546040805183815273ffffffffffffffffffffffffffffffffffffffff909216602083018190523383830152905190917fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba919081900360600190a16002546040517f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee916105e89185918591339174010000000000000000000000000000000000000000900460ff1690610f34565b60405180910390a1600160025474010000000000000000000000000000000000000000900460ff16600181111561062157610621610e0d565b036106c5575f6106318284610caa565b9050806106c0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610517565b505090565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216600482015262061a806024820152606060448201525f60648201527342000000000000000000000000000000000000169063c2b3e5ac9084906084015f604051808303818588803b158015610755575f80fd5b505af1158015610767573d5f803e3d5ffd5b50505050505090565b5f8061079a7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107bd57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026108009190610f75565b604080513060208201525f918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061085a906060015b604051602081830303815290604052805190602001205490565b14610891576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091525f906108b290606001610840565b905073ffffffffffffffffffffffffffffffffffffffff811615610944578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610919573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061093d9190610f8c565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61097e610c52565b600180549082905560408051828152602081018490527f895a067c78583e800418fabf3da26a9496aab2ff3429cebdf7fefa642b2e420391016103cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015610a065750825b90505f8267ffffffffffffffff166001148015610a225750303b155b905081158015610a30575080155b15610a67576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315610ac85784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b610ad0610cbd565b6002805473ffffffffffffffffffffffffffffffffffffffff8a167fffffffffffffffffffffffff000000000000000000000000000000000000000082168117835560018a81558993927fffffffffffffffffffffff000000000000000000000000000000000000000000169091179074010000000000000000000000000000000000000000908490811115610b6857610b68610e0d565b02179055508315610bce5784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610be1610770565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c29573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c4d9190610f8c565b905090565b33610c5b610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614610ca8576040517f7f12c64b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f610cb6835a84610d3e565b9392505050565b33610cc6610770565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d07575033610cee610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610ca8576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f805f858888f1949350505050565b803560028110610d5d575f80fd5b919050565b5f60208284031215610d72575f80fd5b610cb682610d4f565b73ffffffffffffffffffffffffffffffffffffffff81168114610d9c575f80fd5b50565b5f60208284031215610daf575f80fd5b8135610cb681610d7b565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60028110610e6f577f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9052565b60208101610e818284610e3a565b92915050565b5f60208284031215610e97575f80fd5b5035919050565b5f805f60608486031215610eb0575f80fd5b8335610ebb81610d7b565b925060208401359150610ed060408501610d4f565b90509250925092565b60408101610ee78285610e3a565b610cb66020830184610e3a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115610e8157610e81610ef4565b84815273ffffffffffffffffffffffffffffffffffffffff84811660208301528316604082015260808101610f6c6060830184610e3a565b95945050505050565b8082028115828204841417610e8157610e81610ef4565b5f60208284031215610f9c575f80fd5b8151610cb681610d7b56fea164736f6c6343000819000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 260550, + "intent": "Deploy L1FeeVault Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000108a6080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b610fb4806100d65f395ff3fe6080604052600436106100e7575f3560e01c806382356d8a11610087578063b49dc74111610057578063b49dc741146102bf578063d0e12f90146102de578063d3e5792b1461030d578063dad544e014610321575f80fd5b806382356d8a146102395780638312f1491461027757806384411d651461028c57806385b5b14d146102a0575f80fd5b80633ccfd60b116100c25780633ccfd60b146101825780633e47158c146101a457806354fd4d50146101b857806366d003ac1461020d575f80fd5b80630d9019e1146100f2578063307f2962146101425780633bbed4a014610163575f80fd5b366100ee57005b5f80fd5b3480156100fd575f80fd5b5060025473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561014d575f80fd5b5061016161015c366004610d62565b610335565b005b34801561016e575f80fd5b5061016161017d366004610d9f565b6103db565b34801561018d575f80fd5b50610196610462565b604051908152602001610139565b3480156101af575f80fd5b50610118610770565b3480156101c3575f80fd5b506102006040518060400160405280600581526020017f312e312e3100000000000000000000000000000000000000000000000000000081525081565b6040516101399190610dba565b348015610218575f80fd5b506002546101189073ffffffffffffffffffffffffffffffffffffffff1681565b348015610244575f80fd5b5060025461026a9074010000000000000000000000000000000000000000900460ff1681565b6040516101399190610e73565b348015610282575f80fd5b5061019660015481565b348015610297575f80fd5b506101965f5481565b3480156102ab575f80fd5b506101616102ba366004610e87565b610976565b3480156102ca575f80fd5b506101616102d9366004610e9e565b6109bc565b3480156102e9575f80fd5b5060025474010000000000000000000000000000000000000000900460ff1661026a565b348015610318575f80fd5b50600154610196565b34801561032c575f80fd5b50610118610bd8565b61033d610c52565b600280547401000000000000000000000000000000000000000080820460ff1692849290917fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff9091169083600181111561039957610399610e0d565b02179055507ff2ec44eb1c3b3acd547b76333eb2c4b27eee311860c57a9fdb04c95f62398fc881836040516103cf929190610ed9565b60405180910390a15050565b6103e3610c52565b6002805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f62e69886a5df0ba8ffcacbfc1388754e7abd9bde24b036354c561f1acd4e459391016103cf565b5f600154471015610520576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b479050805f808282546105339190610f21565b90915550506002546040805183815273ffffffffffffffffffffffffffffffffffffffff909216602083018190523383830152905190917fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba919081900360600190a16002546040517f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee916105e89185918591339174010000000000000000000000000000000000000000900460ff1690610f34565b60405180910390a1600160025474010000000000000000000000000000000000000000900460ff16600181111561062157610621610e0d565b036106c5575f6106318284610caa565b9050806106c0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610517565b505090565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216600482015262061a806024820152606060448201525f60648201527342000000000000000000000000000000000000169063c2b3e5ac9084906084015f604051808303818588803b158015610755575f80fd5b505af1158015610767573d5f803e3d5ffd5b50505050505090565b5f8061079a7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156107bd57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026108009190610f75565b604080513060208201525f918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061085a906060015b604051602081830303815290604052805190602001205490565b14610891576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091525f906108b290606001610840565b905073ffffffffffffffffffffffffffffffffffffffff811615610944578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610919573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061093d9190610f8c565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61097e610c52565b600180549082905560408051828152602081018490527f895a067c78583e800418fabf3da26a9496aab2ff3429cebdf7fefa642b2e420391016103cf565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff165f81158015610a065750825b90505f8267ffffffffffffffff166001148015610a225750303b155b905081158015610a30575080155b15610a67576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001660011785558315610ac85784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b610ad0610cbd565b6002805473ffffffffffffffffffffffffffffffffffffffff8a167fffffffffffffffffffffffff000000000000000000000000000000000000000082168117835560018a81558993927fffffffffffffffffffffff000000000000000000000000000000000000000000169091179074010000000000000000000000000000000000000000908490811115610b6857610b68610e0d565b02179055508315610bce5784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b5f610be1610770565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c29573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c4d9190610f8c565b905090565b33610c5b610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614610ca8576040517f7f12c64b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f610cb6835a84610d3e565b9392505050565b33610cc6610770565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d07575033610cee610bd8565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610ca8576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f805f858888f1949350505050565b803560028110610d5d575f80fd5b919050565b5f60208284031215610d72575f80fd5b610cb682610d4f565b73ffffffffffffffffffffffffffffffffffffffff81168114610d9c575f80fd5b50565b5f60208284031215610daf575f80fd5b8135610cb681610d7b565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60028110610e6f577f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b9052565b60208101610e818284610e3a565b92915050565b5f60208284031215610e97575f80fd5b5035919050565b5f805f60608486031215610eb0575f80fd5b8335610ebb81610d7b565b925060208401359150610ed060408501610d4f565b90509250925092565b60408101610ee78285610e3a565b610cb66020830184610e3a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115610e8157610e81610ef4565b84815273ffffffffffffffffffffffffffffffffffffffff84811660208301528316604082015260808101610f6c6060830184610e3a565b95945050505050565b8082028115828204841417610e8157610e81610ef4565b5f60208284031215610f9c575f80fd5b8151610cb681610d7b56fea164736f6c6343000819000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1504000, + "intent": "Deploy OperatorFeeVault Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000081e608060405234801561001057600080fd5b506107fe806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d501461004657806360d7a27814610098578063a2ea7c6e146100b9575b600080fd5b6100826040518060400160405280600c81526020017f312e332e312d626574612e32000000000000000000000000000000000000000081525081565b60405161008f9190610473565b60405180910390f35b6100ab6100a636600461048d565b6100d9565b60405190815260200161008f565b6100cc6100c736600461053f565b61029d565b60405161008f9190610558565b60008060405180608001604052806000801b81526020018573ffffffffffffffffffffffffffffffffffffffff168152602001841515815260200187878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052509390945250929350915061015b9050826103c5565b600081815260208190526040902054909150156101a4576040517f23369fa600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80825260008181526020818152604091829020845181559084015160018201805493860151151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00000000000000000000000000000000000000000090941673ffffffffffffffffffffffffffffffffffffffff9092169190911792909217909155606083015183919060028201906102409082610682565b509050503373ffffffffffffffffffffffffffffffffffffffff16817fd0b86852e21f9e5fa4bc3b0cff9757ffe243d50c4b43968a42202153d651ea5e8460405161028b9190610558565b60405180910390a39695505050505050565b604080516080810182526000808252602082018190529181019190915260608082015260008281526020818152604091829020825160808101845281548152600182015473ffffffffffffffffffffffffffffffffffffffff8116938201939093527401000000000000000000000000000000000000000090920460ff1615159282019290925260028201805491929160608401919061033c906105e0565b80601f0160208091040260200160405190810160405280929190818152602001828054610368906105e0565b80156103b55780601f1061038a576101008083540402835291602001916103b5565b820191906000526020600020905b81548152906001019060200180831161039857829003601f168201915b5050505050815250509050919050565b60008160600151826020015183604001516040516020016103e89392919061079c565b604051602081830303815290604052805190602001209050919050565b60005b83811015610420578181015183820152602001610408565b50506000910152565b60008151808452610441816020860160208601610405565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104866020830184610429565b9392505050565b600080600080606085870312156104a357600080fd5b843567ffffffffffffffff808211156104bb57600080fd5b818701915087601f8301126104cf57600080fd5b8135818111156104de57600080fd5b8860208285010111156104f057600080fd5b6020928301965094505085013573ffffffffffffffffffffffffffffffffffffffff8116811461051f57600080fd5b91506040850135801515811461053457600080fd5b939692955090935050565b60006020828403121561055157600080fd5b5035919050565b602081528151602082015273ffffffffffffffffffffffffffffffffffffffff6020830151166040820152604082015115156060820152600060608301516080808401526105a960a0840182610429565b949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600181811c908216806105f457607f821691505b60208210810361062d577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f82111561067d57600081815260208120601f850160051c8101602086101561065a5750805b601f850160051c820191505b8181101561067957828155600101610666565b5050505b505050565b815167ffffffffffffffff81111561069c5761069c6105b1565b6106b0816106aa84546105e0565b84610633565b602080601f83116001811461070357600084156106cd5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610679565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561075057888601518255948401946001909101908401610731565b508582101561078c57878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b600084516107ae818460208901610405565b60609490941b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000169190930190815290151560f81b60148201526015019291505056fea164736f6c6343000813000a0000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 805000, + "intent": "Deploy SchemaRegistry Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000046c961016060405234801561001157600080fd5b50604080518082018252600381526245415360e81b60208083019182528351808501855260058152640312e332e360dc1b908201529151812060e08190527f6a08c3e203132c561752255a4d52ffae85bb9c5d33cb3291520dea1b843563896101008190524660a081815286517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f818801819052818901959095526060810193909352608080840192909252308382018190528751808503909201825260c093840190975280519501949094209093529290915261012091909152516101405260805160a05160c05160e05161010051610120516101405161457e61014b600039600061073701526000612784015260006127d3015260006127ae01526000612707015260006127310152600061275b015261457e6000f3fe60806040526004361061018b5760003560e01c806395411525116100d6578063d45c44351161007f578063ed24911d11610059578063ed24911d146104fd578063f10b5cc814610512578063f17325e71461054157600080fd5b8063d45c443514610467578063e30bb5631461049e578063e71ff365146104dd57600080fd5b8063b469318d116100b0578063b469318d146103ba578063b83010d314610414578063cf190f341461044757600080fd5b80639541152514610367578063a3112a641461037a578063a6d4dbc7146103a757600080fd5b806344adc90e116101385780634d003070116101125780634d003070146102de57806354fd4d50146102fe57806379f7573a1461034757600080fd5b806344adc90e1461029857806346926267146102b85780634cb7e9e5146102cb57600080fd5b806317d7de7c1161016957806317d7de7c146102205780632d0335ab146102425780633c0427151461028557600080fd5b80630eabf6601461019057806312b11a17146101a557806313893f61146101e7575b600080fd5b6101a361019e3660046134c8565b610554565b005b3480156101b157600080fd5b507ffeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d9880765b6040519081526020015b60405180910390f35b3480156101f357600080fd5b506102076102023660046134c8565b6106eb565b60405167ffffffffffffffff90911681526020016101de565b34801561022c57600080fd5b50610235610730565b6040516101de9190613578565b34801561024e57600080fd5b506101d461025d3660046135bd565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6101d46102933660046135da565b610760565b6102ab6102a63660046134c8565b610863565b6040516101de9190613615565b6101a36102c6366004613659565b6109e4565b6101a36102d93660046134c8565b610a68565b3480156102ea57600080fd5b506102076102f9366004613671565b610b4b565b34801561030a57600080fd5b506102356040518060400160405280600c81526020017f312e342e312d626574612e33000000000000000000000000000000000000000081525081565b34801561035357600080fd5b506101a3610362366004613671565b610b58565b6102ab6103753660046134c8565b610bef565b34801561038657600080fd5b5061039a610395366004613671565b610e62565b6040516101de9190613771565b6101a36103b5366004613784565b611025565b3480156103c657600080fd5b506102076103d5366004613797565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152603460209081526040808320938352929052205467ffffffffffffffff1690565b34801561042057600080fd5b507fb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e756101d4565b34801561045357600080fd5b50610207610462366004613671565b6110ca565b34801561047357600080fd5b50610207610482366004613671565b60009081526033602052604090205467ffffffffffffffff1690565b3480156104aa57600080fd5b506104cd6104b9366004613671565b600090815260326020526040902054151590565b60405190151581526020016101de565b3480156104e957600080fd5b506102076104f83660046134c8565b6110d8565b34801561050957600080fd5b506101d4611110565b34801561051e57600080fd5b5060405173420000000000000000000000000000000000002081526020016101de565b6101d461054f3660046137c3565b61111a565b348160005b818110156106e4577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82018114600086868481811061059a5761059a6137fe565b90506020028101906105ac919061382d565b6105b590613ac3565b60208101518051919250908015806105d257508260400151518114155b15610609576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b818110156106ad576106a56040518060a001604052808660000151815260200185848151811061063e5761063e6137fe565b6020026020010151815260200186604001518481518110610661576106616137fe565b60200260200101518152602001866060015173ffffffffffffffffffffffffffffffffffffffff168152602001866080015167ffffffffffffffff168152506111d8565b60010161060c565b506106c383600001518385606001518a886113e9565b6106cd9088613bed565b9650505050506106dd8160010190565b9050610559565b5050505050565b60004282825b818110156107245761071c3387878481811061070f5761070f6137fe565b9050602002013585611a18565b6001016106f1565b50909150505b92915050565b606061075b7f0000000000000000000000000000000000000000000000000000000000000000611b17565b905090565b600061077361076e83613d22565b611ca5565b604080516001808252818301909252600091816020015b6040805160c081018252600080825260208083018290529282018190526060808301829052608083015260a082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161078a5790505090506107f86020840184613d9d565b61080190613dd1565b81600081518110610814576108146137fe565b602090810291909101015261083d83358261083560c0870160a088016135bd565b346001611e2f565b60200151600081518110610853576108536137fe565b6020026020010151915050919050565b60608160008167ffffffffffffffff8111156108815761088161386b565b6040519080825280602002602001820160405280156108b457816020015b606081526020019060019003908161089f5790505b509050600034815b848110156109ce577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85018114368989848181106108fc576108fc6137fe565b905060200281019061090e9190613ddd565b905061091d6020820182613e11565b9050600003610958576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061097d823561096c6020850185613e11565b61097591613e79565b338887611e2f565b805190915061098c9086613bed565b945080602001518785815181106109a5576109a56137fe565b6020026020010181905250806020015151860195505050506109c78160010190565b90506108bc565b506109d98383612541565b979650505050505050565b604080516001808252818301909252600091816020015b60408051808201909152600080825260208201528152602001906001900390816109fb579050509050610a3636839003830160208401613eed565b81600081518110610a4957610a496137fe565b6020908102919091010152610a63823582333460016113e9565b505050565b348160005b818110156106e4577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201811436868684818110610aad57610aad6137fe565b9050602002810190610abf9190613ddd565b9050610b2c8135610ad36020840184613f09565b808060200260200160405190810160405280939291908181526020016000905b82821015610b1f57610b1060408302860136819003810190613eed565b81526020019060010190610af3565b50505050503388866113e9565b610b369086613bed565b94505050610b448160010190565b9050610a6d565b60004261072a838261262b565b33600090815260208190526040902054808211610ba1576040517f756688fe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152602081815260409182902084905581518381529081018490527f57b09af877df9068fd60a69d7b21f5576b8b38955812d6ae4ac52942f1e38fb7910160405180910390a15050565b60608160008167ffffffffffffffff811115610c0d57610c0d61386b565b604051908082528060200260200182016040528015610c4057816020015b6060815260200190600190039081610c2b5790505b509050600034815b848110156109ce577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8501811436898984818110610c8857610c886137fe565b9050602002810190610c9a919061382d565b9050366000610cac6020840184613e11565b909250905080801580610ccd5750610cc76040850185613f71565b90508114155b15610d04576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b81811015610de557610ddd6040518060a0016040528087600001358152602001868685818110610d3957610d396137fe565b9050602002810190610d4b9190613d9d565b610d5490613dd1565b8152602001610d666040890189613f71565b85818110610d7657610d766137fe565b905060600201803603810190610d8c9190613fd8565b8152602001610da16080890160608a016135bd565b73ffffffffffffffffffffffffffffffffffffffff168152602001610dcc60a0890160808a01613ff4565b67ffffffffffffffff169052611ca5565b600101610d07565b506000610e0e8535610df78587613e79565b610e076080890160608a016135bd565b8b8a611e2f565b8051909150610e1d9089613bed565b975080602001518a8881518110610e3657610e366137fe565b602002602001018190525080602001515189019850505050505050610e5b8160010190565b9050610c48565b604080516101408101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082019290925261012081019190915260008281526032602090815260409182902082516101408101845281548152600182015492810192909252600281015467ffffffffffffffff808216948401949094526801000000000000000081048416606084015270010000000000000000000000000000000090049092166080820152600382015460a0820152600482015473ffffffffffffffffffffffffffffffffffffffff90811660c0830152600583015490811660e083015274010000000000000000000000000000000000000000900460ff16151561010082015260068201805491929161012084019190610f9c9061400f565b80601f0160208091040260200160405190810160405280929190818152602001828054610fc89061400f565b80156110155780601f10610fea57610100808354040283529160200191611015565b820191906000526020600020905b815481529060010190602001808311610ff857829003601f168201915b5050505050815250509050919050565b61103c6110373683900383018361405c565b6111d8565b604080516001808252818301909252600091816020015b604080518082019091526000808252602082015281526020019060019003908161105357905050905061108e36839003830160208401613eed565b816000815181106110a1576110a16137fe565b6020908102919091010152610a638235826110c260e0860160c087016135bd565b3460016113e9565b60004261072a338483611a18565b60004282825b81811015610724576111088686838181106110fb576110fb6137fe565b905060200201358461262b565b6001016110de565b600061075b6126ed565b604080516001808252818301909252600091829190816020015b6040805160c081018252600080825260208083018290529282018190526060808301829052608083015260a082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816111345790505090506111a26020840184613d9d565b6111ab90613dd1565b816000815181106111be576111be6137fe565b602090810291909101015261083d83358233346001611e2f565b608081015167ffffffffffffffff161580159061120c57504267ffffffffffffffff16816080015167ffffffffffffffff16105b15611243576040517f1ab7da6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808201516040808401516060850151855184518587015173ffffffffffffffffffffffffffffffffffffffff84166000908152978890529487208054969794969495611337957fb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e7595949392886112ba836140ca565b909155506080808c015160408051602081019990995273ffffffffffffffffffffffffffffffffffffffff9097169688019690965260608701949094529285019190915260a084015260c083015267ffffffffffffffff1660e0820152610100015b60405160208183030381529060405280519060200120612821565b90506113ad84606001518284602001518560400151866000015160405160200161139993929190928352602083019190915260f81b7fff0000000000000000000000000000000000000000000000000000000000000016604082015260410190565b604051602081830303815290604052612834565b6113e3576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050565b6040517fa2ea7c6e0000000000000000000000000000000000000000000000000000000081526004810186905260009081907342000000000000000000000000000000000000209063a2ea7c6e90602401600060405180830381865afa158015611457573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261149d9190810190614102565b80519091506114d8576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b855160008167ffffffffffffffff8111156114f5576114f561386b565b60405190808252806020026020018201604052801561159457816020015b60408051610140810182526000808252602080830182905292820181905260608083018290526080830182905260a0830182905260c0830182905260e0830182905261010083019190915261012082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816115135790505b50905060008267ffffffffffffffff8111156115b2576115b261386b565b6040519080825280602002602001820160405280156115db578160200160208202803683370190505b50905060005b838110156119fa5760008a82815181106115fd576115fd6137fe565b6020908102919091018101518051600090815260329092526040909120805491925090611656576040517fc5723b5100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8c816001015414611693576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600581015473ffffffffffffffffffffffffffffffffffffffff8c81169116146116e9576040517f4ca8886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600581015474010000000000000000000000000000000000000000900460ff1661173f576040517f157bd4c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002810154700100000000000000000000000000000000900467ffffffffffffffff1615611799576040517f905e710700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b426002820180547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff811670010000000000000000000000000000000067ffffffffffffffff948516810291821793849055604080516101408101825287548152600188015460208201529386169286169290921791830191909152680100000000000000008304841660608301529091049091166080820152600382015460a0820152600482015473ffffffffffffffffffffffffffffffffffffffff90811660c0830152600583015490811660e083015274010000000000000000000000000000000000000000900460ff16151561010082015260068201805483916101208401916118a59061400f565b80601f01602080910402602001604051908101604052809291908181526020018280546118d19061400f565b801561191e5780601f106118f35761010080835404028352916020019161191e565b820191906000526020600020905b81548152906001019060200180831161190157829003601f168201915b505050505081525050858481518110611939576119396137fe565b6020026020010181905250816020015184848151811061195b5761195b6137fe565b6020026020010181815250508c8b73ffffffffffffffffffffffffffffffffffffffff16868581518110611991576119916137fe565b602002602001015160c0015173ffffffffffffffffffffffffffffffffffffffff167ff930a6e2523c9cc298691873087a740550b8fc85a0680830414c148ed927f61585600001516040516119e891815260200190565b60405180910390a450506001016115e1565b50611a0a84838360018b8b612a03565b9a9950505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff83166000908152603460209081526040808320858452918290529091205467ffffffffffffffff1615611a8c576040517fec9d6eeb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526020829052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff861690811790915590519091859173ffffffffffffffffffffffffffffffffffffffff8816917f92a1f7a41a7c585a8b09e25b195e225b1d43248daca46b0faf9e0792777a222991a450505050565b604080516020808252818301909252606091600091906020820181803683370190505090506000805b6020811015611be2576000858260208110611b5d57611b5d6137fe565b1a60f81b90507fff000000000000000000000000000000000000000000000000000000000000008116600003611b935750611be2565b80848481518110611ba657611ba66137fe565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053505060019182019101611b40565b5060008167ffffffffffffffff811115611bfe57611bfe61386b565b6040519080825280601f01601f191660200182016040528015611c28576020820181803683370190505b50905060005b82811015611c9c57838181518110611c4857611c486137fe565b602001015160f81c60f81b828281518110611c6557611c656137fe565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600101611c2e565b50949350505050565b608081015167ffffffffffffffff1615801590611cd957504267ffffffffffffffff16816080015167ffffffffffffffff16105b15611d10576040517f1ab7da6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808201516040808401516060808601518651855186880151868801519488015160808901518051908b012060a08a015173ffffffffffffffffffffffffffffffffffffffff871660009081529b8c9052988b2080549a9b989a9899611337997ffeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d988076999493928c611da0836140ca565b919050558e6080015160405160200161131c9b9a999897969594939291909a8b5273ffffffffffffffffffffffffffffffffffffffff998a1660208c015260408b019890985295909716606089015267ffffffffffffffff938416608089015291151560a088015260c087015260e0860152610100850193909352610120840152166101408201526101600190565b60408051808201909152600081526060602082015284516040805180820190915260008152606060208201528167ffffffffffffffff811115611e7457611e7461386b565b604051908082528060200260200182016040528015611e9d578160200160208202803683370190505b5060208201526040517fa2ea7c6e000000000000000000000000000000000000000000000000000000008152600481018990526000907342000000000000000000000000000000000000209063a2ea7c6e90602401600060405180830381865afa158015611f0f573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052611f559190810190614102565b8051909150611f90576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008367ffffffffffffffff811115611fab57611fab61386b565b60405190808252806020026020018201604052801561204a57816020015b60408051610140810182526000808252602080830182905292820181905260608083018290526080830182905260a0830182905260c0830182905260e0830182905261010083019190915261012082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff909201910181611fc95790505b50905060008467ffffffffffffffff8111156120685761206861386b565b604051908082528060200260200182016040528015612091578160200160208202803683370190505b50905060005b858110156125205760008b82815181106120b3576120b36137fe565b60200260200101519050600067ffffffffffffffff16816020015167ffffffffffffffff16141580156120fe57504267ffffffffffffffff16816020015167ffffffffffffffff1611155b15612135576040517f08e8b93700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8460400151158015612148575080604001515b1561217f576040517f157bd4c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006040518061014001604052806000801b81526020018f81526020016121a34290565b67ffffffffffffffff168152602001836020015167ffffffffffffffff168152602001600067ffffffffffffffff16815260200183606001518152602001836000015173ffffffffffffffffffffffffffffffffffffffff1681526020018d73ffffffffffffffffffffffffffffffffffffffff16815260200183604001511515815260200183608001518152509050600080600090505b6122458382612df4565b600081815260326020526040902054909250156122645760010161223b565b81835260008281526032602090815260409182902085518155908501516001820155908401516002820180546060870151608088015167ffffffffffffffff908116700100000000000000000000000000000000027fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff92821668010000000000000000027fffffffffffffffffffffffffffffffff000000000000000000000000000000009094169190951617919091171691909117905560a0840151600382015560c084015160048201805473ffffffffffffffffffffffffffffffffffffffff9283167fffffffffffffffffffffffff000000000000000000000000000000000000000090911617905560e0850151600583018054610100880151151574010000000000000000000000000000000000000000027fffffffffffffffffffffff000000000000000000000000000000000000000000909116929093169190911791909117905561012084015184919060068201906123e49082614228565b50505060608401511561243b57606084015160009081526032602052604090205461243b576040517fc5723b5100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8287868151811061244e5761244e6137fe565b60200260200101819052508360a00151868681518110612470576124706137fe565b6020026020010181815250508189602001518681518110612493576124936137fe565b6020026020010181815250508f8e73ffffffffffffffffffffffffffffffffffffffff16856000015173ffffffffffffffffffffffffffffffffffffffff167f8bf46bf4cfd674fa735a3d63ec1c9ad4153f033c290341f3a588b75685141b358560405161250391815260200190565b60405180910390a4505050506125198160010190565b9050612097565b5061253083838360008c8c612a03565b845250919998505050505050505050565b606060008267ffffffffffffffff81111561255e5761255e61386b565b604051908082528060200260200182016040528015612587578160200160208202803683370190505b508451909150600090815b818110156126205760008782815181106125ae576125ae6137fe565b6020026020010151905060008151905060005b8181101561260c578281815181106125db576125db6137fe565b60200260200101518787815181106125f5576125f56137fe565b6020908102919091010152600195860195016125c1565b5050506126198160010190565b9050612592565b509195945050505050565b60008281526033602052604090205467ffffffffffffffff161561267b576040517f2e26794600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526033602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff85169081179091559051909184917f5aafceeb1c7ad58e4a84898bdee37c02c0fc46e7d24e6b60e8209449f183459f9190a35050565b60003073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614801561275357507f000000000000000000000000000000000000000000000000000000000000000046145b1561277d57507f000000000000000000000000000000000000000000000000000000000000000090565b50604080517f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401527f000000000000000000000000000000000000000000000000000000000000000060608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b600061072a61282e6126ed565b83612e53565b60008060006128438585612e95565b9092509050600081600481111561285c5761285c614342565b14801561289457508573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16145b156128a4576001925050506129fc565b6000808773ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b88886040516024016128d9929190614371565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516129629190614392565b600060405180830381855afa9150503d806000811461299d576040519150601f19603f3d011682016040523d82523d6000602084013e6129a2565b606091505b50915091508180156129b5575080516020145b80156129f5575080517f1626ba7e00000000000000000000000000000000000000000000000000000000906129f390830160209081019084016143a4565b145b9450505050505b9392505050565b84516000906001819003612a5b57612a538888600081518110612a2857612a286137fe565b602002602001015188600081518110612a4357612a436137fe565b6020026020010151888888612eda565b915050612dea565b602088015173ffffffffffffffffffffffffffffffffffffffff8116612afc5760005b82811015612ae157878181518110612a9857612a986137fe565b6020026020010151600014612ad9576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612a7e565b508315612af157612af1856131f9565b600092505050612dea565b6000808273ffffffffffffffffffffffffffffffffffffffff1663ce46e0466040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6e91906143bd565b905060005b84811015612c2b5760008a8281518110612b8f57612b8f6137fe565b6020026020010151905080600003612ba75750612c23565b82612bde576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b88811115612c18576040517f1101129400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b978890039792909201915b600101612b73565b508715612d06576040517f88e5b2d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416906388e5b2d9908490612c88908e908e906004016143da565b60206040518083038185885af1158015612ca6573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612ccb91906143bd565b612d01576040517fbf2f3a8b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612dd5565b6040517f91db0b7e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416906391db0b7e908490612d5c908e908e906004016143da565b60206040518083038185885af1158015612d7a573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612d9f91906143bd565b612dd5576040517fe8bee83900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8515612de457612de4876131f9565b50925050505b9695505050505050565b60208083015160c084015160e0850151604080870151606088015161010089015160a08a01516101208b01519451600099612e3599989796918c9101614493565b60405160208183030381529060405280519060200120905092915050565b6040517f190100000000000000000000000000000000000000000000000000000000000060208201526022810183905260428101829052600090606201612e35565b6000808251604103612ecb5760208301516040840151606085015160001a612ebf8782858561320c565b94509450505050612ed3565b506000905060025b9250929050565b602086015160009073ffffffffffffffffffffffffffffffffffffffff8116612f4e578515612f35576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8215612f4457612f44846131f9565b6000915050612dea565b8515613039578073ffffffffffffffffffffffffffffffffffffffff1663ce46e0466040518163ffffffff1660e01b8152600401602060405180830381865afa158015612f9f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fc391906143bd565b612ff9576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83861115613033576040517f1101129400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85840393505b8415613111576040517fe49617e100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063e49617e1908890613093908b90600401613771565b60206040518083038185885af11580156130b1573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906130d691906143bd565b61310c576040517fccf3bb2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6131de565b6040517fe60c350500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063e60c3505908890613165908b90600401613771565b60206040518083038185885af1158015613183573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906131a891906143bd565b6131de576040517fbd8ba84d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82156131ed576131ed846131f9565b50939695505050505050565b8015613209576132093382613324565b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115613243575060009050600361331b565b8460ff16601b1415801561325b57508460ff16601c14155b1561326c575060009050600461331b565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa1580156132c0573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81166133145760006001925092505061331b565b9150600090505b94509492505050565b80471015613393576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064015b60405180910390fd5b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d80600081146133ed576040519150601f19603f3d011682016040523d82523d6000602084013e6133f2565b606091505b5050905080610a63576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d61792068617665207265766572746564000000000000606482015260840161338a565b60008083601f84011261349557600080fd5b50813567ffffffffffffffff8111156134ad57600080fd5b6020830191508360208260051b8501011115612ed357600080fd5b600080602083850312156134db57600080fd5b823567ffffffffffffffff8111156134f257600080fd5b6134fe85828601613483565b90969095509350505050565b60005b8381101561352557818101518382015260200161350d565b50506000910152565b6000815180845261354681602086016020860161350a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006129fc602083018461352e565b73ffffffffffffffffffffffffffffffffffffffff8116811461320957600080fd5b80356135b88161358b565b919050565b6000602082840312156135cf57600080fd5b81356129fc8161358b565b6000602082840312156135ec57600080fd5b813567ffffffffffffffff81111561360357600080fd5b820160e081850312156129fc57600080fd5b6020808252825182820181905260009190848201906040850190845b8181101561364d57835183529284019291840191600101613631565b50909695505050505050565b60006060828403121561366b57600080fd5b50919050565b60006020828403121561368357600080fd5b5035919050565b6000610140825184526020830151602085015260408301516136b8604086018267ffffffffffffffff169052565b5060608301516136d4606086018267ffffffffffffffff169052565b5060808301516136f0608086018267ffffffffffffffff169052565b5060a083015160a085015260c083015161372260c086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060e083015161374a60e086018273ffffffffffffffffffffffffffffffffffffffff169052565b506101008381015115159085015261012080840151818601839052612dea8387018261352e565b6020815260006129fc602083018461368a565b6000610100828403121561366b57600080fd5b600080604083850312156137aa57600080fd5b82356137b58161358b565b946020939093013593505050565b6000602082840312156137d557600080fd5b813567ffffffffffffffff8111156137ec57600080fd5b8201604081850312156129fc57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6183360301811261386157600080fd5b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405160a0810167ffffffffffffffff811182821017156138bd576138bd61386b565b60405290565b60405160c0810167ffffffffffffffff811182821017156138bd576138bd61386b565b6040516080810167ffffffffffffffff811182821017156138bd576138bd61386b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156139505761395061386b565b604052919050565b600067ffffffffffffffff8211156139725761397261386b565b5060051b60200190565b60006040828403121561398e57600080fd5b6040516040810181811067ffffffffffffffff821117156139b1576139b161386b565b604052823581526020928301359281019290925250919050565b6000606082840312156139dd57600080fd5b6040516060810181811067ffffffffffffffff82111715613a0057613a0061386b565b604052905080823560ff81168114613a1757600080fd5b8082525060208301356020820152604083013560408201525092915050565b600082601f830112613a4757600080fd5b81356020613a5c613a5783613958565b613909565b82815260609283028501820192828201919087851115613a7b57600080fd5b8387015b85811015613a9e57613a9189826139cb565b8452928401928101613a7f565b5090979650505050505050565b803567ffffffffffffffff811681146135b857600080fd5b600060a08236031215613ad557600080fd5b613add61389a565b8235815260208084013567ffffffffffffffff80821115613afd57600080fd5b9085019036601f830112613b1057600080fd5b8135613b1e613a5782613958565b81815260069190911b83018401908481019036831115613b3d57600080fd5b938501935b82851015613b6657613b54368661397c565b82528582019150604085019450613b42565b80868801525050506040860135925080831115613b8257600080fd5b5050613b9036828601613a36565b604083015250613ba2606084016135ad565b6060820152613bb360808401613aab565b608082015292915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561072a5761072a613bbe565b801515811461320957600080fd5b600067ffffffffffffffff821115613c2857613c2861386b565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600060c08284031215613c6657600080fd5b613c6e6138c3565b90508135613c7b8161358b565b81526020613c8a838201613aab565b818301526040830135613c9c81613c00565b604083015260608381013590830152608083013567ffffffffffffffff811115613cc557600080fd5b8301601f81018513613cd657600080fd5b8035613ce4613a5782613c0e565b8181528684838501011115613cf857600080fd5b818484018583013760008483830101528060808601525050505060a082013560a082015292915050565b600060e08236031215613d3457600080fd5b613d3c61389a565b82358152602083013567ffffffffffffffff811115613d5a57600080fd5b613d6636828601613c54565b602083015250613d7936604085016139cb565b604082015260a0830135613d8c8161358b565b6060820152613bb360c08401613aab565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4183360301811261386157600080fd5b600061072a3683613c54565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc183360301811261386157600080fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613e4657600080fd5b83018035915067ffffffffffffffff821115613e6157600080fd5b6020019150600581901b3603821315612ed357600080fd5b6000613e87613a5784613958565b80848252602080830192508560051b850136811115613ea557600080fd5b855b81811015613ee157803567ffffffffffffffff811115613ec75760008081fd5b613ed336828a01613c54565b865250938201938201613ea7565b50919695505050505050565b600060408284031215613eff57600080fd5b6129fc838361397c565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613f3e57600080fd5b83018035915067ffffffffffffffff821115613f5957600080fd5b6020019150600681901b3603821315612ed357600080fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613fa657600080fd5b83018035915067ffffffffffffffff821115613fc157600080fd5b6020019150606081023603821315612ed357600080fd5b600060608284031215613fea57600080fd5b6129fc83836139cb565b60006020828403121561400657600080fd5b6129fc82613aab565b600181811c9082168061402357607f821691505b60208210810361366b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000610100828403121561406f57600080fd5b61407761389a565b82358152614088846020850161397c565b602082015261409a84606085016139cb565b604082015260c08301356140ad8161358b565b60608201526140be60e08401613aab565b60808201529392505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036140fb576140fb613bbe565b5060010190565b6000602080838503121561411557600080fd5b825167ffffffffffffffff8082111561412d57600080fd5b908401906080828703121561414157600080fd5b6141496138e6565b825181528383015161415a8161358b565b81850152604083015161416c81613c00565b604082015260608301518281111561418357600080fd5b80840193505086601f84011261419857600080fd5b825191506141a8613a5783613c0e565b82815287858486010111156141bc57600080fd5b6141cb8386830187870161350a565b60608201529695505050505050565b601f821115610a6357600081815260208120601f850160051c810160208610156142015750805b601f850160051c820191505b818110156142205782815560010161420d565b505050505050565b815167ffffffffffffffff8111156142425761424261386b565b61425681614250845461400f565b846141da565b602080601f8311600181146142a957600084156142735750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555614220565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156142f6578886015182559484019460019091019084016142d7565b508582101561433257878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b82815260406020820152600061438a604083018461352e565b949350505050565b6000825161386181846020870161350a565b6000602082840312156143b657600080fd5b5051919050565b6000602082840312156143cf57600080fd5b81516129fc81613c00565b6000604082016040835280855180835260608501915060608160051b8601019250602080880160005b8381101561444f577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa088870301855261443d86835161368a565b95509382019390820190600101614403565b50508584038187015286518085528782019482019350915060005b828110156144865784518452938101939281019260010161446a565b5091979650505050505050565b89815260007fffffffffffffffffffffffffffffffffffffffff000000000000000000000000808b60601b166020840152808a60601b166034840152507fffffffffffffffff000000000000000000000000000000000000000000000000808960c01b166048840152808860c01b1660508401525085151560f81b6058830152846059830152835161452c81607985016020880161350a565b80830190507fffffffff000000000000000000000000000000000000000000000000000000008460e01b166079820152607d81019150509a995050505050505050505056fea164736f6c6343000813000a0000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 6251000, + "intent": "Deploy EAS Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000005d8608060405234801561001057600080fd5b506105b8806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d5014610046578063cdcb760a14610098578063e0145f5c146100d0575b600080fd5b6100826040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161008f91906103f7565b60405180910390f35b6100ab6100a6366004610440565b6100ea565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161008f565b734e59b44847b379578588920ca78fbf26c0b4956c6100ab565b8051602080830191909120604080517fff00000000000000000000000000000000000000000000000000000000000000818501527f4e59b44847b379578588920ca78fbf26c0b4956c000000000000000000000000602182015260358101869052605580820184905282518083039091018152607590910190915280519201919091206000919073ffffffffffffffffffffffffffffffffffffffff81163b156101d85760405173ffffffffffffffffffffffffffffffffffffffff8216907ffbe57d889a7f75a4e0c7da304cd158fcaddc4b925cdd9f4cfb115c0f9e48009b90600090a291506103779050565b600080734e59b44847b379578588920ca78fbf26c0b4956c73ffffffffffffffffffffffffffffffffffffffff168787604051602001610219929190610519565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152908290526102519161053f565b6000604051808303816000865af19150503d806000811461028e576040519150601f19603f3d011682016040523d82523d6000602084013e610293565b606091505b5091509150806102a29061055b565b60601c94508115806102e057508273ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b1561032257806040517fcb0fc6f700000000000000000000000000000000000000000000000000000000815260040161031991906103f7565b60405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff167f9b7318127ed899f286ea9ddd7925ed8ad24a682b6a825c3b5b3d88a3f00bc1d28860405161036a91815260200190565b60405180910390a2505050505b92915050565b60005b83811015610398578181015183820152602001610380565b838111156103a7576000848401525b50505050565b600081518084526103c581602086016020860161037d565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061040a60208301846103ad565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806040838503121561045357600080fd5b82359150602083013567ffffffffffffffff8082111561047257600080fd5b818501915085601f83011261048657600080fd5b81358181111561049857610498610411565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104de576104de610411565b816040528281528860208487010111156104f757600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b8281526000825161053181602085016020870161037d565b919091016020019392505050565b6000825161055181846020870161037d565b9190910192915050565b6000815160208301517fffffffffffffffffffffffffffffffffffffffff000000000000000000000000808216935060148310156105a35780818460140360031b1b83161693505b50505091905056fea164736f6c634300080f000a0000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 116400, + "intent": "Deploy ConditionalDeployer Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000283608060405234801561001057600080fd5b50610263806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80631bec9d65146100515780631d8a4e921461006657806354fd4d501461009b57806378ecabce146100e4575b600080fd5b61006461005f3660046101ca565b610107565b005b7fc8bc8f9195cfb2d040744aac63412d02ffc186ea9bd519039edc4666ee9032bc546040519081526020015b60405180910390f35b6100d76040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b60405161009291906101e3565b6100f76100f23660046101ca565b610178565b6040519015158152602001610092565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610154576040517fee37fa8500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fc8bc8f9195cfb2d040744aac63412d02ffc186ea9bd519039edc4666ee9032bc55565b60006101ab6101a57fc8bc8f9195cfb2d040744aac63412d02ffc186ea9bd519039edc4666ee9032bc5490565b836101b1565b92915050565b600081158015906101c3575081828416145b9392505050565b6000602082840312156101dc57600080fd5b5035919050565b600060208083528351808285015260005b81811015610210578581018301518582016040015282016101f4565b81811115610222576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a0000000000000000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 315000, + "intent": "Deploy L2DevFeatureFlags Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000069e6080604052348015600e575f80fd5b506106828061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c8063331b637f1461004357806354fd4d5014610069578063ab4d6f75146100b2575b5f80fd5b610056610051366004610512565b6100c7565b6040519081526020015b60405180910390f35b6100a56040518060400160405280600581526020017f312e302e3200000000000000000000000000000000000000000000000000000081525081565b604051610060919061053b565b6100c56100c036600461058e565b61039e565b005b5f67ffffffffffffffff801683602001511115610110576040517fd1f79e8200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604083015163ffffffff1015610152576040517f94338eba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606083015167ffffffffffffffff1015610198576040517f596a19a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82516040515f916101dd91859060200160609290921b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000168252601482015260340190565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00181528282528051602091820120878201516060890151898501515f9487018590527fffffffffffffffff00000000000000000000000000000000000000000000000060c084811b8216602c8a015283901b1660348801527fffffffff0000000000000000000000000000000000000000000000000000000060e082901b16603c88015292965090949093919291016040516020818303038152906040526102ac906105bc565b90505f85826040516020016102cb929190918252602082015260400190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828252805160209182012060808d01519184018190529183015291505f90606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0300000000000000000000000000000000000000000000000000000000000000179a9950505050505050505050565b5f6103b76103b136859003850185610601565b836100c7565b90505f6103c38261043b565b509050806103fd576040517fe3c0081600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b827f5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba78560405161042d9190610622565b60405180910390a250505050565b5f805a835491505a6103e891031115939092509050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610475575f80fd5b919050565b5f60a0828403121561048a575f80fd5b60405160a0810181811067ffffffffffffffff821117156104d2577f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6040529050806104e183610452565b8152602083013560208201526040830135604082015260608301356060820152608083013560808201525092915050565b5f8060c08385031215610523575f80fd5b61052d848461047a565b9460a0939093013593505050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b5f8082840360c08112156105a0575f80fd5b60a08112156105ad575f80fd5b50919360a08501359350915050565b805160208083015191908110156105fb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b5f60a08284031215610611575f80fd5b61061b838361047a565b9392505050565b60a0810173ffffffffffffffffffffffffffffffffffffffff61064484610452565b168252602083013560208301526040830135604083015260608301356060830152608083013560808301529291505056fea164736f6c6343000819000a0000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 668000, + "intent": "Deploy CrossL2Inbox Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000011836080604052348015600e575f80fd5b506111678061001c5f395ff3fe6080604052600436106100b8575f3560e01c80637056f41f11610071578063b1b1b2091161004c578063b1b1b20914610228578063bc294d7d14610266578063ecc7042814610291575f80fd5b80637056f41f146101b65780637936cbee146101d55780638d1d298f14610215575f80fd5b806352617f3c116100a157806352617f3c1461011c57806354fd4d50146101425780636b0c3c5e14610197575f80fd5b806324794462146100bc57806338ffde18146100e3575b5f80fd5b3480156100c7575f80fd5b506100d06102c5565b6040519081526020015b60405180910390f35b3480156100ee575f80fd5b506100f7610344565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100da565b348015610127575f80fd5b5061012f5f81565b60405161ffff90911681526020016100da565b34801561014d575f80fd5b5061018a6040518060400160405280600581526020017f312e332e3100000000000000000000000000000000000000000000000000000081525081565b6040516100da9190610c7e565b3480156101a2575f80fd5b506100d06101b1366004610d00565b6103c3565b3480156101c1575f80fd5b506100d06101d0366004610d77565b6104ae565b3480156101e0575f80fd5b506101e96106ba565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152016100da565b61018a610223366004610dcf565b61075e565b348015610233575f80fd5b50610256610242366004610e25565b5f6020819052908152604090205460ff1681565b60405190151581526020016100da565b348015610271575f80fd5b506100d0610280366004610e25565b60026020525f908152604090205481565b34801561029c575f80fd5b506001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff166100d0565b5f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c61031e576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b507f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75c90565b5f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c61039d576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b507fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35c90565b5f610407874688888888888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610ae192505050565b5f878152600260205260409020549091508114610450576040517f6eca2e4b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b858473ffffffffffffffffffffffffffffffffffffffff16887f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f32088878760405161049c93929190610e3c565b60405180910390a49695505050505050565b5f4685036104e8576040517f8ed9a95d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fffffffffffffffffffffffffbdffffffffffffffffffffffffffffffffffffdd73ffffffffffffffffffffffffffffffffffffffff851601610557576040517f4faa250900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f6105816001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b90506105c6864683338989898080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610ae192505050565b5f828152600260205260408120829055600180549294507dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff909216919061060a83610ea5565b91906101000a8154817dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02191690837dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16021790555050808573ffffffffffffffffffffffffffffffffffffffff16877f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3203388886040516106a993929190610e3c565b60405180910390a450949350505050565b5f807ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c610714576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50507fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35c907f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75c90565b60607ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c156107b9576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60017ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5d7342000000000000000000000000000000000000236107ff6020860186610f06565b73ffffffffffffffffffffffffffffffffffffffff161461084c576040517f7987c15700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002273ffffffffffffffffffffffffffffffffffffffff1663ab4d6f7585858560405161088d929190610f21565b6040519081900381207fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1682526108cc9291600401610f30565b5f604051808303815f87803b1580156108e3575f80fd5b505af11580156108f5573d5f803e3d5ffd5b505050505f805f805f6109088888610b1f565b9450945094509450945046851461094b576040517f31ac221100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60808901355f61095f878387878a88610ae1565b5f8181526020819052604090205490915060ff16156109aa576040517f9ca9480b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f81815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790556109ea8285610be8565b5f8673ffffffffffffffffffffffffffffffffffffffff163485604051610a119190610f89565b5f6040518083038185875af1925050503d805f8114610a4b576040519150601f19603f3d011682016040523d82523d5f602084013e610a50565b606091505b509950905080610a6257885189602001fd5b8186847fc270d73e26d2d39dee7ef92093555927e344e243415547ecc350b2b5385b68a28c80519060200120604051610a9d91815260200190565b60405180910390a4610aaf5f80610be8565b50505050505050505f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5d9392505050565b5f868686868686604051602001610afd96959493929190610f9f565b6040516020818303038152906040528051906020012090509695505050505050565b5f808080606081610b33602082898b610ff5565b810190610b409190610e25565b90507f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3208114610b9b576040517fdf1eb58600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610ba960806020898b610ff5565b810190610bb6919061101c565b91975095509350610bca876080818b610ff5565b810190610bd7919061107e565b969995985093965092949392505050565b817f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75d807fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35d5050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f610c906020830184610c32565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610cb8575f80fd5b50565b5f8083601f840112610ccb575f80fd5b50813567ffffffffffffffff811115610ce2575f80fd5b602083019150836020828501011115610cf9575f80fd5b9250929050565b5f805f805f8060a08789031215610d15575f80fd5b86359550602087013594506040870135610d2e81610c97565b93506060870135610d3e81610c97565b9250608087013567ffffffffffffffff811115610d59575f80fd5b610d6589828a01610cbb565b979a9699509497509295939492505050565b5f805f8060608587031215610d8a575f80fd5b843593506020850135610d9c81610c97565b9250604085013567ffffffffffffffff811115610db7575f80fd5b610dc387828801610cbb565b95989497509550505050565b5f805f83850360c0811215610de2575f80fd5b60a0811215610def575f80fd5b5083925060a084013567ffffffffffffffff811115610e0c575f80fd5b610e1886828701610cbb565b9497909650939450505050565b5f60208284031215610e35575f80fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff8416815260406020820152816040820152818360608301375f818301606090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016010192915050565b5f7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808316818103610efc577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b6001019392505050565b5f60208284031215610f16575f80fd5b8135610c9081610c97565b818382375f9101908152919050565b60c081018335610f3f81610c97565b73ffffffffffffffffffffffffffffffffffffffff1682526020848101359083015260408085013590830152606080850135908301526080938401359382019390935260a0015290565b5f82518060208501845e5f920191825250919050565b8681528560208201528460408201525f73ffffffffffffffffffffffffffffffffffffffff808616606084015280851660808401525060c060a0830152610fe960c0830184610c32565b98975050505050505050565b5f8085851115611003575f80fd5b8386111561100f575f80fd5b5050820193919092039150565b5f805f6060848603121561102e575f80fd5b83359250602084013561104081610c97565b929592945050506040919091013590565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f806040838503121561108f575f80fd5b823561109a81610c97565b9150602083013567ffffffffffffffff808211156110b6575f80fd5b818501915085601f8301126110c9575f80fd5b8135818111156110db576110db611051565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561112157611121611051565b81604052828152886020848701011115611139575f80fd5b826020860160208301375f602084830101528095505050505050925092905056fea164736f6c6343000819000a0000000000000000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1611000, + "intent": "Deploy L2ToL2CrossDomainMessenger Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000007ab608060405234801561001057600080fd5b5061078b806100206000396000f3fe6080604052600436106100345760003560e01c80634f0edcc91461003957806354fd4d501461005b57806364a197f3146100ba575b600080fd5b34801561004557600080fd5b506100596100543660046105ae565b6100db565b005b34801561006757600080fd5b506100a46040518060400160405280600581526020017f312e302e3100000000000000000000000000000000000000000000000000000081525081565b6040516100b1919061065a565b60405180910390f35b6100cd6100c8366004610674565b610340565b6040519081526020016100b1565b3373420000000000000000000000000000000000002314610128576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008073420000000000000000000000000000000000002373ffffffffffffffffffffffffffffffffffffffff16637936cbee6040518163ffffffff1660e01b81526004016040805180830381865afa158015610189573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ad91906106a0565b909250905073ffffffffffffffffffffffffffffffffffffffff82163014610201576040517fbc22e2aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa0712d68000000000000000000000000000000000000000000000000000000008152600481018490527342000000000000000000000000000000000000259063a0712d6890602401600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b50505050828460405161028d9061057d565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156102c7573d6000803e3d6000fd5b50508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d3598584604051610331929190918252602082015260400190565b60405180910390a35050505050565b600073ffffffffffffffffffffffffffffffffffffffff831661038f576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002573ffffffffffffffffffffffffffffffffffffffff166344df8e70346040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103eb57600080fd5b505af11580156103ff573d6000803e3d6000fd5b50506040805133602482015273ffffffffffffffffffffffffffffffffffffffff881660448201523460648083019190915282518083039091018152608490910182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f4f0edcc90000000000000000000000000000000000000000000000000000000017905290517f7056f41f0000000000000000000000000000000000000000000000000000000081527342000000000000000000000000000000000000239450637056f41f93506104de9250869130916004016106ce565b6020604051808303816000875af11580156104fd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610521919061070c565b604080513481526020810185905291925073ffffffffffffffffffffffffffffffffffffffff85169133917fed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402910160405180910390a392915050565b60598061072683390190565b73ffffffffffffffffffffffffffffffffffffffff811681146105ab57600080fd5b50565b6000806000606084860312156105c357600080fd5b83356105ce81610589565b925060208401356105de81610589565b929592945050506040919091013590565b6000815180845260005b81811015610615576020818501810151868301820152016105f9565b81811115610627576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061066d60208301846105ef565b9392505050565b6000806040838503121561068757600080fd5b823561069281610589565b946020939093013593505050565b600080604083850312156106b357600080fd5b82516106be81610589565b6020939093015192949293505050565b83815273ffffffffffffffffffffffffffffffffffffffff8316602082015260606040820152600061070360608301846105ef565b95945050505050565b60006020828403121561071e57600080fd5b505191905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 757000, + "intent": "Deploy SuperchainETHBridge Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000003b3608060405234801561001057600080fd5b50610393806100206000396000f3fe60806040526004361061003f5760003560e01c806344df8e701461004457806354fd4d501461004e578063a0712d68146100ad578063b60d4288146100cd575b600080fd5b61004c6100d5565b005b34801561005a57600080fd5b506100976040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b6040516100a491906102a1565b60405180910390f35b3480156100b957600080fd5b5061004c6100c8366004610314565b61015a565b61004c610229565b3373420000000000000000000000000000000000002414610122576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907f875e07afd7ce17c6531b1a6b7b34829dcd8b7e6639448afbd6a8e29fa1422b82906020015b60405180910390a2565b33734200000000000000000000000000000000000024146101a7576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80336040516101b590610295565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156101ef573d6000803e3d6000fd5b505060405181815233907f85719716ac5bd2744ae7ed3d16702129383049b97123b506320e7a5826ebbbba9060200160405180910390a250565b34600003610263576040517f2c5211c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907fbb9e497a5b82d1a37f9496dd70c6efb97ba0d98c66c3422d05010105d063359890602001610150565b60598061032e83390190565b600060208083528351808285015260005b818110156102ce578581018301518582016040015282016102b2565b818111156102e0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561032657600080fd5b503591905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a00000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 423000, + "intent": "Deploy ETHLiquidity Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000010b1608060405234801561001057600080fd5b50611091806100206000396000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80635cf24969116100f9578063c598591811610097578063e591b28211610071578063e591b28214610434578063e81b2c6d1461044e578063f820614014610457578063fe3d57101461046057600080fd5b8063c598591814610404578063d844471514610424578063dad544e01461042c57600080fd5b80638381f58a116100d35780638381f58a146103be5780638b239f73146103d25780639e8c4966146103db578063b80777ea146103e457600080fd5b80635cf249691461038557806364ca23ef1461038e57806368d5dca6146103a257600080fd5b80634397dfef1161016657806347af267b1161014057806347af267b146103145780634d5d9a2a1461033757806354fd4d5014610368578063550fcdc91461037d57600080fd5b80634397dfef146102c3578063440a5e20146102f957806346a4d7801461030157600080fd5b806316d3bc7f116101a257806316d3bc7f14610202578063213268491461022f5780633db6be2b1461028e5780633e47158c1461029657600080fd5b8063015d8eb9146101c9578063098999be146101de57806309bd5a60146101e6575b600080fd5b6101dc6101d7366004610d9c565b610491565b005b6101dc6105d1565b6101ef60025481565b6040519081526020015b60405180910390f35b6008546102169067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101f9565b7f435553544f4d5f4741535f544f4b454e0000000000000000000000000000000060005260096020527f4ad9936a67aeb1898ef7b848aecdf71a1f8999fbf63ff2f5b5691cb14bedfe4d5460ff165b60405190151581526020016101f9565b6101dc6105e4565b61029e61060e565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101f9565b6102cb610819565b6040805173ffffffffffffffffffffffffffffffffffffffff909316835260ff9091166020830152016101f9565b6101dc610880565b6101dc61030f366004610e0e565b6108d7565b61027e610322366004610e0e565b60096020526000908152604090205460ff1681565b6008546103539068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101f9565b6103706108ec565b6040516101f99190610e57565b61037061094c565b6101ef60015481565b6003546102169067ffffffffffffffff1681565b6003546103539068010000000000000000900463ffffffff1681565b6000546102169067ffffffffffffffff1681565b6101ef60055481565b6101ef60065481565b6000546102169068010000000000000000900467ffffffffffffffff1681565b600354610353906c01000000000000000000000000900463ffffffff1681565b610370610a88565b61029e610b79565b73deaddeaddeaddeaddeaddeaddeaddeaddead000161029e565b6101ef60045481565b6101ef60075481565b60085461047e906c01000000000000000000000000900461ffff1681565b60405161ffff90911681526020016101f9565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610539576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c756573000000000060648201526084015b60405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b6105d9610880565b60a43560a01c600855565b6105ec610880565b6dffff00000000000000000000000060b03560901c1660a43560a01c17600855565b6000806106397fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff81161561065c57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000081525051600261069f9190610ea8565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e67657200000000000091909117906106fa906060015b604051602081830303815290604052805190602001205490565b14610731576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051306020820152600191810191909152600090610753906060016106e0565b905073ffffffffffffffffffffffffffffffffffffffff8116156107e7578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156107bc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107e09190610f0c565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4c31426c6f636b4347543a20646570726563617465640000000000000000000060448201526000908190606401610530565b73deaddeaddeaddeaddeaddeaddeaddeaddead00013381146108aa57633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b6108e033610bf1565b6108e981610cce565b50565b606061092860408051808201909152600581527f312e392e30000000000000000000000000000000000000000000000000000000602082015290565b6040516020016109389190610f49565b604051602081830303815290604052905090565b60606109a27f435553544f4d5f4741535f544f4b454e0000000000000000000000000000000060005260096020527f4ad9936a67aeb1898ef7b848aecdf71a1f8999fbf63ff2f5b5691cb14bedfe4d5460ff1690565b6109de575060408051808201909152600381527f4554480000000000000000000000000000000000000000000000000000000000602082015290565b73420000000000000000000000000000000000002a73ffffffffffffffffffffffffffffffffffffffff1663550fcdc96040518163ffffffff1660e01b8152600401600060405180830381865afa158015610a3d573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a839190810190610fb9565b905090565b6060610ade7f435553544f4d5f4741535f544f4b454e0000000000000000000000000000000060005260096020527f4ad9936a67aeb1898ef7b848aecdf71a1f8999fbf63ff2f5b5691cb14bedfe4d5460ff1690565b610b1a575060408051808201909152600581527f4574686572000000000000000000000000000000000000000000000000000000602082015290565b73420000000000000000000000000000000000002a73ffffffffffffffffffffffffffffffffffffffff1663d84447156040518163ffffffff1660e01b8152600401600060405180830381865afa158015610a3d573d6000803e3d6000fd5b6000610b8361060e565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bcd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a839190610f0c565b73ffffffffffffffffffffffffffffffffffffffff811673deaddeaddeaddeaddeaddeaddeaddeaddead00011480610c5b5750610c2c610b79565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b80610c985750610c6961060e565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b6108e9576040517fbe9d7ca600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526009602052604090205460ff1615610d17576040517f4f45326000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526009602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660019081179091559051909183917fb876f6594132c89891d2fd198e925e999be741ec809abb58bfe9b966876cc06c9190a350565b803567ffffffffffffffff81168114610d9757600080fd5b919050565b600080600080600080600080610100898b031215610db957600080fd5b610dc289610d7f565b9750610dd060208a01610d7f565b96506040890135955060608901359450610dec60808a01610d7f565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208284031215610e2057600080fd5b5035919050565b60005b83811015610e42578181015183820152602001610e2a565b83811115610e51576000848401525b50505050565b6020815260008251806020840152610e76816040850160208701610e27565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615610f07577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b600060208284031215610f1e57600080fd5b815173ffffffffffffffffffffffffffffffffffffffff81168114610f4257600080fd5b9392505050565b60008251610f5b818460208701610e27565b7f2b637573746f6d2d6761732d746f6b656e2e3100000000000000000000000000920191825250601301919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600060208284031215610fcb57600080fd5b815167ffffffffffffffff80821115610fe357600080fd5b818401915084601f830112610ff757600080fd5b81518181111561100957611009610f8a565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561104f5761104f610f8a565b8160405282815287602084870101111561106857600080fd5b611079836020830160208801610e27565b97965050505050505056fea164736f6c634300080f000a000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1568000, + "intent": "Deploy L1BlockCGT Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000862608060405234801561001057600080fd5b50610842806100206000396000f3fe6080604052600436106100695760003560e01c806382e3702d1161004357806382e3702d146100f6578063c2b3e5ac14610136578063ecc704281461014957600080fd5b80633f827a5a1461009257806344df8e70146100bf57806354fd4d50146100d457600080fd5b3661008d5761008b33620186a0604051806020016040528060008152506101ae565b005b600080fd5b34801561009e57600080fd5b506100a7600181565b60405161ffff90911681526020015b60405180910390f35b3480156100cb57600080fd5b5061008b610284565b3480156100e057600080fd5b506100e96102bc565b6040516100b691906105dd565b34801561010257600080fd5b506101266101113660046105f7565b60006020819052908152604090205460ff1681565b60405190151581526020016100b6565b61008b61014436600461063f565b6101ae565b34801561015557600080fd5b506101a06001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016100b6565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663213268496040518163ffffffff1660e01b8152600401602060405180830381865afa15801561020d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102319190610743565b801561023d5750600034115b15610274576040517fcdfaa11100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61027f83838361031c565b505050565b4761028e816104e0565b60405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b60606102f860408051808201909152600581527f312e322e30000000000000000000000000000000000000000000000000000000602082015290565b6040516020016103089190610765565b604051602081830303815290604052905090565b60006103b26040518060c001604052806103766001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b815233602082015273ffffffffffffffffffffffffffffffffffffffff871660408201523460608201526080810186905260a00184905261050a565b600081815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055905073ffffffffffffffffffffffffffffffffffffffff84163361044d6001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b7f02a52367d10742d8032712c1bb8e0144ff1ec5ffda1ed7d70bb05a27449550543487878760405161048294939291906107a6565b60405180910390a45050600180547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8082168301167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b806040516104ed90610557565b6040518091039082f090508015801561027f573d6000803e3d6000fd5b80516020808301516040808501516060860151608087015160a0880151935160009761053a9790969591016107d6565b604051602081830303815290604052805190602001209050919050565b60088061082e83390190565b60005b8381101561057e578181015183820152602001610566565b8381111561058d576000848401525b50505050565b600081518084526105ab816020860160208601610563565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006105f06020830184610593565b9392505050565b60006020828403121561060957600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060006060848603121561065457600080fd5b833573ffffffffffffffffffffffffffffffffffffffff8116811461067857600080fd5b925060208401359150604084013567ffffffffffffffff8082111561069c57600080fd5b818601915086601f8301126106b057600080fd5b8135818111156106c2576106c2610610565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561070857610708610610565b8160405282815289602084870101111561072157600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b60006020828403121561075557600080fd5b815180151581146105f057600080fd5b60008251610777818460208701610563565b7f2b637573746f6d2d6761732d746f6b656e000000000000000000000000000000920191825250601101919050565b8481528360208201526080604082015260006107c56080830185610593565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a083015261082160c0830184610593565b9897505050505050505056fe608060405230fffea164736f6c634300080f000a000000000000000000000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 827000, + "intent": "Deploy L2ToL1MessagePasserCGT Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000146a608060405234801561001057600080fd5b5061001961001e565b6100de565b600054610100900460ff161561008a5760405162461bcd60e51b815260206004820152602760248201527f496e697469616c697a61626c653a20636f6e747261637420697320696e697469604482015266616c697a696e6760c81b606482015260840160405180910390fd5b60005460ff90811610156100dc576000805460ff191660ff9081179091556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b565b61137d806100ed6000396000f3fe6080604052600436106100dd5760003560e01c80638da5cb5b1161007f578063d844471511610059578063d844471514610256578063dad544e01461026b578063f2fde38b14610280578063f46eccc4146102a057600080fd5b80638da5cb5b146101eb5780639065714714610216578063c6f69fbb1461023657600080fd5b806344df8e70116100bb57806344df8e701461016357806354fd4d501461016b578063550fcdc9146101c1578063715018a6146101d657600080fd5b80630c984832146100e25780633e47158c1461010457806340c10f1914610143575b600080fd5b3480156100ee57600080fd5b506101026100fd366004610ec7565b6102e0565b005b34801561011057600080fd5b5061011961035f565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561014f57600080fd5b5061010261015e366004610eeb565b61056a565b6101026106cc565b34801561017757600080fd5b506101b46040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b60405161013a9190610f17565b3480156101cd57600080fd5b506101b46107c2565b3480156101e257600080fd5b50610102610850565b3480156101f757600080fd5b5060335473ffffffffffffffffffffffffffffffffffffffff16610119565b34801561022257600080fd5b50610102610231366004611064565b610864565b34801561024257600080fd5b50610102610251366004610ec7565b610a29565b34801561026257600080fd5b506101b4610aa5565b34801561027757600080fd5b50610119610ab2565b34801561028c57600080fd5b5061010261029b366004610ec7565b610b2f565b3480156102ac57600080fd5b506102d06102bb366004610ec7565b60656020526000908152604090205460ff1681565b604051901515815260200161013a565b6102e8610be6565b73ffffffffffffffffffffffffffffffffffffffff811660008181526065602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055517f83b05b6735acd4b85e3bded8e72c851d1a87718f81e3c8e6f0c9d9a2baa88e469190a250565b60008061038a7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b905073ffffffffffffffffffffffffffffffffffffffff8116156103ad57919050565b6040518060400160405280601a81526020017f4f564d5f4c3143726f7373446f6d61696e4d657373656e6765720000000000008152505160026103f091906110da565b604080513060208201526000918101919091527f4f564d5f4c3143726f7373446f6d61696e4d657373656e676572000000000000919091179061044b906060015b604051602081830303815290604052805190602001205490565b14610482576040517f54e433cd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080513060208201526001918101919091526000906104a490606001610431565b905073ffffffffffffffffffffffffffffffffffffffff811615610538578073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561050d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610531919061113e565b9250505090565b6040517f332144db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3360009081526065602052604090205460ff166105b3576040517f5fbc4ede00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f2e1a7d4d0000000000000000000000000000000000000000000000000000000081526004810182905273420000000000000000000000000000000000002990632e1a7d4d90602401600060405180830381600087803b15801561061957600080fd5b505af115801561062d573d6000803e3d6000fd5b50505050808260405161063f90610e99565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f0905080158015610679573d6000803e3d6000fd5b505060405181815273ffffffffffffffffffffffffffffffffffffffff83169033907fec89d80a36947288037745287dde87d62cd8c141d5323130b3d26d97d84004c79060200160405180910390a35050565b3360009081526065602052604090205460ff16610715576040517f5fbc4ede00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002973ffffffffffffffffffffffffffffffffffffffff1663d0e30db0346040518263ffffffff1660e01b81526004016000604051808303818588803b15801561077157600080fd5b505af1158015610785573d6000803e3d6000fd5b50506040513481523393507f875e07afd7ce17c6531b1a6b7b34829dcd8b7e6639448afbd6a8e29fa1422b829250602001905060405180910390a2565b606780546107cf9061115b565b80601f01602080910402602001604051908101604052809291908181526020018280546107fb9061115b565b80156108485780601f1061081d57610100808354040283529160200191610848565b820191906000526020600020905b81548152906001019060200180831161082b57829003601f168201915b505050505081565b610858610be6565b6108626000610c67565b565b600054610100900460ff16158080156108845750600054600160ff909116105b8061089e5750303b15801561089e575060005460ff166001145b61092f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084015b60405180910390fd5b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561098d57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b610995610cde565b61099d610d5f565b6109a684610c67565b60666109b284826111fd565b5060676109bf83826111fd565b508015610a2357600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b610a31610be6565b73ffffffffffffffffffffffffffffffffffffffff811660008181526065602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517fdf6bf03dfab5b4ccec3ba95544b98d7ecc9d4b9293d8673e86cb6edb5ac0cb629190a250565b606680546107cf9061115b565b6000610abc61035f565b73ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b06573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2a919061113e565b905090565b610b37610be6565b73ffffffffffffffffffffffffffffffffffffffff8116610bda576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f64647265737300000000000000000000000000000000000000000000000000006064820152608401610926565b610be381610c67565b50565b60335473ffffffffffffffffffffffffffffffffffffffff163314610862576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610926565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b33610ce761035f565b73ffffffffffffffffffffffffffffffffffffffff1614158015610d28575033610d0f610ab2565b73ffffffffffffffffffffffffffffffffffffffff1614155b15610862576040517fc4050a2600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600054610100900460ff16610df6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610926565b610862600054610100900460ff16610e90576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610926565b61086233610c67565b60598061131883390190565b73ffffffffffffffffffffffffffffffffffffffff81168114610be357600080fd5b600060208284031215610ed957600080fd5b8135610ee481610ea5565b9392505050565b60008060408385031215610efe57600080fd5b8235610f0981610ea5565b946020939093013593505050565b600060208083528351808285015260005b81811015610f4457858101830151858201604001528201610f28565b81811115610f56576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f830112610fca57600080fd5b813567ffffffffffffffff80821115610fe557610fe5610f8a565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561102b5761102b610f8a565b8160405283815286602085880101111561104457600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561107957600080fd5b833561108481610ea5565b9250602084013567ffffffffffffffff808211156110a157600080fd5b6110ad87838801610fb9565b935060408601359150808211156110c357600080fd5b506110d086828701610fb9565b9150509250925092565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615611139577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500290565b60006020828403121561115057600080fd5b8151610ee481610ea5565b600181811c9082168061116f57607f821691505b6020821081036111a8577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156111f857600081815260208120601f850160051c810160208610156111d55750805b601f850160051c820191505b818110156111f4578281556001016111e1565b5050505b505050565b815167ffffffffffffffff81111561121757611217610f8a565b61122b81611225845461115b565b846111ae565b602080601f83116001811461127e57600084156112485750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556111f4565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156112cb578886015182559484019460019091019084016112ac565b508582101561130757878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b0190555056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a00000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 1870000, + "intent": "Deploy LiquidityController Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000036d608060405234801561001057600080fd5b5061034d806100206000396000f3fe6080604052600436106100345760003560e01c80632e1a7d4d1461003957806354fd4d501461005b578063d0e30db0146100ba575b600080fd5b34801561004557600080fd5b5061005961005436600461025b565b6100c2565b005b34801561006757600080fd5b506100a46040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100b19190610274565b60405180910390f35b6100596101cb565b3373420000000000000000000000000000000000002a1461010f576040517f565369fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b47811115610149576040517f7b7f21e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80336040516101579061024f565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f0905080158015610191573d6000803e3d6000fd5b505060405181815233907fb1cce8684b4ffa8667b4577654e61ee3480d661ee9c27522ac80e211f6bd4d259060200160405180910390a250565b3373420000000000000000000000000000000000002a14610218576040517f565369fa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907f7ff07ce9a287649537e4b012e45cf012d90228b12e2b56bb03515a6b5436fcdf9060200160405180910390a2565b6059806102e883390190565b60006020828403121561026d57600080fd5b5035919050565b600060208083528351808285015260005b818110156102a157858101830151858201604001528201610285565b818111156102b3576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a00000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 392000, + "intent": "Deploy NativeAssetLiquidity Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0x3659cfe6000000000000000000000000893c2ceeb71d38514daf67728d3ff1b213fc4b5f", + "from": "0x0000000000000000000000000000000000000000", + "gasLimit": 49711, + "intent": "Upgrade L2ProxyAdmin Implementation", + "to": "0x4200000000000000000000000000000000000018" + }, + { + "data": "0xcdcb760a9b217f1b15f9c04316d04d42f550c340c5b2ee8e5ae05cab4f8cd9cb21970ca40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000400b6103e06040523480156200001257600080fd5b5060405162003ccb38038062003ccb8339810160408190526200003591620001c7565b30608090815281516001600160a01b0390811660a09081526020840151821660e09081526040850151831660c0908152606086015184166101009081529486015184166101209081529286015184166101409081529086015184166101609081529186015184166101809081529486015184166101a09081529286015184166101c09081529086015184166101e09081529186015184166102009081529486015184166102209081529286015184166102409081529086015184166102609081529186015184166102809081529486015184166102a09081529286015184166102c09081529086015184166102e0908152918601518416610300908152948601518416610320908152928601518416610340528501518316610360528401518216610380529183015181166103a052910151166103c052620003e6565b60405161034081016001600160401b0381118282101715620001a457634e487b7160e01b600052604160045260246000fd5b60405290565b80516001600160a01b0381168114620001c257600080fd5b919050565b60006103408284031215620001db57600080fd5b620001e562000172565b620001f083620001aa565b81526200020060208401620001aa565b60208201526200021360408401620001aa565b60408201526200022660608401620001aa565b60608201526200023960808401620001aa565b60808201526200024c60a08401620001aa565b60a08201526200025f60c08401620001aa565b60c08201526200027260e08401620001aa565b60e082015261010062000287818501620001aa565b908201526101206200029b848201620001aa565b90820152610140620002af848201620001aa565b90820152610160620002c3848201620001aa565b90820152610180620002d7848201620001aa565b908201526101a0620002eb848201620001aa565b908201526101c0620002ff848201620001aa565b908201526101e062000313848201620001aa565b9082015261020062000327848201620001aa565b908201526102206200033b848201620001aa565b908201526102406200034f848201620001aa565b9082015261026062000363848201620001aa565b9082015261028062000377848201620001aa565b908201526102a06200038b848201620001aa565b908201526102c06200039f848201620001aa565b908201526102e0620003b3848201620001aa565b90820152610300620003c7848201620001aa565b90820152610320620003db848201620001aa565b908201529392505050565b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051610320516103405161036051610380516103a0516103c0516136bb62000610600039600081816104af01526118ea0152600081816104860152611af001526000818161045d01526112b2015260008181610434015261193301526000818161040b0152611a360152600081816103e201526119f80152600081816103b901526119ba015260008181610390015261197c0152600081816103670152611ab201526000818161033e0152611a7401526000818161031501526115cb0152600081816102ec015261154b0152600081816102c301526114cc01526000818161029a01526118ac01526000818161027101526111b2015260008181610248015261186e01526000818161021f01526118480152600081816101f601526116a20152600081816101ce015261167c0152600081816101a6015261108e01526000818161017e015261111a01526000818161015601526113af01526000818161012e0152610f8801526000818160df0152610e82015260008181610107015261163401526000818160ba01528181610ea401528181610faa015281816110b00152818161113c015281816111d4015281816112d4015281816113d1015281816114ee0152818161156d01526115ed0152600061050201526136bb6000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d5014610046578063615f64fe14610098578063d55ec697146104e1575b600080fd5b6100826040518060400160405280600581526020017f312e382e3000000000000000000000000000000000000000000000000000000081525081565b60405161008f91906130db565b60405180910390f35b604080516103408101825273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811682527f0000000000000000000000000000000000000000000000000000000000000000811660208301527f00000000000000000000000000000000000000000000000000000000000000008116828401527f0000000000000000000000000000000000000000000000000000000000000000811660608301527f0000000000000000000000000000000000000000000000000000000000000000811660808301527f0000000000000000000000000000000000000000000000000000000000000000811660a08301527f0000000000000000000000000000000000000000000000000000000000000000811660c08301527f0000000000000000000000000000000000000000000000000000000000000000811660e08301527f000000000000000000000000000000000000000000000000000000000000000081166101008301527f000000000000000000000000000000000000000000000000000000000000000081166101208301527f000000000000000000000000000000000000000000000000000000000000000081166101408301527f000000000000000000000000000000000000000000000000000000000000000081166101608301527f000000000000000000000000000000000000000000000000000000000000000081166101808301527f000000000000000000000000000000000000000000000000000000000000000081166101a08301527f000000000000000000000000000000000000000000000000000000000000000081166101c08301527f000000000000000000000000000000000000000000000000000000000000000081166101e08301527f000000000000000000000000000000000000000000000000000000000000000081166102008301527f000000000000000000000000000000000000000000000000000000000000000081166102208301527f000000000000000000000000000000000000000000000000000000000000000081166102408301527f000000000000000000000000000000000000000000000000000000000000000081166102608301527f000000000000000000000000000000000000000000000000000000000000000081166102808301527f000000000000000000000000000000000000000000000000000000000000000081166102a08301527f000000000000000000000000000000000000000000000000000000000000000081166102c08301527f000000000000000000000000000000000000000000000000000000000000000081166102e08301527f000000000000000000000000000000000000000000000000000000000000000081166103008301527f000000000000000000000000000000000000000000000000000000000000000016610320820152905161008f91906130ee565b6104e96104eb565b005b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016300361055a576040517fada337cf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610564610572565b905061056f81610e43565b50565b61057a612f3a565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663213268496040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105fd919061332f565b15156101408201526040517f47af267b0000000000000000000000000000000000000000000000000000000081527f494e5445524f50000000000000000000000000000000000000000000000000006004820152734200000000000000000000000000000000000015906347af267b90602401602060405180830381865afa9250505080156106c7575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526106c49181019061332f565b60015b6106d85760006101608201526106e1565b15156101608201525b80610160015180156106fa57506106f86001611b14565b155b15610731576040517fa27dcc8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604051806020016040528073420000000000000000000000000000000000000773ffffffffffffffffffffffffffffffffffffffff16639fce812c6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561079b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107bf9190613373565b73ffffffffffffffffffffffffffffffffffffffff16905281526040805160208082018084527f7f46ddb200000000000000000000000000000000000000000000000000000000905291519091829173420000000000000000000000000000000000001091637f46ddb29160248086019291908187030181865afa15801561084b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086f9190613373565b73ffffffffffffffffffffffffffffffffffffffff168152508160200181905250604051806020016040528073420000000000000000000000000000000000001473ffffffffffffffffffffffffffffffffffffffff16637f46ddb26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108fa573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061091e9190613373565b73ffffffffffffffffffffffffffffffffffffffff168152508160400181905250604051806020016040528073420000000000000000000000000000000000001273ffffffffffffffffffffffffffffffffffffffff1663ee9a31a26040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109cd9190613373565b73ffffffffffffffffffffffffffffffffffffffff1690526060820152604080518082018083527fee9a31a2000000000000000000000000000000000000000000000000000000009052905181907342000000000000000000000000000000000000179063ee9a31a2906044808501916020918187030181865afa158015610a59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a7d9190613373565b73ffffffffffffffffffffffffffffffffffffffff16815260200173420000000000000000000000000000000000001773ffffffffffffffffffffffffffffffffffffffff16637d1d0c5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610af7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b1b9190613390565b90526080820152610b3f734200000000000000000000000000000000000011611c77565b60a0820152610b61734200000000000000000000000000000000000019611c77565b60c0820152610b8373420000000000000000000000000000000000001a611c77565b60e0820152610ba573420000000000000000000000000000000000001b611c77565b61010082015261014081015115610e4057600073420000000000000000000000000000000000002a905060008173ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610c56575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252610c5391810190613373565b60015b610ce45773420000000000000000000000000000000000001873ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cb9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cdd9190613373565b9050610ce7565b90505b60405180606001604052808273ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1663d84447156040518163ffffffff1660e01b8152600401600060405180830381865afa158015610d59573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610d9f919081019061346a565b81526020018373ffffffffffffffffffffffffffffffffffffffff1663550fcdc96040518163ffffffff1660e01b8152600401600060405180830381865afa158015610def573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610e35919081019061346a565b905261012084015250505b90565b80515160405173ffffffffffffffffffffffffffffffffffffffff9091166024820152610f4690734200000000000000000000000000000000000007907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090604401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc4d66de80000000000000000000000000000000000000000000000000000000017905260006014611e58565b60208101515160405173ffffffffffffffffffffffffffffffffffffffff909116602482015261104c90734200000000000000000000000000000000000010907f0000000000000000000000000000000000000000000000000000000000000000907f0000000000000000000000000000000000000000000000000000000000000000906044015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fc4d66de800000000000000000000000000000000000000000000000000000000179052600080611e58565b60408082015151905173ffffffffffffffffffffffffffffffffffffffff90911660248201526110d890734200000000000000000000000000000000000014907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090604401610fce565b60608101515160405173ffffffffffffffffffffffffffffffffffffffff909116602482015261116490734200000000000000000000000000000000000012907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090604401610fce565b6080810151805160209091015160405173ffffffffffffffffffffffffffffffffffffffff9092166024830152604482015261127690734200000000000000000000000000000000000017907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090606401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fcd6dc6870000000000000000000000000000000000000000000000000000000017905260016000611e58565b8061014001511561137f5761012081015180516020820151604092830151925161137f9373420000000000000000000000000000000000002a937f0000000000000000000000000000000000000000000000000000000000000000937f00000000000000000000000000000000000000000000000000000000000000009361130193906024016134bb565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f9065714700000000000000000000000000000000000000000000000000000000179052600080611e58565b60a081015180516020820151604092830151925161149c93734200000000000000000000000000000000000011937f0000000000000000000000000000000000000000000000000000000000000000937f0000000000000000000000000000000000000000000000000000000000000000936113fe9390602401613535565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fb49dc741000000000000000000000000000000000000000000000000000000001790527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006000611e58565b60c081015180516020820151604092830151925161151b93734200000000000000000000000000000000000019937f0000000000000000000000000000000000000000000000000000000000000000937f0000000000000000000000000000000000000000000000000000000000000000936113fe9390602401613535565b60e081015180516020820151604092830151925161159a9373420000000000000000000000000000000000001a937f0000000000000000000000000000000000000000000000000000000000000000937f0000000000000000000000000000000000000000000000000000000000000000936113fe9390602401613535565b61010081015180516020820151604092830151925161161a9373420000000000000000000000000000000000001b937f0000000000000000000000000000000000000000000000000000000000000000937f0000000000000000000000000000000000000000000000000000000000000000936113fe9390602401613535565b61165873420000000000000000000000000000000000000f7f0000000000000000000000000000000000000000000000000000000000000000612686565b6116c67342000000000000000000000000000000000000158261014001516116a0577f0000000000000000000000000000000000000000000000000000000000000000612686565b7f0000000000000000000000000000000000000000000000000000000000000000612686565b806101400151801561178157506040517f47af267b0000000000000000000000000000000000000000000000000000000081527f435553544f4d5f4741535f544f4b454e000000000000000000000000000000006004820152734200000000000000000000000000000000000015906347af267b90602401602060405180830381865afa15801561175b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061177f919061332f565b155b15611824576040517f46a4d7800000000000000000000000000000000000000000000000000000000081527f435553544f4d5f4741535f544f4b454e000000000000000000000000000000006004820152734200000000000000000000000000000000000015906346a4d78090602401600060405180830381600087803b15801561180b57600080fd5b505af115801561181f573d6000803e3d6000fd5b505050505b61189273420000000000000000000000000000000000001682610140015161186c577f0000000000000000000000000000000000000000000000000000000000000000612686565b7f0000000000000000000000000000000000000000000000000000000000000000612686565b6118d07342000000000000000000000000000000000000187f0000000000000000000000000000000000000000000000000000000000000000612686565b61190e73420000000000000000000000000000000000002d7f0000000000000000000000000000000000000000000000000000000000000000612686565b80610140015115611957576119577342000000000000000000000000000000000000297f0000000000000000000000000000000000000000000000000000000000000000612686565b80610160015115611a5a576119a07342000000000000000000000000000000000000227f0000000000000000000000000000000000000000000000000000000000000000612686565b6119de7342000000000000000000000000000000000000237f0000000000000000000000000000000000000000000000000000000000000000612686565b611a1c7342000000000000000000000000000000000000247f0000000000000000000000000000000000000000000000000000000000000000612686565b611a5a7342000000000000000000000000000000000000257f0000000000000000000000000000000000000000000000000000000000000000612686565b611a987342000000000000000000000000000000000000207f0000000000000000000000000000000000000000000000000000000000000000612686565b611ad67342000000000000000000000000000000000000217f0000000000000000000000000000000000000000000000000000000000000000612686565b61056f73420000000000000000000000000000000000002c7f0000000000000000000000000000000000000000000000000000000000000000612686565b6040517f204e1c7a00000000000000000000000000000000000000000000000000000000815273420000000000000000000000000000000000002d600482015260009081907342000000000000000000000000000000000000189063204e1c7a90602401602060405180830381865afa158015611b95573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bb99190613373565b90508073ffffffffffffffffffffffffffffffffffffffff163b600003611be35750600092915050565b6040517f78ecabce0000000000000000000000000000000000000000000000000000000081526004810184905273420000000000000000000000000000000000002d906378ecabce90602401602060405180830381865afa158015611c4c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c70919061332f565b9392505050565b611c9860408051606081018252600080825260208201819052909182015290565b60008273ffffffffffffffffffffffffffffffffffffffff1663d0e12f906040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611d1f575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252611d1c9181019061359e565b60015b611d2b57506000611d2e565b90505b600083905060405180606001604052808273ffffffffffffffffffffffffffffffffffffffff16630d9019e16040518163ffffffff1660e01b8152600401602060405180830381865afa158015611d89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dad9190613373565b73ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1663d3e5792b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611e13573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e379190613390565b8152602001836001811115611e4e57611e4e613506565b9052949350505050565b611e61866129c2565b611eb4576040517fc3fe4a6600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff871660048201526024015b60405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff163b600003611f1d576040517f1d38e98500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152602401611eab565b8473ffffffffffffffffffffffffffffffffffffffff163b600003611f86576040517f1d38e98500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff86166004820152602401611eab565b6040517f204e1c7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff871660048201526000907342000000000000000000000000000000000000189063204e1c7a90602401602060405180830381865afa158015612007573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061202b9190613373565b905073ffffffffffffffffffffffffffffffffffffffff81163b1580159061217857506121788773ffffffffffffffffffffffffffffffffffffffff166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa15801561209c573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526120e2919081019061346a565b8773ffffffffffffffffffffffffffffffffffffffff166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa15801561212d573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052612173919081019061346a565b612a0b565b156121c7576040517ff8ce5d1600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff88166004820152602401611eab565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152881690633659cfe690602401600060405180830381600087803b15801561223057600080fd5b505af1158015612244573d6000803e3d6000fd5b507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0092505050838114801561227b575060ff831615155b156122b2576040517f10415a2900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b808414612446576040517fa6ed563e0000000000000000000000000000000000000000000000000000000081526004810185905260009073ffffffffffffffffffffffffffffffffffffffff8a169063a6ed563e90602401602060405180830381865afa158015612327573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061234b9190613390565b90506123588460016135ee565b6123669060ff166008613613565b81901c60ff16156123a3576040517fc996d78400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006123b360ff86166008613613565b6040517f4e91db080000000000000000000000000000000000000000000000000000000081526004810188905260ff90911b198381166024830152915073ffffffffffffffffffffffffffffffffffffffff8b1690634e91db0890604401600060405180830381600087803b15801561242b57600080fd5b505af115801561243f573d6000803e3d6000fd5b5050505050505b6040517fa6ed563e0000000000000000000000000000000000000000000000000000000081526004810182905260009073ffffffffffffffffffffffffffffffffffffffff8a169063a6ed563e90602401602060405180830381865afa1580156124b4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124d89190613390565b905060ff604082901c1615612519576040517fc996d78400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f4e91db08000000000000000000000000000000000000000000000000000000008152600481018390527fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000008216602482015273ffffffffffffffffffffffffffffffffffffffff8a1690634e91db0890604401600060405180830381600087803b1580156125a957600080fd5b505af11580156125bd573d6000803e3d6000fd5b50506040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8c169250634f1ef2869150612615908b908a90600401613650565b6000604051808303816000875af1158015612634573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261267a919081019061346a565b50505050505050505050565b8073ffffffffffffffffffffffffffffffffffffffff163b6000036126ef576040517f1d38e98500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401611eab565b6126f8826129c2565b612746576040517fc3fe4a6600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83166004820152602401611eab565b6040517f204e1c7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526000907342000000000000000000000000000000000000189063204e1c7a90602401602060405180830381865afa1580156127c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127eb9190613373565b905073ffffffffffffffffffffffffffffffffffffffff81163b158015906128ed57506128ed8373ffffffffffffffffffffffffffffffffffffffff166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa15801561285c573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526128a2919081019061346a565b8373ffffffffffffffffffffffffffffffffffffffff166354fd4d506040518163ffffffff1660e01b8152600401600060405180830381865afa15801561212d573d6000803e3d6000fd5b1561293c576040517ff8ce5d1600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84166004820152602401611eab565b6040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152841690633659cfe690602401600060405180830381600087803b1580156129a557600080fd5b505af11580156129b9573d6000803e3d6000fd5b50505050505050565b60007208400000000000000000000000000000000000600b83901c721fffffffffffffffffffffffffffffffffffff16148015612a055750612a0382612a31565b155b92915050565b6000612a178383612a99565b158015611c705750612a298383612ae9565b159392505050565b600073ffffffffffffffffffffffffffffffffffffffff82167342000000000000000000000000000000000000421480612a05575073ffffffffffffffffffffffffffffffffffffffff82167342000000000000000000000000000000000000061492915050565b600080612aa584612b5e565b90506000612ab284612b5e565b80518351919250148015612acd575080602001518260200151145b8015612ae0575080604001518260400151145b95945050505050565b600080612af584612b5e565b90506000612b0284612b5e565b805183519192501180612b28575080518251148015612b28575080602001518260200151105b80612ae0575080518251148015612b46575080602001518260200151145b8015612ae05750604090810151910151109392505050565b612b8260405180606001604052806000815260200160008152602001600081525090565b6000612bc3836040518060400160405280600181526020017f2e00000000000000000000000000000000000000000000000000000000000000815250612d2a565b9050600381511015612c01576040517f9eda858c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612c5c82600281518110612c1957612c1961367f565b60200260200101516040518060400160405280600181526020017f2d00000000000000000000000000000000000000000000000000000000000000815250612d2a565b90506000612cb982600081518110612c7657612c7661367f565b60200260200101516040518060400160405280600181526020017f2b00000000000000000000000000000000000000000000000000000000000000815250612d2a565b90506040518060600160405280612ce985600081518110612cdc57612cdc61367f565b6020026020010151612dd5565b8152602001612d0485600181518110612cdc57612cdc61367f565b8152602001612d1f83600081518110612cdc57612cdc61367f565b905295945050505050565b60606000612d388484612e4d565b9050601f1960208201600183510160051b81018651838201526001845101845260005b825160608452818114612da05760405182820380825286601f8201165b8b850181015183820152870180612d785750600082820160200152603f018616810160405284525b875160209490940193019050818310612d5b57505050508091508251612dce57602081019150600281510382525b5092915050565b80516000907f1999999999999999999999999999999999999999999999999999999999999999825b600181019050603060ff82870151160382851185600a028281019650600983118188108317171586029550505050828110612dfd57505080612e475763101827966000526004601cfd5b50919050565b606082518251818111612f32576020850194506020840193506020604051019250846001828488010301600060208410612e8657508286205b601f841660200360031b87515b8951818118831c612ee8578315612ec65783878c2014612ec65760018b019a50848b10612ec05750612ef7565b50612e93565b858b038952998601996020909801978615612ee857848b10612ec05750612ef7565b5060018a019950838a10612e93575b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08189030160051c8152602090970190525050505b505092915050565b604080516101a08101825260006101808201818152825282516020808201855282825280840191909152835180820185528281528385015283518082018552828152606084015283518085019094528184528301529060808201908152602001612fbb60408051606081018252600080825260208201819052909182015290565b8152602001612fe160408051606081018252600080825260208201819052909182015290565b815260200161300760408051606081018252600080825260208201819052909182015290565b815260200161302d60408051606081018252600080825260208201819052909182015290565b8152604080516060808201835260008252602082810182905292820152910190815260006020820181905260409091015290565b60005b8381101561307c578181015183820152602001613064565b8381111561308b576000848401525b50505050565b600081518084526130a9816020860160208601613061565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000611c706020830184613091565b815173ffffffffffffffffffffffffffffffffffffffff16815261034081016020830151613134602084018273ffffffffffffffffffffffffffffffffffffffff169052565b50604083015161315c604084018273ffffffffffffffffffffffffffffffffffffffff169052565b506060830151613184606084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060808301516131ac608084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060a08301516131d460a084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060c08301516131fc60c084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060e083015161322460e084018273ffffffffffffffffffffffffffffffffffffffff169052565b506101008381015173ffffffffffffffffffffffffffffffffffffffff90811691840191909152610120808501518216908401526101408085015182169084015261016080850151821690840152610180808501518216908401526101a0808501518216908401526101c0808501518216908401526101e08085015182169084015261020080850151821690840152610220808501518216908401526102408085015182169084015261026080850151821690840152610280808501518216908401526102a0808501518216908401526102c0808501518216908401526102e08085015182169084015261030080850151821690840152610320808501519182168185015290612f32565b60006020828403121561334157600080fd5b81518015158114611c7057600080fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461056f57600080fd5b60006020828403121561338557600080fd5b8151611c7081613351565b6000602082840312156133a257600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600067ffffffffffffffff808411156133f3576133f36133a9565b604051601f85017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908282118183101715613439576134396133a9565b8160405280935085815286868601111561345257600080fd5b613460866020830187613061565b5050509392505050565b60006020828403121561347c57600080fd5b815167ffffffffffffffff81111561349357600080fd5b8201601f810184136134a457600080fd5b6134b3848251602084016133d8565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff841681526060602082015260006134ea6060830185613091565b82810360408401526134fc8185613091565b9695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff84168152602081018390526060810160028310613590577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b826040830152949350505050565b6000602082840312156135b057600080fd5b815160028110611c7057600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600060ff821660ff84168060ff0382111561360b5761360b6135bf565b019392505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561364b5761364b6135bf565b500290565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260006134b36040830184613091565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fdfea164736f6c634300080f000a0000000000000000000000002a5a3eabb9fd571a3af0299eebdf8eaafe29a914000000000000000000000000250af3f400cf8aac8d410c90f1ba3968dd87df96000000000000000000000000547d0fba434877d7237d511cf87fabe2ee26b152000000000000000000000000ae9ed42f43a3ee45c3a9def8ae6b48cbb58ed1a1000000000000000000000000b178cdaa8336f25624a63c049edb5af7ca36c2da000000000000000000000000af87f2fd347acb94656f9f715b4f2409b98e75b9000000000000000000000000716ead0cf3e7ff86a02d4f8cb41a6d14922fa8330000000000000000000000006a97c5d55a21265326150efe12fc30fb21cbff56000000000000000000000000a0734858ba5085ff6db493021a0f8c54605c2cda00000000000000000000000027e51b2254433a3284d9ba73ea551c397db2a124000000000000000000000000a0f4ffff79a0a3e039fcbef738751efba8e84f96000000000000000000000000f43862b9d814bb4504158ceccb0b74b31265e4ee000000000000000000000000893c2ceeb71d38514daf67728d3ff1b213fc4b5f000000000000000000000000f7bed7215eef1003fac426682cf2edeb958569f7000000000000000000000000f7bed7215eef1003fac426682cf2edeb958569f7000000000000000000000000eddf416c7159387cc6df3015700f79cfb891137300000000000000000000000070de55bc0bfbc52c5d0cca1da5816c2428886a34000000000000000000000000bec660b456b84a081e90af29be43385bda5bf7b600000000000000000000000093a8a7a9c98cb998d88dba3373a6c7f8ee2e8a460000000000000000000000009c6f280ffa7499eaa5d9c2d081f6f2c333432601000000000000000000000000784f1fae11f1c3a9c413423fe1b370a3636b8d560000000000000000000000002f76618143d9d2731c56778192d3893864b423d7000000000000000000000000dda87ef358082ab3f4ba8982290c671efdc4d1590000000000000000000000003e6a107f4e87af774001b4e42a0d9dabf277c288000000000000000000000000906835344844979ffd3a752eaa23728d513db00b000000000000000000000000e35b194efc4907f383b7e3b87f4c2c339ce239f6000000000000000000000000000000000000000000", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 4942996, + "intent": "Deploy L2ContractsManager Implementation", + "to": "0x420000000000000000000000000000000000002C" + }, + { + "data": "0x7c36f37e0000000000000000000000005398a70eb0929dd7bfc73c59e7137d8c7cdf6669", + "from": "0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001", + "gasLimit": 2115000, + "intent": "L2ProxyAdmin Upgrade Predeploys", + "to": "0x4200000000000000000000000000000000000018" + } + ] +} \ No newline at end of file diff --git a/op-core/nuts/fork_lock.toml b/op-core/nuts/fork_lock.toml new file mode 100644 index 00000000000..6458e66ad43 --- /dev/null +++ b/op-core/nuts/fork_lock.toml @@ -0,0 +1,15 @@ +# To update a fork's bundle, run: just nut-snapshot-for +# Contract changes must be merged to develop before snapshotting. + +[karst] + bundle = "op-core/nuts/bundles/karst_nut_bundle.json" + hash = "sha256:08f5df36f7ff29a9421ab84beebe96684a7455df9a5d234a2468613a4008ad91" + commit = "f2e5bfe4a502788d33d7914d2d5cd42793c0bce5" + +[interop] + bundle = "op-core/nuts/bundles/interop_nut_bundle.json" + hash = "sha256:9c2753c8ff0c63503c2a8bfdb6f2f1058f7251b42399559b97c7ca84492c4d93" + commit = "e45f4ca007569a0429e66d6657dae51e21acc89f" + +# REVIEWER NOTE: Changes to this file affect which NUT bundles are embedded +# into op-node and kona-node for hardfork activations. Review carefully. diff --git a/op-core/nuts/lock.go b/op-core/nuts/lock.go new file mode 100644 index 00000000000..989c3357a49 --- /dev/null +++ b/op-core/nuts/lock.go @@ -0,0 +1,94 @@ +package nuts + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/BurntSushi/toml" + + "github.com/ethereum-optimism/optimism/op-core/forks" + opservice "github.com/ethereum-optimism/optimism/op-service" +) + +// ForkLockEntry represents a single fork's entry in fork_lock.toml. +type ForkLockEntry struct { + Bundle string `toml:"bundle"` + Hash string `toml:"hash"` + Commit string `toml:"commit"` +} + +// ForkLock is the full contents of fork_lock.toml, keyed by fork name. +type ForkLock map[string]ForkLockEntry + +// LockFilePath returns the absolute path to fork_lock.toml relative to the given directory. +func LockFilePath(dir string) (string, error) { + root, err := opservice.FindMonorepoRoot(dir) + if err != nil { + return "", fmt.Errorf("finding monorepo root: %w", err) + } + return filepath.Join(root, "op-core", "nuts", "fork_lock.toml"), nil +} + +// ReadLockFile reads and parses fork_lock.toml from the monorepo root. +func ReadLockFile(dir string) (ForkLock, string, error) { + lockPath, err := LockFilePath(dir) + if err != nil { + return nil, "", err + } + var locks ForkLock + if _, err := toml.DecodeFile(lockPath, &locks); err != nil { + return nil, "", fmt.Errorf("reading fork lock file: %w", err) + } + return locks, lockPath, nil +} + +// WriteLockFile writes fork_lock.toml back to disk with a header comment. +func WriteLockFile(lockPath string, locks ForkLock) error { + f, err := os.Create(lockPath) + if err != nil { + return fmt.Errorf("opening fork lock file for writing: %w", err) + } + defer f.Close() + + _, err = fmt.Fprint(f, `# To update a fork's bundle, run: just nut-snapshot-for +# Contract changes must be merged to develop before snapshotting. + +`) + if err != nil { + return err + } + + // Encode entries in chronological fork order so that adding a new fork + // doesn't reshuffle older entries. + enc := toml.NewEncoder(f) + written := make(map[string]bool, len(locks)) + for _, fork := range forks.All { + entry, ok := locks[string(fork)] + if !ok { + continue + } + if err := enc.Encode(map[string]ForkLockEntry{string(fork): entry}); err != nil { + return fmt.Errorf("writing fork lock file: %w", err) + } + written[string(fork)] = true + } + // Write any unknown fork names last (alphabetical fallback). + for name, entry := range locks { + if written[name] { + continue + } + if err := enc.Encode(map[string]ForkLockEntry{name: entry}); err != nil { + return fmt.Errorf("writing fork lock file: %w", err) + } + } + + _, err = fmt.Fprint(f, ` +# REVIEWER NOTE: Changes to this file affect which NUT bundles are embedded +# into op-node and kona-node for hardfork activations. Review carefully. +`) + if err != nil { + return err + } + return nil +} diff --git a/op-core/predeploys/addresses.go b/op-core/predeploys/addresses.go index c0e935ec70e..fc677c21c99 100644 --- a/op-core/predeploys/addresses.go +++ b/op-core/predeploys/addresses.go @@ -30,10 +30,8 @@ const ( L2toL2CrossDomainMessenger = "0x4200000000000000000000000000000000000023" SuperchainETHBridge = "0x4200000000000000000000000000000000000024" ETHLiquidity = "0x4200000000000000000000000000000000000025" - SuperchainTokenBridge = "0x4200000000000000000000000000000000000028" NativeAssetLiquidity = "0x4200000000000000000000000000000000000029" LiquidityController = "0x420000000000000000000000000000000000002a" - FeeSplitter = "0x420000000000000000000000000000000000002b" Create2Deployer = "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2" MultiCall3 = "0xcA11bde05977b3631167028862bE2a173976CA11" Safe_v130 = "0x69f4D1788e39c87893C980c06EdF4b7f686e2938" @@ -43,9 +41,6 @@ const ( DeterministicDeploymentProxy = "0x4e59b44847b379578588920cA78FbF26c0B4956C" MultiSend_v130 = "0x998739BFdAAdde7C933B942a68053933098f9EDa" Permit2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3" - SenderCreator = "0x7fc98430eaedbb6070b35b39d798725049088348" - EntryPoint = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" - PoPPoints = "0x455f8F0B8dF5399873700f60aa931D6b89Ac9c79" SenderCreator_v060 = "0x7fc98430eaedbb6070b35b39d798725049088348" EntryPoint_v060 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789" SenderCreator_v070 = "0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C" @@ -77,10 +72,8 @@ var ( L2toL2CrossDomainMessengerAddr = common.HexToAddress(L2toL2CrossDomainMessenger) SuperchainETHBridgeAddr = common.HexToAddress(SuperchainETHBridge) ETHLiquidityAddr = common.HexToAddress(ETHLiquidity) - SuperchainTokenBridgeAddr = common.HexToAddress(SuperchainTokenBridge) NativeAssetLiquidityAddr = common.HexToAddress(NativeAssetLiquidity) LiquidityControllerAddr = common.HexToAddress(LiquidityController) - FeeSplitterAddr = common.HexToAddress(FeeSplitter) Create2DeployerAddr = common.HexToAddress(Create2Deployer) MultiCall3Addr = common.HexToAddress(MultiCall3) Safe_v130Addr = common.HexToAddress(Safe_v130) @@ -90,9 +83,6 @@ var ( DeterministicDeploymentProxyAddr = common.HexToAddress(DeterministicDeploymentProxy) MultiSend_v130Addr = common.HexToAddress(MultiSend_v130) Permit2Addr = common.HexToAddress(Permit2) - SenderCreatorAddr = common.HexToAddress(SenderCreator) - EntryPointAddr = common.HexToAddress(EntryPoint) - PoPPointsAddr = common.HexToAddress(PoPPoints) SenderCreator_v060Addr = common.HexToAddress(SenderCreator_v060) EntryPoint_v060Addr = common.HexToAddress(EntryPoint_v060) SenderCreator_v070Addr = common.HexToAddress(SenderCreator_v070) @@ -117,8 +107,6 @@ func init() { Predeploys["L2toL2CrossDomainMessenger"] = &Predeploy{Address: L2toL2CrossDomainMessengerAddr} Predeploys["SuperchainETHBridge"] = &Predeploy{Address: SuperchainETHBridgeAddr} Predeploys["ETHLiquidity"] = &Predeploy{Address: ETHLiquidityAddr} - Predeploys["SuperchainTokenBridge"] = &Predeploy{Address: SuperchainTokenBridgeAddr} - Predeploys["FeeSplitter"] = &Predeploy{Address: FeeSplitterAddr} Predeploys["GovernanceToken"] = &Predeploy{ Address: GovernanceTokenAddr, ProxyDisabled: true, diff --git a/op-core/superchain/chain.go b/op-core/superchain/chain.go new file mode 100644 index 00000000000..af82a36bee8 --- /dev/null +++ b/op-core/superchain/chain.go @@ -0,0 +1,209 @@ +package superchain + +import ( + "archive/zip" + "bytes" + _ "embed" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "path" + "sort" + "sync" + + "github.com/BurntSushi/toml" + "github.com/klauspost/compress/zstd" +) + +//go:embed superchain-configs.zip +var builtInConfigData []byte + +var BuiltInConfigs *ChainConfigLoader + +var Chains map[uint64]*Chain + +var ErrUnknownChain = errors.New("unknown chain") + +type ChainConfigLoader struct { + configDataReader fs.FS + Chains map[uint64]*Chain + idsByName map[string]uint64 + superchainsByNetwork map[string]Superchain + mtx sync.Mutex +} + +func NewChainConfigLoader(configData []byte) (*ChainConfigLoader, error) { + configDataReader, err := zip.NewReader(bytes.NewReader(configData), int64(len(configData))) + if err != nil { + return nil, fmt.Errorf("opening zip reader: %w", err) + } + dictR, err := configDataReader.Open("dictionary") + if err != nil { + return nil, fmt.Errorf("error opening dictionary: %w", err) + } + defer dictR.Close() + genesisZstdDict, err := io.ReadAll(dictR) + if err != nil { + return nil, fmt.Errorf("error reading dictionary: %w", err) + } + chainFile, err := configDataReader.Open("chains.json") + if err != nil { + return nil, fmt.Errorf("error opening chains file: %w", err) + } + defer chainFile.Close() + chains := make(map[uint64]*Chain) + if err := json.NewDecoder(chainFile).Decode(&chains); err != nil { + return nil, fmt.Errorf("error decoding chains file: %w", err) + } + for _, chain := range chains { + chain.configDataReader = configDataReader + chain.genesisZstdDict = genesisZstdDict + } + + idsByName := make(map[string]uint64) + for chainID, chain := range chains { + idsByName[chain.Name+"-"+chain.Network] = chainID + } + return &ChainConfigLoader{ + superchainsByNetwork: make(map[string]Superchain), + configDataReader: configDataReader, + Chains: chains, + idsByName: idsByName, + }, nil +} + +func ChainIDByName(name string) (uint64, error) { + return BuiltInConfigs.ChainIDByName(name) +} + +func (c *ChainConfigLoader) ChainIDByName(name string) (uint64, error) { + id, ok := c.idsByName[name] + if !ok { + return 0, fmt.Errorf("%w %q", ErrUnknownChain, name) + } + return id, nil +} + +func ChainNames() []string { + return BuiltInConfigs.ChainNames() +} + +func (c *ChainConfigLoader) ChainNames() []string { + var out []string + for _, ch := range c.Chains { + out = append(out, ch.Name+"-"+ch.Network) + } + sort.Strings(out) + return out +} + +func GetChain(chainID uint64) (*Chain, error) { + return BuiltInConfigs.GetChain(chainID) +} + +func GetDepset(chainID uint64) (map[string]Dependency, error) { + chain, err := BuiltInConfigs.GetChain(chainID) + if err != nil { + return nil, err + } + cfg, err := chain.Config() + if err != nil { + return nil, err + } + + // depset of 1 (self) is the default when no dependencies are specified but interop_time is set + if cfg.Interop == nil { + cfg.Interop = &Interop{ + Dependencies: make(map[string]Dependency), + } + cfg.Interop.Dependencies[fmt.Sprintf("%d", cfg.ChainID)] = Dependency{} + } + + return cfg.Interop.Dependencies, nil +} + +func (c *ChainConfigLoader) GetChain(chainID uint64) (*Chain, error) { + chain, ok := c.Chains[chainID] + if !ok { + return nil, fmt.Errorf("%w ID: %d", ErrUnknownChain, chainID) + } + return chain, nil +} + +type Chain struct { + Name string `json:"name"` + Network string `json:"network"` + + configDataReader fs.FS + genesisZstdDict []byte + + config *ChainConfig + genesis []byte + + // The config and genesis initialization is separated + // to allow for lazy loading. Reading genesis files is + // very expensive in Cannon so we only want to do it + // when necessary. + configOnce sync.Once + genesisOnce sync.Once + err error +} + +func (c *Chain) Config() (*ChainConfig, error) { + c.configOnce.Do(c.populateConfig) + return c.config, c.err +} + +func (c *Chain) GenesisData() ([]byte, error) { + c.genesisOnce.Do(c.populateGenesis) + return c.genesis, c.err +} + +func (c *Chain) populateConfig() { + configFile, err := c.configDataReader.Open(path.Join("configs", c.Network, c.Name+".toml")) + if err != nil { + c.err = fmt.Errorf("error opening chain config file %s/%s: %w", c.Network, c.Name, err) + return + } + defer configFile.Close() + + var cfg ChainConfig + if _, err := toml.NewDecoder(configFile).Decode(&cfg); err != nil { + c.err = fmt.Errorf("error decoding chain config file %s/%s: %w", c.Network, c.Name, err) + return + } + c.config = &cfg +} + +func (c *Chain) populateGenesis() { + genesisFile, err := c.configDataReader.Open(path.Join("genesis", c.Network, c.Name+".json.zst")) + if err != nil { + c.err = fmt.Errorf("error opening compressed genesis file %s/%s: %w", c.Network, c.Name, err) + return + } + defer genesisFile.Close() + zstdR, err := zstd.NewReader(genesisFile, zstd.WithDecoderDicts(c.genesisZstdDict)) + if err != nil { + c.err = fmt.Errorf("error creating zstd reader for %s/%s: %w", c.Network, c.Name, err) + return + } + defer zstdR.Close() + + out, err := io.ReadAll(zstdR) + if err != nil { + c.err = fmt.Errorf("error reading genesis file for %s/%s: %w", c.Network, c.Name, err) + return + } + c.genesis = out +} + +func init() { + var err error + BuiltInConfigs, err = NewChainConfigLoader(builtInConfigData) + if err != nil { + panic(err) + } + Chains = BuiltInConfigs.Chains +} diff --git a/op-core/superchain/chain_config.go b/op-core/superchain/chain_config.go new file mode 100644 index 00000000000..06fa72d6b3d --- /dev/null +++ b/op-core/superchain/chain_config.go @@ -0,0 +1,27 @@ +package superchain + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/params" + gethsuperchain "github.com/ethereum/go-ethereum/superchain" +) + +// LoadChainConfigFromChainID loads the OP-Stack params.ChainConfig for the +// given L2 chain ID from the superchain registry. +// +// TODO(20271): once the params.ChainConfig type and the +// params.LoadOPStackChainConfig function have been migrated into this +// repository, this wrapper should use the local ChainConfig loader instead of +// the op-geth one. +func LoadChainConfigFromChainID(chainID uint64) (*params.ChainConfig, error) { + chain, err := gethsuperchain.GetChain(chainID) + if err != nil { + return nil, fmt.Errorf("unable to get chain %d from superchain registry: %w", chainID, err) + } + chainCfg, err := chain.Config() + if err != nil { + return nil, fmt.Errorf("unable to retrieve chain %d config: %w", chainID, err) + } + return params.LoadOPStackChainConfig(chainCfg) +} diff --git a/op-core/superchain/chain_config_test.go b/op-core/superchain/chain_config_test.go new file mode 100644 index 00000000000..cdc0f4e9cb3 --- /dev/null +++ b/op-core/superchain/chain_config_test.go @@ -0,0 +1,24 @@ +package superchain + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/stretchr/testify/require" +) + +func TestLoadChainConfigFromChainID(t *testing.T) { + t.Run("mainnet", func(t *testing.T) { + chainID := uint64(10) + cfg, err := LoadChainConfigFromChainID(chainID) + require.NoError(t, err) + require.Equal(t, chainID, bigs.Uint64Strict(cfg.ChainID)) + }) + + t.Run("nonexistent chain", func(t *testing.T) { + chainID := uint64(23409527340) + cfg, err := LoadChainConfigFromChainID(chainID) + require.Error(t, err) + require.Nil(t, cfg) + }) +} diff --git a/op-core/superchain/chain_test.go b/op-core/superchain/chain_test.go new file mode 100644 index 00000000000..88800c47edf --- /dev/null +++ b/op-core/superchain/chain_test.go @@ -0,0 +1,146 @@ +package superchain + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetChain(t *testing.T) { + t.Run("OP Mainnet found", func(t *testing.T) { + chain, err := GetChain(10) + require.NoError(t, err) + require.NotNil(t, chain) + }) + + // Celo mainnet skipped due to custom genesis + t.Run("Celo Mainnet skipped", func(t *testing.T) { + chain, err := GetChain(42220) + require.Error(t, err) + require.Nil(t, chain) + }) +} + +func TestGetDepset(t *testing.T) { + // Save BuiltInConfigs to restore later + originalConfigs := BuiltInConfigs + t.Cleanup(func() { + BuiltInConfigs = originalConfigs + }) + + t.Run("unknown chainID", func(t *testing.T) { + BuiltInConfigs = &ChainConfigLoader{ + Chains: map[uint64]*Chain{}, + } + + depset, err := GetDepset(999999) + require.Nil(t, depset) + require.ErrorIs(t, err, ErrUnknownChain) + require.Contains(t, err.Error(), "unknown chain ID") + }) + + t.Run("nil InteropTime", func(t *testing.T) { + mockChain := &Chain{ + Name: "test", + Network: "test", + config: &ChainConfig{ + ChainID: 42, + Hardforks: HardforkConfig{ + InteropTime: nil, + }, + }, + } + + // Set configOnce as already done + mockChain.configOnce.Do(func() {}) + + // Replace chains map with our test chain + BuiltInConfigs = &ChainConfigLoader{ + Chains: map[uint64]*Chain{42: mockChain}, + } + + depset, err := GetDepset(42) + require.NoError(t, err) + require.NotNil(t, depset) + + // Verify the default dependency was created + selfDep, exists := depset["42"] + require.True(t, exists) + require.Equal(t, selfDep, Dependency{}) + }) + + t.Run("nil Interop creates default depset", func(t *testing.T) { + // Create mock chain with InteropTime but nil Interop + activationTime := uint64(1234567890) + mockChain := &Chain{ + Name: "test", + Network: "test", + config: &ChainConfig{ + ChainID: 42, + Hardforks: HardforkConfig{ + InteropTime: &activationTime, + }, + Interop: nil, + }, + } + + // Set configOnce as already done + mockChain.configOnce.Do(func() {}) + + // Replace chains map with our test chain + BuiltInConfigs = &ChainConfigLoader{ + Chains: map[uint64]*Chain{42: mockChain}, + } + + depset, err := GetDepset(42) + require.NoError(t, err) + require.NotNil(t, depset) + + // Verify the default dependency was created + selfDep, exists := depset["42"] + require.True(t, exists) + require.Equal(t, selfDep, Dependency{}) + }) + + t.Run("existing Interop depset returned", func(t *testing.T) { + // Create mock chain with existing Interop dependencies + activationTime := uint64(1234567890) + mockChain := &Chain{ + Name: "test", + Network: "test", + config: &ChainConfig{ + ChainID: 42, + Hardforks: HardforkConfig{ + InteropTime: &activationTime, + }, + Interop: &Interop{ + Dependencies: map[string]Dependency{ + "42": {}, + "43": {}, + }, + }, + }, + } + + // Set configOnce as already done + mockChain.configOnce.Do(func() {}) + + // Replace chains map with our test chain + BuiltInConfigs = &ChainConfigLoader{ + Chains: map[uint64]*Chain{42: mockChain}, + } + + depset, err := GetDepset(42) + require.NoError(t, err) + require.NotNil(t, depset) + require.Equal(t, 2, len(depset)) + + selfDep, exists := depset["42"] + require.True(t, exists) + require.Equal(t, selfDep, Dependency{}) + + otherDep, exists := depset["43"] + require.True(t, exists) + require.Equal(t, otherDep, Dependency{}) + }) +} diff --git a/op-core/superchain/init.go b/op-core/superchain/init.go new file mode 100644 index 00000000000..d560e9b0ce3 --- /dev/null +++ b/op-core/superchain/init.go @@ -0,0 +1,104 @@ +package superchain + +import ( + "archive/zip" + "bytes" + "crypto/sha256" + _ "embed" + "encoding/hex" + "fmt" + "io" + "strings" + + gethsuperchain "github.com/ethereum/go-ethereum/superchain" +) + +// pinnedCommit is the superchain-registry commit the embedded bundle is pinned to. +// Always present in git; the source of truth for which SR snapshot this build expects. +// +//go:embed superchain-registry-commit.txt +var pinnedCommit string + +// pinnedZipSHA is the sha256sum-format file pinning the expected SHA256 of the +// embedded superchain-configs.zip. Committed to git (the zip itself isn't), so +// any drift between built and approved bundles surfaces as a .sha256 diff. +// +//go:embed superchain-configs.zip.sha256 +var pinnedZipSHA string + +// VerifyEmbeddedCommit asserts the embedded bundle is consistent with what was +// approved in review. Both init() and TestSyncSuperchain call this so the same +// check runs at process startup and in CI tests. +// +// Three checks, ordered most-strict first: +// 1. SHA256 of the embedded zip matches superchain-configs.zip.sha256 — catches +// any drift (commit bump without sync, non-deterministic build, tampering). +// 2. COMMIT entry inside the zip matches superchain-registry-commit.txt — kept +// for clearer diagnostics in the most common failure mode (bumped txt without +// re-running sync). +// 3. op-geth bundles the same SR commit (until the registry is decoupled from +// op-geth, both copies must agree). +// +// A missing zip is caught at compile time by //go:embed, not here. +func VerifyEmbeddedCommit() error { + expected := strings.TrimSpace(pinnedCommit) + + // (1) SHA256 of embedded zip == committed .sha256 file. + gotSHA := sha256.Sum256(builtInConfigData) + gotSHAHex := hex.EncodeToString(gotSHA[:]) + expectedSHA := parseSHA256SumLine(pinnedZipSHA) + if gotSHAHex != expectedSHA { + return fmt.Errorf( + "embedded superchain-configs.zip SHA256 is %s, but superchain-configs.zip.sha256 pins %s.\n"+ + "Run `just sync-superchain` and commit the updated .sha256 file.", + gotSHAHex, expectedSHA) + } + + // (2) COMMIT entry inside zip == superchain-registry-commit.txt. + zr, err := zip.NewReader(bytes.NewReader(builtInConfigData), int64(len(builtInConfigData))) + if err != nil { + return fmt.Errorf("opening embedded superchain-configs.zip: %w", err) + } + f, err := zr.Open("COMMIT") + if err != nil { + return fmt.Errorf("reading COMMIT entry from embedded zip: %w", err) + } + defer f.Close() + raw, err := io.ReadAll(f) + if err != nil { + return fmt.Errorf("reading COMMIT entry contents: %w", err) + } + if actual := strings.TrimSpace(string(raw)); actual != expected { + return fmt.Errorf( + "superchain-configs.zip is at commit %s, but superchain-registry-commit.txt expects %s.\n"+ + "The embedded bundle is stale. Run `just sync-superchain` and rebuild.", + actual, expected) + } + + // (3) Cross-check: op-geth still bundles its own copy of the superchain-registry + // (until the decoupling is complete in #20257). The two pinned commits must match. + if gethCommit := gethsuperchain.EmbeddedRegistryCommit(); gethCommit != expected { + return fmt.Errorf( + "op-core/superchain pins commit %s but op-geth bundles commit %s.\n"+ + "Bump op-core/superchain/superchain-registry-commit.txt and run `just sync-superchain`,\n"+ + "or bump the op-geth replace directive in go.mod — whichever is the stale side.", + expected, gethCommit) + } + return nil +} + +// parseSHA256SumLine extracts the hex digest from a sha256sum-format line: +// "<64-hex> ". Returns "" if the format is unexpected. +func parseSHA256SumLine(s string) string { + fields := strings.Fields(s) + if len(fields) == 0 { + return "" + } + return fields[0] +} + +func init() { + if err := VerifyEmbeddedCommit(); err != nil { + panic("op-core/superchain: " + err.Error()) + } +} diff --git a/op-core/superchain/init_test.go b/op-core/superchain/init_test.go new file mode 100644 index 00000000000..892025a1f7f --- /dev/null +++ b/op-core/superchain/init_test.go @@ -0,0 +1,14 @@ +package superchain + +import "testing" + +// TestSyncSuperchain asserts that the embedded bundle's COMMIT entry matches +// superchain-registry-commit.txt. The init() function in init.go panics on +// mismatch (so any importer fails fast at process start); this test runs the +// same check via VerifyEmbeddedCommit so a mismatch surfaces as a clean test +// failure rather than a panic during package import. +func TestSyncSuperchain(t *testing.T) { + if err := VerifyEmbeddedCommit(); err != nil { + t.Fatal(err) + } +} diff --git a/op-core/superchain/superchain-configs.zip.sha256 b/op-core/superchain/superchain-configs.zip.sha256 new file mode 100644 index 00000000000..eb797a19014 --- /dev/null +++ b/op-core/superchain/superchain-configs.zip.sha256 @@ -0,0 +1 @@ +fbf5dd15ad78377a0bd7efbd7b2b0225459c335aa89af0dc3d9264e6c546c145 superchain-configs.zip diff --git a/op-core/superchain/superchain-registry-commit.txt b/op-core/superchain/superchain-registry-commit.txt new file mode 100644 index 00000000000..dd6e75b3b9c --- /dev/null +++ b/op-core/superchain/superchain-registry-commit.txt @@ -0,0 +1 @@ +cc07e96d91a1647cbce7eef6572098bece1f7fca diff --git a/op-core/superchain/superchain.go b/op-core/superchain/superchain.go new file mode 100644 index 00000000000..99482a02482 --- /dev/null +++ b/op-core/superchain/superchain.go @@ -0,0 +1,50 @@ +package superchain + +import ( + "fmt" + "path" + + "github.com/BurntSushi/toml" + "github.com/ethereum/go-ethereum/common" +) + +type Superchain struct { + Name string `toml:"name"` + SuperchainConfigAddr common.Address `toml:"superchain_config_addr"` + OpContractsManagerAddr common.Address `toml:"op_contracts_manager_addr"` + SaferSafesAddr common.Address `toml:"safer_safes_addr"` + Hardforks HardforkConfig + L1 L1Config +} + +type L1Config struct { + ChainID uint64 `toml:"chain_id"` + PublicRPC string `toml:"public_rpc"` + Explorer string `toml:"explorer"` +} + +func GetSuperchain(network string) (Superchain, error) { + return BuiltInConfigs.GetSuperchain(network) +} + +func (c *ChainConfigLoader) GetSuperchain(network string) (Superchain, error) { + c.mtx.Lock() + defer c.mtx.Unlock() + + var sc Superchain + if sc, ok := c.superchainsByNetwork[network]; ok { + return sc, nil + } + + zr, err := c.configDataReader.Open(path.Join("configs", network, "superchain.toml")) + if err != nil { + return sc, err + } + + if _, err := toml.NewDecoder(zr).Decode(&sc); err != nil { + return sc, fmt.Errorf("error decoding superchain config: %w", err) + } + + c.superchainsByNetwork[network] = sc + return sc, nil +} diff --git a/op-core/superchain/superchain_test.go b/op-core/superchain/superchain_test.go new file mode 100644 index 00000000000..b6555606b44 --- /dev/null +++ b/op-core/superchain/superchain_test.go @@ -0,0 +1,46 @@ +package superchain + +import ( + "strings" + "testing" + + "github.com/BurntSushi/toml" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestGetSuperchain(t *testing.T) { + mainnet, err := GetSuperchain("mainnet") + require.NoError(t, err) + + require.Equal(t, "Mainnet", mainnet.Name) + require.Equal(t, common.HexToAddress("0x95703e0982140D16f8ebA6d158FccEde42f04a4C"), mainnet.SuperchainConfigAddr) + require.Equal(t, common.HexToAddress("0xA8447329e52F64AED2bFc9E7a2506F7D369f483a"), mainnet.SaferSafesAddr) + require.Equal(t, uint64(1764691201), *mainnet.Hardforks.JovianTime) + require.EqualValues(t, 1, mainnet.L1.ChainID) + + _, err = GetSuperchain("not a network") + require.Error(t, err) +} + +// TestSuperchain_DecodeIgnoresLegacyProtocolVersionsAddr proves the TOML +// decoder silently ignores the legacy `protocol_versions_addr` key. The +// embedded superchain-registry configs still carry it, so removing the +// struct field must not break decoding. +func TestSuperchain_DecodeIgnoresLegacyProtocolVersionsAddr(t *testing.T) { + const data = ` +name = "Legacy" +protocol_versions_addr = "0x8062AbC286f5e7D9428a0Ccb9AbD71e50d93b935" +superchain_config_addr = "0x95703e0982140D16f8ebA6d158FccEde42f04a4C" +op_contracts_manager_addr = "0x0000000000000000000000000000000000000001" +safer_safes_addr = "0xA8447329e52F64AED2bFc9E7a2506F7D369f483a" + +[L1] +chain_id = 1 +` + var sc Superchain + _, err := toml.NewDecoder(strings.NewReader(data)).Decode(&sc) + require.NoError(t, err) + require.Equal(t, "Legacy", sc.Name) + require.Equal(t, common.HexToAddress("0x95703e0982140D16f8ebA6d158FccEde42f04a4C"), sc.SuperchainConfigAddr) +} diff --git a/op-core/superchain/sync-superchain.sh b/op-core/superchain/sync-superchain.sh new file mode 100755 index 00000000000..69dae7f3b5e --- /dev/null +++ b/op-core/superchain/sync-superchain.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash + +# This script is used to sync superchain configs in the registry with op-core/superchain. +# +# The resulting zip is gitignored. Run this script before building the Go workspace. +# Skips work if the on-disk zip already matches the pinned commit. + +set -euo pipefail + +# Constants +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +REGISTRY_COMMIT=$(cat "$SCRIPT_DIR/superchain-registry-commit.txt") +ZIP="$SCRIPT_DIR/superchain-configs.zip" + +# Short-circuit: if the existing zip already pins the same commit, do nothing. +# +# NOTE: this only checks the SR commit, not whether THIS script has changed +# since the zip was built. If you've edited sync-superchain.sh and want to +# force regeneration, delete superchain-configs.zip first: +# +# rm op-core/superchain/superchain-configs.zip && just sync-superchain +# +# CI is the safety net for un-rebuilt script changes — it always rebuilds +# (cache key includes the script's hash) and fails on a .sha256 diff. +if [[ -f "$ZIP" ]]; then + existing=$(unzip -p "$ZIP" COMMIT 2>/dev/null | tr -d '[:space:]' || true) + if [[ "$existing" == "$REGISTRY_COMMIT" ]]; then + echo "[sync-superchain] up to date at commit $REGISTRY_COMMIT" + exit 0 + fi +fi + +repodir=$(mktemp -d) +workdir=$(mktemp -d) + +# Clone the registry +echo "Cloning SR..." +cd "$repodir" +git clone --no-checkout --depth 1 --shallow-submodules https://github.com/ethereum-optimism/superchain-registry.git +cd "$repodir/superchain-registry" +git fetch --depth 1 origin "$REGISTRY_COMMIT" +git checkout "$REGISTRY_COMMIT" + +echo "Copying configs..." +cp -r superchain/configs "$workdir/configs" +cp -r superchain/extra/genesis "$workdir/genesis" +cp -r superchain/extra/dictionary "$workdir/dictionary" + +cd "$workdir" +echo "Using $workdir as workdir..." + +# Create a simple mapping of chain id -> config name to make looking up chains by their ID easier. +echo "Generating index of configs..." + +echo "{}" >chains.json + +# Function to process each network directory +process_network_dir() { + local network_dir="$1" + local network_name + network_name=$(basename "$network_dir") + + echo "Processing chains in $network_name superchain..." + + # Find all TOML files in the network directory + find "$network_dir" -type f -name "*.toml" | LC_ALL=C sort | while read -r toml_file; do + if [[ "$toml_file" == "configs/$network_name/superchain.toml" ]]; then + continue + fi + + echo "Processing $toml_file..." + # Extract chain_id from TOML file using yq + chain_id=$(yq -r '.chain_id' "$toml_file") + chain_name="$(basename "${toml_file%.*}")" + + if [[ -z "$chain_id" + # Boba Sepolia + || "$chain_id" -eq 28882 + # Boba Mainnet + || "$chain_id" -eq 288 + # Celo Mainnet: non-standard genesis format (forked from Ethereum, then converted to L2) + || "$chain_id" -eq 42220 ]]; + then + echo "Skipping $network_name/$chain_name ($chain_id)" + rm "$toml_file" + rm -f "genesis/$network_name/$chain_name.json.zst" + continue + fi + + # Create JSON object for this config + config_json=$(jq -n \ + --arg name "$chain_name" \ + --arg network "$network_name" \ + '{ + "name": $name, + "network": $network + }') + + # Add this config to the result JSON using the chain_id as the key + jq --argjson config "$config_json" \ + --arg chain_id "$chain_id" \ + '. + {($chain_id): $config}' chains.json >temp.json + mv temp.json chains.json + done +} + +# Process each network directory in configs +for network_dir in configs/*; do + if [ -d "$network_dir" ]; then + process_network_dir "$network_dir" + fi +done + +# Archive the genesis configs as a ZIP file. ZIP is used since it can be efficiently used as a filesystem. +echo "Archiving configs..." +echo "$REGISTRY_COMMIT" >COMMIT +# We need to normalize the lastmod dates and permissions to ensure the ZIP file is deterministic. +find . -exec touch -t 198001010000.00 {} + +chmod -R 755 ./* +files=$(find . -type f | LC_ALL=C sort) +echo -n "$files" | xargs zip -9 -oX --quiet superchain-configs.zip +zipinfo superchain-configs.zip +mv superchain-configs.zip "$SCRIPT_DIR/superchain-configs.zip" + +# Persist the bundle's SHA256 alongside it. The hash is committed to git +# (the zip itself isn't); this gives strong consistency: any drift between +# what a developer/CI builds and what was approved in review surfaces as a +# .sha256 diff. +sha256sum "$SCRIPT_DIR/superchain-configs.zip" \ + | awk '{print $1 " superchain-configs.zip"}' \ + > "$SCRIPT_DIR/superchain-configs.zip.sha256" + +echo "Cleaning up..." +rm -rf "$repodir" +rm -rf "$workdir" + +echo "Done." diff --git a/op-core/superchain/types.go b/op-core/superchain/types.go new file mode 100644 index 00000000000..6426a0d0016 --- /dev/null +++ b/op-core/superchain/types.go @@ -0,0 +1,121 @@ +package superchain + +import ( + "github.com/ethereum/go-ethereum/common" +) + +type ChainConfig struct { + Name string `toml:"name"` + PublicRPC string `toml:"public_rpc"` + SequencerRPC string `toml:"sequencer_rpc"` + Explorer string `toml:"explorer"` + SuperchainLevel int `toml:"superchain_level"` + GovernedByOptimism bool `toml:"governed_by_optimism"` + SuperchainTime *uint64 `toml:"superchain_time"` + DataAvailabilityType string `toml:"data_availability_type"` + DeploymentTxHash *common.Hash `toml:"deployment_tx_hash"` + + ChainID uint64 `toml:"chain_id"` + BatchInboxAddr common.Address `toml:"batch_inbox_addr"` + BlockTime uint64 `toml:"block_time"` + SeqWindowSize uint64 `toml:"seq_window_size"` + MaxSequencerDrift uint64 `toml:"max_sequencer_drift"` + GasPayingToken *common.Address `toml:"gas_paying_token"` + Hardforks HardforkConfig `toml:"hardforks"` + Interop *Interop `toml:"interop,omitempty"` + Optimism *OptimismConfig `toml:"optimism,omitempty"` + + AltDA *AltDAConfig `toml:"alt_da,omitempty"` + + Genesis GenesisConfig `toml:"genesis"` + + Roles RolesConfig `toml:"roles"` + + Addresses AddressesConfig `toml:"addresses"` +} + +type Dependency struct{} + +type Interop struct { + Dependencies map[string]Dependency `json:"dependencies" toml:"dependencies"` +} + +type HardforkConfig struct { + CanyonTime *uint64 `toml:"canyon_time"` + DeltaTime *uint64 `toml:"delta_time"` + EcotoneTime *uint64 `toml:"ecotone_time"` + FjordTime *uint64 `toml:"fjord_time"` + GraniteTime *uint64 `toml:"granite_time"` + HoloceneTime *uint64 `toml:"holocene_time"` + IsthmusTime *uint64 `toml:"isthmus_time"` + JovianTime *uint64 `toml:"jovian_time"` + KarstTime *uint64 `toml:"karst_time"` + InteropTime *uint64 `toml:"interop_time"` + // Optional Forks + PectraBlobScheduleTime *uint64 `toml:"pectra_blob_schedule_time,omitempty"` +} + +type OptimismConfig struct { + EIP1559Elasticity uint64 `toml:"eip1559_elasticity"` + EIP1559Denominator uint64 `toml:"eip1559_denominator"` + EIP1559DenominatorCanyon *uint64 `toml:"eip1559_denominator_canyon"` +} + +type AltDAConfig struct { + DaChallengeContractAddress common.Address `toml:"da_challenge_contract_address"` + DaChallengeWindow uint64 `toml:"da_challenge_window"` + DaResolveWindow uint64 `toml:"da_resolve_window"` + DaCommitmentType string `toml:"da_commitment_type"` +} + +type GenesisConfig struct { + L2Time uint64 `toml:"l2_time"` + L1 GenesisRef `toml:"l1"` + L2 GenesisRef `toml:"l2"` + SystemConfig SystemConfig `toml:"system_config"` +} + +type GenesisRef struct { + Hash common.Hash `toml:"hash"` + Number uint64 `toml:"number"` +} + +type SystemConfig struct { + BatcherAddr common.Address `json:"batcherAddr" toml:"batcherAddress"` + Overhead common.Hash `json:"overhead" toml:"overhead"` + Scalar common.Hash `json:"scalar" toml:"scalar"` + GasLimit uint64 `json:"gasLimit" toml:"gasLimit"` + BaseFeeScalar *uint64 `json:"baseFeeScalar,omitempty" toml:"baseFeeScalar,omitempty"` + BlobBaseFeeScalar *uint64 `json:"blobBaseFeeScalar,omitempty" toml:"blobBaseFeeScalar,omitempty"` +} + +type RolesConfig struct { + SystemConfigOwner *common.Address `json:"SystemConfigOwner" toml:"SystemConfigOwner"` + ProxyAdminOwner *common.Address `json:"ProxyAdminOwner" toml:"ProxyAdminOwner"` + Guardian *common.Address `json:"Guardian" toml:"Guardian"` + Challenger *common.Address `json:"Challenger" toml:"Challenger"` + Proposer *common.Address `json:"Proposer,omitempty" toml:"Proposer,omitempty"` + UnsafeBlockSigner *common.Address `json:"UnsafeBlockSigner,omitempty" toml:"UnsafeBlockSigner,omitempty"` + BatchSubmitter *common.Address `json:"BatchSubmitter" toml:"BatchSubmitter"` +} + +type AddressesConfig struct { + AddressManager *common.Address `toml:"AddressManager,omitempty" json:"AddressManager,omitempty"` + L1CrossDomainMessengerProxy *common.Address `toml:"L1CrossDomainMessengerProxy,omitempty" json:"L1CrossDomainMessengerProxy,omitempty"` + L1ERC721BridgeProxy *common.Address `toml:"L1ERC721BridgeProxy,omitempty" json:"L1ERC721BridgeProxy,omitempty"` + L1StandardBridgeProxy *common.Address `toml:"L1StandardBridgeProxy,omitempty" json:"L1StandardBridgeProxy,omitempty"` + L2OutputOracleProxy *common.Address `toml:"L2OutputOracleProxy,omitempty" json:"L2OutputOracleProxy,omitempty"` + OptimismMintableERC20FactoryProxy *common.Address `toml:"OptimismMintableERC20FactoryProxy,omitempty" json:"OptimismMintableERC20FactoryProxy,omitempty"` + OptimismPortalProxy *common.Address `toml:"OptimismPortalProxy,omitempty" json:"OptimismPortalProxy,omitempty"` + SystemConfigProxy *common.Address `toml:"SystemConfigProxy,omitempty" json:"SystemConfigProxy,omitempty"` + ProxyAdmin *common.Address `toml:"ProxyAdmin,omitempty" json:"ProxyAdmin,omitempty"` + SuperchainConfig *common.Address `toml:"SuperchainConfig,omitempty" json:"SuperchainConfig,omitempty"` + AnchorStateRegistryProxy *common.Address `toml:"AnchorStateRegistryProxy,omitempty" json:"AnchorStateRegistryProxy,omitempty"` + DelayedWETHProxy *common.Address `toml:"DelayedWETHProxy,omitempty" json:"DelayedWETHProxy,omitempty"` + DisputeGameFactoryProxy *common.Address `toml:"DisputeGameFactoryProxy,omitempty" json:"DisputeGameFactoryProxy,omitempty"` + FaultDisputeGame *common.Address `toml:"FaultDisputeGame,omitempty" json:"FaultDisputeGame,omitempty"` + MIPS *common.Address `toml:"MIPS,omitempty" json:"MIPS,omitempty"` + PermissionedDisputeGame *common.Address `toml:"PermissionedDisputeGame,omitempty" json:"PermissionedDisputeGame,omitempty"` + PreimageOracle *common.Address `toml:"PreimageOracle,omitempty" json:"PreimageOracle,omitempty"` + DAChallengeAddress *common.Address `toml:"DAChallengeAddress,omitempty" json:"DAChallengeAddress,omitempty"` +} diff --git a/op-deployer/book/.gitignore b/op-deployer/book/.gitignore deleted file mode 100644 index 7585238efed..00000000000 --- a/op-deployer/book/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/op-deployer/book/book.toml b/op-deployer/book/book.toml deleted file mode 100644 index acb79766830..00000000000 --- a/op-deployer/book/book.toml +++ /dev/null @@ -1,12 +0,0 @@ -[book] -authors = ["Optimism Contributors"] -language = "en" -multilingual = false -src = "src" -title = "OP Deployer Book" - -[output.html] -site-url = "/op-deployer/" -git-repository-url = "https://github.com/ethereum-optimism/optimism/tree/develop/op-deployer/book" -edit-url-template = "https://github.com/ethereum-optimism/optimism/tree/develop/op-deployer/book/{path}" -additional-css = ["custom.css"] diff --git a/op-deployer/book/custom.css b/op-deployer/book/custom.css deleted file mode 100644 index 7c94143752a..00000000000 --- a/op-deployer/book/custom.css +++ /dev/null @@ -1,5 +0,0 @@ -.content main { - max-width: 85%; - margin-left: auto; - margin-right: auto; -} diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index 43d0e2c4484..af88d5940b1 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -23,6 +23,8 @@ import ( "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/optimism/op-service/prestate" + "github.com/ethereum-optimism/optimism/op-validator/pkg/service" + "github.com/ethereum-optimism/optimism/op-validator/pkg/validations" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" diff --git a/op-deployer/pkg/deployer/pipeline/superchain.go b/op-deployer/pkg/deployer/pipeline/superchain.go index 9253df43b0b..b3a3f498161 100644 --- a/op-deployer/pkg/deployer/pipeline/superchain.go +++ b/op-deployer/pkg/deployer/pipeline/superchain.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" - "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum/go-ethereum/params" ) func DeploySuperchain(env *Env, intent *state.Intent, st *state.State) error { @@ -20,18 +20,35 @@ func DeploySuperchain(env *Env, intent *state.Intent, st *state.State) error { lgr.Info("deploying superchain") - dso, err := env.Scripts.DeploySuperchain.Run( - opcm.DeploySuperchainInput{ - SuperchainProxyAdminOwner: intent.SuperchainRoles.SuperchainProxyAdminOwner, - ProtocolVersionsOwner: intent.SuperchainRoles.ProtocolVersionsOwner, - Guardian: intent.SuperchainRoles.SuperchainGuardian, - Paused: false, - RequiredProtocolVersion: rollup.OPStackSupport, - RecommendedProtocolVersion: rollup.OPStackSupport, - }, - ) - if err != nil { - return fmt.Errorf("failed to deploy superchain: %w", err) + input := opcm.DeploySuperchainInput{ + SuperchainProxyAdminOwner: intent.SuperchainRoles.SuperchainProxyAdminOwner, + ProtocolVersionsOwner: intent.SuperchainRoles.ProtocolVersionsOwner, + Guardian: intent.SuperchainRoles.SuperchainGuardian, + Paused: false, + RequiredProtocolVersion: params.OPStackSupport, + RecommendedProtocolVersion: params.OPStackSupport, + } + + var dso opcm.DeploySuperchainOutput + var err error + + if env.UseForge { + lgr.Info("using Forge for DeploySuperchain") + forgeEnv := &opcm.ForgeEnv{ + Client: env.ForgeClient, + Context: env.Context, + L1RPCUrl: env.L1RPCUrl, + PrivateKey: env.PrivateKey, + } + dso, err = opcm.DeploySuperchainViaForge(forgeEnv, input) + if err != nil { + return err + } + } else { + dso, err = env.Scripts.DeploySuperchain.Run(input) + if err != nil { + return fmt.Errorf("failed to deploy superchain: %w", err) + } } st.SuperchainDeployment = &addresses.SuperchainContracts{ diff --git a/op-deployer/pkg/deployer/pipeline/superchain_test.go b/op-deployer/pkg/deployer/pipeline/superchain_test.go new file mode 100644 index 00000000000..aebdd003134 --- /dev/null +++ b/op-deployer/pkg/deployer/pipeline/superchain_test.go @@ -0,0 +1,198 @@ +package pipeline + +import ( + "context" + "fmt" + "log/slog" + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/addresses" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/broadcaster" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/forge" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/testutil" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/env" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-service/testutils/devnet" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" +) + +func TestDeploySuperchain_WithForge(t *testing.T) { + ctx := context.Background() + tmpDir := t.TempDir() + + embeddedArtifactsFS, err := artifacts.ExtractEmbedded(tmpDir) + require.NoError(t, err) + + forgeClient, err := forge.NewStandardClient(fmt.Sprintf("%v", embeddedArtifactsFS)) + require.NoError(t, err) + + _, afacts := testutil.LocalArtifacts(t) + lgr := testlog.Logger(t, slog.LevelInfo) + anvil, err := devnet.NewAnvil(lgr) + require.NoError(t, err) + require.NoError(t, anvil.Start()) + t.Cleanup(func() { + require.NoError(t, anvil.Stop()) + }) + + l1RPCUrl := anvil.RPCUrl() + privateKey := "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + lgr, + common.Address{'D'}, + afacts, + ) + require.NoError(t, err) + + opcmScripts, err := opcm.NewScripts(host) + require.NoError(t, err) + + intent := &state.Intent{ + SuperchainRoles: &addresses.SuperchainRoles{ + SuperchainProxyAdminOwner: common.BigToAddress(big.NewInt(1)), + ProtocolVersionsOwner: common.BigToAddress(big.NewInt(2)), + SuperchainGuardian: common.BigToAddress(big.NewInt(3)), + }, + } + st := &state.State{ + Version: 1, + } + + pEnv := &Env{ + Logger: lgr, + Scripts: opcmScripts, + ForgeClient: forgeClient, + UseForge: true, + Context: ctx, + Broadcaster: broadcaster.NoopBroadcaster(), + StateWriter: NoopStateWriter(), + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + } + + err = DeploySuperchain(pEnv, intent, st) + require.NoError(t, err) + + require.NotNil(t, st.SuperchainDeployment) + require.NotNil(t, st.SuperchainRoles) + + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.SuperchainProxyAdminImpl) + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.SuperchainConfigProxy) + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.SuperchainConfigImpl) + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.ProtocolVersionsProxy) + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.ProtocolVersionsImpl) + + require.Equal(t, intent.SuperchainRoles.SuperchainProxyAdminOwner, st.SuperchainRoles.SuperchainProxyAdminOwner) + require.Equal(t, intent.SuperchainRoles.ProtocolVersionsOwner, st.SuperchainRoles.ProtocolVersionsOwner) + require.Equal(t, intent.SuperchainRoles.SuperchainGuardian, st.SuperchainRoles.SuperchainGuardian) +} + +func TestDeploySuperchain_WithForgeEverywhere(t *testing.T) { + ctx := context.Background() + tmpDir := t.TempDir() + + embeddedArtifactsFS, err := artifacts.ExtractEmbedded(tmpDir) + require.NoError(t, err) + + forgeClient, err := forge.NewStandardClient(fmt.Sprintf("%v", embeddedArtifactsFS)) + require.NoError(t, err) + + _, afacts := testutil.LocalArtifacts(t) + lgr := testlog.Logger(t, slog.LevelInfo) + anvil, err := devnet.NewAnvil(lgr) + require.NoError(t, err) + require.NoError(t, anvil.Start()) + t.Cleanup(func() { + require.NoError(t, anvil.Stop()) + }) + + l1RPCUrl := anvil.RPCUrl() + privateKey := "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + + host, err := env.DefaultScriptHost( + broadcaster.NoopBroadcaster(), + lgr, + common.Address{'D'}, + afacts, + ) + require.NoError(t, err) + + opcmScripts, err := opcm.NewScripts(host) + require.NoError(t, err) + + intent := &state.Intent{ + SuperchainRoles: &addresses.SuperchainRoles{ + SuperchainProxyAdminOwner: common.BigToAddress(big.NewInt(1)), + ProtocolVersionsOwner: common.BigToAddress(big.NewInt(2)), + SuperchainGuardian: common.BigToAddress(big.NewInt(3)), + }, + } + st := &state.State{ + Version: 1, + } + + pEnv := &Env{ + Logger: lgr, + Scripts: opcmScripts, + ForgeClient: forgeClient, + UseForge: true, + Context: ctx, + Broadcaster: broadcaster.NoopBroadcaster(), + StateWriter: NoopStateWriter(), + L1RPCUrl: l1RPCUrl, + PrivateKey: privateKey, + } + + err = DeploySuperchain(pEnv, intent, st) + require.NoError(t, err) + + require.NotNil(t, st.SuperchainDeployment) + require.NotNil(t, st.SuperchainRoles) + + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.SuperchainProxyAdminImpl) + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.SuperchainConfigProxy) + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.SuperchainConfigImpl) + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.ProtocolVersionsProxy) + require.NotEqual(t, common.Address{}, st.SuperchainDeployment.ProtocolVersionsImpl) +} + +func TestDeploySuperchain_WithForge_ManualCall(t *testing.T) { + ctx := context.Background() + tmpDir := t.TempDir() + + embeddedArtifactsFS, err := artifacts.ExtractEmbedded(tmpDir) + require.NoError(t, err) + + forgeClient, err := forge.NewStandardClient(fmt.Sprintf("%v", embeddedArtifactsFS)) + require.NoError(t, err) + + deploySuperchain := opcm.NewDeploySuperchainForgeCaller(forgeClient) + + input := opcm.DeploySuperchainInput{ + Guardian: common.BigToAddress(big.NewInt(1)), + ProtocolVersionsOwner: common.BigToAddress(big.NewInt(2)), + SuperchainProxyAdminOwner: common.BigToAddress(big.NewInt(3)), + Paused: false, + RequiredProtocolVersion: params.OPStackSupport, + RecommendedProtocolVersion: params.OPStackSupport, + } + + output, recompiled, err := deploySuperchain(ctx, input) + require.NoError(t, err) + require.False(t, recompiled, "script should not be recompiled") + require.NotNil(t, output) + + require.NotEqual(t, common.Address{}, output.SuperchainProxyAdmin) + require.NotEqual(t, common.Address{}, output.SuperchainConfigProxy) + require.NotEqual(t, common.Address{}, output.SuperchainConfigImpl) + require.NotEqual(t, common.Address{}, output.ProtocolVersionsProxy) + require.NotEqual(t, common.Address{}, output.ProtocolVersionsImpl) +} diff --git a/op-devstack/README.md b/op-devstack/README.md new file mode 100644 index 00000000000..235102894af --- /dev/null +++ b/op-devstack/README.md @@ -0,0 +1,139 @@ +# op-devstack + +Devstack provides typed test presets and DSL helpers for integration and acceptance testing. + +## Overview + +### Packages + +- `devtest`: test handles and lifecycle helpers. +- `stack`: shared interfaces and common types used across DSL and runtime code. +- `sysgo`: process-backed and in-process runtime constructors for local test systems. +- `presets`: typed constructors that build fresh systems per test and expose convenient frontends. +- `dsl`: test interaction helpers built on top of preset frontends. + +```mermaid +graph TD + sysgo --builds runtimes--> presets + presets --wraps runtimes--> dsl + dsl --uses shared interfaces--> stack + + userMain --creates test setup--> presets + userTest --uses test setup--> presets +``` + + +### Patterns + +There are some common patterns in this package: + +- `stack.X` (interface): presents a component +- `stack.X` (interface): stable protocol for a component category. +- `sysgo`: builds explicit runtime graphs for local test systems. +- `presets`: translate runtime references into typed preset outputs and DSL helpers. +- Preset constructors build explicit component graphs directly and return those references to tests. + +### Components + +Available components: + +- `System`: a collection of chains and other components +- `L1Network`: a L1 chain configuration and registered L1 components + - `L1ELNode`: L1 execution-layer node, like geth or reth. + - `L1CLNode`: L1 consensus-layer node. A full beacon node or a mock consensus replacement for testing. +- `L2Network`: a L2 chain configuration and registered L2 components + - `L2ELNode`: L2 execution-engine, like op-geth or op-reth + - `L2CLNode`: op-node service, or equivalent + - `L2Batcher`: op-batcher, or equivalent + - `L2Proposer`: op-proposer, or equivalent + - `L2Challenger`: op-challenger, or equivalent + - `L2MetricsDashboard`: runs prometheus and grafana instances if any component registers metrics endpoints +- `Supervisor`: op-supervisor service, or equivalent +- `Faucet`: util to fund eth to test accounts + +### DSL-only components + +Some components are DSL-only: these are ephemeral, +live only for the duration of a test-case, and do not share state with other tests. + +Available components: +- `Key`: a chain-agnostic private key to sign ethereum things with. +- `HDWallet`: a source to create new `Key`s from. +- `EOA`: an Externally-Owned-Account (EOA) is a private-key backed ethereum account, specific to a single chain. + This is a `Key` coupled to an `ELNode` (L1 or L2). +- `Funder`: a wallet combined with a faucet and EL node, to create pre-funded `EOA`s + +#### `presets`, `Option`, `TestSetup` + +The `presets` package provides options, generally named `With...`. + +Each `Option` may apply changes to one or more of the setup stages. +E.g. some options may customize contract deployments, others may customize nodes, +and others may do post-validation of test setups. + +A `TestSetup` is a function that prepares the frontend specific to a test, +and returns a typed output that the test then may use. + +## Design choices + +- Interfaces FIRST. Composes much better. +- Incremental system composition. In the DSL package, maximize reusability by implementing DSL methods on the "lowest common denominator", e.g. prefer EL over Network. In tests, maximize readability by using the highest level of abstraction possible. +- Type-safety is important. Internals may be more verbose where needed. +- Everything is a resource and has a typed ID +- Embedding and composition de-dup a lot of code. +- Avoid generics in method signatures, these make composition of the general base types through interfaces much harder. +- Each component has access to commons such as logging and a test handle to assert on. + - The test-handle is very minimal, so that tooling can implement it, and is only made accessible for internal sanity-check assertions. +- Option pattern for each type, taking the interface, so that the system can be composed by external packages, eg: + - Kurtosis + - System like op-e2e + - Action-test +- Implementations should take `client.RPC` (or equivalent), not raw endpoints. Dialing is best done by the system composer, which can customize retries, in-process RPC pipes, lazy-dialing, etc. as needed. +- The system composer is responsible for tracking raw RPC URLs. These are not portable, and would expose too much low-level detail in the System interface. +- The system composer is responsible for the lifecycle of each component; in-process systems couple to the test lifecycle and shut down via `t.Cleanup`. +- Test gates do not have direct access to the `Orchestrator`; tests interact through typed preset outputs. +- Tests are isolated by default: each test constructs its own fresh system target. +- There are no "chains": the word "chain" is reserved for the protocol typing of the onchain / state-transition related logic. Instead, there are "networks", which include the offchain resources and attached services of a chain. +- Do not abbreviate "client" to "cl", to avoid confusion with "consensus-layer". + +## Environment Variables + +### The following environment variables can be used to configure devstack: + +- `DEVSTACK_KEYS_SALT`: Seeds the keys generated with `NewHDWallet`. This is useful for "isolating" test runs, and might be needed to reproduce CI and/or acceptance test runs. It can be any string, including the empty one to use the "usual" devkeys. +- `DEVNET_EXPECT_PRECONDITIONS_MET`: This can be set of force test failures when their pre-conditions are not met, which would otherwise result in them being skipped. This is helpful in particular for runs that do intend to run specific tests (as opposed to whatever is available). `op-acceptor` does set that variable, for example. + +### Rust stack env vars: +- `DEVSTACK_L2CL_KIND=kona-node` to select kona-node as default L2 CL node +- `DEVSTACK_L2EL_KIND=op-reth` to select op-reth as default L2 EL node +- `KONA_NODE_EXEC_PATH=/home/USERHERE/projects/kona/target/debug/kona-node` to select the kona-node executable to run +- `OP_RETH_EXEC_PATH=/home/USERHERE/projects/reth/target/release/op-reth` to select the op-reth executable to run + +### Go stack env vars: +- `DEVSTACK_L1EL_KIND=geth` to select geth as default L1 EL node +- `SYSGO_GETH_EXEC_PATH=/path/to/geth` to select the geth executable to run + +### Metrics env vars: +- `SYSGO_METRICS_ENABLED` set to `true` to enable metrics to be exposed via prometheus and grafana for all running components that expose metrics (default: `false`) +- `SYSGO_DOCKER_EXEC_PATH` path to docker executable (defaults to `docker` assuming it is in your `PATH`) +- `SYSGO_GRAFANA_PROVISIONING_DIR` to provide a local grafana provisioning dir to use (otherwise a temp dir will be created and removed at the end of tests) +- `SYSGO_GRAFANA_DATA_DIR` to provide a local grafana data dir to use (otherwise a temp dir will be created and removed at the end of tests) +- `SYSGO_GRAFANA_DOCKER_IMAGE_TAG` indicates which grafana docker image tag should be used (default is `12.2`). +- `SYSGO_PROMETHEUS_DOCKER_IMAGE_TAG` indicates which prometheus docker image tag should be used (default is `v3.7.2`). + +### Other useful env vars: +- `DISABLE_OP_E2E_LEGACY=true` to disable the op-e2e package from loading build-artifacts that are not used by devstack. + +## Metrics +If [metrics are enabled](#metrics-env-vars) and any component is configured to expose metrics, prometheus and grafana instances will be created via docker to serve them to the user. + +If metrics are enabled, the grafana instance will be served at `http://localhost:3000`. If that port is in use, the dev stack may fail to deploy. + +### Requirements +The [Docker](https://docs.docker.com) binary must be installed and either be available in your `PATH` as `docker` or configured via `SYSGO_DOCKER_EXEC_PATH` See [Environment Variables](#metrics-env-vars). + +### Configuring dashboards +When configuring dashboards, note that there is a single prometheus datasource available at `http://host.docker.internal:9999`. + +> [!TIP] +> It is recommended to set a `SYSGO_GRAFANA_PROVISIONING_DIR` and `SYSGO_GRAFANA_DATA_DIR`. This allows any configured datasources, dashboards, and visualizations to persist across restarts. If these variables are not set, temporary directories will be used and any configuration will be lost on restart. diff --git a/op-devstack/dsl/bridge.go b/op-devstack/dsl/bridge.go new file mode 100644 index 00000000000..b1bd6a88596 --- /dev/null +++ b/op-devstack/dsl/bridge.go @@ -0,0 +1,580 @@ +package dsl + +import ( + "encoding/hex" + "errors" + "fmt" + "math/big" + "strings" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-core/predeploys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + nodebindings "github.com/ethereum-optimism/optimism/op-node/bindings" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/withdrawals" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" + "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/gethclient" + "github.com/holiman/uint256" +) + +// ProvenWithdrawalParameters is the set of parameters to pass to the ProveWithdrawalTransaction +// and FinalizeWithdrawalTransaction functions +type ProvenWithdrawalParameters struct { + Nonce *big.Int + Sender common.Address + Target common.Address + Value *big.Int + GasLimit *big.Int + DisputeGameAddress common.Address + DisputeGameIndex *big.Int + Data []byte + OutputRootProof bindings.OutputRootProof + WithdrawalProof [][]byte // List of trie nodes to prove L2 storage +} + +type StandardBridge struct { + commonImpl + l1PortalAddr common.Address + l1Portal bindings.OptimismPortal2 + l2tol1MessagePasser bindings.L2ToL1MessagePasser + disputeGameFactory bindings.DisputeGameFactory + rollupCfg *rollup.Config + + l1Client *L1ELNode + l2Client apis.EthClient + + // L1 bridge contract + l1StandardBridge bindings.L1StandardBridge +} + +func NewStandardBridge(t devtest.T, l2Network *L2Network, l1EL *L1ELNode) *StandardBridge { + l1Client := l1EL.EthClient() + l1PortalAddr := l2Network.DepositContractAddr() + l1Portal := bindings.NewBindings[bindings.OptimismPortal2]( + bindings.WithClient(l1Client), + bindings.WithTo(l1PortalAddr), + bindings.WithTest(t)) + l2Client := l2Network.PrimaryEL().EthClient() + l2tol1MessagePasser := bindings.NewBindings[bindings.L2ToL1MessagePasser]( + bindings.WithClient(l2Client), + bindings.WithTo(predeploys.L2ToL1MessagePasserAddr), + bindings.WithTest(t)) + + disputeGameFactory := bindings.NewBindings[bindings.DisputeGameFactory]( + bindings.WithClient(l1Client), + bindings.WithTo(l2Network.DisputeGameFactoryProxyAddr())) + + l1StandardBridge := bindings.NewBindings[bindings.L1StandardBridge]( + bindings.WithClient(l1Client), + bindings.WithTo(l2Network.Escape().Deployment().L1StandardBridgeProxyAddr()), + bindings.WithTest(t)) + + return &StandardBridge{ + commonImpl: commonFromT(t), + l1PortalAddr: l1PortalAddr, + l1Portal: l1Portal, + l2tol1MessagePasser: l2tol1MessagePasser, + disputeGameFactory: disputeGameFactory, + rollupCfg: l2Network.inner.RollupConfig(), + + l1Client: l1EL, + l2Client: l2Client, + l1StandardBridge: l1StandardBridge, + } +} + +func (b *StandardBridge) GameResolutionDelay() time.Duration { + gameType := b.RespectedGameType() + gameImplAddr, err := contractio.Read(b.disputeGameFactory.GameImpls(gameType), b.ctx) + b.require.NoErrorf(err, "failed to get implementation for game type %v", gameType) + game := bindings.NewBindings[bindings.FaultDisputeGame](bindings.WithClient(b.l1Client.EthClient()), bindings.WithTo(gameImplAddr), bindings.WithTest(b.t)) + clockDuration, err := contractio.Read(game.MaxClockDuration(), b.ctx) + b.require.NoErrorf(err, "failed to get max clock duration for game type %v", gameType) + return time.Duration(clockDuration) * time.Second +} + +func (b *StandardBridge) WithdrawalDelay() time.Duration { + delaySeconds, err := contractio.Read(b.l1Portal.ProofMaturityDelaySeconds(), b.ctx) + b.require.NoError(err, "Failed to read proof maturity delay") + return time.Duration(delaySeconds.Int64()) * time.Second +} + +func (b *StandardBridge) DisputeGameFinalityDelay() time.Duration { + delaySeconds, err := contractio.Read(b.l1Portal.DisputeGameFinalityDelaySeconds(), b.ctx) + b.require.NoError(err, "Failed to read dispute game finality delay") + return time.Duration(delaySeconds.Int64()) * time.Second +} + +func (b *StandardBridge) RespectedGameType() uint32 { + gameType, err := contractio.Read(b.l1Portal.RespectedGameType(), b.ctx) + b.require.NoError(err, "Failed to read respected game type") + return gameType +} + +func (b *StandardBridge) PortalVersion() string { + version, err := contractio.Read(b.l1Portal.Version(), b.ctx) + b.require.NoError(err, "Failed to read portal version") + return version +} + +func (b *StandardBridge) UsesSuperRoots() bool { + // Only interop contracts have SuperRootsActive functionality + version := b.PortalVersion() + if !strings.HasSuffix(version, "+interop") { + return false + } + + superRootsActive, err := contractio.Read(b.l1Portal.SuperRootsActive(), b.ctx) + b.require.NoError(err, "Failed to read super roots active") + return superRootsActive +} + +type Deposit struct { + bridge *StandardBridge + l1Receipt *types.Receipt +} + +func (d Deposit) GasCost() eth.ETH { + if d.bridge == nil { + panic("bridge reference not set on deposit") + } + return d.bridge.gasCost(d.l1Receipt, d.bridge.l1Client.EthClient()) +} + +func (b *StandardBridge) Deposit(amount eth.ETH, from *EOA) Deposit { + depositTx := from.Transfer(b.l1PortalAddr, amount) + l1DepositReceipt, err := depositTx.Included.Eval(b.ctx) + b.require.NoErrorf(err, "Failed to send deposit transaction from %v for %v", from, amount) + + // Wait for the deposit to be processed on the L2 + // Construct the L2 deposit tx to check the tx is included at L2 + idx := len(l1DepositReceipt.Logs) - 1 + l2DepositTx, err := derive.UnmarshalDepositLogEvent(l1DepositReceipt.Logs[idx]) + b.require.NoError(err, "Could not reconstruct L2 Deposit") + l2DepositTxHash := types.NewTx(l2DepositTx).Hash() + // Give time for L2CL to include the L2 deposit tx + var l2DepositReceipt *types.Receipt + b.require.Eventually(func() bool { + l2DepositReceipt, err = b.l2Client.TransactionReceipt(b.ctx, l2DepositTxHash) + return err == nil + }, 60*time.Second, 500*time.Millisecond, "L2 Deposit never found") + b.require.Equal(types.ReceiptStatusSuccessful, l2DepositReceipt.Status) + return Deposit{ + bridge: b, + l1Receipt: l1DepositReceipt, + } +} + +func (b *StandardBridge) InitiateWithdrawal(amount eth.ETH, from *EOA) *Withdrawal { + withdrawTx := from.Transfer(predeploys.L2ToL1MessagePasserAddr, amount) + withdrawRcpt, err := withdrawTx.Included.Eval(b.ctx) + b.require.NoErrorf(err, "Failed to initiate withdrawal from %v for %v", from, amount) + b.require.Equal(types.ReceiptStatusSuccessful, withdrawRcpt.Status, "initiating withdrawal failed") + return &Withdrawal{ + commonImpl: commonFromT(b.t), + bridge: b, + initReceipt: withdrawRcpt, + } +} + +// ERC20Deposit performs an ERC20 deposit from L1 to L2 +func (b *StandardBridge) ERC20Deposit(l1TokenAddr common.Address, l2TokenAddr common.Address, amount eth.ETH, from *EOA) *Deposit { + // Use the l1StandardBridge to deposit ERC20 tokens + depositCall := b.l1StandardBridge.DepositERC20To(l1TokenAddr, l2TokenAddr, from.Address(), amount, 200000, []byte{}) + depositReceipt, err := contractio.Write(depositCall, b.ctx, from.Plan()) + b.require.NoError(err, "Failed to send ERC20 deposit transaction") + b.require.Equal(types.ReceiptStatusSuccessful, depositReceipt.Status, "ERC20 deposit should succeed") + + // Wait for the deposit to be processed on the L2 + // Find the deposit log to get the L2 deposit transaction + var l2DepositTx *types.DepositTx + for _, log := range depositReceipt.Logs { + if l2DepositTx, err = derive.UnmarshalDepositLogEvent(log); err == nil { + break + } + } + b.require.NotNil(l2DepositTx, "Could not find L2 deposit transaction in logs") + + l2DepositTxHash := types.NewTx(l2DepositTx).Hash() + + // Give time for L2CL to include the L2 deposit tx + sequencingWindowDuration := time.Duration(b.rollupCfg.SeqWindowSize) * b.l1Client.EstimateBlockTime() + var l2DepositReceipt *types.Receipt + b.require.Eventually(func() bool { + l2DepositReceipt, err = b.l2Client.TransactionReceipt(b.ctx, l2DepositTxHash) + return err == nil + }, sequencingWindowDuration, 500*time.Millisecond, "L2 ERC20 deposit never found") + b.require.Equal(types.ReceiptStatusSuccessful, l2DepositReceipt.Status, "L2 ERC20 deposit should succeed") + + return &Deposit{ + bridge: b, + l1Receipt: depositReceipt, + } +} + +// CreateL2Token creates an L2 token using OptimismMintableERC20Factory and returns the token address +func (b *StandardBridge) CreateL2Token(l1TokenAddr common.Address, name string, symbol string, from *EOA) common.Address { + factoryContract := bindings.NewBindings[bindings.OptimismMintableERC20Factory]( + bindings.WithTest(b.t), + bindings.WithClient(b.l2Client), + bindings.WithTo(predeploys.OptimismMintableERC20FactoryAddr), + ) + + createCall := factoryContract.CreateOptimismMintableERC20(l1TokenAddr, name, symbol) + createReceipt, err := contractio.Write(createCall, b.ctx, from.Plan()) + b.require.NoError(err, "Failed to create L2 token") + b.require.Equal(types.ReceiptStatusSuccessful, createReceipt.Status, "L2 token creation should succeed") + + // Extract L2 token address from logs + l2TokenAddress := b.extractL2TokenFromLogs(createReceipt) + b.log.Info("Created L2 token", "l1Token", l1TokenAddr, "l2Token", l2TokenAddress, "name", name, "symbol", symbol) + return l2TokenAddress +} + +// extractL2TokenFromLogs extracts the L2 token address from OptimismMintableERC20Created event +func (b *StandardBridge) extractL2TokenFromLogs(receipt *types.Receipt) common.Address { + // Look for the OptimismMintableERC20Created event + for _, log := range receipt.Logs { + if log.Address == predeploys.OptimismMintableERC20FactoryAddr && len(log.Topics) > 2 { + // The token address is in the indexed topics + return common.HexToAddress(log.Topics[2].Hex()) + } + } + b.require.Fail("Failed to find L2 token address from events") + return common.Address{} // Never reached +} + +type disputeGame struct { + Index *big.Int + Address common.Address + L2BlockNumber uint64 + SequenceNumber uint64 + UsesSuperRoots bool +} + +// forGamePublished waits until a game is published on L1 for the given l2BlockNumber +// Note that the l2 block number is passed even for super games. Conversion to timestamp is done automatically +// when required by the respected game type +func (b *StandardBridge) forGamePublished(l2BlockNumber *big.Int) disputeGame { + respectedGameType := b.RespectedGameType() + l2SequenceNumber := bigs.Uint64Strict(l2BlockNumber) + superRootsActive := b.UsesSuperRoots() + if superRootsActive { + l2SequenceNumber = b.rollupCfg.TimestampForBlock(l2SequenceNumber) + } + + var game bindings.DisputeGame + var gameSeqNum uint64 + var gameIndex *big.Int + b.require.Eventuallyf(func() bool { + var err error + game, gameIndex, err = b.findLatestGame(respectedGameType) + if err != nil { + b.log.Warn("No game of required type found", "err", err) + return false + } + gameContract := bindings.NewBindings[bindings.FaultDisputeGame]( + bindings.WithClient(b.l1Client.EthClient()), + bindings.WithTo(game.Proxy), + bindings.WithTest(b.t)) + seqNum, err := contractio.Read(gameContract.L2SequenceNumber(), b.ctx) + b.require.NoError(err, "Failed to read sequence number") + gameSeqNum = bigs.Uint64Strict(seqNum) + b.log.Info("Found latest game", "index", gameIndex, "seqNum", gameSeqNum) + return gameSeqNum >= l2SequenceNumber + }, 90*time.Second, 100*time.Millisecond, "did not find a game of type %v at or after l2 sequence number %v", respectedGameType, l2SequenceNumber) + + gameBlockNum := gameSeqNum + if superRootsActive { + blockNum, err := b.rollupCfg.TargetBlockNumber(gameSeqNum) + b.require.NoError(err, "Failed to convert game timestamp to block number") + gameBlockNum = blockNum + } + return disputeGame{ + Index: gameIndex, + Address: game.Proxy, + L2BlockNumber: gameBlockNum, + SequenceNumber: gameSeqNum, + UsesSuperRoots: superRootsActive, + } +} + +// findLatestGame finds the latest game in the DisputeGameFactory contract. +// Ported from op-node/withdrawals/utils.go to fit in the op-devstack, using op-service ethclient +func (b *StandardBridge) findLatestGame(gameType uint32) (bindings.DisputeGame, *big.Int, error) { + gameCount, err := contractio.Read(b.disputeGameFactory.GameCount(), b.ctx) + b.require.NoError(err, "Failed to read game count") + if gameCount.Cmp(common.Big0) == 0 { + return bindings.DisputeGame{}, nil, errors.New("no games") + } + + gameIdx := new(big.Int).Sub(gameCount, common.Big1) + for gameIdx.Cmp(common.Big0) >= 0 { + latestGame, err := contractio.Read(b.disputeGameFactory.GameAtIndex(gameIdx), b.ctx) + b.require.NoErrorf(err, "Failed to find latest game for %v", gameType) + if latestGame.GameType != gameType { + // Wrong game type, continue searching backwards + gameIdx = new(big.Int).Sub(gameIdx, common.Big1) + continue + } + return latestGame, gameIdx, nil + } + return bindings.DisputeGame{}, nil, errors.New("no suitable games found") +} + +type Withdrawal struct { + commonImpl + bridge *StandardBridge + initReceipt *types.Receipt + + proveParams ProvenWithdrawalParameters + proveReceipt *types.Receipt + finalizeReceipt *types.Receipt +} + +func (w *Withdrawal) InitiateGasCost() eth.ETH { + return w.bridge.gasCost(w.initReceipt, w.bridge.l2Client) +} + +func (w *Withdrawal) ProveGasCost() eth.ETH { + w.require.NotNil(w.proveReceipt, "Must have proven withdrawal before calculating gas cost") + return w.bridge.gasCost(w.proveReceipt, w.bridge.l1Client.EthClient()) +} + +func (w *Withdrawal) FinalizeGasCost() eth.ETH { + w.require.NotNil(w.finalizeReceipt, "Must have finalized withdrawal before calculating gas cost") + return w.bridge.gasCost(w.finalizeReceipt, w.bridge.l1Client.EthClient()) +} + +func (w *Withdrawal) InitiateBlockHash() common.Hash { + return w.initReceipt.BlockHash +} + +func (w *Withdrawal) Prove(user *EOA) { + var params ProvenWithdrawalParameters + + w.t.Log("proveWithdrawal: proving withdrawal...") + params = w.proveWithdrawalParameters() + tx := bindings.WithdrawalTransaction{ + Nonce: params.Nonce, + Sender: params.Sender, + Target: params.Target, + Value: params.Value, + GasLimit: params.GasLimit, + Data: params.Data, + } + + call := w.bridge.l1Portal.ProveWithdrawalTransaction(tx, params.DisputeGameIndex, params.OutputRootProof, params.WithdrawalProof) + // Retry as withdrawals can't be proven in the same block as the game is created. + // estimateGas works against the current head so we may need to retry until it has progressed enough. + w.require.Eventually(func() bool { + proveReceipt, err := contractio.Write(call, w.ctx, user.Plan()) + if err != nil { + w.log.Error("Failed to send prove transaction", "err", err) + return false + } + w.require.Equal(types.ReceiptStatusSuccessful, proveReceipt.Status, "prove withdrawal was not successful") + w.require.Equal(2, len(proveReceipt.Logs)) // emit WithdrawalProven, WithdrawalProvenExtension1 + + w.proveParams = params + w.proveReceipt = proveReceipt + return true + }, 30*time.Second, 1*time.Second, "Sending prove transaction") +} + +// ProveWithdrawalParameters calls ProveWithdrawalParametersForBlock with the most recent L2 output after the latest game. +// Ported from op-node/withdrawals/utils.go to fit in the op-devstack +func (w *Withdrawal) proveWithdrawalParameters() ProvenWithdrawalParameters { + // Wait for a suitable game to be published + latestGame := w.bridge.forGamePublished(w.initReceipt.BlockNumber) + + // Fetch the block header from the L2 node + l2Header, err := w.bridge.l2Client.InfoByNumber(w.ctx, latestGame.L2BlockNumber) + w.require.NoErrorf(err, "failed to fetch block header %v", latestGame.L2BlockNumber) + + ev, err := withdrawals.ParseMessagePassed(w.initReceipt) + w.require.NoError(err, "failed to parse message passed receipt") + return w.proveWithdrawalParametersForEvent(ev, l2Header, latestGame) +} + +// proveWithdrawalParametersForEvent queries L1 to generate all withdrawal parameters and proof necessary to prove a withdrawal on L1. +// The l2Header provided is very important. It should be a block for which there is a submitted output in the L2 Output Oracle +// contract. If not, the withdrawal will fail as it the storage proof cannot be verified if there is no submitted state root. +// Ported from op-node/withdrawals/utils.go to fit in the op-devstack, using op-service ethclient +func (w *Withdrawal) proveWithdrawalParametersForEvent(ev *nodebindings.L2ToL1MessagePasserMessagePassed, l2Header eth.BlockInfo, disputeGame disputeGame) ProvenWithdrawalParameters { + // Generate then verify the withdrawal proof + withdrawalHash, err := withdrawals.WithdrawalHash(ev) + w.require.NoErrorf(err, "failed to calculate hash for withdrawal %v", ev) + w.require.Equal(withdrawalHash[:], ev.WithdrawalHash[:], "computed withdrawal hash incorrectly") + slot := withdrawals.StorageSlotOfWithdrawalHash(withdrawalHash) + + p, err := w.bridge.l2Client.GetProof(w.ctx, predeploys.L2ToL1MessagePasserAddr, []common.Hash{slot}, hexutil.Uint64(l2Header.NumberU64()).String()) + w.require.NoErrorf(err, "failed to fetch proof for withdrawal %v", ev) + w.require.Len(p.StorageProof, 1, "invalid amount of storage proofs") + + err = verifyProof(l2Header.Root(), p) + w.require.NoErrorf(err, "failed to verify proof for withdrawal") + + // Encode it as expected by the contract + trieNodes := make([][]byte, len(p.StorageProof[0].Proof)) + for i, s := range p.StorageProof[0].Proof { + trieNodes[i] = s + } + + return ProvenWithdrawalParameters{ + Nonce: ev.Nonce, + Sender: ev.Sender, + Target: ev.Target, + Value: ev.Value, + GasLimit: ev.GasLimit, + DisputeGameAddress: disputeGame.Address, + DisputeGameIndex: disputeGame.Index, + Data: ev.Data, + OutputRootProof: bindings.OutputRootProof{ + Version: [32]byte{}, // Empty for version 1 + StateRoot: l2Header.Root(), + MessagePasserStorageRoot: *l2Header.WithdrawalsRoot(), + LatestBlockhash: l2Header.Hash(), + }, + WithdrawalProof: trieNodes, + } +} + +// Ported from op-node/withdrawals/proof.go to fit in the op-devstack, using op-service proof types +func verifyProof(stateRoot common.Hash, proof *eth.AccountResult) error { + balance, overflow := uint256.FromBig(proof.Balance.ToInt()) + if overflow { + return fmt.Errorf("proof balance overflows uint256: %d", proof.Balance.ToInt()) + } + proofHex := []string{} + for _, p := range proof.AccountProof { + proofHex = append(proofHex, hex.EncodeToString(p)) + } + err := withdrawals.VerifyAccountProof( + stateRoot, + proof.Address, + types.StateAccount{ + Nonce: uint64(proof.Nonce), + Balance: balance, + Root: proof.StorageHash, + CodeHash: proof.CodeHash[:], + }, + proofHex, + ) + if err != nil { + return fmt.Errorf("failed to validate account: %w", err) + } + for i, storageProof := range proof.StorageProof { + proofHex := []string{} + for _, p := range storageProof.Proof { + proofHex = append(proofHex, hex.EncodeToString(p)) + } + convertedProof := gethclient.StorageResult{ + Key: storageProof.Key.String(), + Value: storageProof.Value.ToInt(), + Proof: proofHex, + } + err = withdrawals.VerifyStorageProof(proof.StorageHash, convertedProof) + if err != nil { + return fmt.Errorf("failed to validate storage proof %d: %w", i, err) + } + } + return nil +} + +func (w *Withdrawal) Finalize(user *EOA) { + wd := crossdomain.Withdrawal{ + Nonce: w.proveParams.Nonce, + Sender: &w.proveParams.Sender, + Target: &w.proveParams.Target, + Value: w.proveParams.Value, + GasLimit: w.proveParams.GasLimit, + Data: w.proveParams.Data, + } + + // Finalize withdrawal + w.log.Info("FinalizeWithdrawal: finalizing withdrawal...") + var finalizeReceipt *types.Receipt + var err error + // Retry as the air gap delay needs to have expired at the head block timestamp for estimateGas to work + w.require.Eventually(func() bool { + finalizeReceipt, err = contractio.Write(w.bridge.l1Portal.FinalizeWithdrawalTransaction(wd.WithdrawalTransaction()), w.ctx, user.Plan()) + if err != nil { + return false + } + w.finalizeReceipt = finalizeReceipt + return types.ReceiptStatusSuccessful == finalizeReceipt.Status + }, 60*time.Second, 100*time.Millisecond, "finalize withdrawal failed") +} + +func (w *Withdrawal) WaitForDisputeGameResolved() { + w.require.NotNil(w.proveReceipt, "Must have proven withdrawal first") + + gameContract := bindings.NewBindings[bindings.FaultDisputeGame]( + bindings.WithClient(w.bridge.l1Client.EthClient()), + bindings.WithTo(w.proveParams.DisputeGameAddress), + bindings.WithTest(w.t)) + w.require.Eventually(func() bool { + status, err := contractio.Read(gameContract.Status(), w.ctx) + w.require.NoError(err, "failed to get game status") + w.log.Info("Waiting for dispute game to resolve", "currentStatus", status) + return gameTypes.GameStatus(status) == gameTypes.GameStatusDefenderWon + }, 60*time.Second, 100*time.Millisecond, "wait for dispute game resolved") +} + +func (b *StandardBridge) gasCost(rcpt *types.Receipt, client apis.EthClient) eth.ETH { + var blockTimestamp *uint64 + if hasOperatorFee(rcpt) { + b.require.NotNil(client, "client is required to resolve operator fee timestamp") + blockTimestamp = b.receiptTimestamp(rcpt, client) + } + return gasCost(rcpt, b.rollupCfg, blockTimestamp) +} + +func hasOperatorFee(rcpt *types.Receipt) bool { + return rcpt.OperatorFeeConstant != nil && rcpt.OperatorFeeScalar != nil +} + +func (b *StandardBridge) receiptTimestamp(rcpt *types.Receipt, client apis.EthClient) *uint64 { + b.require.NotNil(rcpt.BlockNumber, "receipt missing block number") + blockInfo, err := client.InfoByNumber(b.ctx, bigs.Uint64Strict(rcpt.BlockNumber)) + b.require.NoError(err, "failed to fetch block info for receipt") + ts := blockInfo.Time() + return &ts +} + +func gasCost(rcpt *types.Receipt, rollupCfg *rollup.Config, blockTimestamp *uint64) eth.ETH { + cost := eth.WeiBig(new(big.Int).Mul(new(big.Int).SetUint64(rcpt.GasUsed), rcpt.EffectiveGasPrice)) + if rcpt.L1Fee != nil { + cost = cost.Add(eth.WeiBig(rcpt.L1Fee)) + } + if hasOperatorFee(rcpt) { + if rollupCfg == nil { + panic("rollup config is required to compute operator fee") + } + if blockTimestamp == nil { + panic("block timestamp is required to compute operator fee") + } + operatorCost := new(big.Int).SetUint64(rcpt.GasUsed) + operatorCost.Mul(operatorCost, new(big.Int).SetUint64(*rcpt.OperatorFeeScalar)) + if rollupCfg.IsJovian(*blockTimestamp) { + operatorCost.Mul(operatorCost, big.NewInt(100)) + } else { + operatorCost.Div(operatorCost, big.NewInt(1_000_000)) + } + operatorCost.Add(operatorCost, new(big.Int).SetUint64(*rcpt.OperatorFeeConstant)) + cost = cost.Add(eth.WeiBig(operatorCost)) + } + return cost +} diff --git a/op-devstack/dsl/check.go b/op-devstack/dsl/check.go new file mode 100644 index 00000000000..231272bd38c --- /dev/null +++ b/op-devstack/dsl/check.go @@ -0,0 +1,259 @@ +package dsl + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" + "github.com/ethereum/go-ethereum/log" + "golang.org/x/sync/errgroup" +) + +type CheckFunc func() error + +func CheckAll(t devtest.T, checks ...CheckFunc) { + var g errgroup.Group + for _, check := range checks { + check := check + g.Go(func() error { + return check() + }) + } + t.Require().NoError(g.Wait()) +} + +type SyncStatusProvider interface { + ChainSyncStatus(chainID eth.ChainID, lvl safety.Level) eth.BlockID + String() string +} + +type ChainBlockProvider interface { + ChainBlockID(chainID eth.ChainID, number uint64) (eth.BlockID, error) +} + +var _ SyncStatusProvider = (*L2CLNode)(nil) + +// LaggedFn returns a lambda that checks the baseNode head with given safety level is lagged with the refNode chain sync status provider +// Composable with other lambdas to wait in parallel +func LaggedFn(baseNode, refNode SyncStatusProvider, log log.Logger, ctx context.Context, lvl safety.Level, chainID eth.ChainID, attempts int, allowMatch bool) CheckFunc { + return func() error { + base := baseNode.ChainSyncStatus(chainID, lvl) + ref := refNode.ChainSyncStatus(chainID, lvl) + logger := log.With("base_id", baseNode, "ref_id", refNode, "chain", chainID, "label", lvl) + logger.Info("Expecting node to lag with reference", "base", base.Number, "ref", ref.Number) + for range attempts { + base = baseNode.ChainSyncStatus(chainID, lvl) + ref = refNode.ChainSyncStatus(chainID, lvl) + cmp := base.Number > ref.Number + msg := "Base chain surpassed" + if !allowMatch { + cmp = base.Number >= ref.Number + msg += " or caught up" + } + if cmp { + logger.Warn(msg, "base", base.Number, "ref", ref.Number) + return fmt.Errorf("expected head to lag: %s", lvl) + } + logger.Info("Node sync status", "base", base.Number, "ref", ref.Number) + time.Sleep(2 * time.Second) + } + logger.Info("Node lagged as expected") + return nil + } +} + +// MatchedFn returns a lambda that checks the baseNode head with given safety level is matched with the refNode chain sync status provider +// Composable with other lambdas to wait in parallel +func MatchedFn(baseNode, refNode SyncStatusProvider, log log.Logger, ctx context.Context, lvl safety.Level, chainID eth.ChainID, attempts int) CheckFunc { + return func() error { + base := baseNode.ChainSyncStatus(chainID, lvl) + ref := refNode.ChainSyncStatus(chainID, lvl) + logger := log.With("base_id", baseNode, "ref_id", refNode, "chain", chainID, "label", lvl) + logger.Info("Expecting node to match with reference", "base", base.Number, "ref", ref.Number) + return retry.Do0(ctx, attempts, &retry.FixedStrategy{Dur: 2 * time.Second}, + func() error { + base = baseNode.ChainSyncStatus(chainID, lvl) + ref = refNode.ChainSyncStatus(chainID, lvl) + if ref.Hash == base.Hash && ref.Number == base.Number { + logger.Info("Node matched", "ref", ref.Number) + return nil + } + logger.Info("Node sync status", "base", base.Number, "ref", ref.Number) + return fmt.Errorf("expected head to match: %s", lvl) + }) + } +} + +// MatchedWithProgressFn returns a lambda that waits for baseNode's head at +// matchLvl to match refNode's head at matchLvl, while requiring baseNode to +// make progress on progressLvl. It is intended for sync checks where the +// matchLvl head can stall behind a slow but progressing pipeline — for +// example the CrossSafe head behind initial EL-sync, where the CL keeps +// receiving unsafe payloads (progressLvl == LocalUnsafe) but the safe head +// cannot advance until the EL completes its initial snap-sync. +// +// The check polls every 2s and succeeds as soon as the matchLvl heads match. +// It fails when one of: +// - the overall maxWait elapses; or +// - baseNode's progressLvl head has not advanced for stallTimeout (i.e. +// the underlying gossip / forward sync has truly stalled). +// +// Using a stall detector lets the budget be generous without papering over +// genuinely stuck systems. The first sample of progressLvl is taken on entry +// and the stall clock resets every time the progressLvl head advances. +func MatchedWithProgressFn(baseNode, refNode SyncStatusProvider, log log.Logger, ctx context.Context, matchLvl, progressLvl safety.Level, chainID eth.ChainID, maxWait, stallTimeout time.Duration) CheckFunc { + return func() error { + logger := log.With("base_id", baseNode, "ref_id", refNode, "chain", chainID, "label", matchLvl, "progress_label", progressLvl) + initialBase := baseNode.ChainSyncStatus(chainID, matchLvl) + initialRef := refNode.ChainSyncStatus(chainID, matchLvl) + logger.Info("Expecting node to match with reference (progress-aware)", + "base", initialBase.Number, "ref", initialRef.Number, + "max_wait", maxWait, "stall_timeout", stallTimeout) + + deadline := time.Now().Add(maxWait) + lastProgress := baseNode.ChainSyncStatus(chainID, progressLvl) + lastProgressTime := time.Now() + + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + for { + base := baseNode.ChainSyncStatus(chainID, matchLvl) + ref := refNode.ChainSyncStatus(chainID, matchLvl) + if ref.Hash == base.Hash && ref.Number == base.Number { + logger.Info("Node matched", "ref", ref.Number) + return nil + } + + progress := baseNode.ChainSyncStatus(chainID, progressLvl) + now := time.Now() + if progress.Number > lastProgress.Number { + lastProgress = progress + lastProgressTime = now + } + stalledFor := now.Sub(lastProgressTime) + logger.Info("Node sync status", + "base", base.Number, "ref", ref.Number, + "progress", progress.Number, "stalled_for", stalledFor) + + if stalledFor >= stallTimeout { + return fmt.Errorf("expected head to match: %s: %s stalled at %d for %s", + matchLvl, progressLvl, progress.Number, stalledFor) + } + if now.After(deadline) { + return fmt.Errorf("expected head to match: %s: timeout after %s (base=%d ref=%d progress=%d)", + matchLvl, maxWait, base.Number, ref.Number, progress.Number) + } + + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + } + } + } +} + +// maxInSyncGap is the largest difference (in blocks) between two node heads +// that InSyncFn will tolerate while still considering the nodes in sync. If +// the heads are further apart than this the slower node has not caught up yet. +const maxInSyncGap = 5 + +// InSyncFn checks that two peer nodes are converged on the same canonical chain +// at the given safety level. Before the retry loop it records the higher of the +// two starting heads as a catch-up target, so the slower node must reach at +// least where the faster node was when the check began. On each attempt it +// re-samples both heads live and considers the nodes in sync when: +// 1. the slower head has reached the catch-up target; and +// 2. the two head numbers differ by at most maxInSyncGap; and +// 3. at the lower of the two heights, both nodes agree on the canonical block hash. +// +// The catch-up target prevents falsely passing when both heads sit below a +// recent divergence point and happen to agree on shared pre-reorg history. +// Unlike MatchedFn this does not require both live heads to be equal in the +// same polling tick. Unlike a single-snapshot approach it tolerates either side +// reorging during the wait, since both heads are re-sampled every attempt. +func InSyncFn(node1, node2 SyncStatusProvider, log log.Logger, ctx context.Context, lvl safety.Level, chainID eth.ChainID, attempts int) CheckFunc { + return func() error { + logger := log.With("node1_id", node1, "node2_id", node2, "chain", chainID, "label", lvl) + provider1, canLookup1 := node1.(ChainBlockProvider) + provider2, canLookup2 := node2.(ChainBlockProvider) + + initial1 := node1.ChainSyncStatus(chainID, lvl) + initial2 := node2.ChainSyncStatus(chainID, lvl) + catchupTarget := initial1.Number + if initial2.Number > catchupTarget { + catchupTarget = initial2.Number + } + logger.Info("Expecting nodes to converge", + "initial_node1", initial1, "initial_node2", initial2, + "catchup_target", catchupTarget, "max_gap", maxInSyncGap) + + return retry.Do0(ctx, attempts, &retry.FixedStrategy{Dur: 2 * time.Second}, + func() error { + h1 := node1.ChainSyncStatus(chainID, lvl) + h2 := node2.ChainSyncStatus(chainID, lvl) + + lower, higher := h1, h2 + lowerSide, higherSide := "node1", "node2" + higherProvider, higherCanLookup := provider2, canLookup2 + if h2.Number < h1.Number { + lower, higher = h2, h1 + lowerSide, higherSide = "node2", "node1" + higherProvider, higherCanLookup = provider1, canLookup1 + } + gap := higher.Number - lower.Number + + if lower.Number < catchupTarget { + logger.Info("Slower node still catching up to initial high water mark", + "node1", h1, "node2", h2, "catchup_target", catchupTarget) + return fmt.Errorf("nodes not in sync: slower head at %d, must reach %d: %s", lower.Number, catchupTarget, lvl) + } + if gap > maxInSyncGap { + logger.Info("Nodes too far apart to be in sync", "node1", h1, "node2", h2, "gap", gap) + return fmt.Errorf("nodes not in sync: heads %d blocks apart (max %d): %s", gap, maxInSyncGap, lvl) + } + + if gap == 0 { + if lower.Hash == higher.Hash { + logger.Info("Nodes in sync at matching head", "head", lower) + return nil + } + logger.Info("Nodes diverged at matching head height", "node1", h1, "node2", h2) + return fmt.Errorf("nodes not in sync: same height %d but different hash: %s", lower.Number, lvl) + } + + // Different heights within the allowed gap: check the higher node's + // canonical block at the lower height matches the lower node's hash. + if !higherCanLookup { + logger.Info("Cannot verify canonical block on higher node", + "lower_side", lowerSide, "lower", lower, + "higher_side", higherSide, "higher", higher) + return fmt.Errorf("nodes not in sync: %s ahead but cannot verify its canonical block: %s", higherSide, lvl) + } + canonical, err := higherProvider.ChainBlockID(chainID, lower.Number) + if err != nil { + logger.Warn("Failed to fetch canonical block on higher node; will retry", + "lower_side", lowerSide, "lower", lower, + "higher_side", higherSide, "higher", higher, "err", err) + return err + } + if canonical.Hash == lower.Hash { + logger.Info("Nodes in sync; higher includes lower as canonical", + "lower_side", lowerSide, "lower", lower, + "higher_side", higherSide, "higher", higher) + return nil + } + logger.Info("Nodes diverged at lower height", + "lower_side", lowerSide, "lower", lower, + "higher_side", higherSide, "higher", higher, + "higher_canonical_at_lower", canonical) + return fmt.Errorf("nodes not in sync: %s canonical block at height %d does not match %s head: %s", higherSide, lower.Number, lowerSide, lvl) + }) + } +} diff --git a/op-devstack/dsl/check_test.go b/op-devstack/dsl/check_test.go new file mode 100644 index 00000000000..c51108477a2 --- /dev/null +++ b/op-devstack/dsl/check_test.go @@ -0,0 +1,177 @@ +package dsl + +import ( + "context" + "strings" + "sync" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +// programmableSyncProvider is a SyncStatusProvider whose returned BlockID per +// (chainID, level) can be updated at runtime from the test goroutine. +type programmableSyncProvider struct { + name string + + mu sync.Mutex + byID map[safety.Level]eth.BlockID +} + +func newProgrammableSyncProvider(name string) *programmableSyncProvider { + return &programmableSyncProvider{ + name: name, + byID: map[safety.Level]eth.BlockID{}, + } +} + +func (p *programmableSyncProvider) set(lvl safety.Level, num uint64, hash common.Hash) { + p.mu.Lock() + defer p.mu.Unlock() + p.byID[lvl] = eth.BlockID{Number: num, Hash: hash} +} + +func (p *programmableSyncProvider) ChainSyncStatus(chainID eth.ChainID, lvl safety.Level) eth.BlockID { + p.mu.Lock() + defer p.mu.Unlock() + return p.byID[lvl] +} + +func (p *programmableSyncProvider) String() string { return p.name } + +func TestMatchedWithProgressFn_SucceedsOnMatch(t *testing.T) { + t.Parallel() + base := newProgrammableSyncProvider("base") + ref := newProgrammableSyncProvider("ref") + chainID := eth.ChainIDFromUInt64(901) + h := common.HexToHash("0xabc") + base.set(safety.CrossSafe, 10, h) + base.set(safety.LocalUnsafe, 10, h) + ref.set(safety.CrossSafe, 10, h) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + check := MatchedWithProgressFn(base, ref, testlog.Logger(t, log.LevelDebug), ctx, + safety.CrossSafe, safety.LocalUnsafe, chainID, + 5*time.Second, 1*time.Second) + + require.NoError(t, check()) +} + +func TestMatchedWithProgressFn_FailsOnStall(t *testing.T) { + t.Parallel() + base := newProgrammableSyncProvider("base") + ref := newProgrammableSyncProvider("ref") + chainID := eth.ChainIDFromUInt64(901) + h1 := common.HexToHash("0x1") + h2 := common.HexToHash("0x2") + // Base CrossSafe never matches; LocalUnsafe never advances either. + base.set(safety.CrossSafe, 0, common.Hash{}) + base.set(safety.LocalUnsafe, 5, h1) + ref.set(safety.CrossSafe, 10, h2) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + check := MatchedWithProgressFn(base, ref, testlog.Logger(t, log.LevelDebug), ctx, + safety.CrossSafe, safety.LocalUnsafe, chainID, + 30*time.Second, 3*time.Second) + + start := time.Now() + err := check() + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "stalled"), "expected stall error, got: %v", err) + // Stall should fire well before the 30s max wait. + require.Less(t, time.Since(start), 8*time.Second, "stall detector took too long: %s", time.Since(start)) +} + +func TestMatchedWithProgressFn_KeepsWaitingWhileProgressing(t *testing.T) { + t.Parallel() + base := newProgrammableSyncProvider("base") + ref := newProgrammableSyncProvider("ref") + chainID := eth.ChainIDFromUInt64(901) + h := common.HexToHash("0xabc") + // Base CrossSafe stalls at 0; LocalUnsafe steadily advances; ref CrossSafe at 10. + base.set(safety.CrossSafe, 0, common.Hash{}) + base.set(safety.LocalUnsafe, 5, common.HexToHash("0x5")) + ref.set(safety.CrossSafe, 10, h) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // Drive LocalUnsafe progress in the background past where a fixed-attempt + // budget would have given up, and then converge CrossSafe. + var driverWG sync.WaitGroup + driverWG.Add(1) + go func() { + defer driverWG.Done() + for i := 6; i <= 12; i++ { + if err := clock.SystemClock.SleepCtx(ctx, 500*time.Millisecond); err != nil { + return + } + base.set(safety.LocalUnsafe, uint64(i), common.BigToHash(common.Big1)) + } + // Now let CrossSafe match. + base.set(safety.CrossSafe, 10, h) + }() + + // Stall timeout is short (1s) but the driver advances LocalUnsafe every + // 500ms, so the stall detector should never trip. + check := MatchedWithProgressFn(base, ref, testlog.Logger(t, log.LevelDebug), ctx, + safety.CrossSafe, safety.LocalUnsafe, chainID, + 30*time.Second, 1*time.Second) + require.NoError(t, check()) + driverWG.Wait() +} + +func TestMatchedWithProgressFn_FailsOnMaxWait(t *testing.T) { + t.Parallel() + base := newProgrammableSyncProvider("base") + ref := newProgrammableSyncProvider("ref") + chainID := eth.ChainIDFromUInt64(901) + h := common.HexToHash("0xabc") + // Base CrossSafe never matches; LocalUnsafe keeps advancing so the stall + // detector never trips — the deadline should be the failure path. + base.set(safety.CrossSafe, 0, common.Hash{}) + base.set(safety.LocalUnsafe, 0, common.Hash{}) + ref.set(safety.CrossSafe, 10, h) + + driverCtx, driverCancel := context.WithCancel(context.Background()) + var driverWG sync.WaitGroup + driverWG.Add(1) + go func() { + defer driverWG.Done() + var i uint64 + for { + select { + case <-driverCtx.Done(): + return + case <-time.After(200 * time.Millisecond): + i++ + base.set(safety.LocalUnsafe, i, common.BigToHash(common.Big1)) + } + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + check := MatchedWithProgressFn(base, ref, testlog.Logger(t, log.LevelDebug), ctx, + safety.CrossSafe, safety.LocalUnsafe, chainID, + 5*time.Second, 30*time.Second) + + start := time.Now() + err := check() + driverCancel() + driverWG.Wait() + + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "timeout"), "expected timeout error, got: %v", err) + require.GreaterOrEqual(t, time.Since(start), 5*time.Second) +} diff --git a/op-devstack/dsl/conductor.go b/op-devstack/dsl/conductor.go new file mode 100644 index 00000000000..fb94fc17cfb --- /dev/null +++ b/op-devstack/dsl/conductor.go @@ -0,0 +1,107 @@ +package dsl + +import ( + "context" + "time" + + "github.com/ethereum-optimism/optimism/op-conductor/consensus" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/retry" +) + +type ConductorSet []*Conductor + +func NewConductorSet(inner []stack.Conductor) ConductorSet { + conductors := make([]*Conductor, len(inner)) + for i, c := range inner { + conductors[i] = NewConductor(c) + } + return conductors +} + +type Conductor struct { + commonImpl + inner stack.Conductor +} + +func NewConductor(inner stack.Conductor) *Conductor { + return &Conductor{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +func (c *Conductor) String() string { + return c.inner.Name() +} + +func (c *Conductor) Escape() stack.Conductor { + return c.inner +} + +func (c *Conductor) FetchClusterMembership() *consensus.ClusterMembership { + c.log.Debug("Fetching cluster membership") + ctx, cancel := context.WithTimeout(c.ctx, DefaultTimeout) + defer cancel() + clusterMembership, err := retry.Do(ctx, 2, retry.Fixed(500*time.Millisecond), func() (*consensus.ClusterMembership, error) { + clusterMembership, err := c.inner.RpcAPI().ClusterMembership(c.ctx) + return clusterMembership, err + }) + c.require.NoError(err, "Failed to fetch cluster membership") + c.log.Info("Fetched cluster membership", + "clusterMembership", clusterMembership) + return clusterMembership +} + +func (c *Conductor) FetchLeader() *consensus.ServerInfo { + c.log.Debug("Fetching leader information") + ctx, cancel := context.WithTimeout(c.ctx, DefaultTimeout) + defer cancel() + leaderInfo, err := retry.Do[*consensus.ServerInfo](ctx, 2, retry.Fixed(500*time.Millisecond), func() (*consensus.ServerInfo, error) { + leaderInfo, err := c.inner.RpcAPI().LeaderWithID(c.ctx) + return leaderInfo, err + }) + c.require.NoError(err, "Failed to fetch leader information") + c.log.Info("Fetched leader information", + "leaderInfo", leaderInfo) + return leaderInfo +} + +func (c *Conductor) FetchSequencerHealthy() bool { + c.log.Debug("Fetching sequencer healthy status") + ctx, cancel := context.WithTimeout(c.ctx, DefaultTimeout) + defer cancel() + healthy, err := c.inner.RpcAPI().SequencerHealthy(ctx) + c.require.NoError(err, "Failed to fetch sequencer healthy status") + c.log.Info("Fetched sequencer healthy status", "healthy", healthy) + return healthy +} + +func (c *Conductor) FetchPaused() bool { + c.log.Debug("Fetching paused status") + ctx, cancel := context.WithTimeout(c.ctx, DefaultTimeout) + defer cancel() + paused, err := c.inner.RpcAPI().Paused(ctx) + c.require.NoError(err, "Failed to fetch paused status") + c.log.Info("Fetched paused status", "paused", paused) + return paused +} + +func (c *Conductor) IsLeader() bool { + c.log.Debug("Checking if conductor is leader") + ctx, cancel := context.WithTimeout(c.ctx, DefaultTimeout) + defer cancel() + leader, err := c.inner.RpcAPI().Leader(ctx) + c.require.NoError(err, "Failed to check if conductor is leader") + c.log.Info("Checked if conductor is leader", "leader", leader) + return leader +} + +func (c *Conductor) TransferLeadershipTo(targetLeaderInfo consensus.ServerInfo) { + c.log.Debug("Transferring leadership to target leader", "targetLeaderID", targetLeaderInfo.ID, "targetLeaderAddr", targetLeaderInfo.Addr) + ctx, cancel := context.WithTimeout(c.ctx, DefaultTimeout) + defer cancel() + err := c.inner.RpcAPI().TransferLeaderToServer(ctx, targetLeaderInfo.ID, targetLeaderInfo.Addr) + c.require.NoError(err, "Failed to transfer leadership to target leader", "targetLeaderID", targetLeaderInfo.ID) + c.log.Info("Transferred leadership to target leader", "targetLeaderID", targetLeaderInfo.ID) +} diff --git a/op-devstack/dsl/ecotone_fees.go b/op-devstack/dsl/ecotone_fees.go new file mode 100644 index 00000000000..898ac06787f --- /dev/null +++ b/op-devstack/dsl/ecotone_fees.go @@ -0,0 +1,201 @@ +package dsl + +import ( + "math/big" + + "github.com/ethereum-optimism/optimism/op-core/predeploys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type EcotoneFees struct { + commonImpl + l2Network *L2Network +} + +type EcotoneFeesValidationResult struct { + TransactionReceipt *types.Receipt + L1Fee *big.Int + L2Fee *big.Int + BaseFee *big.Int + PriorityFee *big.Int + TotalFee *big.Int + VaultBalances VaultBalances + WalletBalanceDiff *big.Int + TransferAmount *big.Int +} + +type VaultBalances struct { + BaseFeeVault *big.Int + L1FeeVault *big.Int + SequencerVault *big.Int + OperatorVault *big.Int +} + +func NewEcotoneFees(t devtest.T, l2Network *L2Network) *EcotoneFees { + return &EcotoneFees{ + commonImpl: commonFromT(t), + l2Network: l2Network, + } +} + +func (ef *EcotoneFees) ValidateTransaction(from *EOA, to *EOA, amount *big.Int) EcotoneFeesValidationResult { + client := ef.l2Network.PrimaryEL().EthClient() + + startBalance := from.GetBalance() + vaultsBefore := ef.getVaultBalances(client) + + tx := from.Transfer(to.Address(), eth.WeiBig(amount)) + receipt, err := tx.Included.Eval(ef.ctx) + ef.require.NoError(err) + ef.require.Equal(types.ReceiptStatusSuccessful, receipt.Status) + + // Get block info for base fee information + blockInfo, err := client.InfoByHash(ef.ctx, receipt.BlockHash) + ef.require.NoError(err) + + endBalance := from.GetBalance() + vaultsAfter := ef.getVaultBalances(client) + vaultIncreases := ef.calculateVaultIncreases(vaultsBefore, vaultsAfter) + + // In Ecotone, L1 fee includes both base fee and blob base fee components + l1Fee := vaultIncreases.L1FeeVault // Use actual vault increase as the source of truth + + // Calculate receipt-based fees for validation + receiptBaseFee := new(big.Int).Mul(blockInfo.BaseFee(), big.NewInt(int64(receipt.GasUsed))) + receiptL2Fee := new(big.Int).Mul(receipt.EffectiveGasPrice, big.NewInt(int64(receipt.GasUsed))) + + // Calculate L2 fees from vault increases + baseFee := vaultIncreases.BaseFeeVault // Use actual vault increase as the source of truth + priorityFee := vaultIncreases.SequencerVault // Use actual vault increase as the source of truth + l2Fee := new(big.Int).Add(baseFee, priorityFee) + + // Total fee is the sum of all vault increases (excluding OperatorVault which should be zero in Ecotone) + totalFee := new(big.Int).Add(vaultIncreases.BaseFeeVault, vaultIncreases.L1FeeVault) + totalFee.Add(totalFee, vaultIncreases.SequencerVault) + + walletBalanceDiff := new(big.Int).Sub(startBalance.ToBig(), endBalance.ToBig()) + walletBalanceDiff.Sub(walletBalanceDiff, amount) + + // Validate total balance first to ensure all fees are accounted for + ef.validateTotalBalance(walletBalanceDiff, totalFee, vaultIncreases) + + // Then validate individual fee components + ef.validateFeeDistribution(l1Fee, baseFee, priorityFee, vaultIncreases) + ef.validateEcotoneFeatures(receipt, l1Fee) + ef.validateReceiptFees(receipt, l1Fee, baseFee, l2Fee, receiptBaseFee, receiptL2Fee) + + return EcotoneFeesValidationResult{ + TransactionReceipt: receipt, + L1Fee: l1Fee, + L2Fee: l2Fee, + BaseFee: baseFee, + PriorityFee: priorityFee, + TotalFee: totalFee, + VaultBalances: vaultIncreases, + WalletBalanceDiff: walletBalanceDiff, + TransferAmount: amount, + } +} + +func (ef *EcotoneFees) getVaultBalances(client apis.EthClient) VaultBalances { + baseFee := ef.getBalance(client, predeploys.BaseFeeVaultAddr) + l1Fee := ef.getBalance(client, predeploys.L1FeeVaultAddr) + sequencer := ef.getBalance(client, predeploys.SequencerFeeVaultAddr) + operator := ef.getBalance(client, predeploys.OperatorFeeVaultAddr) + + return VaultBalances{ + BaseFeeVault: baseFee, + L1FeeVault: l1Fee, + SequencerVault: sequencer, + OperatorVault: operator, + } +} + +func (ef *EcotoneFees) getBalance(client apis.EthClient, addr common.Address) *big.Int { + balance, err := client.BalanceAt(ef.ctx, addr, nil) + ef.require.NoError(err) + return balance +} + +func (ef *EcotoneFees) calculateVaultIncreases(before, after VaultBalances) VaultBalances { + return VaultBalances{ + BaseFeeVault: new(big.Int).Sub(after.BaseFeeVault, before.BaseFeeVault), + L1FeeVault: new(big.Int).Sub(after.L1FeeVault, before.L1FeeVault), + SequencerVault: new(big.Int).Sub(after.SequencerVault, before.SequencerVault), + OperatorVault: new(big.Int).Sub(after.OperatorVault, before.OperatorVault), + } +} + +func (ef *EcotoneFees) validateFeeDistribution(l1Fee, baseFee, priorityFee *big.Int, vaults VaultBalances) { + ef.require.True(l1Fee.Sign() >= 0, "L1 fee must be non-negative") + ef.require.True(baseFee.Sign() > 0, "Base fee must be positive") + ef.require.True(priorityFee.Sign() >= 0, "Priority fee must be non-negative") + + ef.require.Equal(l1Fee, vaults.L1FeeVault, "L1 fee must match L1FeeVault increase") + ef.require.Equal(baseFee, vaults.BaseFeeVault, "Base fee must match BaseFeeVault increase") + ef.require.Equal(priorityFee, vaults.SequencerVault, "Priority fee must match SequencerFeeVault increase") + + // In Ecotone, operator fees should not exist (introduced in Isthmus) + ef.require.Equal(vaults.OperatorVault.Cmp(big.NewInt(0)), 0, + "Operator vault increase must be zero in Ecotone (operator fees introduced in Isthmus)") +} + +func (ef *EcotoneFees) validateTotalBalance(walletDiff *big.Int, totalFee *big.Int, vaults VaultBalances) { + // In Ecotone, only BaseFeeVault, L1FeeVault, and SequencerVault should have increases + totalVaultIncrease := new(big.Int).Add(vaults.BaseFeeVault, vaults.L1FeeVault) + totalVaultIncrease.Add(totalVaultIncrease, vaults.SequencerVault) + + ef.require.Equal(walletDiff, totalFee, "Wallet balance difference must equal total fees") + ef.require.Equal(totalVaultIncrease, totalFee, "Total vault increases must equal total fees") +} + +func (ef *EcotoneFees) validateEcotoneFeatures(receipt *types.Receipt, l1Fee *big.Int) { + ef.require.NotNil(receipt.L1Fee, "L1 fee should be present in Ecotone") + ef.require.True(l1Fee.Cmp(big.NewInt(0)) > 0, "L1 fee should be greater than 0 in Ecotone") + ef.require.Greater(receipt.GasUsed, uint64(20000), "Gas used should be reasonable for transfer") + ef.require.Less(receipt.GasUsed, uint64(50000), "Gas used should not be excessive") + ef.require.Greater(bigs.Uint64Strict(receipt.EffectiveGasPrice), uint64(0), "Effective gas price should be > 0") +} + +func (ef *EcotoneFees) validateReceiptFees(receipt *types.Receipt, l1Fee, vaultBaseFee, vaultL2Fee, receiptBaseFee, receiptL2Fee *big.Int) { + // Check that receipt's L1Fee matches the vault increase + if receipt.L1Fee != nil { + ef.require.Equal(receipt.L1Fee, l1Fee, "Receipt L1Fee must match L1FeeVault increase") + } + + // Sanity check: Receipt-calculated fees should match vault-based fees + ef.require.Equal(receiptBaseFee, vaultBaseFee, + "Receipt-calculated base fee (block.BaseFee * gasUsed) must match BaseFeeVault increase") + ef.require.Equal(receiptL2Fee, vaultL2Fee, + "Receipt-calculated L2 fee (effectiveGasPrice * gasUsed) must match L2 vault increases (BaseFee + SequencerFee)") + + // Validate receipt-based calculations are positive + ef.require.True(receiptBaseFee.Sign() > 0, "Receipt-based base fee must be positive") + ef.require.True(receiptL2Fee.Sign() > 0, "Receipt-based L2 fee must be positive") + + // The effective gas price should be consistent with the calculated L2 fee + ef.require.Equal(receiptL2Fee.Cmp(receiptBaseFee) >= 0, true, + "Receipt L2 fee (effectiveGasPrice * gasUsed) should be >= base fee") +} + +func (ef *EcotoneFees) LogResults(result EcotoneFeesValidationResult) { + ef.log.Info("Comprehensive Ecotone fees validation completed", + "gasUsed", result.TransactionReceipt.GasUsed, + "effectiveGasPrice", result.TransactionReceipt.EffectiveGasPrice, + "l1Fee", result.L1Fee, + "l2Fee", result.L2Fee, + "baseFee", result.BaseFee, + "priorityFee", result.PriorityFee, + "totalFee", result.TotalFee, + "baseFeeVault", result.VaultBalances.BaseFeeVault, + "l1FeeVault", result.VaultBalances.L1FeeVault, + "sequencerVault", result.VaultBalances.SequencerVault, + "operatorVault", result.VaultBalances.OperatorVault, + "walletBalanceDiff", result.WalletBalanceDiff, + "transferAmount", result.TransferAmount) +} diff --git a/op-devstack/dsl/faucet.go b/op-devstack/dsl/faucet.go new file mode 100644 index 00000000000..6ace03b4668 --- /dev/null +++ b/op-devstack/dsl/faucet.go @@ -0,0 +1,50 @@ +package dsl + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" +) + +// Faucet wraps a stack.Faucet interface for DSL operations. +// A Faucet is chain-specific. +// Note: Faucet wraps a stack component, to share faucet operations in kurtosis by hosting it as service, +// and prevent race-conditions with the account that sends out the faucet funds. +type Faucet struct { + commonImpl + inner stack.Faucet +} + +// NewFaucet creates a new Faucet DSL wrapper +func NewFaucet(inner stack.Faucet) *Faucet { + return &Faucet{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +func (f *Faucet) String() string { + return f.inner.Name() +} + +// Escape returns the underlying stack.Faucet +func (f *Faucet) Escape() stack.Faucet { + return f.inner +} + +// Fund funds the given address with the given amount of ETH +func (f *Faucet) Fund(addr common.Address, amount eth.ETH) { + if amount.IsZero() { + return + } + err := retry.Do0(f.ctx, 3, retry.Exponential(), func() error { + err := f.inner.API().RequestETH(f.ctx, addr, amount) + if err != nil { + f.log.Warn("Failed to fund address", "addr", addr, "amount", amount, "err", err) + } + return err + }) + f.require.NoError(err, "must fund account %s with %s", addr, amount) +} diff --git a/op-devstack/dsl/fjord_fees.go b/op-devstack/dsl/fjord_fees.go new file mode 100644 index 00000000000..5e83055568c --- /dev/null +++ b/op-devstack/dsl/fjord_fees.go @@ -0,0 +1,369 @@ +package dsl + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum-optimism/optimism/op-core/predeploys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" + "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" + "github.com/ethereum-optimism/optimism/op-service/txplan" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type FjordFees struct { + commonImpl + l2Network *L2Network +} + +type FjordFeesValidationResult struct { + TransactionReceipt *types.Receipt + L1Fee *big.Int + L2Fee *big.Int + BaseFee *big.Int + PriorityFee *big.Int + TotalFee *big.Int + VaultBalances VaultBalances + WalletBalanceDiff *big.Int + TransferAmount *big.Int + FastLzSize uint64 + EstimatedBrotliSize *big.Int + OperatorFee *big.Int + CoinbaseDiff *big.Int +} + +func NewFjordFees(t devtest.T, l2Network *L2Network) *FjordFees { + return &FjordFees{ + commonImpl: commonFromT(t), + l2Network: l2Network, + } +} + +// ValidateTransaction validates the transaction and returns the validation result +func (ff *FjordFees) ValidateTransaction(from *EOA, to *EOA, amount *big.Int) FjordFeesValidationResult { + client := ff.l2Network.PrimaryEL().EthClient() + + startBalance := from.GetBalance() + vaultsBefore := ff.getVaultBalances(client) + coinbaseStartBalance := ff.getCoinbaseBalance(client) + + tx := from.Transfer(to.Address(), eth.WeiBig(amount)) + receipt, err := tx.Included.Eval(ff.ctx) + ff.require.NoError(err) + ff.require.Equal(types.ReceiptStatusSuccessful, receipt.Status) + + endBalance := from.GetBalance() + vaultsAfter := ff.getVaultBalances(client) + vaultIncreases := ff.calculateVaultIncreases(vaultsBefore, vaultsAfter) + coinbaseEndBalance := ff.getCoinbaseBalance(client) + coinbaseDiff := new(big.Int).Sub(coinbaseEndBalance, coinbaseStartBalance) + + l1Fee := big.NewInt(0) + if receipt.L1Fee != nil { + l1Fee = receipt.L1Fee + } + + block, err := client.InfoByHash(ff.ctx, receipt.BlockHash) + ff.require.NoError(err) + + baseFee := new(big.Int).Mul(block.BaseFee(), big.NewInt(int64(receipt.GasUsed))) + totalGasFee := new(big.Int).Mul(receipt.EffectiveGasPrice, big.NewInt(int64(receipt.GasUsed))) + priorityFee := new(big.Int).Sub(totalGasFee, baseFee) + + l2Fee := new(big.Int).Set(priorityFee) + + operatorFee := vaultIncreases.OperatorVault + + ff.validateVaultIncreaseFees(l2Fee, baseFee, priorityFee, l1Fee, operatorFee, coinbaseDiff, vaultsAfter, vaultsBefore) + + totalFee := new(big.Int).Add(l1Fee, l2Fee) + totalFee.Add(totalFee, baseFee) + totalFee.Add(totalFee, operatorFee) + + walletBalanceDiff := new(big.Int).Sub(startBalance.ToBig(), endBalance.ToBig()) + walletBalanceDiff.Sub(walletBalanceDiff, amount) + + fastLzSize, estimatedBrotliSize := ff.validateFjordFeatures(receipt, l1Fee) + ff.validateFeeDistribution(l1Fee, baseFee, priorityFee, operatorFee, vaultIncreases) + ff.validateTotalBalance(walletBalanceDiff, totalFee, vaultIncreases) + + return FjordFeesValidationResult{ + TransactionReceipt: receipt, + L1Fee: l1Fee, + L2Fee: l2Fee, + BaseFee: baseFee, + PriorityFee: priorityFee, + TotalFee: totalFee, + VaultBalances: vaultIncreases, + WalletBalanceDiff: walletBalanceDiff, + TransferAmount: amount, + FastLzSize: fastLzSize, + EstimatedBrotliSize: estimatedBrotliSize, + OperatorFee: operatorFee, + CoinbaseDiff: coinbaseDiff, + } +} + +// getVaultBalances gets the balances of the vaults +func (ff *FjordFees) getVaultBalances(client apis.EthClient) VaultBalances { + baseFee := ff.getBalance(client, predeploys.BaseFeeVaultAddr) + l1Fee := ff.getBalance(client, predeploys.L1FeeVaultAddr) + sequencer := ff.getBalance(client, predeploys.SequencerFeeVaultAddr) + operator := ff.getBalance(client, predeploys.OperatorFeeVaultAddr) + + return VaultBalances{ + BaseFeeVault: baseFee, + L1FeeVault: l1Fee, + SequencerVault: sequencer, + OperatorVault: operator, + } +} + +// getBalance gets the balance of an address +func (ff *FjordFees) getBalance(client apis.EthClient, addr common.Address) *big.Int { + balance, err := client.BalanceAt(ff.ctx, addr, nil) + ff.require.NoError(err) + return balance +} + +// calculateVaultIncreases calculates the increases in the vaults +func (ff *FjordFees) calculateVaultIncreases(before, after VaultBalances) VaultBalances { + return VaultBalances{ + BaseFeeVault: new(big.Int).Sub(after.BaseFeeVault, before.BaseFeeVault), + L1FeeVault: new(big.Int).Sub(after.L1FeeVault, before.L1FeeVault), + SequencerVault: new(big.Int).Sub(after.SequencerVault, before.SequencerVault), + OperatorVault: new(big.Int).Sub(after.OperatorVault, before.OperatorVault), + } +} + +// validateFjordFeatures validates that the features of the Fjord transaction are correct +func (ff *FjordFees) validateFjordFeatures(receipt *types.Receipt, l1Fee *big.Int) (uint64, *big.Int) { + ff.require.NotNil(receipt.L1Fee, "L1 fee should be present in Fjord") + ff.require.True(l1Fee.Cmp(big.NewInt(0)) > 0, "L1 fee should be greater than 0 in Fjord") + + client := ff.l2Network.PrimaryEL().EthClient() + + _, txs, err := client.InfoAndTxsByHash(ff.ctx, receipt.BlockHash) + ff.require.NoError(err) + + var signedTx *types.Transaction + for _, tx := range txs { + if tx.Hash() == receipt.TxHash { + signedTx = tx + break + } + } + ff.require.NotNil(signedTx, "should find the signed transaction") + + unsignedTx := types.NewTx(&types.DynamicFeeTx{ + Nonce: signedTx.Nonce(), + To: signedTx.To(), + Value: signedTx.Value(), + Gas: signedTx.Gas(), + GasFeeCap: signedTx.GasFeeCap(), + GasTipCap: signedTx.GasTipCap(), + Data: signedTx.Data(), + }) + + txUnsigned, err := unsignedTx.MarshalBinary() + ff.require.NoError(err) + txSigned, err := signedTx.MarshalBinary() + ff.require.NoError(err) + + fastLzSizeUnsigned := uint64(types.FlzCompressLen(txUnsigned) + 68) // overhead used by the original test + fastLzSizeSigned := uint64(types.FlzCompressLen(txSigned)) + + // Validate that FastLZ compression produces reasonable results + ff.require.Greater(fastLzSizeUnsigned, uint64(0), "FastLZ size should be positive") + ff.require.Greater(fastLzSizeSigned, uint64(0), "FastLZ size should be positive") + + txLenGPO := len(txUnsigned) + 68 + flzUpperBound := uint64(txLenGPO + txLenGPO/255 + 16) + ff.require.LessOrEqual(fastLzSizeUnsigned, flzUpperBound, "Compressed size should not exceed upper bound") + + signedUpperBound := uint64(len(txSigned) + len(txSigned)/255 + 16) + ff.require.LessOrEqual(fastLzSizeSigned, signedUpperBound, "Compressed size should not exceed upper bound") + + receiptL1Fee := receipt.L1Fee + if receiptL1Fee == nil { + ff.t.Logf("L1 fee is nil in receipt, skipping L1 fee validation") + return fastLzSizeSigned, nil + } + + expectedFee, err := CalculateFjordL1Cost(ff.ctx, client, signedTx.RollupCostData(), receipt.BlockHash) + ff.require.NoError(err, "should calculate L1 fee") + + ff.require.Equalf(expectedFee, receiptL1Fee, "Calculated L1 fee should match receipt L1 fee (expected=%s actual=%s)", expectedFee.String(), receiptL1Fee.String()) + + ff.require.Equalf(expectedFee, receipt.L1Fee, "L1 fee in receipt must be correct (expected=%s actual=%s)", expectedFee.String(), receipt.L1Fee.String()) + + return fastLzSizeSigned, expectedFee +} + +// validateFeeDistribution validates that the fees are distributed correctly to the vaults +func (ff *FjordFees) validateFeeDistribution(l1Fee, baseFee, priorityFee, operatorFee *big.Int, vaults VaultBalances) { + ff.require.True(l1Fee.Sign() >= 0, "L1 fee must be non-negative") + ff.require.True(baseFee.Sign() > 0, "Base fee must be positive") + ff.require.True(priorityFee.Sign() >= 0, "Priority fee must be non-negative") + ff.require.True(operatorFee.Sign() >= 0, "Operator fee must be non-negative") + + ff.require.Equal(l1Fee, vaults.L1FeeVault, "L1 fee must match L1FeeVault increase") + ff.require.Equal(baseFee, vaults.BaseFeeVault, "Base fee must match BaseFeeVault increase") + ff.require.Equal(priorityFee, vaults.SequencerVault, "Priority fee must match SequencerFeeVault increase") + ff.require.Equal(operatorFee, vaults.OperatorVault, "Operator fee must match OperatorFeeVault increase") +} + +// validateTotalBalance validates that the total balance of the wallet and the vaults is correct +func (ff *FjordFees) validateTotalBalance(walletDiff *big.Int, totalFee *big.Int, vaults VaultBalances) { + totalVaultIncrease := new(big.Int).Add(vaults.BaseFeeVault, vaults.L1FeeVault) + totalVaultIncrease.Add(totalVaultIncrease, vaults.SequencerVault) + totalVaultIncrease.Add(totalVaultIncrease, vaults.OperatorVault) + + ff.require.Equal(walletDiff, totalFee, "Wallet balance difference must equal total fees") + ff.require.Equal(totalVaultIncrease, totalFee, "Total vault increases must equal total fees") +} + +// getCoinbaseBalance gets the balance of the coinbase address (block miner/sequencer) +func (ff *FjordFees) getCoinbaseBalance(client apis.EthClient) *big.Int { + block, err := client.InfoByLabel(ff.ctx, "latest") + ff.require.NoError(err, "should get latest block") + + coinbase := block.Coinbase() + balance, err := client.BalanceAt(ff.ctx, coinbase, nil) + ff.require.NoError(err, "should get coinbase balance") + return balance +} + +// validateVaultIncreaseFees validates that the fees are distributed correctly to the vaults +func (ff *FjordFees) validateVaultIncreaseFees( + l2Fee, baseFee, priorityFee, l1Fee, operatorFee, coinbaseDiff *big.Int, + vaultsAfter, vaultsBefore VaultBalances) { + + ff.require.Equal(l2Fee, coinbaseDiff, "L2 fee must equal coinbase difference (coinbase is always sequencer fee vault)") + + vaultsIncrease := ff.calculateVaultIncreases(vaultsBefore, vaultsAfter) + ff.require.Equal(baseFee, vaultsIncrease.BaseFeeVault, "base fee must match BaseFeeVault increase") + + ff.require.Equal(priorityFee, vaultsIncrease.SequencerVault, "priority fee must match SequencerFeeVault increase") + + ff.require.Equal(l1Fee, vaultsIncrease.L1FeeVault, "L1 fee must match L1FeeVault increase") + + ff.require.Equal(operatorFee, vaultsIncrease.OperatorVault, "operator fee must match OperatorFeeVault increase") + + ff.t.Logf("Comprehensive fee validation passed:") + ff.t.Logf(" L2 Fee: %s (coinbase diff: %s)", l2Fee, coinbaseDiff) + ff.t.Logf(" Base Fee: %s (vault increase: %s)", baseFee, vaultsIncrease.BaseFeeVault) + ff.t.Logf(" Priority Fee: %s (vault increase: %s)", priorityFee, vaultsIncrease.SequencerVault) + ff.t.Logf(" L1 Fee: %s (vault increase: %s)", l1Fee, vaultsIncrease.L1FeeVault) + ff.t.Logf(" Operator Fee: %s (vault increase: %s)", operatorFee, vaultsIncrease.OperatorVault) +} + +// FindSignedTransactionFromReceipt finds the signed transaction from a receipt and block +func FindSignedTransactionFromReceipt(ctx context.Context, client apis.EthClient, receipt *types.Receipt) (*types.Transaction, error) { + _, txs, err := client.InfoAndTxsByHash(ctx, receipt.BlockHash) + if err != nil { + return nil, err + } + + for _, tx := range txs { + if tx.Hash() == receipt.TxHash { + return tx, nil + } + } + return nil, fmt.Errorf("signed transaction not found for hash %s", receipt.TxHash) +} + +// CreateUnsignedTransactionFromSigned creates an unsigned transaction from a signed one +func CreateUnsignedTransactionFromSigned(signedTx *types.Transaction) (*types.Transaction, error) { + return types.NewTx(&types.DynamicFeeTx{ + Nonce: signedTx.Nonce(), + To: signedTx.To(), + Value: signedTx.Value(), + Gas: signedTx.Gas(), + GasFeeCap: signedTx.GasFeeCap(), + GasTipCap: signedTx.GasTipCap(), + Data: signedTx.Data(), + }), nil +} + +// ReadGasPriceOracleL1FeeAt reads the L1 fee from GasPriceOracle for an unsigned transaction +// evaluated against a specific L2 block hash. +func ReadGasPriceOracleL1FeeAt(ctx context.Context, client apis.EthClient, gpo *bindings.GasPriceOracle, txUnsigned []byte, blockHash common.Hash) (*big.Int, error) { + overrideBlockOpt := func(ptx *txplan.PlannedTx) { + ptx.AgainstBlock.Fn(func(ctx context.Context) (eth.BlockInfo, error) { + return client.InfoByHash(ctx, blockHash) + }) + } + result, err := contractio.Read(gpo.GetL1Fee(txUnsigned), ctx, overrideBlockOpt) + if err != nil { + return nil, err + } + return result.ToBig(), nil +} + +// ReadGasPriceOracleL1FeeUpperBoundAt reads the L1 fee upper bound for a tx length pinned to a block hash. +func ReadGasPriceOracleL1FeeUpperBoundAt(ctx context.Context, client apis.EthClient, gpo *bindings.GasPriceOracle, txLen int, blockHash common.Hash) (*big.Int, error) { + overrideBlockOpt := func(ptx *txplan.PlannedTx) { + ptx.AgainstBlock.Fn(func(ctx context.Context) (eth.BlockInfo, error) { + return client.InfoByHash(ctx, blockHash) + }) + } + result, err := contractio.Read(gpo.GetL1FeeUpperBound(big.NewInt(int64(txLen))), ctx, overrideBlockOpt) + if err != nil { + return nil, err + } + return result.ToBig(), nil +} + +// ValidateL1FeeMatches checks that the calculated L1 fee matches the actual receipt L1 fee +func ValidateL1FeeMatches(t devtest.T, calculatedFee, receiptFee *big.Int) { + require := t.Require() + require.NotNil(receiptFee, "L1 fee should be present in receipt") + require.Equalf(bigs.Uint64Strict(calculatedFee), bigs.Uint64Strict(receiptFee), "L1 fee mismatch (expected=%v actual=%v)", calculatedFee, receiptFee) +} + +// CalculateFjordL1Cost calculates L1 cost using Fjord formula with block-specific L1 state +func CalculateFjordL1Cost(ctx context.Context, client apis.EthClient, rollupCostData types.RollupCostData, blockHash common.Hash) (*big.Int, error) { + l1Block := bindings.NewL1Block( + bindings.WithClient(client), + bindings.WithTo(predeploys.L1BlockAddr), + ) + + overrideBlockOpt := func(ptx *txplan.PlannedTx) { + ptx.AgainstBlock.Fn(func(ctx context.Context) (eth.BlockInfo, error) { + return client.InfoByHash(ctx, blockHash) + }) + } + + baseFeeScalar, err := contractio.Read(l1Block.BasefeeScalar(), ctx, overrideBlockOpt) + if err != nil { + return nil, err + } + l1BaseFee, err := contractio.Read(l1Block.Basefee(), ctx, overrideBlockOpt) + if err != nil { + return nil, err + } + blobBaseFeeScalar, err := contractio.Read(l1Block.BlobBaseFeeScalar(), ctx, overrideBlockOpt) + if err != nil { + return nil, err + } + blobBaseFee, err := contractio.Read(l1Block.BlobBaseFee(), ctx, overrideBlockOpt) + if err != nil { + return nil, err + } + + costFunc := types.NewL1CostFuncFjord( + l1BaseFee, + blobBaseFee, + new(big.Int).SetUint64(uint64(baseFeeScalar)), + new(big.Int).SetUint64(uint64(blobBaseFeeScalar))) + + fee, _ := costFunc(rollupCostData) + return fee, nil +} diff --git a/op-devstack/dsl/funder.go b/op-devstack/dsl/funder.go new file mode 100644 index 00000000000..da40b463b88 --- /dev/null +++ b/op-devstack/dsl/funder.go @@ -0,0 +1,76 @@ +package dsl + +import ( + "sync" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type Funder struct { + commonImpl + wallet *HDWallet + faucet *Faucet + el ELNode +} + +func NewFunder(w *HDWallet, f *Faucet, el ELNode) *Funder { + f.t.Require().Equal(f.inner.ChainID(), el.ChainID(), "faucet and EL must be on same chain") + return &Funder{ + commonImpl: commonFromT(w.t), + wallet: w, + faucet: f, + el: el, + } +} + +func (f *Funder) NewFundedEOA(amount eth.ETH) *EOA { + eoa := f.wallet.NewEOA(f.el) + f.FundAtLeast(eoa, amount) + return eoa +} + +func (f *Funder) NewFundedEOAs(count int, amount eth.ETH) []*EOA { + eoas := func() []*EOA { + eoas := make([]*EOA, count) + var wg sync.WaitGroup + defer wg.Wait() + for idx := range len(eoas) { + wg.Add(1) + go func() { + defer wg.Done() + eoas[idx] = f.NewFundedEOA(amount) + }() + } + return eoas + }() + for _, eoa := range eoas { + // For a large count, the faucet may fail. + // This sanity check prevents surprising errors down the line. + f.require.NotNil(eoa) + } + return eoas +} + +func (f *Funder) Fund(wallet *EOA, amount eth.ETH) eth.ETH { + currentBalance := wallet.balance() + f.faucet.Fund(wallet.Address(), amount) + finalBalance := currentBalance.Add(amount) + wallet.WaitForBalance(finalBalance) + return finalBalance +} + +func (f *Funder) FundNoWait(wallet *EOA, amount eth.ETH) { + f.faucet.Fund(wallet.Address(), amount) +} + +func (f *Funder) FundAtLeast(wallet *EOA, amount eth.ETH) eth.ETH { + currentBalance := wallet.balance() + if currentBalance.Lt(amount) { + missing := amount.Sub(currentBalance) + f.faucet.Fund(wallet.Address(), missing) + finalBalance := currentBalance.Add(missing) + wallet.WaitForBalance(finalBalance) + return finalBalance + } + return currentBalance +} diff --git a/op-devstack/dsl/l1_cl.go b/op-devstack/dsl/l1_cl.go new file mode 100644 index 00000000000..13ccbbb4fc8 --- /dev/null +++ b/op-devstack/dsl/l1_cl.go @@ -0,0 +1,38 @@ +package dsl + +import "github.com/ethereum-optimism/optimism/op-devstack/stack" + +// L1CLNode wraps a stack.L1CLNode interface for DSL operations +type L1CLNode struct { + commonImpl + inner stack.L1CLNode +} + +// NewL1CLNode creates a new L1CLNode DSL wrapper +func NewL1CLNode(inner stack.L1CLNode) *L1CLNode { + return &L1CLNode{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +func (cl *L1CLNode) String() string { + return cl.inner.Name() +} + +// Escape returns the underlying stack.L1CLNode +func (cl *L1CLNode) Escape() stack.L1CLNode { + return cl.inner +} + +func (cl *L1CLNode) Start() { + lifecycle, ok := cl.inner.(stack.Lifecycle) + cl.require.Truef(ok, "L1CL node %s is not lifecycle-controllable", cl.inner.Name()) + lifecycle.Start() +} + +func (cl *L1CLNode) Stop() { + lifecycle, ok := cl.inner.(stack.Lifecycle) + cl.require.Truef(ok, "L1CL node %s is not lifecycle-controllable", cl.inner.Name()) + lifecycle.Stop() +} diff --git a/op-devstack/dsl/l1_el.go b/op-devstack/dsl/l1_el.go new file mode 100644 index 00000000000..9cff65d4517 --- /dev/null +++ b/op-devstack/dsl/l1_el.go @@ -0,0 +1,125 @@ +package dsl + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" +) + +// L1ELNode wraps a stack.L1ELNode interface for DSL operations +type L1ELNode struct { + *elNode + inner stack.L1ELNode +} + +// NewL1ELNode creates a new L1ELNode DSL wrapper +func NewL1ELNode(inner stack.L1ELNode) *L1ELNode { + return &L1ELNode{ + elNode: newELNode(commonFromT(inner.T()), inner), + inner: inner, + } +} + +func (el *L1ELNode) String() string { + return el.inner.Name() +} + +// Escape returns the underlying stack.L1ELNode +func (el *L1ELNode) Escape() stack.L1ELNode { + return el.inner +} + +func (el *L1ELNode) EthClient() apis.EthClient { + return el.inner.EthClient() +} + +// EstimateBlockTime estimates the L1 block based on the last 1000 blocks +// (or since genesis, if insufficient blocks). +func (el *L1ELNode) EstimateBlockTime() time.Duration { + var latest eth.BlockRef + for { + var err error + latest, err = el.inner.EthClient().BlockRefByLabel(el.t.Ctx(), eth.Unsafe) + el.require.NoError(err) + if latest.Number > 0 { + break + } + select { + case <-time.After(time.Millisecond * 500): + case <-el.ctx.Done(): + el.require.Fail("context was canceled before L1 block time could be estimated") + } + } + lowerNum := uint64(0) + if latest.Number > 1000 { + lowerNum = latest.Number - 1000 + } + lowerBlock, err := el.inner.EthClient().BlockRefByNumber(el.t.Ctx(), lowerNum) + el.require.NoError(err) + deltaTime := latest.Time - lowerBlock.Time + deltaNum := latest.Number - lowerBlock.Number + return time.Duration(deltaTime) * time.Second / time.Duration(deltaNum) +} + +func (el *L1ELNode) BlockRefByLabel(label eth.BlockLabel) eth.L1BlockRef { + ctx, cancel := context.WithTimeout(el.ctx, DefaultTimeout) + defer cancel() + block, err := el.inner.EthClient().BlockRefByLabel(ctx, label) + el.require.NoError(err, "block not found using block label") + return block +} + +func (el *L1ELNode) BlockRefByNumber(number uint64) eth.L1BlockRef { + ctx, cancel := context.WithTimeout(el.ctx, DefaultTimeout) + defer cancel() + block, err := el.inner.EthClient().BlockRefByNumber(ctx, number) + el.require.NoError(err, "block not found using block number %d", number) + return block +} + +// ReorgTriggeredFn returns a lambda that checks that a L1 reorg occurred on the expected block +// Composable with other lambdas to wait in parallel +func (el *L1ELNode) ReorgTriggeredFn(target eth.L1BlockRef, attempts int) CheckFunc { + return func() error { + el.log.Info("expecting chain to reorg on block ref", "name", el.inner.Name(), "chain", el.inner.ChainID(), "target", target) + return retry.Do0(el.ctx, attempts, &retry.FixedStrategy{Dur: 7 * time.Second}, + func() error { + reorged, err := el.inner.EthClient().BlockRefByNumber(el.ctx, target.Number) + if err != nil { + if strings.Contains(err.Error(), "not found") { // reorg is happening wait a bit longer + el.log.Info("chain still hasn't been reorged", "chain", el.inner.ChainID(), "error", err) + return err + } + return err + } + + if target.Hash == reorged.Hash { // want not equal + el.log.Info("chain still hasn't been reorged", "chain", el.inner.ChainID(), "ref", reorged) + return fmt.Errorf("expected head to reorg %s, but got %s", target, reorged) + } + + if target.ParentHash != reorged.ParentHash { + return fmt.Errorf("expected parent of target to be the same as the parent of the reorged head, but they are different") + } + + el.log.Info("reorg on divergence block", "chain", el.inner.ChainID(), "pre_blockref", target) + el.log.Info("reorg on divergence block", "chain", el.inner.ChainID(), "post_blockref", reorged) + + return nil + }) + } +} + +func (el *L1ELNode) ReorgTriggered(target eth.L1BlockRef, attempts int) { + el.require.NoError(el.ReorgTriggeredFn(target, attempts)()) +} + +func (el *L1ELNode) TransactionTimeout() time.Duration { + return el.inner.TransactionTimeout() +} diff --git a/op-devstack/dsl/l1_network.go b/op-devstack/dsl/l1_network.go new file mode 100644 index 00000000000..09085238985 --- /dev/null +++ b/op-devstack/dsl/l1_network.go @@ -0,0 +1,81 @@ +package dsl + +import ( + "fmt" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// L1Network wraps a stack.L1Network interface for DSL operations +type L1Network struct { + commonImpl + inner stack.L1Network + primaryEL *L1ELNode + primaryCL *L1CLNode +} + +// NewL1Network creates a new L1Network DSL wrapper +func NewL1Network(inner stack.L1Network, primaryEL *L1ELNode, primaryCL *L1CLNode) *L1Network { + return &L1Network{ + commonImpl: commonFromT(inner.T()), + inner: inner, + primaryEL: primaryEL, + primaryCL: primaryCL, + } +} + +func (n *L1Network) String() string { + return n.inner.Name() +} + +func (n *L1Network) ChainID() eth.ChainID { + return n.inner.ChainID() +} + +// Escape returns the underlying stack.L1Network +func (n *L1Network) Escape() stack.L1Network { + return n.inner +} + +func (n *L1Network) PrimaryEL() *L1ELNode { + n.require.NotNil(n.primaryEL, "l1 network %s is missing a primary EL node", n.String()) + return n.primaryEL +} + +func (n *L1Network) PrimaryCL() *L1CLNode { + n.require.NotNil(n.primaryCL, "l1 network %s is missing a primary CL node", n.String()) + return n.primaryCL +} + +func (n *L1Network) WaitForBlock() eth.BlockRef { + return n.PrimaryEL().WaitForBlock() +} + +// PrintChain is used for testing/debugging, it prints the blockchain hashes and parent hashes to logs, which is useful when developing reorg tests +func (n *L1Network) PrintChain() { + l1_el := n.PrimaryEL().Escape() + + unsafeHeadRef, err := l1_el.EthClient().InfoByLabel(n.ctx, "latest") + n.require.NoError(err, "Expected to get latest block from L1 execution client") + + var entries []string + for i := unsafeHeadRef.NumberU64(); i > 0; i-- { + ref, txs, err := l1_el.EthClient().InfoAndTxsByNumber(n.ctx, i) + n.require.NoError(err, "Expected to get block ref by number") + + entries = append(entries, fmt.Sprintf("Time: %d Block: %s Txs: %d Parent: %s", ref.Time(), eth.InfoToL1BlockRef(ref), len(txs), ref.ParentHash())) + } + + n.log.Info("Printing block hashes and parent hashes", "network", n.String(), "chain", n.ChainID()) + spew.Dump(entries) +} + +func (n *L1Network) WaitForFinalization() eth.BlockRef { + return n.PrimaryEL().WaitForFinalization() +} + +func (n *L1Network) WaitForOnline() { + n.PrimaryEL().WaitForOnline() +} diff --git a/op-devstack/dsl/l2_batcher.go b/op-devstack/dsl/l2_batcher.go new file mode 100644 index 00000000000..62b292b8ad6 --- /dev/null +++ b/op-devstack/dsl/l2_batcher.go @@ -0,0 +1,56 @@ +package dsl + +import ( + "fmt" + "strings" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/stretchr/testify/require" +) + +// L2Batcher wraps a stack.L2Batcher interface for DSL operations +type L2Batcher struct { + commonImpl + inner stack.L2Batcher +} + +// NewL2Batcher creates a new L2Batcher DSL wrapper +func NewL2Batcher(inner stack.L2Batcher) *L2Batcher { + return &L2Batcher{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +func (b *L2Batcher) String() string { + return b.inner.Name() +} + +// Escape returns the underlying stack.L2Batcher +func (b *L2Batcher) Escape() stack.L2Batcher { + return b.inner +} + +func (b *L2Batcher) ActivityAPI() apis.BatcherActivity { + return b.inner.ActivityAPI() +} + +func (b *L2Batcher) Stop() { + err := retry.Do0(b.ctx, 3, retry.Exponential(), func() error { + err := b.Escape().ActivityAPI().StopBatcher(b.ctx) + if err != nil && strings.Contains(err.Error(), "batcher is not running") { + return nil + } + return err + }) + require.NoError(b.t, err, fmt.Sprintf("Expected to be able to call StopBatcher API on chain %s, but got error", b.inner.ChainID())) +} + +func (b *L2Batcher) Start() { + err := retry.Do0(b.ctx, 3, retry.Exponential(), func() error { + return b.inner.ActivityAPI().StartBatcher(b.ctx) + }) + require.NoError(b.t, err, fmt.Sprintf("Expected to be able to call StartBatcher API on chain %s, but got error", b.inner.ChainID())) +} diff --git a/op-devstack/dsl/l2_challenger.go b/op-devstack/dsl/l2_challenger.go new file mode 100644 index 00000000000..cafa071cedd --- /dev/null +++ b/op-devstack/dsl/l2_challenger.go @@ -0,0 +1,26 @@ +package dsl + +import "github.com/ethereum-optimism/optimism/op-devstack/stack" + +// L2Challenger wraps a stack.L2Challenger interface for DSL operations +type L2Challenger struct { + commonImpl + inner stack.L2Challenger +} + +// NewL2Challenger creates a new L2Challenger DSL wrapper +func NewL2Challenger(inner stack.L2Challenger) *L2Challenger { + return &L2Challenger{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +func (c *L2Challenger) String() string { + return c.inner.Name() +} + +// Escape returns the underlying stack.L2Challenger +func (c *L2Challenger) Escape() stack.L2Challenger { + return c.inner +} diff --git a/op-devstack/dsl/l2_cl.go b/op-devstack/dsl/l2_cl.go new file mode 100644 index 00000000000..8730fc83f4b --- /dev/null +++ b/op-devstack/dsl/l2_cl.go @@ -0,0 +1,594 @@ +package dsl + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-node/node/safedb" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" + "github.com/ethereum/go-ethereum/common" +) + +// L2CLNode wraps a stack.L2CLNode interface for DSL operations +type L2CLNode struct { + commonImpl + inner stack.L2CLNode + managedPeers map[string]*L2CLNode +} + +// NewL2CLNode creates a new L2CLNode DSL wrapper +func NewL2CLNode(inner stack.L2CLNode) *L2CLNode { + return &L2CLNode{ + commonImpl: commonFromT(inner.T()), + inner: inner, + managedPeers: make(map[string]*L2CLNode), + } +} + +func (cl *L2CLNode) Name() string { + return cl.inner.Name() +} + +func (cl *L2CLNode) String() string { + return cl.inner.Name() +} + +// Escape returns the underlying stack.L2CLNode +func (cl *L2CLNode) Escape() stack.L2CLNode { + return cl.inner +} + +func (cl *L2CLNode) SafeL2BlockRef() eth.L2BlockRef { + return cl.HeadBlockRef(safety.CrossSafe) +} + +func (cl *L2CLNode) Start() { + lifecycle, ok := cl.inner.(stack.Lifecycle) + cl.require.Truef(ok, "L2CL node %s is not lifecycle-controllable", cl.inner.Name()) + lifecycle.Start() + cl.restoreManagedPeers() +} + +func (cl *L2CLNode) Stop() { + lifecycle, ok := cl.inner.(stack.Lifecycle) + cl.require.Truef(ok, "L2CL node %s is not lifecycle-controllable", cl.inner.Name()) + lifecycle.Stop() +} + +func (cl *L2CLNode) ManagePeer(peer *L2CLNode) { + cl.managedPeers[peer.Name()] = peer + peer.managedPeers[cl.Name()] = cl +} + +func (cl *L2CLNode) restoreManagedPeers() { + for _, peer := range cl.managedPeers { + cl.connectPeerRaw(peer) + peer.connectPeerRaw(cl) + } +} + +func (cl *L2CLNode) StartSequencer() { + // The op-node Sequencer.Start RPC requires the caller to pass the hash of op-node's + // current unsafe head. Reading the head and issuing the start call are two separate + // RPCs, so any unsafe payload that op-node processes in between (e.g. blocks gossiped + // by another sequencer over p2p) invalidates the hash we just read. Retry on that + // specific mismatch error with a freshly-read head. + const maxAttempts = 10 + var err error +loop: + for range maxAttempts { + unsafe := cl.HeadBlockRef(safety.LocalUnsafe) + cl.log.Info("Continue sequencing with consensus node (op-node)", "chain", cl.ChainID(), "unsafe", unsafe) + err = cl.inner.RollupAPI().StartSequencer(cl.ctx, unsafe.Hash) + if err == nil { + break + } + if !strings.Contains(err.Error(), "block hash does not match") { + break + } + cl.log.Info("Unsafe head advanced between read and StartSequencer; retrying", "chain", cl.ChainID(), "err", err) + select { + case <-cl.ctx.Done(): + err = cl.ctx.Err() + break loop + case <-time.After(250 * time.Millisecond): + } + } + cl.require.NoError(err, fmt.Sprintf("Expected to be able to start sequencer on chain %d", cl.ChainID())) + + // wait for the sequencer to become active + var active bool + err = wait.For(cl.ctx, 1*time.Second, func() (bool, error) { + active, err = cl.inner.RollupAPI().SequencerActive(cl.ctx) + return active, err + }) + cl.require.NoError(err, fmt.Sprintf("Expected to be able to call SequencerActive API on chain %d, and wait for an active state for sequencer, but got error", cl.ChainID())) + + cl.log.Info("Rollup node sequencer status", "chain", cl.ChainID(), "active", active) +} + +func (cl *L2CLNode) StopSequencer() common.Hash { + unsafeHead, err := cl.inner.RollupAPI().StopSequencer(cl.ctx) + cl.require.NoError(err, "Expected to be able to call StopSequencer API, but got error") + + // wait for the sequencer to become inactive + var active bool + err = wait.For(cl.ctx, 1*time.Second, func() (bool, error) { + active, err = cl.inner.RollupAPI().SequencerActive(cl.ctx) + return !active, err + }) + cl.require.NoError(err, fmt.Sprintf("Expected to be able to call SequencerActive API on chain %d, and wait for inactive state for sequencer, but got error", cl.ChainID())) + + cl.log.Info("Rollup node sequencer status", "chain", cl.ChainID(), "active", active, "unsafeHead", unsafeHead) + return unsafeHead +} + +func (cl *L2CLNode) SetSequencerRecoverMode(b bool) error { + return cl.inner.RollupAPI().SetRecoverMode(cl.ctx, b) +} + +func (cl *L2CLNode) SyncStatus() *eth.SyncStatus { + ctx, cancel := context.WithTimeout(cl.ctx, DefaultTimeout) + defer cancel() + syncStatus, err := cl.inner.RollupAPI().SyncStatus(ctx) + cl.require.NoError(err) + return syncStatus +} + +// headBlockRef is the error-returning variant of HeadBlockRef, for use inside +// retry/eventually loops. +func (cl *L2CLNode) headBlockRef(lvl safety.Level) (eth.L2BlockRef, error) { + syncStatus, err := cl.syncStatus() + if err != nil { + return eth.L2BlockRef{}, err + } + switch lvl { + case safety.Finalized: + return syncStatus.FinalizedL2, nil + case safety.CrossSafe: + return syncStatus.SafeL2, nil + case safety.LocalSafe: + return syncStatus.LocalSafeL2, nil + case safety.CrossUnsafe: + return syncStatus.CrossUnsafeL2, nil + case safety.LocalUnsafe: + return syncStatus.UnsafeL2, nil + default: + return eth.L2BlockRef{}, fmt.Errorf("invalid safety level: %v", lvl) + } +} + +// HeadBlockRef fetches L2CL sync status and returns block ref with given safety level +func (cl *L2CLNode) HeadBlockRef(lvl safety.Level) eth.L2BlockRef { + blockRef, err := cl.headBlockRef(lvl) + cl.require.NoError(err) + return blockRef +} + +func (cl *L2CLNode) ChainID() eth.ChainID { + return cl.inner.ChainID() +} + +func (cl *L2CLNode) AwaitMinL1Processed(minL1 uint64) { + ctx, cancel := context.WithTimeout(cl.ctx, DefaultTimeout) + defer cancel() + // Wait for CurrentL1 to be at least one block _past_ minL1 since CurrentL1 may not yet be fully processed. + err := wait.For(ctx, 1*time.Second, func() (bool, error) { + return cl.SyncStatus().CurrentL1.Number > minL1, nil + }) + cl.require.NoErrorf(err, "CurrentL1 did not reach %v", minL1+1) +} + +// AdvancedFn returns a lambda that checks the L2CL chain head with given safety level advanced more than delta block number +// Composable with other lambdas to wait in parallel +func (cl *L2CLNode) AdvancedFn(lvl safety.Level, delta uint64, attempts int) CheckFunc { + return func() error { + initial := cl.HeadBlockRef(lvl) + target := initial.Number + delta + cl.log.Info("Expecting chain to advance", "name", cl.inner.Name(), "chain", cl.ChainID(), "label", lvl, "delta", delta) + return cl.ReachedFn(lvl, target, attempts)() + } +} + +func (cl *L2CLNode) NotAdvancedFn(lvl safety.Level, attempts int) CheckFunc { + return func() error { + initial := cl.HeadBlockRef(lvl) + logger := cl.log.With("name", cl.inner.Name(), "chain", cl.ChainID(), "label", lvl, "target", initial.Number) + logger.Info("Expecting chain not to advance") + for range attempts { + time.Sleep(2 * time.Second) + head := cl.HeadBlockRef(lvl) + logger.Info("Chain sync status", "current", head.Number) + if head.Hash == initial.Hash { + continue + } + return fmt.Errorf("expected head not to advance: %s", lvl) + } + logger.Info("Chain not advanced") + return nil + } +} + +// awaitSafeHeadsStalled waits until every node's safe head has stopped advancing +// for at least 10 seconds. +func (cl *L2CLNode) WaitForStall(lvl safety.Level) { + var last eth.BlockID + var stableSince time.Time + cl.require.Eventuallyf(func() bool { + head, err := cl.headBlockRef(lvl) + if err != nil { + cl.log.Warn("SyncStatus RPC failed while waiting for stall; will retry", "err", err) + return false + } + cur := head.ID() + if cur == last { + if stableSince.IsZero() { + stableSince = time.Now() + } + return time.Since(stableSince) >= 10*time.Second + } + last = cur + stableSince = time.Time{} + return false + }, 2*time.Minute, 2*time.Second, "expected %v head to stall", lvl) +} + +// ReachedFn returns a lambda that checks the L2CL chain head with given safety level reaches the target block number +// Composable with other lambdas to wait in parallel +func (cl *L2CLNode) ReachedFn(lvl safety.Level, target uint64, attempts int) CheckFunc { + return func() error { + logger := cl.log.With("name", cl.inner.Name(), "chain", cl.ChainID(), "label", lvl, "target", target) + logger.Info("Expecting chain to reach") + return retry.Do0(cl.ctx, attempts, &retry.FixedStrategy{Dur: 2 * time.Second}, + func() error { + head := cl.HeadBlockRef(lvl) + if head.Number >= target { + logger.Info("Chain advanced", "target", target) + return nil + } + logger.Info("Chain sync status", "current", head.Number) + return fmt.Errorf("expected head to advance: %s", lvl) + }) + } +} + +// ReachedRefFn is same as Reached, but has an additional check to ensure that the block referenced is not reorged +// Composable with other lambdas to wait in parallel +func (cl *L2CLNode) ReachedRefFn(lvl safety.Level, target eth.BlockID, attempts int) CheckFunc { + return func() error { + err := cl.ReachedFn(lvl, target.Number, attempts)() + if err != nil { + return err + } + + ethclient := cl.inner.ELClient() + result, err := ethclient.BlockRefByNumber(cl.ctx, target.Number) + if err != nil { + return err + } + if result.Hash != target.Hash { + return fmt.Errorf("expected block ref to be the same as target %s, got but %s", target.Hash, result.Hash) + } + return nil + } +} + +// RewindedFn returns a lambda that checks the L2CL chain head with given safety level rewinded more than the delta block number +// Composable with other lambdas to wait in parallel +func (cl *L2CLNode) RewindedFn(lvl safety.Level, delta uint64, attempts int) CheckFunc { + return func() error { + initial := cl.HeadBlockRef(lvl) + cl.require.GreaterOrEqual(initial.Number, delta, "cannot rewind before genesis") + target := initial.Number - delta + logger := cl.log.With("name", cl.inner.Name(), "chain", cl.ChainID(), "label", lvl) + logger.Info("Expecting chain to rewind", "target", target, "delta", delta) + // check rewind more aggressively, in shorter interval + return retry.Do0(cl.ctx, attempts, &retry.FixedStrategy{Dur: 250 * time.Millisecond}, + func() error { + head := cl.HeadBlockRef(lvl) + if head.Number <= target { + logger.Info("Chain rewinded", "target", target) + return nil + } + logger.Info("Chain sync status", "target", target, "current", head.Number) + return fmt.Errorf("expected head to rewind: %s", lvl) + }) + } +} + +func (cl *L2CLNode) Advanced(lvl safety.Level, delta uint64, attempts int) { + cl.require.NoError(cl.AdvancedFn(lvl, delta, attempts)()) +} + +func (cl *L2CLNode) AdvancedUnsafe(delta uint64, attempts int) { + cl.Advanced(safety.LocalUnsafe, delta, attempts) +} + +func (cl *L2CLNode) NotAdvanced(lvl safety.Level, attempts int) { + cl.require.NoError(cl.NotAdvancedFn(lvl, attempts)()) +} + +func (cl *L2CLNode) NotAdvancedUnsafe(attempts int) { + cl.NotAdvanced(safety.LocalUnsafe, attempts) +} + +func (cl *L2CLNode) Reached(lvl safety.Level, target uint64, attempts int) { + cl.require.NoError(cl.ReachedFn(lvl, target, attempts)()) +} + +func (cl *L2CLNode) ReachedUnsafe(target uint64, attempts int) { + cl.Reached(safety.LocalUnsafe, target, attempts) +} + +func (cl *L2CLNode) ReachedRef(lvl safety.Level, target eth.BlockID, attempts int) { + cl.require.NoError(cl.ReachedRefFn(lvl, target, attempts)()) +} + +func (cl *L2CLNode) Rewinded(lvl safety.Level, delta uint64, attempts int) { + cl.require.NoError(cl.RewindedFn(lvl, delta, attempts)()) +} + +// ChainSyncStatus satisfies that the L2CLNode can provide sync status per chain +func (cl *L2CLNode) ChainSyncStatus(chainID eth.ChainID, lvl safety.Level) eth.BlockID { + cl.require.Equal(chainID, cl.inner.ChainID(), "chain ID mismatch") + return cl.HeadBlockRef(lvl).ID() +} + +func (cl *L2CLNode) safeHeadAtL1Block(l1BlockNum uint64) *eth.SafeHeadResponse { + resp, err := cl.inner.RollupAPI().SafeHeadAtL1Block(cl.ctx, l1BlockNum) + if errors.Is(err, safedb.ErrNotFound) { + return nil + } + cl.require.NoErrorf(err, "failed to get safe head at l1 block %v", l1BlockNum) + return resp +} + +// LaggedFn returns a lambda that checks the L2CL chain head with given safety level is lagged with the reference chain sync status provider +// Composable with other lambdas to wait in parallel +func (cl *L2CLNode) LaggedFn(refNode SyncStatusProvider, lvl safety.Level, attempts int, allowMatch bool) CheckFunc { + return LaggedFn(cl, refNode, cl.log, cl.ctx, lvl, cl.ChainID(), attempts, allowMatch) +} + +// MatchedFn returns a lambda that checks the L2CLNode head with given safety level is matched with the refNode chain sync status provider +// Composable with other lambdas to wait in parallel +func (cl *L2CLNode) MatchedFn(refNode SyncStatusProvider, lvl safety.Level, attempts int) CheckFunc { + return MatchedFn(cl, refNode, cl.log, cl.ctx, lvl, cl.ChainID(), attempts) +} + +// MatchedWithProgressFn returns a lambda that waits for cl's matchLvl head to +// match refNode's matchLvl head while requiring cl to keep making progress on +// progressLvl. See MatchedWithProgressFn in check.go for the precise semantics. +// Composable with other lambdas to wait in parallel. +func (cl *L2CLNode) MatchedWithProgressFn(refNode SyncStatusProvider, matchLvl, progressLvl safety.Level, maxWait, stallTimeout time.Duration) CheckFunc { + return MatchedWithProgressFn(cl, refNode, cl.log, cl.ctx, matchLvl, progressLvl, cl.ChainID(), maxWait, stallTimeout) +} + +func (cl *L2CLNode) InSyncFn(other SyncStatusProvider, lvl safety.Level, attempts int) CheckFunc { + return InSyncFn(cl, other, cl.log, cl.ctx, lvl, cl.ChainID(), attempts) +} + +func (cl *L2CLNode) Lagged(refNode SyncStatusProvider, lvl safety.Level, attempts int, allowMatch bool) { + cl.require.NoError(cl.LaggedFn(refNode, lvl, attempts, allowMatch)()) +} + +func (cl *L2CLNode) Matched(refNode SyncStatusProvider, lvl safety.Level, attempts int) { + cl.require.NoError(cl.MatchedFn(refNode, lvl, attempts)()) +} + +func (cl *L2CLNode) InSync(other SyncStatusProvider, lvl safety.Level, attempts int) { + cl.require.NoError(cl.InSyncFn(other, lvl, attempts)()) +} + +func (cl *L2CLNode) MatchedUnsafe(refNode SyncStatusProvider, attempts int) { + cl.Matched(refNode, safety.LocalUnsafe, attempts) +} + +func (cl *L2CLNode) PeerInfo() *apis.PeerInfo { + peerInfo, err := retry.Do(cl.ctx, 3, retry.Exponential(), func() (*apis.PeerInfo, error) { + return cl.inner.P2PAPI().Self(cl.ctx) + }) + cl.require.NoError(err, "failed to get peer info") + return peerInfo +} + +func (cl *L2CLNode) Peers() *apis.PeerDump { + peerDump, err := retry.Do(cl.ctx, 3, retry.Exponential(), func() (*apis.PeerDump, error) { + return cl.inner.P2PAPI().Peers(cl.ctx, true) + }) + cl.require.NoError(err, "failed to get peers") + return peerDump +} + +func (cl *L2CLNode) DisconnectPeer(peer *L2CLNode) { + delete(cl.managedPeers, peer.Name()) + delete(peer.managedPeers, cl.Name()) + cl.disconnectPeerRaw(peer) +} + +func (cl *L2CLNode) disconnectPeerRaw(peer *L2CLNode) { + peerInfo := peer.PeerInfo() + err := retry.Do0(cl.ctx, 3, retry.Exponential(), func() error { + return cl.inner.P2PAPI().DisconnectPeer(cl.ctx, peerInfo.PeerID) + }) + cl.require.NoError(err, "failed to disconnect peer") +} + +func (cl *L2CLNode) ConnectPeer(peer *L2CLNode) { + cl.managedPeers[peer.Name()] = peer + peer.managedPeers[cl.Name()] = cl + cl.connectPeerRaw(peer) +} + +func (cl *L2CLNode) connectPeerRaw(peer *L2CLNode) { + peerInfo := peer.PeerInfo() + cl.require.NotZero(len(peerInfo.Addresses), "failed to get peer address") + // graceful backoff for p2p connection, to avoid dial backoff or connection refused error + strategy := &retry.ExponentialStrategy{Min: 10 * time.Second, Max: 30 * time.Second, MaxJitter: 250 * time.Millisecond} + err := retry.Do0(cl.ctx, 5, strategy, func() error { + return cl.inner.P2PAPI().ConnectPeer(cl.ctx, peerInfo.Addresses[0]) + }) + cl.require.NoError(err, "failed to connect peer") +} + +func (cl *L2CLNode) IsP2PConnected(peer *L2CLNode) { + myInfo := cl.PeerInfo() + strategy := &retry.ExponentialStrategy{Min: 10 * time.Second, Max: 30 * time.Second, MaxJitter: 250 * time.Millisecond} + err := retry.Do0(cl.ctx, 5, strategy, func() error { + for _, p := range peer.Peers().Peers { + if p.PeerID == myInfo.PeerID { + return nil + } + } + return errors.New("peer not connected yet") + }) + cl.require.NoError(err, "peer not connected") +} + +func (cl *L2CLNode) IsP2PDisconnected(peer *L2CLNode) { + myInfo := cl.PeerInfo() + strategy := &retry.ExponentialStrategy{Min: 10 * time.Second, Max: 30 * time.Second, MaxJitter: 250 * time.Millisecond} + err := retry.Do0(cl.ctx, 5, strategy, func() error { + for _, p := range peer.Peers().Peers { + if p.PeerID == myInfo.PeerID { + return errors.New("peer still connected") + } + } + return nil + }) + cl.require.NoError(err, "peer not disconnected") +} + +type safeHeadDbMatchOpts struct { + minRequiredL2Block *uint64 +} + +func WithMinRequiredL2Block(blockNum uint64) func(opts *safeHeadDbMatchOpts) { + return func(opts *safeHeadDbMatchOpts) { + opts.minRequiredL2Block = &blockNum + } +} + +func (cl *L2CLNode) VerifySafeHeadDatabaseMatches(sourceOfTruth *L2CLNode, args ...func(opts *safeHeadDbMatchOpts)) { + opts := applyOpts(safeHeadDbMatchOpts{}, args...) + l1Block := cl.SyncStatus().CurrentL1.Number + cl.log.Info("Verifying safe head database matches", "maxL1Block", l1Block) + cl.AwaitMinL1Processed(l1Block) // Ensure this block is fully processed before checking safe head db + sourceOfTruth.AwaitMinL1Processed(l1Block) + checkSafeHeadConsistent(cl.t, l1Block, cl, sourceOfTruth, opts.minRequiredL2Block) +} + +func (cl *L2CLNode) WaitForNonZeroUnsafeTime(ctx context.Context) *eth.SyncStatus { + require := cl.require + + var ss *eth.SyncStatus + err := retry.Do0(ctx, 10, retry.Fixed(2*time.Second), func() error { + ss = cl.SyncStatus() + require.NotNil(ss, "L2CL should have sync status") + if ss.UnsafeL2.Time == 0 { + return fmt.Errorf("L2CL unsafe time is still zero") + } + return nil + }) + require.NoError(err, "L2CL unsafe time should be set within retry limit") + require.NotZero(ss.UnsafeL2.Time, "L2CL unsafe time should not be zero") + + return ss +} + +func (cl *L2CLNode) SignalTarget(refNode *L2ELNode, targetNum uint64) { + cl.log.Info("Signaling L2CL", "target", targetNum, "refNode", refNode) + payload := refNode.PayloadByNumber(targetNum) + cl.PostUnsafePayload(payload) +} + +func (cl *L2CLNode) PostUnsafePayload(payload *eth.ExecutionPayloadEnvelope) { + cl.log.Info("PostUnsafePayload", "target", payload.ExecutionPayload.BlockNumber) + err := retry.Do0(cl.ctx, 3, retry.Fixed(2*time.Second), func() error { + return cl.inner.RollupAPI().PostUnsafePayload(cl.ctx, payload) + }) + cl.require.NoErrorf(err, "failed to post unsafe payload via admin API: target %d", payload.ExecutionPayload.BlockNumber) +} + +func (cl *L2CLNode) Reset(lvl safety.Level, target eth.L2BlockRef) { + cl.require.NoError(retry.Do0(cl.ctx, 5, &retry.FixedStrategy{Dur: 2 * time.Second}, + func() error { + res := cl.HeadBlockRef(lvl) + cl.log.Info("Chain sync Status", lvl, res) + if res.Hash == target.Hash { + return nil + } + return errors.New("waiting to reset") + })) +} + +func (cl *L2CLNode) AppendUnsafePayloadUntilTip(verEL, seqEL *L2ELNode, maxAttempts int) { + trial := 0 + cl.require.NoError( + retry.Do0(cl.ctx, 200, &retry.FixedStrategy{Dur: 250 * time.Millisecond}, func() error { + verUnsafe := verEL.BlockRefByLabel(eth.Unsafe) + seqUnsafe := seqEL.BlockRefByLabel(eth.Unsafe) + gap := seqUnsafe.Number - verUnsafe.Number + cl.log.Info("Filling in the gap by appending unsafe payload", "gap", gap, "ver", verUnsafe, "seq", seqUnsafe, "trial", trial) + if gap == 0 { + return nil + } + trial += 1 + cl.SignalTarget(seqEL, verUnsafe.Number+1) + return fmt.Errorf("unsafe gap with size %d still exists", gap) + })) +} + +func (cl *L2CLNode) UnsafeHead() *BlockRefResult { + return &BlockRefResult{T: cl.t, BlockRef: cl.HeadBlockRef(safety.LocalUnsafe)} +} + +func (cl *L2CLNode) SafeHead() *BlockRefResult { + return &BlockRefResult{T: cl.t, BlockRef: cl.HeadBlockRef(safety.CrossSafe)} +} + +func (cl *L2CLNode) CurrentL1MatchedFn(refNode *L2CLNode, attempts int) CheckFunc { + return func() error { + return retry.Do0(cl.ctx, attempts, &retry.FixedStrategy{Dur: 1 * time.Second}, + func() error { + currentL1 := cl.SyncStatus().CurrentL1 + ref := refNode.SyncStatus().CurrentL1 + if currentL1 == ref { + cl.log.Info("CurrentL1 reached", "currentL1", currentL1) + return nil + } + cl.log.Info("Chain sync status", "currentL1", currentL1.Number, "ref", ref) + return fmt.Errorf("expected currentL1 to match") + }) + } +} + +func (cl *L2CLNode) LocalGameInputs(agreedBlock, claimBlock uint64) *utils.LocalGameInputs { + cl.Reached(safety.LocalSafe, claimBlock, 60) + + rollupAPI := cl.Escape().RollupAPI() + + agreedOutput, err := rollupAPI.OutputAtBlock(cl.ctx, agreedBlock) + cl.require.NoError(err, "fetch output at agreed block %d", agreedBlock) + + claimedOutput, err := rollupAPI.OutputAtBlock(cl.ctx, claimBlock) + cl.require.NoError(err, "fetch output at claim block %d", claimBlock) + + syncStatus, err := rollupAPI.SyncStatus(cl.ctx) + cl.require.NoError(err, "fetch L2 sync status") + + return &utils.LocalGameInputs{ + L1Head: syncStatus.CurrentL1.Hash, + L2Head: agreedOutput.BlockRef.Hash, + L2OutputRoot: common.Hash(agreedOutput.OutputRoot), + L2Claim: common.Hash(claimedOutput.OutputRoot), + L2SequenceNumber: new(big.Int).SetUint64(claimBlock), + } +} diff --git a/op-devstack/dsl/l2_el.go b/op-devstack/dsl/l2_el.go index 41452a72ce7..5f4e3b1125b 100644 --- a/op-devstack/dsl/l2_el.go +++ b/op-devstack/dsl/l2_el.go @@ -12,10 +12,10 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/stack" "github.com/ethereum-optimism/optimism/op-devstack/sysgo" "github.com/ethereum-optimism/optimism/op-service/apis" - "github.com/ethereum-optimism/optimism/op-service/bigs" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/retry" - suptypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -363,15 +363,15 @@ func (el *L2ELNode) FinishedELSync(refNode *L2ELNode, unsafe, safe, finalized ui })) } -func (el *L2ELNode) ChainSyncStatus(chainID eth.ChainID, lvl suptypes.SafetyLevel) eth.BlockID { +func (el *L2ELNode) ChainSyncStatus(chainID eth.ChainID, lvl safety.Level) eth.BlockID { el.require.Equal(chainID, el.inner.ChainID(), "chain ID mismatch") var blockRef eth.L2BlockRef switch lvl { - case suptypes.Finalized: + case safety.Finalized: blockRef = el.BlockRefByLabel(eth.Finalized) - case suptypes.CrossSafe, suptypes.LocalSafe: + case safety.CrossSafe, safety.LocalSafe: blockRef = el.BlockRefByLabel(eth.Safe) - case suptypes.CrossUnsafe, suptypes.LocalUnsafe: + case safety.CrossUnsafe, safety.LocalUnsafe: blockRef = el.BlockRefByLabel(eth.Unsafe) default: el.require.NoError(errors.New("invalid safety level")) @@ -394,16 +394,24 @@ func (el *L2ELNode) WaitForReceipt(txHash common.Hash) *types.Receipt { return receipt } -func (el *L2ELNode) MatchedFn(refNode SyncStatusProvider, lvl suptypes.SafetyLevel, attempts int) CheckFunc { +func (el *L2ELNode) MatchedFn(refNode SyncStatusProvider, lvl safety.Level, attempts int) CheckFunc { return MatchedFn(el, refNode, el.log, el.ctx, lvl, el.ChainID(), attempts) } -func (el *L2ELNode) Matched(refNode SyncStatusProvider, lvl suptypes.SafetyLevel, attempts int) { +func (el *L2ELNode) InSyncFn(other SyncStatusProvider, lvl safety.Level, attempts int) CheckFunc { + return InSyncFn(el, other, el.log, el.ctx, lvl, el.ChainID(), attempts) +} + +func (el *L2ELNode) Matched(refNode SyncStatusProvider, lvl safety.Level, attempts int) { el.require.NoError(el.MatchedFn(refNode, lvl, attempts)()) } +func (el *L2ELNode) InSync(other SyncStatusProvider, lvl safety.Level, attempts int) { + el.require.NoError(el.InSyncFn(other, lvl, attempts)()) +} + func (el *L2ELNode) MatchedUnsafe(refNode SyncStatusProvider, attempts int) { - el.Matched(refNode, suptypes.LocalUnsafe, attempts) + el.Matched(refNode, safety.LocalUnsafe, attempts) } // WaitForPendingNonceMatchFn returns a lambda that waits for the pending nonce of an account to match the provided reference nonce diff --git a/op-devstack/dsl/l2_network.go b/op-devstack/dsl/l2_network.go new file mode 100644 index 00000000000..bed0d91f790 --- /dev/null +++ b/op-devstack/dsl/l2_network.go @@ -0,0 +1,552 @@ +package dsl + +import ( + "bytes" + "fmt" + "io" + "math" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +// L2Network wraps a stack.L2Network interface for DSL operations +type L2Network struct { + commonImpl + inner stack.L2Network + primaryEL *L2ELNode + primaryCL *L2CLNode + primaryL1 *L1ELNode + archiveEL *L2ELNode + publicRPC *L2ELNode +} + +// NewL2Network creates a new L2Network DSL wrapper +func NewL2Network(inner stack.L2Network, primaryEL *L2ELNode, primaryCL *L2CLNode, primaryL1 *L1ELNode, archiveEL *L2ELNode, publicRPC *L2ELNode) *L2Network { + if archiveEL == nil { + archiveEL = primaryEL + } + if publicRPC == nil { + publicRPC = primaryEL + } + return &L2Network{ + commonImpl: commonFromT(inner.T()), + inner: inner, + primaryEL: primaryEL, + primaryCL: primaryCL, + primaryL1: primaryL1, + archiveEL: archiveEL, + publicRPC: publicRPC, + } +} + +func (n *L2Network) String() string { + return n.inner.Name() +} + +func (n *L2Network) ChainID() eth.ChainID { + return n.inner.ChainID() +} + +// TimestampForBlockNum returns the timestamp for the given L2 block number. +func (n *L2Network) TimestampForBlockNum(blockNum uint64) uint64 { + return n.inner.RollupConfig().TimestampForBlock(blockNum) +} + +// Escape returns the underlying stack.L2Network +func (n *L2Network) Escape() stack.L2Network { + return n.inner +} + +func (n *L2Network) PrimaryEL() *L2ELNode { + n.require.NotNil(n.primaryEL, "l2 network %s is missing a primary EL node", n.String()) + return n.primaryEL +} + +func (n *L2Network) ArchiveEL() *L2ELNode { + n.require.NotNil(n.archiveEL, "l2 network %s is missing an archive EL node", n.String()) + return n.archiveEL +} + +func (n *L2Network) PrimaryCL() *L2CLNode { + n.require.NotNil(n.primaryCL, "l2 network %s is missing a primary CL node", n.String()) + return n.primaryCL +} + +func (n *L2Network) PrimaryL1EL() *L1ELNode { + n.require.NotNil(n.primaryL1, "l2 network %s is missing a primary L1 EL node", n.String()) + return n.primaryL1 +} + +func (n *L2Network) L2ELNodes() []*L2ELNode { + innerNodes := n.inner.L2ELNodes() + nodes := make([]*L2ELNode, len(innerNodes)) + for i, inner := range innerNodes { + nodes[i] = NewL2ELNode(inner) + } + return nodes +} + +func (n *L2Network) CatchUpTo(o *L2Network) { + this := n.PrimaryEL().Escape() + other := o.PrimaryEL().Escape() + + err := wait.For(n.ctx, 5*time.Second, func() (bool, error) { + a, err := this.EthClient().InfoByLabel(n.ctx, "latest") + if err != nil { + return false, err + } + + b, err := other.EthClient().InfoByLabel(n.ctx, "latest") + if err != nil { + return false, err + } + + eps := 6.0 // 6 seconds + if math.Abs(float64(a.Time()-b.Time())) > eps { + n.log.Warn("L2 networks too far off each other", n.String(), a.Time(), o.String(), b.Time()) + return false, nil + } + + return true, nil + }) + n.require.NoError(err, "Expected to get latest block from L2 execution clients") +} + +func (n *L2Network) WaitForBlock() eth.BlockRef { + return n.PrimaryEL().WaitForBlock() +} + +func (n *L2Network) PublicRPC() *L2ELNode { + n.require.NotNil(n.publicRPC, "l2 network %s is missing a public RPC node", n.String()) + return n.publicRPC +} + +// PrintChain is used for testing/debugging, it prints the blockchain hashes and parent hashes to logs, which is useful when developing reorg tests +func (n *L2Network) PrintChain() { + l2_el := n.PrimaryEL().Escape() + l2_cl := n.PrimaryCL().Escape() + l1_el := n.PrimaryL1EL().Escape() + + biAddr := n.inner.RollupConfig().BatchInboxAddress + dgfAddr := n.inner.Deployment().DisputeGameFactoryProxyAddr() + + var entries []string + var totalL2Txs int + err := retry.Do0(n.ctx, 3, &retry.FixedStrategy{Dur: 200 * time.Millisecond}, func() error { + entries = []string{} + totalL2Txs = 0 + + ref := n.unsafeHeadRef() + + for i := ref.Number; i > 0; i-- { + ref, err := l2_el.L2EthClient().L2BlockRefByNumber(n.ctx, i) + if err != nil { + return err + } + + _, l2Txs, err := l2_el.EthClient().InfoAndTxsByHash(n.ctx, ref.Hash) + if err != nil { + return err + } + + _, txs, err := l1_el.EthClient().InfoAndTxsByHash(n.ctx, ref.L1Origin.Hash) + if err != nil { + return err + } + + var batchTxs, dgfTxs int + for _, tx := range txs { + to := tx.To() + if to != nil && *to == biAddr { + batchTxs++ + } + if to != nil && *to == dgfAddr { + dgfTxs++ + } + } + + entries = append(entries, fmt.Sprintf("Time: %d Block: %s Parent: %s L1 Origin: %s Txs (L2: %d; Batch: %d; DGF: %d)", ref.Time, ref, ref.ParentID(), ref.L1Origin, len(l2Txs), batchTxs, dgfTxs)) + totalL2Txs += len(l2Txs) + } + + return nil + }) + n.require.NoError(err, "could not PrintChain after many attempts") + + syncStatus, err := l2_cl.RollupAPI().SyncStatus(n.ctx) + n.require.NoError(err, "Expected to get sync status") + + entries = append(entries, "") + entries = append(entries, fmt.Sprintf("Total L2 Txs: %d", totalL2Txs)) + entries = append(entries, "") + entries = append(entries, "Supervisor Sync view") + entries = append(entries, "") + entries = append(entries, fmt.Sprintf("Current L1: %s", syncStatus.CurrentL1)) + entries = append(entries, fmt.Sprintf("Head L1: %s", syncStatus.HeadL1)) + entries = append(entries, fmt.Sprintf("Safe L1: %s", syncStatus.SafeL1)) + entries = append(entries, fmt.Sprintf("Unsafe L2: %s", syncStatus.UnsafeL2)) + entries = append(entries, fmt.Sprintf("Local-Safe L2: %s", syncStatus.LocalSafeL2)) + entries = append(entries, fmt.Sprintf("Cross-Unsafe L2: %s", syncStatus.CrossUnsafeL2)) + entries = append(entries, fmt.Sprintf("Cross-Safe L2: %s", syncStatus.SafeL2)) + + n.log.Info("Printing block hashes and parent hashes", "network", n.String(), "chain", n.ChainID()) + spew.Dump(entries) +} + +func (n *L2Network) unsafeHeadRef() eth.L2BlockRef { + l2_el := n.PrimaryEL().Escape() + + unsafeHead, err := l2_el.EthClient().InfoByLabel(n.ctx, eth.Unsafe) + n.require.NoError(err, "Expected to get latest block from L2 execution client") + + unsafeHeadRef, err := l2_el.L2EthClient().L2BlockRefByHash(n.ctx, unsafeHead.Hash()) + n.require.NoError(err, "Expected to get block ref by hash") + + return unsafeHeadRef +} + +// IsActivated checks if a given fork has been activated +func (n *L2Network) IsActivated(timestamp uint64) bool { + blockNum, err := n.Escape().RollupConfig().TargetBlockNumber(timestamp) + n.require.NoError(err) + + head, err := n.PrimaryEL().EthClient().BlockRefByLabel(n.ctx, eth.Unsafe) + n.require.NoError(err) + + return head.Number >= blockNum +} + +func (n *L2Network) IsForkActive(fork forks.Name) bool { + timestamp := n.PrimaryEL().BlockRefByLabel(eth.Unsafe).Time + return n.IsForkActiveAt(fork, timestamp) +} + +func (n *L2Network) IsForkActiveAt(forkName forks.Name, timestamp uint64) bool { + return n.Escape().RollupConfig().IsForkActive(forkName, timestamp) +} + +// LatestBlockBeforeTimestamp finds the latest block before fork activation +func (n *L2Network) LatestBlockBeforeTimestamp(t devtest.T, timestamp uint64) eth.BlockRef { + require := t.Require() + + t.Gate().Greater(timestamp, uint64(0), "Must not start fork at genesis") + + blockNum, err := n.Escape().RollupConfig().TargetBlockNumber(timestamp) + require.NoError(err) + + head, err := n.PrimaryEL().EthClient().BlockRefByLabel(t.Ctx(), eth.Unsafe) + require.NoError(err) + + t.Logger().Info("Preparing", + "head", head, "head_time", head.Time, + "target_num", blockNum, "target_time", timestamp) + + if head.Number < blockNum { + t.Logger().Info("No block with given timestamp yet, checking head block instead") + return head + } else { + t.Logger().Info("Reached block already, proceeding with last block before timestamp") + v, err := n.PrimaryEL().EthClient().BlockRefByNumber(t.Ctx(), blockNum-1) + require.NoError(err) + return v + } +} + +// AwaitActivation awaits the fork activation time, and returns the activation block +func (n *L2Network) AwaitActivation(t devtest.T, forkName rollup.ForkName) eth.BlockID { + require := t.Require() + + rollupCfg := n.Escape().RollupConfig() + maybeActivationTime := rollupCfg.ActivationTime(forkName) + require.NotNil(maybeActivationTime, "Required fork is not scheduled for activation") + activationTime := *maybeActivationTime + if activationTime == 0 { + block, err := n.PrimaryEL().EthClient().BlockRefByNumber(t.Ctx(), 0) + require.NoError(err, "Fork activated at genesis, but failed to get genesis block") + return block.ID() + } + blockNum, err := rollupCfg.TargetBlockNumber(activationTime) + require.NoError(err) + activationBlock := eth.ToBlockID(n.PrimaryEL().WaitForBlockNumber(blockNum)) + t.Logger().Info("Activation block", "block", activationBlock) + return activationBlock + +} + +func (n *L2Network) DisputeGameFactoryProxyAddr() common.Address { + return n.inner.Deployment().DisputeGameFactoryProxyAddr() +} + +func (n *L2Network) DepositContractAddr() common.Address { + return n.inner.RollupConfig().DepositContractAddress +} + +func (n *L2Network) DeriveData(blocks int) (channels []derive.ChannelID, channelFrames map[derive.ChannelID][]derive.Frame, l2Txs map[common.Address][]*ethtypes.Transaction) { + l := n.log + ctx := n.ctx + + channelFrames = make(map[derive.ChannelID][]derive.Frame) + channels = make([]derive.ChannelID, 0) + l2Txs = make(map[common.Address][]*ethtypes.Transaction) + + rollupCfg := n.inner.RollupConfig() + batchInboxAddr := rollupCfg.BatchInboxAddress + + l1EC := n.PrimaryL1EL().EthClient() + + // Get current L1 block number before starting to monitor + startBlockRef, err := l1EC.BlockRefByLabel(ctx, eth.Unsafe) + n.require.NoError(err, "Failed to get start block number") + + seenChannels := make(map[derive.ChannelID]bool) + lastBlockRef := startBlockRef + + // Monitor L1 blocks for batch transactions + for range blocks { + n.PrimaryL1EL().WaitForBlock() + + // Get current block number + currentBlockRef, err := l1EC.BlockRefByLabel(ctx, eth.Unsafe) + n.require.NoError(err, "Failed to get current block number") + blockNum := currentBlockRef.Number + lastBlockRef = currentBlockRef + + _, txs, err := l1EC.InfoAndTxsByNumber(ctx, blockNum) + n.require.NoError(err, "Failed to get block %d", blockNum) + + // Process transactions in this block + for _, tx := range txs { + // Check if transaction is targeted to BatchInbox + if tx.To() != nil && *tx.To() == batchInboxAddr { + // Get transaction sender + chainID := n.inner.L1().ChainID() + chainIDBig := chainID.ToBig() + signer := ethtypes.LatestSignerForChainID(chainIDBig) + sender, err := signer.Sender(tx) + n.require.NoError(err, "Failed to get transaction sender") + + l.Debug("Found batch transaction", + "txHash", tx.Hash(), + "block", blockNum, + "sender", sender) + + var datas [][]byte + if tx.Type() != ethtypes.BlobTxType { + // Regular transaction - data is in tx.Data() + datas = append(datas, tx.Data()) + } else { + // Blob transaction - need to fetch blobs from beacon + // For now, log that we found a blob tx but skip detailed parsing + // as it requires beacon API access + l.Error("Found blob transaction (skipping blob fetch for now)", + "txHash", tx.Hash(), + "blobHashes", tx.BlobHashes()) + continue + } + + // Parse frames from transaction data + for _, data := range datas { + frames, err := derive.ParseFrames(data) + if err != nil { + l.Warn("Failed to parse frames from transaction", + "txHash", tx.Hash(), + "error", err) + n.require.NoError(err) + } + + l.Debug("Parsed frames from transaction", + "txHash", tx.Hash(), + "frameCount", len(frames)) + + // Process each frame + for _, frame := range frames { + channelID := frame.ID + if !seenChannels[channelID] { + seenChannels[channelID] = true + l.Debug("Found new channel", + "channelID", channelID.String(), + "txHash", tx.Hash(), + "block", blockNum) + channels = append(channels, channelID) + } + channelFrames[channelID] = append(channelFrames[channelID], frame) + l.Debug("Frame added to channel", + "channelID", channelID.String(), + "frameNumber", frame.FrameNumber, + "dataLength", len(frame.Data), + "isLast", frame.IsLast, + "txHash", tx.Hash()) + } + } + } + } + } + + // Reassemble channels and extract batches + for channelID, frames := range channelFrames { + l.Debug("Processing channel", + "channelID", channelID.String(), + "frameCount", len(frames)) + + // Sort frames by frame number + sortedFrames := make([]derive.Frame, len(frames)) + copy(sortedFrames, frames) + for i := 0; i < len(sortedFrames); i++ { + for j := i + 1; j < len(sortedFrames); j++ { + if sortedFrames[i].FrameNumber > sortedFrames[j].FrameNumber { + sortedFrames[i], sortedFrames[j] = sortedFrames[j], sortedFrames[i] + } + } + } + + // Create a channel and add frames to it + // We need an L1 block ref for the channel - use the last processed block as the origin + originBlock := lastBlockRef + ch := derive.NewChannel(channelID, originBlock, false) + + for _, frame := range sortedFrames { + err := ch.AddFrame(frame, originBlock) + if err != nil { + l.Warn("Failed to add frame to channel", + "channelID", channelID.String(), + "frameNumber", frame.FrameNumber, + "error", err) + continue + } + } + + l.Debug("Channel is ready, extracting batches", + "channelID", channelID.String(), + "size", ch.Size()) + + channelReader := ch.Reader() + channelData, err := io.ReadAll(channelReader) + if err != nil { + l.Warn("Failed to read channel data", + "channelID", channelID.String(), + "error", err) + continue + } + + l.Debug("Read channel data", + "channelID", channelID.String(), + "dataLength", len(channelData)) + + spec := rollup.NewChainSpec(rollupCfg) + maxRLPBytes := spec.MaxRLPBytesPerChannel(originBlock.Time) + isFjord := rollupCfg.IsFjord(originBlock.Time) + batchReader, err := derive.BatchReader(bytes.NewReader(channelData), maxRLPBytes, isFjord) + if err != nil { + l.Warn("Failed to create batch reader", + "channelID", channelID.String(), + "error", err) + continue + } + + // Read all batches from the channel + batchCount := 0 + for { + batchData, err := batchReader() + if err == io.EOF { + break + } + if err != nil { + l.Warn("Failed to read batch from channel", + "channelID", channelID.String(), + "batchCount", batchCount, + "error", err) + break + } + + batchCount++ + batchType := batchData.GetBatchType() + + l.Debug("Found batch in channel", + "channelID", channelID.String(), + "batchNumber", batchCount, + "batchType", batchType, + "compressionAlgo", batchData.ComprAlgo) + + // Decode the batch based on type + if batchType == derive.SingularBatchType { + singularBatch, err := derive.GetSingularBatch(batchData) + if err != nil { + l.Warn("Failed to decode singular batch", + "channelID", channelID.String(), + "batchNumber", batchCount, + "error", err) + n.require.NoError(err) + } + + for _, txData := range singularBatch.Transactions { + var tx ethtypes.Transaction + n.require.NoError(tx.UnmarshalBinary(txData)) + + signer := ethtypes.LatestSignerForChainID(rollupCfg.L2ChainID) + fromAddr, err := signer.Sender(&tx) + n.require.NoError(err) + + l2Txs[fromAddr] = append(l2Txs[fromAddr], &tx) + } + + } else if batchType == derive.SpanBatchType { + spanBatch, err := derive.DeriveSpanBatch( + batchData, + rollupCfg.BlockTime, + rollupCfg.Genesis.L2Time, + rollupCfg.L2ChainID, + ) + if err != nil { + l.Warn("Failed to decode span batch", + "channelID", channelID.String(), + "batchNumber", batchCount, + "error", err) + continue + } + + for blockIdx, batchElement := range spanBatch.Batches { + l.Debug("L2 block in span batch", + "channelID", channelID.String(), + "batchNumber", batchCount, + "blockIndex", blockIdx, + "epochNum", batchElement.EpochNum, + "timestamp", batchElement.Timestamp, + "txCount", len(batchElement.Transactions)) + + for _, txData := range batchElement.Transactions { + var tx ethtypes.Transaction + n.require.NoError(tx.UnmarshalBinary(txData)) + + signer := ethtypes.LatestSignerForChainID(rollupCfg.L2ChainID) + fromAddr, err := signer.Sender(&tx) + n.require.NoError(err) + + l2Txs[fromAddr] = append(l2Txs[fromAddr], &tx) + } + } + } else { + l.Warn("Unknown batch type", + "channelID", channelID.String(), + "batchNumber", batchCount, + "batchType", batchType) + } + } + + l.Debug("Finished processing channel", + "channelID", channelID.String(), + "totalBatches", batchCount) + } + + return channels, channelFrames, l2Txs +} diff --git a/op-devstack/dsl/l2_op_rbuilder.go b/op-devstack/dsl/l2_op_rbuilder.go index 5e9aa6c2d32..4dc222e6127 100644 --- a/op-devstack/dsl/l2_op_rbuilder.go +++ b/op-devstack/dsl/l2_op_rbuilder.go @@ -54,8 +54,3 @@ func (el *OPRBuilderNode) Start() { el.require.Truef(ok, "op-rbuilder node %s is not lifecycle-controllable", el.inner.Name()) lifecycle.Start() } - -func (el *OPRBuilderNode) UpdateRuleSet(rulesYaml string) { - el.log.Info("Updating rule", "content", rulesYaml) - el.require.NoError(el.inner.UpdateRuleSet(rulesYaml), "failed to update rule: %s", rulesYaml) -} diff --git a/op-devstack/dsl/l2_proposer.go b/op-devstack/dsl/l2_proposer.go new file mode 100644 index 00000000000..679659db794 --- /dev/null +++ b/op-devstack/dsl/l2_proposer.go @@ -0,0 +1,26 @@ +package dsl + +import "github.com/ethereum-optimism/optimism/op-devstack/stack" + +// L2Proposer wraps a stack.L2Proposer interface for DSL operations +type L2Proposer struct { + commonImpl + inner stack.L2Proposer +} + +// NewL2Proposer creates a new L2Proposer DSL wrapper +func NewL2Proposer(inner stack.L2Proposer) *L2Proposer { + return &L2Proposer{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +func (p *L2Proposer) String() string { + return p.inner.Name() +} + +// Escape returns the underlying stack.L2Proposer +func (p *L2Proposer) Escape() stack.L2Proposer { + return p.inner +} diff --git a/op-devstack/dsl/multi_client.go b/op-devstack/dsl/multi_client.go new file mode 100644 index 00000000000..0bae50cc0bc --- /dev/null +++ b/op-devstack/dsl/multi_client.go @@ -0,0 +1,237 @@ +package dsl + +import ( + "context" + "errors" + "fmt" + "regexp" + "sync" + "time" + + "math/big" + + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// HeaderProvider interface for multi-client operations +type HeaderProvider interface { + InfoByNumber(ctx context.Context, number uint64) (eth.BlockInfo, error) + InfoByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, error) + InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) +} + +// CheckForChainFork checks that the L2 chain has not forked now, and returns a +// function that check again (to be called at the end of the test). An error is +// returned from this function (and the returned function) if a chain fork has +// been detected. +func CheckForChainFork(ctx context.Context, networks []*L2Network, logger log.Logger) (func(bool) error, error) { + var allClients []HeaderProvider + for _, network := range networks { + clients, err := getEthClientsFromL2Network(network) + if err != nil { + return nil, fmt.Errorf("failed to get eth clients from network %s: %w", network.String(), err) + } + allClients = append(allClients, clients...) + } + + return checkForChainFork(ctx, allClients, logger) +} + +// getEthClientsFromL2Network extracts HeaderProvider clients from an L2Network +func getEthClientsFromL2Network(network *L2Network) ([]HeaderProvider, error) { + stackNetwork := network.Escape() + hps := make([]HeaderProvider, 0, len(stackNetwork.L2ELNodes())) + for _, n := range stackNetwork.L2ELNodes() { + ethClient := n.L2EthClient() + if !regexp.MustCompile(`snapsync-\d+$`).MatchString(n.Name()) { + hps = append(hps, ethClient) + } + } + return hps, nil +} + +func checkForChainFork(ctx context.Context, clients []HeaderProvider, logger log.Logger) (func(bool) error, error) { + l2MultiClient := NewMultiClient(clients) + + // Setup chain fork detection + logger.Info("Running fork detection precheck") + l2StartInfo, err := l2MultiClient.InfoByLabel(ctx, eth.Unsafe) + if err != nil { + return nil, fmt.Errorf("fork detection precheck failed: %w", err) + } + + return func(failed bool) error { + logger.Info("Running fork detection postcheck") + l2EndInfo, err := l2MultiClient.InfoByLabel(ctx, eth.Unsafe) + if err != nil { + return fmt.Errorf("fork detection postcheck failed: %w", err) + } + if l2EndInfo.NumberU64() <= l2StartInfo.NumberU64() { + if !failed { + return fmt.Errorf("L2 chain has not progressed: start=%d, end=%d", l2StartInfo.NumberU64(), l2EndInfo.NumberU64()) + } else { + logger.Debug("L2 chain has not progressed, but the test failed so we will not error again") + } + } + return nil + }, nil +} + +// MultiClient is a simple client that checks hash consistency between underlying clients +type MultiClient struct { + clients []HeaderProvider + retryStrategy retry.Strategy + maxAttempts int +} + +// NewMultiClient creates a new MultiClient with the specified underlying clients +func NewMultiClient(clients []HeaderProvider) *MultiClient { + return &MultiClient{ + clients: clients, + maxAttempts: 3, + retryStrategy: retry.Fixed(500 * time.Millisecond), + } +} + +// InfoByNumber returns block info from the first client while verifying hash consistency +func (mc *MultiClient) InfoByNumber(ctx context.Context, number uint64) (eth.BlockInfo, error) { + if len(mc.clients) == 0 { + return nil, errors.New("no clients configured") + } + + // Single client optimization + info, err := mc.clients[0].InfoByNumber(ctx, number) + if err != nil || len(mc.clients) == 1 { + return info, err + } + + // Fetch with consistency check + err = mc.verifyFollowersWithRetry(ctx, big.NewInt(int64(number)), info.Hash()) + return info, err +} + +// InfoByLabel returns block info from the first client while verifying hash consistency +func (mc *MultiClient) InfoByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, error) { + if len(mc.clients) == 0 { + return nil, errors.New("no clients configured") + } + + info, err := mc.clients[0].InfoByLabel(ctx, label) + if err != nil { + return nil, err + } + if info == nil { + return nil, fmt.Errorf("no block info found for label %v", label) + } + if len(mc.clients) == 1 { + return info, nil + } + + // Verify consistency with retry for followers + err = mc.verifyFollowersWithRetry(ctx, big.NewInt(int64(info.NumberU64())), info.Hash()) + + return info, err +} + +// InfoByHash returns block info from the first client while verifying hash consistency +func (mc *MultiClient) InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error) { + if len(mc.clients) == 0 { + return nil, errors.New("no clients configured") + } + + info, err := mc.clients[0].InfoByHash(ctx, hash) + if err != nil { + return nil, err + } + if info == nil { + return nil, fmt.Errorf("no block info found for hash %v", hash) + } + if len(mc.clients) == 1 { + return info, nil + } + + // Verify consistency with retry for followers + err = mc.verifyFollowersWithRetry(ctx, big.NewInt(int64(info.NumberU64())), info.Hash()) + + return info, err +} + +// verifyFollowersWithRetry checks hash consistency with retries in case of temporary sync issues +func (mc *MultiClient) verifyFollowersWithRetry( + ctx context.Context, + blockNum *big.Int, + primaryHash common.Hash, +) error { + var wg sync.WaitGroup + errs := make(chan error) + + // Track which clients still need verification + for clientIndex, c := range mc.clients[1:] { + actualIndex := clientIndex + 1 + client := c // copy so the goroutine closure has a stable reference + wg.Add(1) + go func() { + defer wg.Done() + hash, err := retry.Do(ctx, mc.maxAttempts, mc.retryStrategy, func() (common.Hash, error) { + info, err := client.InfoByNumber(ctx, bigs.Uint64Strict(blockNum)) + if err != nil { + return common.Hash{}, err + } + return info.Hash(), nil + }) + if err != nil { + errs <- err + return + } + // Detect chain split + if hash != primaryHash { + errs <- formatChainSplitError(blockNum, primaryHash, actualIndex, hash) + return + } + }() + } + + go func() { + wg.Wait() + close(errs) + }() + + allErrs := []error{} + for err := range errs { + allErrs = append(allErrs, err) + } + + if len(allErrs) > 0 { + return errors.Join(allErrs...) + } + + return nil +} + +// formatChainSplitError creates a descriptive error when a chain split is detected +func formatChainSplitError(blockNum *big.Int, primaryHash common.Hash, clientIdx int, hash common.Hash) error { + return fmt.Errorf("chain split detected at block #%s: primary=%s, client%d=%s", + blockNum, primaryHash.Hex()[:10], clientIdx, hash.Hex()[:10]) +} + +// MultiClientForL2Network creates a MultiClient from an L2Network +func MultiClientForL2Network(network *L2Network) (*MultiClient, error) { + clients := make([]HeaderProvider, 0) + for _, node := range network.Escape().L2ELNodes() { + clients = append(clients, node.EthClient()) + } + return NewMultiClient(clients), nil +} + +// MultiClientForL1Network creates a MultiClient from an L1Network +func MultiClientForL1Network(network *L1Network) (*MultiClient, error) { + clients := make([]HeaderProvider, 0) + for _, node := range network.Escape().L1ELNodes() { + clients = append(clients, node.EthClient()) + } + return NewMultiClient(clients), nil +} diff --git a/op-devstack/dsl/operator_fee.go b/op-devstack/dsl/operator_fee.go new file mode 100644 index 00000000000..6d217b8cab1 --- /dev/null +++ b/op-devstack/dsl/operator_fee.go @@ -0,0 +1,260 @@ +package dsl + +import ( + "math/big" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-core/predeploys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" + "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" + "github.com/ethereum/go-ethereum/core/types" +) + +type OperatorFee struct { + commonImpl + + l1Client *L1ELNode + l2Network *L2Network + systemConfig bindings.SystemConfig + l1Block bindings.L1Block + gasPriceOracle bindings.GasPriceOracle + + originalScalar uint32 + originalConstant uint64 +} + +type OperatorFeeValidationResult struct { + TransactionReceipt *types.Receipt + ExpectedOperatorFee *big.Int + ActualTotalFee *big.Int + VaultBalanceIncrease *big.Int +} + +func NewOperatorFee(t devtest.T, l2Network *L2Network, l1EL *L1ELNode) *OperatorFee { + systemConfig := bindings.NewBindings[bindings.SystemConfig]( + bindings.WithClient(l1EL.EthClient()), + bindings.WithTo(l2Network.Escape().Deployment().SystemConfigProxyAddr()), + bindings.WithTest(t)) + + l1Block := bindings.NewBindings[bindings.L1Block]( + bindings.WithClient(l2Network.PrimaryEL().EthClient()), + bindings.WithTo(predeploys.L1BlockAddr), + bindings.WithTest(t)) + + gasPriceOracle := bindings.NewBindings[bindings.GasPriceOracle]( + bindings.WithClient(l2Network.PrimaryEL().EthClient()), + bindings.WithTo(predeploys.GasPriceOracleAddr), + bindings.WithTest(t)) + + originalScalar, err := contractio.Read(systemConfig.OperatorFeeScalar(), t.Ctx()) + t.Require().NoError(err) + originalConstant, err := contractio.Read(systemConfig.OperatorFeeConstant(), t.Ctx()) + t.Require().NoError(err) + + return &OperatorFee{ + commonImpl: commonFromT(t), + l1Client: l1EL, + l2Network: l2Network, + systemConfig: systemConfig, + l1Block: l1Block, + gasPriceOracle: gasPriceOracle, + originalScalar: originalScalar, + originalConstant: originalConstant, + } +} + +func (of *OperatorFee) CheckCompatibility() bool { + _, err := contractio.Read(of.systemConfig.OperatorFeeScalar(), of.ctx) + if err != nil { + of.t.Skipf("Operator fee methods not available in devstack: %v", err) + return false + } + return true +} + +func (of *OperatorFee) GetSystemOwner() *EOA { + systemOwnerKey := devkeys.SystemConfigOwner.Key(of.l2Network.ChainID().ToBig()) + return NewKey(of.t, of.l2Network.Escape().Keys().Secret(systemOwnerKey)).User(of.l1Client) +} + +func (of *OperatorFee) SetOperatorFee(scalar uint32, constant uint64) { + systemOwner := of.GetSystemOwner() + + _, err := contractio.Write( + of.systemConfig.SetOperatorFeeScalars(scalar, constant), + of.ctx, + systemOwner.Plan()) + of.require.NoError(err) + + of.t.Logf("Set operator fee on L1: scalar=%d, constant=%d", scalar, constant) +} + +func (of *OperatorFee) WaitForL2SyncWithCurrentL1State() { + // Read current L1 values + l1Scalar, err := contractio.Read(of.systemConfig.OperatorFeeScalar(), of.ctx) + of.require.NoError(err) + l1Constant, err := contractio.Read(of.systemConfig.OperatorFeeConstant(), of.ctx) + of.require.NoError(err) + + // Wait for L2 to sync with current L1 values + of.WaitForL2Sync(l1Scalar, l1Constant) +} + +func (of *OperatorFee) WaitForL2Sync(expectedScalar uint32, expectedConstant uint64) { + of.require.Eventually(func() bool { + scalar, err := contractio.Read(of.l1Block.OperatorFeeScalar(), of.ctx) + if err != nil { + return false + } + constant, err := contractio.Read(of.l1Block.OperatorFeeConstant(), of.ctx) + if err != nil { + return false + } + + return scalar == expectedScalar && constant == expectedConstant + }, 2*time.Minute, 5*time.Second, "L2 operator fee parameters did not sync within 2 minutes") +} + +func (of *OperatorFee) VerifyL2Config(expectedScalar uint32, expectedConstant uint64) { + scalar, err := contractio.Read(of.l1Block.OperatorFeeScalar(), of.ctx) + of.require.NoError(err) + of.require.Equal(expectedScalar, scalar) + + constant, err := contractio.Read(of.l1Block.OperatorFeeConstant(), of.ctx) + of.require.NoError(err) + of.require.Equal(expectedConstant, constant) +} + +func (of *OperatorFee) ValidateTransactionFees(from *EOA, to *EOA, amount *big.Int, expectedScalar uint32, expectedConstant uint64) OperatorFeeValidationResult { + // Ensure there is at least one user transaction, to trigger flow of operator fees to vault. + tx := from.Transfer(to.Address(), eth.WeiBig(amount)) + receipt, err := tx.Included.Eval(of.ctx) + of.require.NoError(err) + of.require.Equal(types.ReceiptStatusSuccessful, receipt.Status) + + blockHash := receipt.BlockHash + info, txs, err := from.el.stackEL().EthClient().InfoAndTxsByHash(of.ctx, blockHash) + of.require.NoError(err) + + // Infer active fork from block info + isJovian := of.l2Network.IsForkActiveAt(forks.Jovian, info.Time()) + + // Verify GPO upgraded when jovian is active + // We have nothing to assert when jovian is inactive because an isthmus L2 can + // run against isthmus L1 contracts or jovian L1 contracts. + if isJovian { + isJovianinGPO, err := contractio.Read(of.gasPriceOracle.IsJovian(), of.ctx) + of.require.NoError(err) + of.require.True(isJovianinGPO) + } + + // Get updated balance in operator fee vault to compute delta + vaultAfter, err := from.el.stackEL().EthClient().BalanceAt(of.ctx, predeploys.OperatorFeeVaultAddr, receipt.BlockNumber) + of.require.NoError(err) + vaultBefore, err := from.el.stackEL().EthClient().BalanceAt(of.ctx, predeploys.OperatorFeeVaultAddr, big.NewInt(0).Sub(receipt.BlockNumber, big.NewInt(1))) + of.require.NoError(err) + vaultIncrease := new(big.Int).Sub(vaultAfter, vaultBefore) + + // Loop through transactions in block to compute expected operator fee vault increase + expectedOperatorFeeVaultIncrease := big.NewInt(0) + if !(expectedScalar == 0 && expectedConstant == 0) { + // The test submits one user transaction but we loop over all user transactions + // to make the test robust to any other traffic on the chain. + for _, tx := range txs { + if tx.Type() == types.DepositTxType { + continue + } + receipt, err := from.el.stackEL().EthClient().TransactionReceipt(of.ctx, tx.Hash()) + of.require.NoError(err) + + operatorFee := new(big.Int).Mul(big.NewInt(int64(receipt.GasUsed)), big.NewInt(int64(expectedScalar))) + if isJovian { + // Jovian formula: (gasUsed * operatorFeeScalar * 100) + operatorFeeConstant + operatorFee.Mul(operatorFee, big.NewInt(100)) + } else { + // Isthmus formula: (gasUsed * operatorFeeScalar / 1e6) + operatorFeeConstant + operatorFee.Div(operatorFee, big.NewInt(1000000)) + } + operatorFee.Add(operatorFee, big.NewInt(int64(expectedConstant))) + expectedOperatorFeeVaultIncrease = + expectedOperatorFeeVaultIncrease.Add(expectedOperatorFeeVaultIncrease, operatorFee) + } + } + + // Use Cmp for big.Int comparison to avoid representation issues + of.require.Equal(0, expectedOperatorFeeVaultIncrease.Cmp(vaultIncrease), + "operator fee vault balance mismatch: expected %s, got %s", + expectedOperatorFeeVaultIncrease.String(), vaultIncrease.String()) + + actualTotalFee := new(big.Int).Mul(receipt.EffectiveGasPrice, big.NewInt(int64(receipt.GasUsed))) + if receipt.L1Fee != nil { + actualTotalFee.Add(actualTotalFee, receipt.L1Fee) + } + + if expectedScalar != 0 || expectedConstant != 0 { + of.require.NotNil(receipt.OperatorFeeScalar) + of.require.NotNil(receipt.OperatorFeeConstant) + + of.require.Equal(expectedScalar, uint32(*receipt.OperatorFeeScalar)) + of.require.Equal(expectedConstant, *receipt.OperatorFeeConstant) + } + + return OperatorFeeValidationResult{ + TransactionReceipt: receipt, + ExpectedOperatorFee: expectedOperatorFeeVaultIncrease, + ActualTotalFee: actualTotalFee, + VaultBalanceIncrease: vaultIncrease, + } +} + +func (of *OperatorFee) RestoreOriginalConfig() { + of.SetOperatorFee(of.originalScalar, of.originalConstant) +} + +func RunOperatorFeeTest(t devtest.T, l2Chain *L2Network, l1EL *L1ELNode, funderL1, funderL2 *Funder) { + fundAmount := eth.OneTenthEther + alice := funderL2.NewFundedEOA(fundAmount) + alice.WaitForBalance(fundAmount) + bob := funderL2.NewFundedEOA(eth.ZeroWei) + + operatorFee := NewOperatorFee(t, l2Chain, l1EL) + operatorFee.CheckCompatibility() + systemOwner := operatorFee.GetSystemOwner() + funderL1.FundAtLeast(systemOwner, fundAmount) + + // First, ensure L2 is synced with current L1 state before starting tests + t.Log("Ensuring L2 is synced with current L1 state...") + operatorFee.WaitForL2SyncWithCurrentL1State() + + testCases := []struct { + name string + scalar uint32 + constant uint64 + }{ + {"ZeroFees", 0, 0}, + {"NonZeroFees", 300, 400}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t devtest.T) { + operatorFee.SetOperatorFee(tc.scalar, tc.constant) + operatorFee.WaitForL2Sync(tc.scalar, tc.constant) + operatorFee.VerifyL2Config(tc.scalar, tc.constant) + + result := operatorFee.ValidateTransactionFees(alice, bob, big.NewInt(1000), tc.scalar, tc.constant) + + t.Log("Test completed successfully:", + "testCase", tc.name, + "gasUsed", result.TransactionReceipt.GasUsed, + "actualTotalFee", result.ActualTotalFee.String(), + "expectedOperatorFee", result.ExpectedOperatorFee.String(), + "vaultBalanceIncrease", result.VaultBalanceIncrease.String()) + }) + } + + operatorFee.RestoreOriginalConfig() +} diff --git a/op-devstack/dsl/proofs/dispute_game_factory.go b/op-devstack/dsl/proofs/dispute_game_factory.go index 3ff363bffde..50bfd3ed973 100644 --- a/op-devstack/dsl/proofs/dispute_game_factory.go +++ b/op-devstack/dsl/proofs/dispute_game_factory.go @@ -22,7 +22,7 @@ import ( gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/eth" - safetyTypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -35,6 +35,7 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl/contract" "github.com/ethereum-optimism/optimism/op-service/apis" "github.com/ethereum-optimism/optimism/op-service/bigs" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" "github.com/ethereum-optimism/optimism/op-service/txplan" ) @@ -407,7 +408,7 @@ func (f *DisputeGameFactory) startOutputRootGameOfType( func (f *DisputeGameFactory) createOutputGameExtraData(blockNum uint64, cfg *GameCfg) []byte { f.require.NotNil(f.l2CL, "L2 CL is required create output games") if !cfg.allowFuture { - f.l2CL.Reached(safetyTypes.LocalSafe, blockNum, 30) + f.l2CL.Reached(safety.LocalSafe, blockNum, 30) } extraData := make([]byte, 32) binary.BigEndian.PutUint64(extraData[24:], blockNum) @@ -531,9 +532,8 @@ func runFPPForStep(f *DisputeGameFactory, tmpDir string, inputs utils.LocalGameI f.require.NoError(err, "Failed to get absolute path to executable") cmd := exec.Command(exePath, oracleCommand[1:]...) cmd.Dir = tmpDir - log := f.log.New("role", "fpp-trace") - cmd.Stdout = &mipsevm.LoggingWriter{Log: log} - cmd.Stderr = &mipsevm.LoggingWriter{Log: log} + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr cmd.Env = append(append(cmd.Env, os.Environ()...), "NO_COLOR=1") err = cmd.Run() f.require.NoError(err, "Failed to execute game") diff --git a/op-devstack/dsl/rollup_boost.go b/op-devstack/dsl/rollup_boost.go new file mode 100644 index 00000000000..733a9e094e4 --- /dev/null +++ b/op-devstack/dsl/rollup_boost.go @@ -0,0 +1,37 @@ +package dsl + +import ( + opclient "github.com/ethereum-optimism/optimism/op-service/client" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" +) + +type RollupBoostNodesSet []*RollupBoostNode + +func NewRollupBoostNodesSet(inner []stack.RollupBoostNode) RollupBoostNodesSet { + rollupBoostNodes := make([]*RollupBoostNode, len(inner)) + for i, c := range inner { + rollupBoostNodes[i] = NewRollupBoostNode(c) + } + return rollupBoostNodes +} + +// RollupBoostNode wraps a stack.RollupBoostNode interface for DSL operations +type RollupBoostNode struct { + inner stack.RollupBoostNode +} + +func (r *RollupBoostNode) Escape() stack.RollupBoostNode { + return r.inner +} + +// NewRollupBoostNode creates a new RollupBoostNode DSL wrapper +func NewRollupBoostNode(inner stack.RollupBoostNode) *RollupBoostNode { + return &RollupBoostNode{ + inner: inner, + } +} + +func (r *RollupBoostNode) FlashblocksClient() *opclient.WSClient { + return r.inner.FlashblocksClient() +} diff --git a/op-devstack/dsl/sequencer.go b/op-devstack/dsl/sequencer.go new file mode 100644 index 00000000000..04e398e538c --- /dev/null +++ b/op-devstack/dsl/sequencer.go @@ -0,0 +1,71 @@ +package dsl + +import ( + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +type TestSequencer struct { + commonImpl + + inner stack.TestSequencer +} + +func NewTestSequencer(inner stack.TestSequencer) *TestSequencer { + return &TestSequencer{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +func (s *TestSequencer) String() string { + return s.inner.Name() +} + +func (s *TestSequencer) Escape() stack.TestSequencer { + return s.inner +} + +// SequenceBlock builds a block at deterministic timestamp (parent.Time + blockTime). +// This is useful for tests that need predictable block timestamps. +func (s *TestSequencer) SequenceBlock(t devtest.T, chainID eth.ChainID, parent common.Hash) { + ca := s.Escape().ControlAPI(chainID) + + require.NoError(t, ca.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent})) + require.NoError(t, ca.Next(t.Ctx())) +} + +// SequenceBlockWithTxs builds a block with timestamp parent.Time + blockTime with the supplied transactions (bypassing the mempool). +// This makes it ideal for same-timestamp interop testing, and avoids the chance that transactions are sequenced into later blocks. +func (s *TestSequencer) SequenceBlockWithTxs(t devtest.T, chainID eth.ChainID, parent common.Hash, rawTxs [][]byte) { + ctx := t.Ctx() + ca := s.Escape().ControlAPI(chainID) + + // Start a new block building job + require.NoError(t, ca.New(ctx, seqtypes.BuildOpts{Parent: parent})) + + // Include each transaction BEFORE opening + // IncludeTx adds to the job's attrs.Transactions which are used when Open() starts block building + for _, rawTx := range rawTxs { + require.NoError(t, ca.IncludeTx(ctx, hexutil.Bytes(rawTx))) + } + + // Open the block building with the included transactions + require.NoError(t, ca.Open(ctx)) + + // Seal, sign, and commit the block + // Commit is what makes the block canonical in the EL + require.NoError(t, ca.Seal(ctx)) + require.NoError(t, ca.Sign(ctx)) + require.NoError(t, ca.Commit(ctx)) + + // Publish is optional - it broadcasts via P2P which may not be enabled in tests. + // The block is already committed and canonical at this point. + _ = ca.Publish(ctx) // ignore publish errors +} diff --git a/op-devstack/dsl/supernode.go b/op-devstack/dsl/supernode.go new file mode 100644 index 00000000000..6e938abdae5 --- /dev/null +++ b/op-devstack/dsl/supernode.go @@ -0,0 +1,167 @@ +package dsl + +import ( + "context" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" +) + +// Supernode wraps a stack.Supernode interface for DSL operations +type Supernode struct { + commonImpl + inner stack.Supernode + testControl stack.InteropTestControl +} + +// NewSupernode creates a new Supernode DSL wrapper +func NewSupernode(inner stack.Supernode) *Supernode { + return &Supernode{ + commonImpl: commonFromT(inner.T()), + inner: inner, + } +} + +// NewSupernodeWithTestControl creates a new Supernode DSL wrapper with test control support. +// The testControl parameter can be nil if no test control is needed. +func NewSupernodeWithTestControl(inner stack.Supernode, testControl stack.InteropTestControl) *Supernode { + return &Supernode{ + commonImpl: commonFromT(inner.T()), + inner: inner, + testControl: testControl, + } +} + +func (s *Supernode) Name() string { + return s.inner.Name() +} + +func (s *Supernode) String() string { + return s.inner.Name() +} + +// Escape returns the underlying stack.Supernode +func (s *Supernode) Escape() stack.Supernode { + return s.inner +} + +// QueryAPI returns the supernode's query API +func (s *Supernode) QueryAPI() apis.SupernodeQueryAPI { + return s.inner.QueryAPI() +} + +// SuperRootAtTimestamp fetches the super-root at the given timestamp +func (s *Supernode) SuperRootAtTimestamp(timestamp uint64) eth.SuperRootAtTimestampResponse { + ctx, cancel := context.WithTimeout(s.ctx, DefaultTimeout) + defer cancel() + resp, err := s.inner.QueryAPI().SuperRootAtTimestamp(ctx, timestamp) + s.require.NoError(err, "failed to get super-root at timestamp %d", timestamp) + return resp +} + +// AssertSuperRootAtTimestamp asserts that the super-root at the given timestamp matches the expected root claim +func (s *Supernode) AssertSuperRootAtTimestamp(l2SequenceNumber uint64, rootClaim common.Hash) { + resp := s.SuperRootAtTimestamp(l2SequenceNumber) + s.require.NotNilf(resp.Data, "super root does not exist at time %d", l2SequenceNumber) + superRoot := eth.SuperRoot(resp.Data.Super) + s.require.Equal(superRoot[:], rootClaim[:]) +} + +// AwaitValidatedTimestamp waits for the super-root at the given timestamp to be fully validated +func (s *Supernode) AwaitValidatedTimestamp(timestamp uint64) { + ctx, cancel := context.WithTimeout(s.ctx, 5*DefaultTimeout) + defer cancel() + err := wait.For(ctx, 1*time.Second, func() (bool, error) { + resp, err := s.inner.QueryAPI().SuperRootAtTimestamp(ctx, timestamp) + if err != nil { + return false, nil // Ignore transient errors. + } + return resp.Data != nil, nil + }) + s.require.NoError(err, "super-root at timestamp %d was not validated in time", timestamp) +} + +// PauseInterop pauses the interop activity at the given timestamp. +// When the interop activity attempts to process this timestamp, it returns early. +// This function is for integration test control only. +// Requires the Supernode to be created with NewSupernodeWithTestControl. +func (s *Supernode) PauseInterop(ts uint64) { + s.require.NotNil(s.testControl, "PauseInterop requires test control; use NewSupernodeWithTestControl") + s.testControl.PauseInteropActivity(ts) +} + +// ResumeInterop clears any pause on the interop activity, allowing normal processing. +// This function is for integration test control only. +// Requires the Supernode to be created with NewSupernodeWithTestControl. +func (s *Supernode) ResumeInterop() { + s.require.NotNil(s.testControl, "ResumeInterop requires test control; use NewSupernodeWithTestControl") + s.testControl.ResumeInteropActivity() +} + +// EnsureInteropPaused pauses the interop activity and verifies it has stopped. +// It takes the local safe timestamps from two CL nodes, uses the maximum, then: +// 1. Pauses interop at localSafeTimestamp + pauseOffset +// 2. Awaits validation of localSafeTimestamp + pauseOffset - 1 +// 3. Finds the first timestamp that is NOT verified (the actual pause point) +// Returns the first unverified timestamp (adjusted if pause came in late). +// Requires the Supernode to be created with NewSupernodeWithTestControl. +func (s *Supernode) EnsureInteropPaused(clA, clB *L2CLNode, pauseOffset uint64) uint64 { + s.require.NotNil(s.testControl, "EnsureInteropPaused requires test control; use NewSupernodeWithTestControl") + + // Get the local safe of both chains from sync status + statusA := clA.SyncStatus() + statusB := clB.SyncStatus() + + // Use the maximum local safe timestamp between both chains + localSafeTimestamp := max(statusA.LocalSafeL2.Time, statusB.LocalSafeL2.Time) + + s.log.Info("EnsureInteropPaused: initial sync status", + "chainA_local_safe_num", statusA.LocalSafeL2.Number, + "chainA_local_safe_ts", statusA.LocalSafeL2.Time, + "chainB_local_safe_num", statusB.LocalSafeL2.Number, + "chainB_local_safe_ts", statusB.LocalSafeL2.Time, + "localSafeTimestamp", localSafeTimestamp, + ) + + pauseTimestamp := localSafeTimestamp + pauseOffset + awaitTimestamp := pauseTimestamp - 1 + + // Pause interop activity at the pause timestamp + s.testControl.PauseInteropActivity(pauseTimestamp) + + // Await interop validation of the timestamp before the pause + s.AwaitValidatedTimestamp(awaitTimestamp) + + s.log.Info("EnsureInteropPaused: validation confirmed before pause", "timestamp", awaitTimestamp) + + // Find the first timestamp that is NOT verified. + // If the pause came in late, some timestamps past pauseTimestamp may already be verified. + // We scan forward to find where interop actually stopped. + ctx, cancel := context.WithTimeout(s.ctx, DefaultTimeout) + defer cancel() + + for ts := pauseTimestamp; ts < pauseTimestamp+100; ts++ { + resp, err := s.inner.QueryAPI().SuperRootAtTimestamp(ctx, ts) + if err != nil || resp.Data == nil { + // Found the first unverified timestamp + s.log.Info("EnsureInteropPaused: confirmed interop is paused", + "intendedPauseTimestamp", pauseTimestamp, + "actualPauseTimestamp", ts, + ) + return ts + } + // This timestamp is verified, continue scanning + s.log.Warn("EnsureInteropPaused: pause came in late, timestamp already verified", + "timestamp", ts, + "intendedPause", pauseTimestamp, + ) + } + + s.t.Error("EnsureInteropPaused: failed to find unverified timestamp within 100 timestamps") + s.t.FailNow() + return pauseTimestamp +} diff --git a/op-devstack/presets/flashblocks.go b/op-devstack/presets/flashblocks.go index b16d067cdd1..9df4e9dce0c 100644 --- a/op-devstack/presets/flashblocks.go +++ b/op-devstack/presets/flashblocks.go @@ -88,7 +88,6 @@ func singleChainWithFlashblocksFromRuntime(t devtest.T, runtime *sysgo.SingleCha l2ChainID, runtime.Flashblocks.Builder.UserRPC(), runtime.Flashblocks.Builder.FlashblocksWSURL(), - runtime.Flashblocks.Builder.UpdateRuleSet, runtime.L2Network.RollupConfig(), runtime.Flashblocks.Builder, ) diff --git a/op-devstack/presets/interop.go b/op-devstack/presets/interop.go new file mode 100644 index 00000000000..ad29ceb3b89 --- /dev/null +++ b/op-devstack/presets/interop.go @@ -0,0 +1,195 @@ +package presets + +import ( + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + challengerConfig "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/dsl/proofs" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/intentbuilder" + "github.com/ethereum-optimism/optimism/op-service/clock" +) + +type SingleChainInterop struct { + Log log.Logger + T devtest.T + timeTravel *clock.AdvancingClock + + SuperRoots *dsl.Supernode + TestSequencer *dsl.TestSequencer + + L1Network *dsl.L1Network + L1EL *dsl.L1ELNode + L1CL *dsl.L1CLNode + + L2ChainA *dsl.L2Network + L2BatcherA *dsl.L2Batcher + L2ELA *dsl.L2ELNode + L2CLA *dsl.L2CLNode + + Wallet *dsl.HDWallet + + FaucetA *dsl.Faucet + FaucetL1 *dsl.Faucet + FunderL1 *dsl.Funder + FunderA *dsl.Funder + + // May be nil if not using sysgo + challengerConfig *challengerConfig.Config +} + +func (s *SingleChainInterop) L2Networks() []*dsl.L2Network { + return []*dsl.L2Network{ + s.L2ChainA, + } +} + +func (s *SingleChainInterop) DisputeGameFactory() *proofs.DisputeGameFactory { + s.T.Require().NotNil(s.SuperRoots, "supernode not configured for this preset") + return proofs.NewDisputeGameFactory(s.T, s.L1Network, s.L1EL.EthClient(), s.L2ChainA.DisputeGameFactoryProxyAddr(), nil, nil, s.SuperRoots, s.challengerConfig) +} + +func (s *SingleChainInterop) AdvanceTime(amount time.Duration) { + s.T.Require().NotNil(s.timeTravel, "attempting to advance time on incompatible system") + s.timeTravel.AdvanceTime(amount) +} + +func (s *SingleChainInterop) proofValidationContext() (devtest.T, *dsl.L1ELNode, []*dsl.L2Network) { + return s.T, s.L1EL, []*dsl.L2Network{s.L2ChainA} +} + +type SimpleInterop struct { + SingleChainInterop + + L2ChainB *dsl.L2Network + L2BatcherB *dsl.L2Batcher + L2ELB *dsl.L2ELNode + L2CLB *dsl.L2CLNode + + FaucetB *dsl.Faucet + FunderB *dsl.Funder +} + +func (s *SimpleInterop) L2Networks() []*dsl.L2Network { + return []*dsl.L2Network{ + s.L2ChainA, s.L2ChainB, + } +} + +func (s *SimpleInterop) proofValidationContext() (devtest.T, *dsl.L1ELNode, []*dsl.L2Network) { + return s.T, s.L1EL, s.L2Networks() +} + +func (s *SingleChainInterop) StandardBridge(l2Chain *dsl.L2Network) *dsl.StandardBridge { + return dsl.NewStandardBridge(s.T, l2Chain, s.L1EL) +} + +// NewSimpleInterop creates a fresh SimpleInterop target for the current +// test using the super-root proofs system backed by op-supernode. +func NewSimpleInterop(t devtest.T, opts ...Option) *SimpleInterop { + presetCfg, _ := collectSupportedPresetConfig(t, "NewSimpleInterop", opts, twoL2SupernodeProofsPresetSupportedOptionKinds) + return simpleInteropFromSupernodeProofsRuntime(t, sysgo.NewTwoL2SupernodeProofsRuntimeWithConfig(t, true, presetCfg)) +} + +// NewSingleChainInterop creates a fresh SingleChainInterop target for the +// current test using the single-chain super-root proofs system backed by op-supernode. +func NewSingleChainInterop(t devtest.T, opts ...Option) *SingleChainInterop { + presetCfg, _ := collectSupportedPresetConfig(t, "NewSingleChainInterop", opts, supernodeProofsPresetSupportedOptionKinds) + return singleChainInteropFromSupernodeProofsRuntime(t, sysgo.NewSingleChainSupernodeProofsRuntimeWithConfig(t, true, presetCfg)) +} + +// NewSimpleInteropIsthmusSuper creates a fresh SimpleInterop target for the current test +// using the Isthmus super-root system backed by op-supernode. +func NewSimpleInteropIsthmusSuper(t devtest.T, opts ...Option) *SimpleInterop { + presetCfg, _ := collectSupportedPresetConfig(t, "NewSimpleInteropIsthmusSuper", opts, supernodeProofsPresetSupportedOptionKinds) + return simpleInteropFromSupernodeProofsRuntime(t, sysgo.NewTwoL2SupernodeProofsRuntimeWithConfig(t, false, presetCfg)) +} + +// NewSingleChainInteropIsthmusSuper creates a fresh SingleChainInterop target for the +// current test using the single-chain Isthmus super-root system backed by op-supernode. +func NewSingleChainInteropIsthmusSuper(t devtest.T, opts ...Option) *SingleChainInterop { + presetCfg, _ := collectSupportedPresetConfig(t, "NewSingleChainInteropIsthmusSuper", opts, supernodeProofsPresetSupportedOptionKinds) + return singleChainInteropFromSupernodeProofsRuntime(t, sysgo.NewSingleChainSupernodeProofsRuntimeWithConfig(t, false, presetCfg)) +} + +// NewSingleChainInteropSuperRootAtGenesis creates a fresh SingleChainInterop +// target where SuperPermissionedDisputeGame is installed in the permissioned +// slot as part of the initial op-deployer apply - no post-deploy OPCMv2 +// migration runs. This exercises the initial-deploy path for super-root +// dispute games tracked by ethereum-optimism/optimism#18729. +func NewSingleChainInteropSuperRootAtGenesis(t devtest.T, opts ...Option) *SingleChainInterop { + presetCfg, _ := collectSupportedPresetConfig(t, "NewSingleChainInteropSuperRootAtGenesis", opts, supernodeProofsPresetSupportedOptionKinds) + return singleChainInteropFromSupernodeProofsRuntime(t, sysgo.NewSingleChainSuperRootAtGenesisRuntimeWithConfig(t, presetCfg)) +} + +// WithSuggestedInteropActivationOffset suggests a hardfork time offset to use. +// This is applied e.g. to the deployment if running against sysgo. +func WithSuggestedInteropActivationOffset(offset uint64) Option { + return WithDeployerOptions( + func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithForkAtOffset(forks.Interop, &offset) + } + }, + ) +} + +// WithSequencingWindow suggests a sequencing window to use, and checks the maximum sequencing window. +// The sequencing windows are expressed in number of L1 execution-layer blocks till sequencing window expiry. +// This is applied to runtime deployment/config validation. +func WithSequencingWindow(suggestedSequencingWindow uint64, maxSequencingWindow uint64) Option { + return option{ + kinds: optionKindDeployer | optionKindMaxSequencingWindow, + applyFn: func(cfg *sysgo.PresetConfig) { + cfg.DeployerOptions = append(cfg.DeployerOptions, sysgo.WithSequencingWindow(suggestedSequencingWindow)) + v := maxSequencingWindow + cfg.MaxSequencingWindow = &v + }, + } +} + +// WithInteropNotAtGenesis adds a test-gate that checks +// if the interop hardfork is configured at a non-genesis time. +func WithInteropNotAtGenesis() Option { + return WithRequireInteropNotAtGenesis() +} + +type MultiSupervisorInterop struct { + SimpleInterop + + // Supervisor does not support multinode so need a additional supervisor for verifier nodes + SupervisorSecondary *dsl.Supervisor + + L2ELA2 *dsl.L2ELNode + L2CLA2 *dsl.L2CLNode + L2ELB2 *dsl.L2ELNode + L2CLB2 *dsl.L2CLNode +} + +// NewMultiSupervisorInterop initializes a fresh multi-supervisor interop target for the +// current test. +func NewMultiSupervisorInterop(t devtest.T, opts ...Option) *MultiSupervisorInterop { + _, _ = collectSupportedPresetConfig(t, "NewMultiSupervisorInterop", opts, 0) + return multiSupervisorInteropFromRuntime(t, sysgo.NewMultiSupervisorInteropRuntime(t)) +} + +// MinimalInteropNoSupervisor is like Minimal but with interop contracts deployed. +// No supervisor is running - this tests interop contract deployment with local finality. +type MinimalInteropNoSupervisor struct { + Minimal +} + +// NewMinimalInteropNoSupervisor creates a fresh MinimalInteropNoSupervisor target for the +// current test. +func NewMinimalInteropNoSupervisor(t devtest.T, opts ...Option) *MinimalInteropNoSupervisor { + _, _ = collectSupportedPresetConfig(t, "NewMinimalInteropNoSupervisor", opts, 0) + return &MinimalInteropNoSupervisor{ + Minimal: *minimalFromRuntime(t, sysgo.NewMinimalInteropNoSupervisorRuntime(t)), + } +} diff --git a/op-devstack/presets/minimal.go b/op-devstack/presets/minimal.go new file mode 100644 index 00000000000..0ad50bbe418 --- /dev/null +++ b/op-devstack/presets/minimal.go @@ -0,0 +1,72 @@ +package presets + +import ( + "time" + + "github.com/ethereum/go-ethereum/log" + + challengerConfig "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/dsl/proofs" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-service/clock" +) + +type Minimal struct { + Log log.Logger + T devtest.T + timeTravel *clock.AdvancingClock + + L1Network *dsl.L1Network + L1EL *dsl.L1ELNode + L1CL *dsl.L1CLNode + + L2Chain *dsl.L2Network + L2Batcher *dsl.L2Batcher + L2EL *dsl.L2ELNode + L2CL *dsl.L2CLNode + + Wallet *dsl.HDWallet + + FaucetL1 *dsl.Faucet + FaucetL2 *dsl.Faucet + FunderL1 *dsl.Funder + FunderL2 *dsl.Funder + + // May be nil if not using sysgo + challengerConfig *challengerConfig.Config +} + +func (m *Minimal) L2Networks() []*dsl.L2Network { + return []*dsl.L2Network{ + m.L2Chain, + } +} + +func (m *Minimal) StandardBridge() *dsl.StandardBridge { + return dsl.NewStandardBridge(m.T, m.L2Chain, m.L1EL) +} + +func (m *Minimal) DisputeGameFactory() *proofs.DisputeGameFactory { + return proofs.NewDisputeGameFactory(m.T, m.L1Network, m.L1EL.EthClient(), m.L2Chain.DisputeGameFactoryProxyAddr(), m.L2CL, m.L2EL, nil, m.challengerConfig) +} + +func (m *Minimal) AdvanceTime(amount time.Duration) { + m.T.Require().NotNil(m.timeTravel, "attempting to advance time on incompatible system") + m.timeTravel.AdvanceTime(amount) +} + +func (m *Minimal) proofValidationContext() (devtest.T, *dsl.L1ELNode, []*dsl.L2Network) { + return m.T, m.L1EL, m.L2Networks() +} + +// NewMinimal creates a fresh Minimal target for the current test. +// +// The target is created from the minimal runtime plus any additional preset options. +func NewMinimal(t devtest.T, opts ...Option) *Minimal { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewMinimal", opts, minimalPresetSupportedOptionKinds) + out := minimalFromRuntime(t, sysgo.NewMinimalRuntimeWithConfig(t, presetCfg)) + presetOpts.applyPreset(out) + return out +} diff --git a/op-devstack/presets/minimal_from_runtime.go b/op-devstack/presets/minimal_from_runtime.go new file mode 100644 index 00000000000..0b8a3512d1b --- /dev/null +++ b/op-devstack/presets/minimal_from_runtime.go @@ -0,0 +1,76 @@ +package presets + +import ( + challengerConfig "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +func minimalFromRuntime(t devtest.T, runtime *sysgo.SingleChainRuntime) *Minimal { + l1ChainID := runtime.L1Network.ChainID() + l2ChainID := runtime.L2Network.ChainID() + + l1Network := newPresetL1Network(t, "l1", runtime.L1Network.ChainConfig()) + l1EL := newL1ELFrontend(t, "l1", l1ChainID, runtime.L1EL.UserRPC()) + l1CL := newL1CLFrontend(t, "l1", l1ChainID, runtime.L1CL.BeaconHTTPAddr(), runtime.L1CL.FakePoS()) + l1Network.AddL1ELNode(l1EL) + l1Network.AddL1CLNode(l1CL) + + l2Chain := newPresetL2Network( + t, + "l2a", + runtime.L2Network.ChainConfig(), + runtime.L2Network.RollupConfig(), + runtime.L2Network.Deployment(), + newKeyring(runtime.Keys, t.Require()), + l1Network, + ) + l2EL := newL2ELFrontend(t, "sequencer", l2ChainID, runtime.L2EL.UserRPC(), runtime.L2EL.EngineRPC(), runtime.L2EL.JWTPath(), runtime.L2Network.RollupConfig()) + l2CL := newL2CLFrontend(t, "sequencer", l2ChainID, runtime.L2CL.UserRPC(), runtime.L2CL) + l2CL.attachEL(l2EL) + l2Batcher := newL2BatcherFrontend(t, "main", l2ChainID, runtime.L2Batcher.UserRPC()) + l2Chain.AddL2ELNode(l2EL) + l2Chain.AddL2CLNode(l2CL) + l2Chain.AddL2Batcher(l2Batcher) + + var challengerCfg *challengerConfig.Config + if runtime.L2Challenger != nil { + challengerCfg = runtime.L2Challenger.Config() + } + if challengerCfg != nil { + l2Chain.AddL2Challenger(newPresetL2Challenger(t, "main", l2ChainID, challengerCfg)) + } + + faucetL1Frontend := newFaucetFrontendForChain(t, runtime.FaucetService, l1ChainID) + faucetL2Frontend := newFaucetFrontendForChain(t, runtime.FaucetService, l2ChainID) + l1Network.AddFaucet(faucetL1Frontend) + l2Chain.AddFaucet(faucetL2Frontend) + faucetL1 := dsl.NewFaucet(faucetL1Frontend) + faucetL2 := dsl.NewFaucet(faucetL2Frontend) + + l1ELDSL := dsl.NewL1ELNode(l1EL) + l1CLDSL := dsl.NewL1CLNode(l1CL) + l2ELDSL := dsl.NewL2ELNode(l2EL) + l2CLDSL := dsl.NewL2CLNode(l2CL) + + out := &Minimal{ + Log: t.Logger(), + T: t, + timeTravel: runtime.TimeTravel, + L1Network: dsl.NewL1Network(l1Network, l1ELDSL, l1CLDSL), + L1EL: l1ELDSL, + L1CL: l1CLDSL, + L2Chain: dsl.NewL2Network(l2Chain, l2ELDSL, l2CLDSL, l1ELDSL, nil, nil), + L2Batcher: dsl.NewL2Batcher(l2Batcher), + L2EL: l2ELDSL, + L2CL: l2CLDSL, + Wallet: dsl.NewRandomHDWallet(t, 30), // Random for test isolation + FaucetL1: faucetL1, + FaucetL2: faucetL2, + challengerConfig: challengerCfg, + } + out.FunderL1 = dsl.NewFunder(out.Wallet, out.FaucetL1, out.L1EL) + out.FunderL2 = dsl.NewFunder(out.Wallet, out.FaucetL2, out.L2EL) + return out +} diff --git a/op-devstack/presets/minimal_with_conductors.go b/op-devstack/presets/minimal_with_conductors.go new file mode 100644 index 00000000000..cab721da079 --- /dev/null +++ b/op-devstack/presets/minimal_with_conductors.go @@ -0,0 +1,25 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type MinimalWithConductors struct { + *Minimal + + ConductorSets map[eth.ChainID]dsl.ConductorSet +} + +// NewMinimalWithConductors creates a fresh MinimalWithConductors target for the current +// test. +// +// The target is created from the runtime plus any additional preset options. +func NewMinimalWithConductors(t devtest.T, opts ...Option) *MinimalWithConductors { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewMinimalWithConductors", opts, minimalWithConductorsPresetSupportedOptionKinds) + out := minimalWithConductorsFromRuntime(t, sysgo.NewMinimalWithConductorsRuntimeWithConfig(t, presetCfg)) + presetOpts.applyPreset(out) + return out +} diff --git a/op-devstack/presets/minimal_with_kona.go b/op-devstack/presets/minimal_with_kona.go new file mode 100644 index 00000000000..5048eec1d66 --- /dev/null +++ b/op-devstack/presets/minimal_with_kona.go @@ -0,0 +1,75 @@ +package presets + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/shared/rustbin" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/ptr" +) + +// MinimalWithKona embeds Minimal and bundles everything needed to run kona-host in --native mode +// against a live devstack. +type MinimalWithKona struct { + *Minimal + + vmConfig *vm.Config + dir string +} + +// NewMinimalWithKona creates a Minimal preset with the kona-host binary located and the chain +// configs needed to invoke kona natively. +func NewMinimalWithKona(t devtest.T, opts ...Option) *MinimalWithKona { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewMinimalWithKona", opts, minimalPresetSupportedOptionKinds) + out := minimalWithKonaFromRuntime(t, sysgo.NewMinimalRuntimeWithConfig(t, presetCfg)) + presetOpts.applyPreset(out) + return out +} + +func minimalWithKonaFromRuntime(t devtest.T, runtime *sysgo.SingleChainRuntime) *MinimalWithKona { + dir := t.TempDir() + return &MinimalWithKona{ + Minimal: minimalFromRuntime(t, runtime), + vmConfig: runtime.VMConfig(t, dir), + dir: dir, + } +} + +func (m *MinimalWithKona) RunKonaNative(agreedBlock, claimBlock uint64) bool { + return rustbin.RunKonaNative(m.T, m.T.Logger(), m.vmConfig, m.dir, m.L2CL.LocalGameInputs(agreedBlock, claimBlock)) +} + +// WithKonaKarstAtGenesis configures kona-host to think Karst was active at +// genesis, regardless of what the live chain's rollup config says. Use to +// deliberately mismatch kona's view of the chain from the live devstack so +// kona disagrees on state transitions that depend on Karst. +func WithKonaKarstAtGenesis() Option { + return option{ + kinds: optionKindAfterBuild, + applyPresetFn: func(target any) { + sys, ok := target.(*MinimalWithKona) + if !ok { + return + } + rollupPath := filepath.Join(sys.dir, "rollup.json") + data, err := os.ReadFile(rollupPath) + sys.T.Require().NoError(err, "read kona rollup config") + + var cfg rollup.Config + sys.T.Require().NoError(json.Unmarshal(data, &cfg), "unmarshal kona rollup config") + + cfg.KarstTime = ptr.Zero64 + + out, err := json.Marshal(&cfg) + sys.T.Require().NoError(err, "marshal kona rollup config") + sys.T.Require().NoError( + os.WriteFile(rollupPath, out, 0o644), + "write kona rollup config") + }, + } +} diff --git a/op-devstack/presets/mixed_frontends.go b/op-devstack/presets/mixed_frontends.go new file mode 100644 index 00000000000..c2a969e6cc7 --- /dev/null +++ b/op-devstack/presets/mixed_frontends.go @@ -0,0 +1,132 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type MixedSingleChainNodeFrontends struct { + Spec sysgo.MixedSingleChainNodeSpec + EL *dsl.L2ELNode + CL *dsl.L2CLNode +} + +type MixedSingleChainFrontends struct { + L1Network *dsl.L1Network + L1EL *dsl.L1ELNode + L1CL *dsl.L1CLNode + L2Network *dsl.L2Network + L2Batcher *dsl.L2Batcher + FaucetL1 *dsl.Faucet + FaucetL2 *dsl.Faucet + TestSequencer *dsl.TestSequencer + Nodes []MixedSingleChainNodeFrontends +} + +func newFaucetFrontendByName(t devtest.T, name string, chainID eth.ChainID, faucetRPC string) *faucetFrontend { + rpcCl, err := client.NewRPC(t.Ctx(), t.Logger(), faucetRPC, client.WithLazyDial()) + t.Require().NoError(err) + t.Cleanup(rpcCl.Close) + + return newPresetFaucet(t, name, chainID, rpcCl) +} + +func NewMixedSingleChainFrontends(t devtest.T, runtime *sysgo.MixedSingleChainRuntime) *MixedSingleChainFrontends { + l1Backend := runtime.L1Network + l2Backend := runtime.L2Network + l1ChainID := eth.ChainIDFromBig(l1Backend.ChainConfig().ChainID) + l2ChainID := eth.ChainIDFromBig(l2Backend.ChainConfig().ChainID) + + l1Network := newPresetL1Network(t, "l1", l1Backend.ChainConfig()) + l1EL := newL1ELFrontend(t, "l1", l1ChainID, runtime.L1EL.UserRPC()) + l1CL := newL1CLFrontend(t, "l1", l1ChainID, runtime.L1CL.BeaconHTTPAddr(), runtime.L1CL.FakePoS()) + l1Network.AddL1ELNode(l1EL) + l1Network.AddL1CLNode(l1CL) + + l2Network := newPresetL2Network( + t, + "l2a", + l2Backend.ChainConfig(), + l2Backend.RollupConfig(), + l2Backend.Deployment(), + newKeyring(l2Backend.Keys(), t.Require()), + l1Network, + ) + l2BatcherBackend := runtime.L2Batcher + l2Batcher := newL2BatcherFrontend(t, "main", l2ChainID, l2BatcherBackend.UserRPC()) + l2Network.AddL2Batcher(l2Batcher) + + l1ELDSL := dsl.NewL1ELNode(l1EL) + l1CLDSL := dsl.NewL1CLNode(l1CL) + + nodes := make([]MixedSingleChainNodeFrontends, 0, len(runtime.Nodes)) + var primaryL2EL *dsl.L2ELNode + var primaryL2CL *dsl.L2CLNode + for _, node := range runtime.Nodes { + l2EL := newL2ELFrontend( + t, + node.Spec.ELKey, + l2ChainID, + node.EL.UserRPC(), + node.EL.EngineRPC(), + node.EL.JWTPath(), + l2Backend.RollupConfig(), + node.EL, + ) + l2CL := newL2CLFrontend(t, node.Spec.CLKey, l2ChainID, node.CL.UserRPC(), node.CL) + l2CL.attachEL(l2EL) + l2Network.AddL2ELNode(l2EL) + l2Network.AddL2CLNode(l2CL) + l2ELDSL := dsl.NewL2ELNode(l2EL) + l2CLDSL := dsl.NewL2CLNode(l2CL) + if primaryL2EL == nil && node.Spec.IsSequencer { + primaryL2EL = l2ELDSL + primaryL2CL = l2CLDSL + } + nodes = append(nodes, MixedSingleChainNodeFrontends{ + Spec: node.Spec, + EL: l2ELDSL, + CL: l2CLDSL, + }) + } + t.Require().NotNil(primaryL2EL, "missing primary mixed L2 EL") + t.Require().NotNil(primaryL2CL, "missing primary mixed L2 CL") + + l1FaucetName, l1FaucetRPC, ok := defaultFaucetForChain(runtime.FaucetService, l1ChainID) + t.Require().Truef(ok, "missing default faucet for chain %s", l1ChainID) + l2FaucetName, l2FaucetRPC, ok := defaultFaucetForChain(runtime.FaucetService, l2ChainID) + t.Require().Truef(ok, "missing default faucet for chain %s", l2ChainID) + faucetL1Frontend := newFaucetFrontendByName(t, l1FaucetName, l1ChainID, l1FaucetRPC) + faucetL2Frontend := newFaucetFrontendByName(t, l2FaucetName, l2ChainID, l2FaucetRPC) + l1Network.AddFaucet(faucetL1Frontend) + l2Network.AddFaucet(faucetL2Frontend) + faucetL1 := dsl.NewFaucet(faucetL1Frontend) + faucetL2 := dsl.NewFaucet(faucetL2Frontend) + + var testSequencer *dsl.TestSequencer + if backend := runtime.TestSequencer; backend != nil { + t.Require().NotEmpty(backend.Name, "expected test sequencer name") + testSequencer = dsl.NewTestSequencer(newTestSequencerFrontend( + t, + backend.Name, + backend.AdminRPC, + backend.ControlRPC, + backend.JWTSecret, + )) + } + + return &MixedSingleChainFrontends{ + L1Network: dsl.NewL1Network(l1Network, l1ELDSL, l1CLDSL), + L1EL: l1ELDSL, + L1CL: l1CLDSL, + L2Network: dsl.NewL2Network(l2Network, primaryL2EL, primaryL2CL, l1ELDSL, nil, nil), + L2Batcher: dsl.NewL2Batcher(l2Batcher), + FaucetL1: faucetL1, + FaucetL2: faucetL2, + TestSequencer: testSequencer, + Nodes: nodes, + } +} diff --git a/op-devstack/presets/networks.go b/op-devstack/presets/networks.go new file mode 100644 index 00000000000..2467fd230bb --- /dev/null +++ b/op-devstack/presets/networks.go @@ -0,0 +1,328 @@ +package presets + +import ( + "slices" + "sort" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/locks" + "github.com/ethereum-optimism/optimism/op-service/testreq" +) + +type presetCommon struct { + log log.Logger + t devtest.T + req *testreq.Assertions + labels *locks.RWMap[string, string] + name string +} + +func newPresetCommon(t devtest.T, name string) presetCommon { + return presetCommon{ + log: t.Logger(), + t: t, + req: t.Require(), + labels: new(locks.RWMap[string, string]), + name: name, + } +} + +func (c *presetCommon) T() devtest.T { + return c.t +} + +func (c *presetCommon) Logger() log.Logger { + return c.log +} + +func (c *presetCommon) Name() string { + return c.name +} + +func (c *presetCommon) Label(key string) string { + out, _ := c.labels.Get(key) + return out +} + +func (c *presetCommon) SetLabel(key, value string) { + c.labels.Set(key, value) +} + +func (c *presetCommon) require() *testreq.Assertions { + return c.req +} + +type presetNetworkBase struct { + presetCommon + chainCfg *params.ChainConfig + chainID eth.ChainID + + faucets []*faucetFrontend + syncTesters []*syncTesterFrontend +} + +func (n *presetNetworkBase) ChainID() eth.ChainID { + return n.chainID +} + +func (n *presetNetworkBase) ChainConfig() *params.ChainConfig { + return n.chainCfg +} + +func (n *presetNetworkBase) Faucets() []stack.Faucet { + return mapSlice(sortByNameFunc(n.faucets), func(v *faucetFrontend) stack.Faucet { return v }) +} + +func (n *presetNetworkBase) AddFaucet(v *faucetFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "faucet %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.faucets, v.Name()) + n.require().False(exists, "faucet %s must not already exist", v.Name()) + n.faucets = append(n.faucets, v) +} + +func (n *presetNetworkBase) SyncTesters() []stack.SyncTester { + return mapSlice(sortByNameFunc(n.syncTesters), func(v *syncTesterFrontend) stack.SyncTester { return v }) +} + +func (n *presetNetworkBase) AddSyncTester(v *syncTesterFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "sync tester %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.syncTesters, v.Name()) + n.require().False(exists, "sync tester %s must not already exist", v.Name()) + n.syncTesters = append(n.syncTesters, v) +} + +type presetL1Network struct { + presetNetworkBase + + l1ELNodes []*l1ELFrontend + l1CLNodes []*l1CLFrontend +} + +var _ stack.L1Network = (*presetL1Network)(nil) + +func newPresetL1Network(t devtest.T, name string, chainCfg *params.ChainConfig) *presetL1Network { + chainID := eth.ChainIDFromBig(chainCfg.ChainID) + t = t.WithCtx(stack.ContextWithChainID(t.Ctx(), chainID)) + t.Require().NotEmpty(name, "l1 network name must not be empty") + return &presetL1Network{ + presetNetworkBase: presetNetworkBase{ + presetCommon: newPresetCommon(t, name), + chainCfg: chainCfg, + chainID: chainID, + }, + } +} + +func (n *presetL1Network) AddL1ELNode(v *l1ELFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "l1 EL node %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.l1ELNodes, v.Name()) + n.require().False(exists, "l1 EL node %s must not already exist", v.Name()) + n.l1ELNodes = append(n.l1ELNodes, v) +} + +func (n *presetL1Network) AddL1CLNode(v *l1CLFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "l1 CL node %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.l1CLNodes, v.Name()) + n.require().False(exists, "l1 CL node %s must not already exist", v.Name()) + n.l1CLNodes = append(n.l1CLNodes, v) +} + +func (n *presetL1Network) L1ELNodes() []stack.L1ELNode { + return mapSlice(sortByNameFunc(n.l1ELNodes), func(v *l1ELFrontend) stack.L1ELNode { return v }) +} + +func (n *presetL1Network) L1CLNodes() []stack.L1CLNode { + return mapSlice(sortByNameFunc(n.l1CLNodes), func(v *l1CLFrontend) stack.L1CLNode { return v }) +} + +type presetL2Network struct { + presetNetworkBase + + rollupCfg *rollup.Config + deployment stack.L2Deployment + keys *keyringImpl + + l1 *presetL1Network + + l2Batchers []*l2BatcherFrontend + l2Proposers []*l2ProposerFrontend + l2Challengers []*l2ChallengerFrontend + l2CLNodes []*l2CLFrontend + l2ELNodes []*l2ELFrontend + conductors []*conductorFrontend + rollupBoostNodes []*rollupBoostFrontend + oprBuilderNodes []*oprBuilderFrontend +} + +var _ stack.L2Network = (*presetL2Network)(nil) + +func newPresetL2Network( + t devtest.T, + name string, + chainCfg *params.ChainConfig, + rollupCfg *rollup.Config, + deployment stack.L2Deployment, + keys *keyringImpl, + l1 *presetL1Network, +) *presetL2Network { + chainID := eth.ChainIDFromBig(chainCfg.ChainID) + t = t.WithCtx(stack.ContextWithChainID(t.Ctx(), chainID)) + t.Require().NotEmpty(name, "l2 network name must not be empty") + t.Require().Equal(l1.ChainID(), eth.ChainIDFromBig(rollupCfg.L1ChainID), "rollup config must match expected L1 chain") + t.Require().Equal(chainID, eth.ChainIDFromBig(rollupCfg.L2ChainID), "rollup config must match expected L2 chain") + return &presetL2Network{ + presetNetworkBase: presetNetworkBase{ + presetCommon: newPresetCommon(t, name), + chainCfg: chainCfg, + chainID: chainID, + }, + rollupCfg: rollupCfg, + deployment: deployment, + keys: keys, + l1: l1, + } +} + +func (n *presetL2Network) RollupConfig() *rollup.Config { + n.require().NotNil(n.rollupCfg, "l2 chain %s must have a rollup config", n.Name()) + return n.rollupCfg +} + +func (n *presetL2Network) Deployment() stack.L2Deployment { + n.require().NotNil(n.deployment, "l2 chain %s must have a deployment", n.Name()) + return n.deployment +} + +func (n *presetL2Network) Keys() stack.Keys { + n.require().NotNil(n.keys, "l2 chain %s must have keys", n.Name()) + return n.keys +} + +func (n *presetL2Network) L1() stack.L1Network { + n.require().NotNil(n.l1, "l2 chain %s must have an L1 chain", n.Name()) + return n.l1 +} + +func (n *presetL2Network) AddL2Batcher(v *l2BatcherFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "l2 batcher %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.l2Batchers, v.Name()) + n.require().False(exists, "l2 batcher %s must not already exist", v.Name()) + n.l2Batchers = append(n.l2Batchers, v) +} + +func (n *presetL2Network) AddL2Proposer(v *l2ProposerFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "l2 proposer %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.l2Proposers, v.Name()) + n.require().False(exists, "l2 proposer %s must not already exist", v.Name()) + n.l2Proposers = append(n.l2Proposers, v) +} + +func (n *presetL2Network) AddL2Challenger(v *l2ChallengerFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "l2 challenger %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.l2Challengers, v.Name()) + n.require().False(exists, "l2 challenger %s must not already exist", v.Name()) + n.l2Challengers = append(n.l2Challengers, v) +} + +func (n *presetL2Network) AddL2CLNode(v *l2CLFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "l2 CL node %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.l2CLNodes, v.Name()) + n.require().False(exists, "l2 CL node %s must not already exist", v.Name()) + n.l2CLNodes = append(n.l2CLNodes, v) +} + +func (n *presetL2Network) AddL2ELNode(v *l2ELFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "l2 EL node %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.l2ELNodes, v.Name()) + n.require().False(exists, "l2 EL node %s must not already exist", v.Name()) + n.l2ELNodes = append(n.l2ELNodes, v) +} + +func (n *presetL2Network) AddConductor(v *conductorFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "conductor %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.conductors, v.Name()) + n.require().False(exists, "conductor %s must not already exist", v.Name()) + n.conductors = append(n.conductors, v) +} + +func (n *presetL2Network) AddRollupBoostNode(v *rollupBoostFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "rollup boost node %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.rollupBoostNodes, v.Name()) + n.require().False(exists, "rollup boost node %s must not already exist", v.Name()) + n.rollupBoostNodes = append(n.rollupBoostNodes, v) +} + +func (n *presetL2Network) AddOPRBuilderNode(v *oprBuilderFrontend) { + n.require().Equal(n.chainID, v.ChainID(), "OPR builder node %s must be on chain %s", v.Name(), n.chainID) + _, exists := componentByName(n.oprBuilderNodes, v.Name()) + n.require().False(exists, "OPR builder node %s must not already exist", v.Name()) + n.oprBuilderNodes = append(n.oprBuilderNodes, v) +} + +func (n *presetL2Network) L2Batchers() []stack.L2Batcher { + return mapSlice(sortByNameFunc(n.l2Batchers), func(v *l2BatcherFrontend) stack.L2Batcher { return v }) +} + +func (n *presetL2Network) L2Proposers() []stack.L2Proposer { + return mapSlice(sortByNameFunc(n.l2Proposers), func(v *l2ProposerFrontend) stack.L2Proposer { return v }) +} + +func (n *presetL2Network) L2Challengers() []stack.L2Challenger { + return mapSlice(sortByNameFunc(n.l2Challengers), func(v *l2ChallengerFrontend) stack.L2Challenger { return v }) +} + +func (n *presetL2Network) L2CLNodes() []stack.L2CLNode { + return mapSlice(sortByNameFunc(n.l2CLNodes), func(v *l2CLFrontend) stack.L2CLNode { return v }) +} + +func (n *presetL2Network) L2ELNodes() []stack.L2ELNode { + return mapSlice(sortByNameFunc(n.l2ELNodes), func(v *l2ELFrontend) stack.L2ELNode { return v }) +} + +func (n *presetL2Network) Conductors() []stack.Conductor { + return mapSlice(sortByNameFunc(n.conductors), func(v *conductorFrontend) stack.Conductor { return v }) +} + +func (n *presetL2Network) RollupBoostNodes() []stack.RollupBoostNode { + return mapSlice(sortByNameFunc(n.rollupBoostNodes), func(v *rollupBoostFrontend) stack.RollupBoostNode { return v }) +} + +func (n *presetL2Network) OPRBuilderNodes() []stack.OPRBuilderNode { + return mapSlice(sortByNameFunc(n.oprBuilderNodes), func(v *oprBuilderFrontend) stack.OPRBuilderNode { return v }) +} + +type named interface { + Name() string +} + +func componentByName[T named](components []T, name string) (T, bool) { + for _, component := range components { + if component.Name() == name { + return component, true + } + } + var zero T + return zero, false +} + +func sortByNameFunc[T named](components []T) []T { + out := slices.Clone(components) + sort.Slice(out, func(i, j int) bool { + return out[i].Name() < out[j].Name() + }) + return out +} + +func mapSlice[T any, U any](items []T, mapFn func(T) U) []U { + out := make([]U, len(items)) + for i, item := range items { + out[i] = mapFn(item) + } + return out +} diff --git a/op-devstack/presets/option_validation.go b/op-devstack/presets/option_validation.go index 793229dcd55..2137c1bed9c 100644 --- a/op-devstack/presets/option_validation.go +++ b/op-devstack/presets/option_validation.go @@ -26,6 +26,8 @@ const ( optionKindRequireInteropNotAtGen optionKindAfterBuild optionKindProofValidation + optionKindMessageExpiryWindow + optionKindInteropFilter ) const allOptionKinds = optionKindDeployer | @@ -42,7 +44,9 @@ const allOptionKinds = optionKindDeployer | optionKindMaxSequencingWindow | optionKindRequireInteropNotAtGen | optionKindAfterBuild | - optionKindProofValidation + optionKindProofValidation | + optionKindMessageExpiryWindow | + optionKindInteropFilter var optionKindLabels = []struct { kind optionKinds @@ -63,6 +67,8 @@ var optionKindLabels = []struct { {kind: optionKindRequireInteropNotAtGen, label: "interop-not-at-genesis"}, {kind: optionKindAfterBuild, label: "after-build hooks"}, {kind: optionKindProofValidation, label: "proof-validation hooks"}, + {kind: optionKindMessageExpiryWindow, label: "message expiry window"}, + {kind: optionKindInteropFilter, label: "interop filter"}, } func (k optionKinds) String() string { @@ -124,20 +130,7 @@ const minimalWithConductorsPresetSupportedOptionKinds = optionKindDeployer | const simpleWithSyncTesterPresetSupportedOptionKinds = minimalPresetSupportedOptionKinds | optionKindGlobalSyncTesterEL -const singleChainInteropPresetSupportedOptionKinds = optionKindDeployer | - optionKindBatcher | - optionKindProposer | - optionKindGlobalL2CL | - optionKindL1EL | - optionKindAddedGameType | - optionKindRespectedGameType | - optionKindTimeTravel | - optionKindMaxSequencingWindow | - optionKindRequireInteropNotAtGen | - optionKindAfterBuild | - optionKindProofValidation - -const simpleInteropSuperProofsPresetSupportedOptionKinds = optionKindDeployer | +const supernodeProofsPresetSupportedOptionKinds = optionKindDeployer | optionKindBatcher | optionKindProposer | optionKindGlobalL2CL | @@ -155,7 +148,7 @@ const twoL2SupernodePresetSupportedOptionKinds = optionKindDeployer | const twoL2SupernodeInteropPresetSupportedOptionKinds = optionKindDeployer | optionKindTimeTravel | - optionKindL1EL + optionKindL1EL | + optionKindInteropFilter -const singleChainWithFlashblocksPresetSupportedOptionKinds = optionKindDeployer | - optionKindOPRBuilder +const singleChainWithFlashblocksPresetSupportedOptionKinds = optionKindOPRBuilder diff --git a/op-devstack/presets/options.go b/op-devstack/presets/options.go index 37dceaaacac..317079ef5ba 100644 --- a/op-devstack/presets/options.go +++ b/op-devstack/presets/options.go @@ -3,7 +3,6 @@ package presets import ( gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-devstack/sysgo" - "github.com/ethereum-optimism/optimism/op-service/eth" ) type Option interface { @@ -101,25 +100,6 @@ func WithDeployerOptions(opts ...sysgo.DeployerOption) Option { } } -// WithLocalContractSourcesAt configures a preset to load local contracts-bedrock -// artifacts from the supplied directory instead of resolving them relative to -// the process working directory. -func WithLocalContractSourcesAt(path string) Option { - var kinds optionKinds - if path != "" { - kinds = optionKindDeployer - } - return option{ - kinds: kinds, - applyFn: func(cfg *sysgo.PresetConfig) { - if path == "" { - return - } - cfg.LocalContractArtifactsPath = path - }, - } -} - func WithBatcherOption(opt sysgo.BatcherOption) Option { var kinds optionKinds if opt != nil { @@ -266,6 +246,17 @@ func WithMaxSequencingWindow(max uint64) Option { } } +// WithInteropFilter enables the in-process op-interop-filter for EL transaction +// validation. Only supported on supernode interop presets. +func WithInteropFilter() Option { + return option{ + kinds: optionKindInteropFilter, + applyFn: func(cfg *sysgo.PresetConfig) { + cfg.UseInteropFilter = true + }, + } +} + func WithRequireInteropNotAtGenesis() Option { return option{ kinds: optionKindRequireInteropNotAtGen, @@ -274,10 +265,3 @@ func WithRequireInteropNotAtGenesis() Option { }, } } - -// WithL2BlockTimes configures per-chain L2 block times via the deployer. -// The blockTimes map keys are L2 chain IDs and values are the desired block -// time in seconds for that chain. -func WithL2BlockTimes(blockTimes map[eth.ChainID]uint64) Option { - return WithDeployerOptions(sysgo.WithL2BlockTimes(blockTimes)) -} diff --git a/op-devstack/presets/options_test.go b/op-devstack/presets/options_test.go index 9b0ab678159..a779ffa738d 100644 --- a/op-devstack/presets/options_test.go +++ b/op-devstack/presets/options_test.go @@ -40,7 +40,6 @@ func TestOptionKindsFromCompositeOptions(t *testing.T) { t.Run("nil adapters do not claim support kinds", func(t *testing.T) { require.Zero(t, WithDeployerOptions(nil).optionKinds()) - require.Zero(t, WithLocalContractSourcesAt("").optionKinds()) require.Zero(t, WithBatcherOption(nil).optionKinds()) require.Zero(t, WithGlobalL2CLOption(nil).optionKinds()) require.Zero(t, WithGlobalSyncTesterELOption(nil).optionKinds()) @@ -50,11 +49,6 @@ func TestOptionKindsFromCompositeOptions(t *testing.T) { }) } -func TestWithLocalContractSourcesAt(t *testing.T) { - cfg, _ := collectPresetConfig([]Option{WithLocalContractSourcesAt("/tmp/contracts-bedrock")}) - require.Equal(t, "/tmp/contracts-bedrock", cfg.LocalContractArtifactsPath) -} - func TestUnsupportedPresetOptionKinds(t *testing.T) { builderOpt := sysgo.OPRBuilderNodeOptionFn(func(devtest.CommonT, sysgo.ComponentTarget, *sysgo.OPRBuilderNodeConfig) {}) @@ -86,10 +80,9 @@ func TestUnsupportedPresetOptionKinds(t *testing.T) { want: optionKindChallengerCannonKona, }, { - name: "flashblocks allows builder and deployer adapters", + name: "flashblocks only allows builder adapters", supported: singleChainWithFlashblocksPresetSupportedOptionKinds, opts: Combine( - WithLocalContractSourcesAt("/tmp/contracts-bedrock"), WithOPRBuilderOption(builderOpt), WithTimeTravelEnabled(), ), diff --git a/op-devstack/presets/proof.go b/op-devstack/presets/proof.go new file mode 100644 index 00000000000..509c2f0af9c --- /dev/null +++ b/op-devstack/presets/proof.go @@ -0,0 +1,106 @@ +package presets + +import ( + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/dsl/contract" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + ps "github.com/ethereum-optimism/optimism/op-proposer/proposer" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" + "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" +) + +type proofValidationTarget interface { + proofValidationContext() (devtest.T, *dsl.L1ELNode, []*dsl.L2Network) +} + +func afterBuildProofValidation(fn func(t devtest.T, elNode *dsl.L1ELNode, l2Networks []*dsl.L2Network)) Option { + var kinds optionKinds + if fn != nil { + kinds = optionKindAfterBuild | optionKindProofValidation + } + return option{ + kinds: kinds, + applyPresetFn: func(target any) { + if fn == nil { + return + } + proofTarget, ok := target.(proofValidationTarget) + if !ok { + return + } + t, elNode, l2Networks := proofTarget.proofValidationContext() + fn(t, elNode, l2Networks) + }, + } +} + +func WithRespectedGameType(gameType gameTypes.GameType) Option { + opts := WithProposerGameType(gameType) + opts = Combine(opts, + WithRespectedGameTypeOverride(gameType), + RequireRespectedGameType(gameType), + ) + return opts +} + +func RequireGameTypePresent(gameType gameTypes.GameType) Option { + return afterBuildProofValidation(func(t devtest.T, elNode *dsl.L1ELNode, l2Networks []*dsl.L2Network) { + for _, l2Network := range l2Networks { + dgf := bindings.NewBindings[bindings.DisputeGameFactory]( + bindings.WithClient(elNode.EthClient()), + bindings.WithTo(l2Network.Escape().Deployment().DisputeGameFactoryProxyAddr()), + bindings.WithTest(t), + ) + gameImpl := contract.Read(dgf.GameImpls(uint32(gameType))) + t.Gate().NotZerof(gameImpl, "Dispute game factory must have a game implementation for %s", gameType) + } + }) +} + +func RequireRespectedGameType(gameType gameTypes.GameType) Option { + return afterBuildProofValidation(func(t devtest.T, elNode *dsl.L1ELNode, l2Networks []*dsl.L2Network) { + for _, l2Network := range l2Networks { + l1PortalAddr := l2Network.Escape().RollupConfig().DepositContractAddress + l1Portal := bindings.NewBindings[bindings.OptimismPortal2]( + bindings.WithClient(elNode.EthClient()), + bindings.WithTo(l1PortalAddr), + bindings.WithTest(t)) + + respectedGameType, err := contractio.Read(l1Portal.RespectedGameType(), t.Ctx()) + t.Require().NoError(err, "Failed to read respected game type") + t.Gate().EqualValuesf(gameType, respectedGameType, "Respected game type must be %s", gameType) + } + }) +} + +func WithProposerGameType(gameType gameTypes.GameType) Option { + return WithProposerOption(func(id sysgo.ComponentTarget, cfg *ps.CLIConfig) { + cfg.DisputeGameType = uint32(gameType) + }) +} + +func WithGuardianMatchL1PAO() Option { + return WithDeployerOptions( + sysgo.WithGuardianMatchL1PAO(), + ) +} + +func WithFinalizationPeriodSeconds(n uint64) Option { + return WithDeployerOptions( + sysgo.WithFinalizationPeriodSeconds(n), + ) +} + +func WithProofMaturityDelaySeconds(seconds uint64) Option { + return WithDeployerOptions( + sysgo.WithProofMaturityDelaySeconds(seconds), + ) +} + +func WithDisputeGameFinalityDelaySeconds(seconds uint64) Option { + return WithDeployerOptions( + sysgo.WithDisputeGameFinalityDelaySeconds(seconds), + ) +} diff --git a/op-devstack/presets/rpc_frontends.go b/op-devstack/presets/rpc_frontends.go index 85d43b2eac5..26d139ace3c 100644 --- a/op-devstack/presets/rpc_frontends.go +++ b/op-devstack/presets/rpc_frontends.go @@ -346,19 +346,17 @@ type oprBuilderFrontend struct { engineClient *sources.EngineClient flashblocksClient *opclient.WSClient lifecycle stack.Lifecycle - updateRuleSet func(rulesYaml string) error } var _ stack.OPRBuilderNode = (*oprBuilderFrontend)(nil) -func newPresetOPRBuilderNode(t devtest.T, name string, chainID eth.ChainID, rpcCl opclient.RPC, rollupCfg *rollup.Config, flashblocksCl *opclient.WSClient, updateRuleSet func(string) error) *oprBuilderFrontend { +func newPresetOPRBuilderNode(t devtest.T, name string, chainID eth.ChainID, rpcCl opclient.RPC, rollupCfg *rollup.Config, flashblocksCl *opclient.WSClient) *oprBuilderFrontend { engineClient, err := sources.NewEngineClient(rpcCl, t.Logger(), nil, sources.EngineClientDefaultConfig(rollupCfg)) t.Require().NoError(err) return &oprBuilderFrontend{ rpcELNode: newRPCELNode(t, name, chainID, rpcCl, 0), engineClient: engineClient, flashblocksClient: flashblocksCl, - updateRuleSet: updateRuleSet, } } @@ -374,10 +372,6 @@ func (r *oprBuilderFrontend) FlashblocksClient() *opclient.WSClient { return r.flashblocksClient } -func (r *oprBuilderFrontend) UpdateRuleSet(rulesYaml string) error { - return r.updateRuleSet(rulesYaml) -} - func (r *oprBuilderFrontend) Start() { r.require().NotNil(r.lifecycle, "OPR builder node %s is not lifecycle-controllable", r.Name()) r.lifecycle.Start() @@ -429,39 +423,6 @@ func (r *rollupBoostFrontend) Stop() { r.lifecycle.Stop() } -type supervisorFrontend struct { - presetCommon - api apis.SupervisorAPI - lifecycle stack.Lifecycle -} - -var _ stack.Supervisor = (*supervisorFrontend)(nil) - -func newPresetSupervisor(t devtest.T, name string, rpcCl opclient.RPC) *supervisorFrontend { - return &supervisorFrontend{ - presetCommon: newPresetCommon(t, name), - api: sources.NewSupervisorClient(rpcCl), - } -} - -func (r *supervisorFrontend) AdminAPI() apis.SupervisorAdminAPI { - return r.api -} - -func (r *supervisorFrontend) QueryAPI() apis.SupervisorQueryAPI { - return r.api -} - -func (r *supervisorFrontend) Start() { - r.require().NotNil(r.lifecycle, "supervisor %s is not lifecycle-controllable", r.Name()) - r.lifecycle.Start() -} - -func (r *supervisorFrontend) Stop() { - r.require().NotNil(r.lifecycle, "supervisor %s is not lifecycle-controllable", r.Name()) - r.lifecycle.Stop() -} - type supernodeFrontend struct { presetCommon api apis.SupernodeQueryAPI diff --git a/op-devstack/presets/simple_with_synctester.go b/op-devstack/presets/simple_with_synctester.go new file mode 100644 index 00000000000..1a46d7cb3bc --- /dev/null +++ b/op-devstack/presets/simple_with_synctester.go @@ -0,0 +1,31 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +type SimpleWithSyncTester struct { + Minimal + + SyncTester *dsl.SyncTester + SyncTesterL2EL *dsl.L2ELNode + L2CL2 *dsl.L2CLNode +} + +// NewSimpleWithSyncTester creates a fresh SimpleWithSyncTester target for the current +// test. +// +// The target is created from the runtime plus any additional preset options. +func NewSimpleWithSyncTester(t devtest.T, opts ...Option) *SimpleWithSyncTester { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewSimpleWithSyncTester", opts, simpleWithSyncTesterPresetSupportedOptionKinds) + out := simpleWithSyncTesterFromRuntime(t, sysgo.NewSimpleWithSyncTesterRuntimeWithConfig(t, presetCfg)) + presetOpts.applyPreset(out) + return out +} + +func WithHardforkSequentialActivation(startFork, endFork forks.Name, delta uint64) Option { + return WithDeployerOptions(sysgo.WithHardforkSequentialActivation(startFork, endFork, &delta)) +} diff --git a/op-devstack/presets/singlechain_from_runtime.go b/op-devstack/presets/singlechain_from_runtime.go new file mode 100644 index 00000000000..7af9d401ea1 --- /dev/null +++ b/op-devstack/presets/singlechain_from_runtime.go @@ -0,0 +1,209 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-service/eth" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +func singleChainMultiNodeFromRuntime(t devtest.T, runtime *sysgo.SingleChainRuntime, runSyncChecks bool) *SingleChainMultiNode { + minimal := minimalFromRuntime(t, runtime) + l2ChainID := runtime.L2Network.ChainID() + nodeB := runtime.Nodes["b"] + t.Require().NotNil(nodeB, "missing single-chain node b") + + l2ELB := newL2ELFrontend( + t, + "b", + l2ChainID, + nodeB.EL.UserRPC(), + nodeB.EL.EngineRPC(), + nodeB.EL.JWTPath(), + runtime.L2Network.RollupConfig(), + nodeB.EL, + ) + l2CLB := newL2CLFrontend( + t, + "b", + l2ChainID, + nodeB.CL.UserRPC(), + nodeB.CL, + ) + l2CLB.attachEL(l2ELB) + l2Net, ok := minimal.L2Chain.Escape().(*presetL2Network) + t.Require().True(ok, "expected preset L2 network") + l2Net.AddL2ELNode(l2ELB) + l2Net.AddL2CLNode(l2CLB) + + preset := &SingleChainMultiNode{ + Minimal: *minimal, + L2ELB: dsl.NewL2ELNode(l2ELB), + L2CLB: dsl.NewL2CLNode(l2CLB), + } + if runtime.P2PEnabled { + preset.L2CLB.ManagePeer(preset.L2CL) + } + if runSyncChecks { + // Ensure the follower node is in sync with the sequencer before starting tests. + // CrossSafe requires derivation to run, which under ELSync can only begin + // after the EL completes P2P sync and the node emits its first forkchoice + // update — so the wait strategy depends on the follower's sync mode. + var crossSafeCheck dsl.CheckFunc + opNode, hasOpNode := nodeB.CL.(*sysgo.OpNode) + isELSync := hasOpNode && opNode.SyncMode() == nodeSync.ELSync + if isELSync { + // EL-sync's CrossSafe wait has unpredictable duration in CI. Wait + // up to initialSyncCrossSafeELSyncMaxWait, but only as long as the + // follower's LocalUnsafe head keeps advancing — if gossip stalls + // for initialSyncCrossSafeELSyncStallTimeout we fail fast rather + // than burn the whole budget. See #20649. + crossSafeCheck = preset.L2CLB.MatchedWithProgressFn( + preset.L2CL, + safety.CrossSafe, safety.LocalUnsafe, + initialSyncCrossSafeELSyncMaxWait, + initialSyncCrossSafeELSyncStallTimeout, + ) + } else { + crossSafeCheck = preset.L2CLB.MatchedFn(preset.L2CL, safety.CrossSafe, initialSyncCheckAttemptsCrossSafe) + } + runInitialSyncChecks(t, preset.L2ELB, isELSync, + crossSafeCheck, + preset.L2CLB.MatchedFn(preset.L2CL, safety.LocalUnsafe, initialSyncCheckAttemptsLocalUnsafe), + ) + } + return preset +} + +func singleChainMultiNodeWithTestSeqFromRuntime(t devtest.T, runtime *sysgo.SingleChainRuntime) *SingleChainMultiNodeWithTestSeq { + preset := singleChainMultiNodeFromRuntime(t, runtime, false) + testSequencer := newTestSequencerFrontend( + t, + runtime.TestSequencer.Name, + runtime.TestSequencer.AdminRPC, + runtime.TestSequencer.ControlRPC, + runtime.TestSequencer.JWTSecret, + ) + return &SingleChainMultiNodeWithTestSeq{ + SingleChainMultiNode: *preset, + TestSequencer: dsl.NewTestSequencer(testSequencer), + } +} + +func singleChainTwoVerifiersFromRuntime(t devtest.T, runtime *sysgo.SingleChainRuntime) *SingleChainTwoVerifiers { + base := singleChainMultiNodeFromRuntime(t, runtime, false) + l2ChainID := runtime.L2Network.ChainID() + nodeC := runtime.Nodes["c"] + t.Require().NotNil(nodeC, "missing single-chain node c") + + l2ELC := newL2ELFrontend( + t, + "c", + l2ChainID, + nodeC.EL.UserRPC(), + nodeC.EL.EngineRPC(), + nodeC.EL.JWTPath(), + runtime.L2Network.RollupConfig(), + nodeC.EL, + ) + l2CLC := newL2CLFrontend( + t, + "c", + l2ChainID, + nodeC.CL.UserRPC(), + nodeC.CL, + ) + l2CLC.attachEL(l2ELC) + l2Net, ok := base.L2Chain.Escape().(*presetL2Network) + t.Require().True(ok, "expected preset L2 network") + l2Net.AddL2ELNode(l2ELC) + l2Net.AddL2CLNode(l2CLC) + testSequencer := newTestSequencerFrontend( + t, + runtime.TestSequencer.Name, + runtime.TestSequencer.AdminRPC, + runtime.TestSequencer.ControlRPC, + runtime.TestSequencer.JWTSecret, + ) + preset := &SingleChainTwoVerifiers{ + SingleChainMultiNode: *base, + L2ELC: dsl.NewL2ELNode(l2ELC), + L2CLC: dsl.NewL2CLNode(l2CLC), + TestSequencer: dsl.NewTestSequencer(testSequencer), + } + preset.L2CLC.ManagePeer(preset.L2CL) + preset.L2CLC.ManagePeer(preset.L2CLB) + return preset +} + +func simpleWithSyncTesterFromRuntime(t devtest.T, runtime *sysgo.SingleChainRuntime) *SimpleWithSyncTester { + minimal := minimalFromRuntime(t, runtime) + l2ChainID := runtime.L2Network.ChainID() + t.Require().NotNil(runtime.SyncTester, "missing sync tester support") + t.Require().NotNil(runtime.SyncTester.Node, "missing sync tester node") + + syncTesterName, syncTesterRPC, ok := runtime.SyncTester.Service.DefaultEndpoint(runtime.L2Network.ChainID()) + t.Require().Truef(ok, "missing sync tester for chain %s", runtime.L2Network.ChainID()) + syncTester := newSyncTesterFrontend(t, syncTesterName, l2ChainID, syncTesterRPC) + + syncTesterL2EL := newL2ELFrontend( + t, + "sync-tester-el", + l2ChainID, + runtime.SyncTester.Node.EL.UserRPC(), + runtime.SyncTester.Node.EL.EngineRPC(), + runtime.SyncTester.Node.EL.JWTPath(), + runtime.L2Network.RollupConfig(), + ) + l2CL2 := newL2CLFrontend( + t, + "verifier", + l2ChainID, + runtime.SyncTester.Node.CL.UserRPC(), + runtime.SyncTester.Node.CL, + ) + l2CL2.attachEL(syncTesterL2EL) + l2Net, ok := minimal.L2Chain.Escape().(*presetL2Network) + t.Require().True(ok, "expected preset L2 network") + l2Net.AddSyncTester(syncTester) + l2Net.AddL2ELNode(syncTesterL2EL) + l2Net.AddL2CLNode(l2CL2) + + preset := &SimpleWithSyncTester{ + Minimal: *minimal, + SyncTester: dsl.NewSyncTester(syncTester), + SyncTesterL2EL: dsl.NewL2ELNode(syncTesterL2EL), + L2CL2: dsl.NewL2CLNode(l2CL2), + } + preset.L2CL2.ManagePeer(preset.L2CL) + return preset +} + +func minimalWithConductorsFromRuntime(t devtest.T, runtime *sysgo.SingleChainRuntime) *MinimalWithConductors { + minimal := minimalFromRuntime(t, runtime) + l2ChainID := runtime.L2Network.ChainID() + t.Require().NotNil(runtime.Conductors, "missing conductor support") + + cAName := "sequencer" + cBName := "b" + cCName := "c" + cA := newConductorFrontend(t, cAName, l2ChainID, runtime.Conductors[cAName].HTTPEndpoint()) + cB := newConductorFrontend(t, cBName, l2ChainID, runtime.Conductors[cBName].HTTPEndpoint()) + cC := newConductorFrontend(t, cCName, l2ChainID, runtime.Conductors[cCName].HTTPEndpoint()) + l2Net, ok := minimal.L2Chain.Escape().(*presetL2Network) + t.Require().True(ok, "expected preset L2 network") + l2Net.AddConductor(cA) + l2Net.AddConductor(cB) + l2Net.AddConductor(cC) + + conductors := []stack.Conductor{cA, cB, cC} + return &MinimalWithConductors{ + Minimal: minimal, + ConductorSets: map[eth.ChainID]dsl.ConductorSet{ + l2ChainID: dsl.NewConductorSet(conductors), + }, + } +} diff --git a/op-devstack/presets/singlechain_multinode.go b/op-devstack/presets/singlechain_multinode.go new file mode 100644 index 00000000000..79c7ee69c89 --- /dev/null +++ b/op-devstack/presets/singlechain_multinode.go @@ -0,0 +1,65 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +type SingleChainMultiNode struct { + Minimal + + L2ELB *dsl.L2ELNode + L2CLB *dsl.L2CLNode +} + +// NewSingleChainMultiNode creates a fresh SingleChainMultiNode target for the current +// test. +// +// The target is created from the runtime plus any additional preset options. +func NewSingleChainMultiNode(t devtest.T, opts ...Option) *SingleChainMultiNode { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewSingleChainMultiNode", opts, minimalPresetSupportedOptionKinds) + out := singleChainMultiNodeFromRuntime(t, sysgo.NewSingleChainMultiNodeRuntimeWithConfig(t, true, presetCfg), true) + presetOpts.applyPreset(out) + return out +} + +// NewSingleChainMultiNodeWithoutCheck creates a fresh SingleChainMultiNode target for the +// current test, without running the initial verifier sync checks. +// +// The target is created from the runtime plus any additional preset options. +func NewSingleChainMultiNodeWithoutCheck(t devtest.T, opts ...Option) *SingleChainMultiNode { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewSingleChainMultiNodeWithoutCheck", opts, minimalPresetSupportedOptionKinds) + out := singleChainMultiNodeFromRuntime(t, sysgo.NewSingleChainMultiNodeRuntimeWithConfig(t, true, presetCfg), false) + presetOpts.applyPreset(out) + return out +} + +// NewSingleChainMultiNodeWithoutP2PWithoutCheck creates a fresh SingleChainMultiNode +// target without preconfigured sequencer/verifier P2P links and without running initial sync +// checks. +// +// The target is created from the runtime plus any additional preset options. +func NewSingleChainMultiNodeWithoutP2PWithoutCheck(t devtest.T, opts ...Option) *SingleChainMultiNode { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewSingleChainMultiNodeWithoutP2PWithoutCheck", opts, minimalPresetSupportedOptionKinds) + out := singleChainMultiNodeFromRuntime(t, sysgo.NewSingleChainMultiNodeRuntimeWithConfig(t, false, presetCfg), false) + presetOpts.applyPreset(out) + return out +} + +type SingleChainMultiNodeWithTestSeq struct { + SingleChainMultiNode + + TestSequencer *dsl.TestSequencer +} + +// NewSingleChainMultiNodeWithTestSeq creates a fresh +// SingleChainMultiNodeWithTestSeq target for the current test. +// +// The target is created from the runtime plus any additional preset options. +func NewSingleChainMultiNodeWithTestSeq(t devtest.T, opts ...Option) *SingleChainMultiNodeWithTestSeq { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewSingleChainMultiNodeWithTestSeq", opts, minimalPresetSupportedOptionKinds) + out := singleChainMultiNodeWithTestSeqFromRuntime(t, sysgo.NewSingleChainMultiNodeRuntimeWithConfig(t, true, presetCfg)) + presetOpts.applyPreset(out) + return out +} diff --git a/op-devstack/presets/singlechain_twoverifiers.go b/op-devstack/presets/singlechain_twoverifiers.go new file mode 100644 index 00000000000..4de57d3c2b9 --- /dev/null +++ b/op-devstack/presets/singlechain_twoverifiers.go @@ -0,0 +1,27 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +type SingleChainTwoVerifiers struct { + SingleChainMultiNode + + L2ELC *dsl.L2ELNode + L2CLC *dsl.L2CLNode + + TestSequencer *dsl.TestSequencer +} + +// NewSingleChainTwoVerifiersWithoutCheck creates a fresh +// SingleChainTwoVerifiers target for the current test. +// +// The target is created from the runtime plus any additional preset options. +func NewSingleChainTwoVerifiersWithoutCheck(t devtest.T, opts ...Option) *SingleChainTwoVerifiers { + presetCfg, presetOpts := collectSupportedPresetConfig(t, "NewSingleChainTwoVerifiersWithoutCheck", opts, minimalPresetSupportedOptionKinds) + out := singleChainTwoVerifiersFromRuntime(t, sysgo.NewSingleChainTwoVerifiersRuntimeWithConfig(t, presetCfg)) + presetOpts.applyPreset(out) + return out +} diff --git a/op-devstack/presets/superproofs_from_runtime.go b/op-devstack/presets/superproofs_from_runtime.go new file mode 100644 index 00000000000..e08bcf6ab15 --- /dev/null +++ b/op-devstack/presets/superproofs_from_runtime.go @@ -0,0 +1,154 @@ +package presets + +import ( + challengerConfig "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +func attachChallenger(t devtest.T, l2Net *dsl.L2Network, name string, chainID eth.ChainID, cfg *challengerConfig.Config) { + if cfg == nil { + return + } + net, ok := l2Net.Escape().(*presetL2Network) + t.Require().True(ok, "expected preset L2 network") + net.AddL2Challenger(newPresetL2Challenger(t, name, chainID, cfg)) +} + +func simpleInteropFromSupernodeProofsRuntime(t devtest.T, runtime *sysgo.MultiChainRuntime) *SimpleInterop { + chainA := runtime.Chains["l2a"] + chainB := runtime.Chains["l2b"] + t.Require().NotNil(chainA, "missing l2a superproofs chain") + t.Require().NotNil(chainB, "missing l2b superproofs chain") + twoL2, components := twoL2FromRuntime(t, runtime) + + supernodeFrontend := newSupernodeFrontend(t, "supernode-two-l2-system", runtime.Supernode.UserRPC()) + testSequencer := newTestSequencerFrontend( + t, + runtime.TestSequencer.Name, + runtime.TestSequencer.AdminRPC, + runtime.TestSequencer.ControlRPC, + runtime.TestSequencer.JWTSecret, + ) + + out := &SimpleInterop{ + SingleChainInterop: SingleChainInterop{ + Log: t.Logger(), + T: t, + timeTravel: runtime.TimeTravel, + SuperRoots: dsl.NewSupernodeWithTestControl(supernodeFrontend, runtime.Supernode), + TestSequencer: dsl.NewTestSequencer(testSequencer), + L1Network: twoL2.L1Network, + L1EL: twoL2.L1EL, + L1CL: twoL2.L1CL, + L2ChainA: twoL2.L2A, + L2BatcherA: dsl.NewL2Batcher(components.l2ABatcher), + L2ELA: dsl.NewL2ELNode(components.l2AEL), + L2CLA: twoL2.L2ACL, + Wallet: dsl.NewRandomHDWallet(t, 30), + FaucetA: components.faucetA, + FaucetL1: dsl.NewFaucet(newFaucetFrontendForChain(t, runtime.FaucetService, runtime.L1Network.ChainID())), + challengerConfig: runtime.L2ChallengerConfig, + }, + L2ChainB: twoL2.L2B, + L2BatcherB: dsl.NewL2Batcher(components.l2BBatcher), + L2ELB: dsl.NewL2ELNode(components.l2BEL), + L2CLB: twoL2.L2BCL, + FaucetB: components.faucetB, + } + out.FunderL1 = dsl.NewFunder(out.Wallet, out.FaucetL1, out.L1EL) + out.FunderA = dsl.NewFunder(out.Wallet, out.FaucetA, out.L2ELA) + out.FunderB = dsl.NewFunder(out.Wallet, out.FaucetB, out.L2ELB) + l1Net, ok := out.L1Network.Escape().(*presetL1Network) + t.Require().True(ok, "expected preset L1 network") + l1Net.AddFaucet(out.FaucetL1.Escape().(*faucetFrontend)) + + attachChallenger(t, out.L2ChainA, "main", chainA.Network.ChainID(), out.challengerConfig) + attachChallenger(t, out.L2ChainB, "main", chainB.Network.ChainID(), out.challengerConfig) + return out +} + +func singleChainInteropFromSupernodeProofsRuntime(t devtest.T, runtime *sysgo.MultiChainRuntime) *SingleChainInterop { + chainA := runtime.Chains["l2a"] + t.Require().NotNil(chainA, "missing l2a superproofs chain") + l1ChainID := runtime.L1Network.ChainID() + l2ChainID := chainA.Network.ChainID() + + l1Network := newPresetL1Network(t, "l1", runtime.L1Network.ChainConfig()) + l1EL := newL1ELFrontend(t, "l1", l1ChainID, runtime.L1EL.UserRPC()) + l1CL := newL1CLFrontend(t, "l1", l1ChainID, runtime.L1CL.BeaconHTTPAddr(), runtime.L1CL.FakePoS()) + l1Network.AddL1ELNode(l1EL) + l1Network.AddL1CLNode(l1CL) + + l2Chain := newPresetL2Network( + t, + "l2a", + chainA.Network.ChainConfig(), + chainA.Network.RollupConfig(), + chainA.Network.Deployment(), + newKeyring(runtime.Keys, t.Require()), + l1Network, + ) + l2EL := newL2ELFrontend( + t, + "sequencer", + l2ChainID, + chainA.EL.UserRPC(), + chainA.EL.EngineRPC(), + chainA.EL.JWTPath(), + chainA.Network.RollupConfig(), + chainA.EL, + ) + l2CL := newL2CLFrontend(t, "sequencer", l2ChainID, chainA.CL.UserRPC(), chainA.CL) + l2CL.attachEL(l2EL) + l2Batcher := newL2BatcherFrontend(t, "main", l2ChainID, chainA.Batcher.UserRPC()) + l2Chain.AddL2ELNode(l2EL) + l2Chain.AddL2CLNode(l2CL) + l2Chain.AddL2Batcher(l2Batcher) + + challengerCfg := runtime.L2ChallengerConfig + if challengerCfg != nil { + l2Chain.AddL2Challenger(newPresetL2Challenger(t, "main", l2ChainID, challengerCfg)) + } + + supernodeFrontend := newSupernodeFrontend(t, "supernode-single-system-proofs", runtime.Supernode.UserRPC()) + testSequencer := newTestSequencerFrontend( + t, + runtime.TestSequencer.Name, + runtime.TestSequencer.AdminRPC, + runtime.TestSequencer.ControlRPC, + runtime.TestSequencer.JWTSecret, + ) + l1ELDSL := dsl.NewL1ELNode(l1EL) + l1CLDSL := dsl.NewL1CLNode(l1CL) + l2ELDSL := dsl.NewL2ELNode(l2EL) + l2CLDSL := dsl.NewL2CLNode(l2CL) + faucetAFrontend := newFaucetFrontendForChain(t, runtime.FaucetService, l2ChainID) + faucetL1Frontend := newFaucetFrontendForChain(t, runtime.FaucetService, l1ChainID) + + out := &SingleChainInterop{ + Log: t.Logger(), + T: t, + timeTravel: runtime.TimeTravel, + SuperRoots: dsl.NewSupernodeWithTestControl(supernodeFrontend, runtime.Supernode), + TestSequencer: dsl.NewTestSequencer(testSequencer), + L1Network: dsl.NewL1Network(l1Network, l1ELDSL, l1CLDSL), + L1EL: l1ELDSL, + L1CL: l1CLDSL, + L2ChainA: dsl.NewL2Network(l2Chain, l2ELDSL, l2CLDSL, l1ELDSL, nil, nil), + L2BatcherA: dsl.NewL2Batcher(l2Batcher), + L2ELA: l2ELDSL, + L2CLA: l2CLDSL, + Wallet: dsl.NewRandomHDWallet(t, 30), + FaucetA: dsl.NewFaucet(faucetAFrontend), + FaucetL1: dsl.NewFaucet(faucetL1Frontend), + challengerConfig: challengerCfg, + } + l1Network.AddFaucet(faucetL1Frontend) + l2Chain.AddFaucet(faucetAFrontend) + out.FunderL1 = dsl.NewFunder(out.Wallet, out.FaucetL1, out.L1EL) + out.FunderA = dsl.NewFunder(out.Wallet, out.FaucetA, out.L2ELA) + return out +} diff --git a/op-devstack/presets/sysgo_runtime.go b/op-devstack/presets/sysgo_runtime.go index 9dee4db2fd7..ee82d7e5615 100644 --- a/op-devstack/presets/sysgo_runtime.go +++ b/op-devstack/presets/sysgo_runtime.go @@ -84,7 +84,7 @@ func newL2BatcherFrontend(t devtest.T, name string, chainID eth.ChainID, rpcEndp return newPresetL2Batcher(t, name, chainID, rpcCl) } -func newOPRBuilderFrontend(t devtest.T, name string, chainID eth.ChainID, userRPC string, flashblocksWSURL string, updateRuleSet func(string) error, rollupCfg *rollup.Config, lifecycle ...stack.Lifecycle) *oprBuilderFrontend { +func newOPRBuilderFrontend(t devtest.T, name string, chainID eth.ChainID, userRPC string, flashblocksWSURL string, rollupCfg *rollup.Config, lifecycle ...stack.Lifecycle) *oprBuilderFrontend { rpcCl, err := client.NewRPC(t.Ctx(), t.Logger(), userRPC, client.WithLazyDial()) t.Require().NoError(err) t.Cleanup(rpcCl.Close) @@ -96,7 +96,7 @@ func newOPRBuilderFrontend(t devtest.T, name string, chainID eth.ChainID, userRP }) t.Require().NoError(err) - oprb := newPresetOPRBuilderNode(t, name, chainID, rpcCl, rollupCfg, wsCl, updateRuleSet) + oprb := newPresetOPRBuilderNode(t, name, chainID, rpcCl, rollupCfg, wsCl) if len(lifecycle) > 0 { oprb.lifecycle = lifecycle[0] } @@ -122,17 +122,6 @@ func newRollupBoostFrontend(t devtest.T, name string, chainID eth.ChainID, userR return rollupBoost } -func newSupervisorFrontend(t devtest.T, name string, userRPC string, lifecycle ...stack.Lifecycle) *supervisorFrontend { - rpcCl, err := client.NewRPC(t.Ctx(), t.Logger(), userRPC, client.WithLazyDial()) - t.Require().NoError(err) - t.Cleanup(rpcCl.Close) - supervisor := newPresetSupervisor(t, name, rpcCl) - if len(lifecycle) > 0 { - supervisor.lifecycle = lifecycle[0] - } - return supervisor -} - func newSupernodeFrontend(t devtest.T, name string, userRPC string) *supernodeFrontend { rpcCl, err := client.NewRPC(t.Ctx(), t.Logger(), userRPC, client.WithLazyDial()) t.Require().NoError(err) diff --git a/op-devstack/presets/timetravel.go b/op-devstack/presets/timetravel.go new file mode 100644 index 00000000000..5e548019af6 --- /dev/null +++ b/op-devstack/presets/timetravel.go @@ -0,0 +1,5 @@ +package presets + +func WithTimeTravel() Option { + return WithTimeTravelEnabled() +} diff --git a/op-devstack/presets/twol2.go b/op-devstack/presets/twol2.go index f383289fa14..7e2a4355a33 100644 --- a/op-devstack/presets/twol2.go +++ b/op-devstack/presets/twol2.go @@ -82,6 +82,10 @@ type TwoL2SupernodeInterop struct { // DelaySeconds is the delay from genesis to interop activation DelaySeconds uint64 + // InteropFilter provides direct access to the in-process interop filter. + // nil if not using interop filter (WithInteropFilter() not set). + InteropFilter *sysgo.InteropFilter + timeTravel *clock.AdvancingClock } @@ -99,8 +103,14 @@ func (s *TwoL2SupernodeInterop) SuperNodeClient() apis.SupernodeQueryAPI { // NewTwoL2SupernodeInterop creates a fresh TwoL2SupernodeInterop target for the current // test. +// +// When WithInteropFilter() is set the test is skipped on op-geth: the interop filter +// is only supported with op-reth, since op-geth does not call the interop_ namespace. func NewTwoL2SupernodeInterop(t devtest.T, delaySeconds uint64, opts ...Option) *TwoL2SupernodeInterop { presetCfg, _ := collectSupportedPresetConfig(t, "NewTwoL2SupernodeInterop", opts, twoL2SupernodeInteropPresetSupportedOptionKinds) + if presetCfg.UseInteropFilter { + sysgo.SkipOnOpGeth(t, "interop filter is only supported with op-reth") + } return twoL2SupernodeInteropFromRuntime(t, sysgo.NewTwoL2SupernodeInteropRuntimeWithConfig(t, delaySeconds, presetCfg)) } diff --git a/op-devstack/presets/twol2_follow_l2.go b/op-devstack/presets/twol2_follow_l2.go new file mode 100644 index 00000000000..39645c22b70 --- /dev/null +++ b/op-devstack/presets/twol2_follow_l2.go @@ -0,0 +1,25 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +// TwoL2SupernodeFollowL2 extends TwoL2SupernodeInterop with one follow-source +// follower per chain. +type TwoL2SupernodeFollowL2 struct { + TwoL2SupernodeInterop + + L2AFollowEL *dsl.L2ELNode + L2AFollowCL *dsl.L2CLNode + L2BFollowEL *dsl.L2ELNode + L2BFollowCL *dsl.L2CLNode +} + +// NewTwoL2SupernodeFollowL2 creates a fresh follow-source variant of the two-L2 +// supernode interop preset for the current test. +func NewTwoL2SupernodeFollowL2(t devtest.T, delaySeconds uint64, opts ...Option) *TwoL2SupernodeFollowL2 { + presetCfg, _ := collectSupportedPresetConfig(t, "NewTwoL2SupernodeFollowL2", opts, twoL2SupernodeInteropPresetSupportedOptionKinds) + return twoL2SupernodeFollowL2FromRuntime(t, sysgo.NewTwoL2SupernodeFollowL2RuntimeWithConfig(t, delaySeconds, presetCfg)) +} diff --git a/op-devstack/presets/twol2_from_runtime.go b/op-devstack/presets/twol2_from_runtime.go new file mode 100644 index 00000000000..68865b2ee3d --- /dev/null +++ b/op-devstack/presets/twol2_from_runtime.go @@ -0,0 +1,210 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +type twoL2RuntimeComponents struct { + l2AEL *l2ELFrontend + l2BEL *l2ELFrontend + + l2ABatcher *l2BatcherFrontend + l2BBatcher *l2BatcherFrontend + + faucetA *dsl.Faucet + faucetB *dsl.Faucet +} + +func twoL2SupernodeFromRuntime(t devtest.T, runtime *sysgo.MultiChainRuntime) *TwoL2 { + preset, _ := twoL2FromRuntime(t, runtime) + return preset +} + +func twoL2FromRuntime(t devtest.T, runtime *sysgo.MultiChainRuntime) (*TwoL2, *twoL2RuntimeComponents) { + chainA := runtime.Chains["l2a"] + chainB := runtime.Chains["l2b"] + t.Require().NotNil(chainA, "missing l2a runtime chain") + t.Require().NotNil(chainB, "missing l2b runtime chain") + l1ChainID := runtime.L1Network.ChainID() + l2AChainID := chainA.Network.ChainID() + l2BChainID := chainB.Network.ChainID() + + l1Network := newPresetL1Network(t, "l1", runtime.L1Network.ChainConfig()) + l1EL := newL1ELFrontend(t, "l1", l1ChainID, runtime.L1EL.UserRPC()) + l1CL := newL1CLFrontend(t, "l1", l1ChainID, runtime.L1CL.BeaconHTTPAddr(), runtime.L1CL.FakePoS()) + l1Network.AddL1ELNode(l1EL) + l1Network.AddL1CLNode(l1CL) + + l2A := newPresetL2Network( + t, + "l2a", + chainA.Network.ChainConfig(), + chainA.Network.RollupConfig(), + chainA.Network.Deployment(), + newKeyring(runtime.Keys, t.Require()), + l1Network, + ) + l2AEL := newL2ELFrontend(t, "sequencer", l2AChainID, chainA.EL.UserRPC(), chainA.EL.EngineRPC(), chainA.EL.JWTPath(), chainA.Network.RollupConfig(), chainA.EL) + l2ACL := newL2CLFrontend(t, "sequencer", l2AChainID, chainA.CL.UserRPC(), chainA.CL) + l2ACL.attachEL(l2AEL) + l2ABatcher := newL2BatcherFrontend(t, "main", l2AChainID, chainA.Batcher.UserRPC()) + l2A.AddL2ELNode(l2AEL) + l2A.AddL2CLNode(l2ACL) + l2A.AddL2Batcher(l2ABatcher) + + l2B := newPresetL2Network( + t, + "l2b", + chainB.Network.ChainConfig(), + chainB.Network.RollupConfig(), + chainB.Network.Deployment(), + newKeyring(runtime.Keys, t.Require()), + l1Network, + ) + l2BEL := newL2ELFrontend(t, "sequencer", l2BChainID, chainB.EL.UserRPC(), chainB.EL.EngineRPC(), chainB.EL.JWTPath(), chainB.Network.RollupConfig(), chainB.EL) + l2BCL := newL2CLFrontend(t, "sequencer", l2BChainID, chainB.CL.UserRPC(), chainB.CL) + l2BCL.attachEL(l2BEL) + l2BBatcher := newL2BatcherFrontend(t, "main", l2BChainID, chainB.Batcher.UserRPC()) + l2B.AddL2ELNode(l2BEL) + l2B.AddL2CLNode(l2BCL) + l2B.AddL2Batcher(l2BBatcher) + + faucetAFrontend := newFaucetFrontendForChain(t, runtime.FaucetService, l2AChainID) + faucetBFrontend := newFaucetFrontendForChain(t, runtime.FaucetService, l2BChainID) + l2A.AddFaucet(faucetAFrontend) + l2B.AddFaucet(faucetBFrontend) + faucetA := dsl.NewFaucet(faucetAFrontend) + faucetB := dsl.NewFaucet(faucetBFrontend) + + l1ELDSL := dsl.NewL1ELNode(l1EL) + l1CLDSL := dsl.NewL1CLNode(l1CL) + l2AELDSL := dsl.NewL2ELNode(l2AEL) + l2ACLDSL := dsl.NewL2CLNode(l2ACL) + l2BELDSL := dsl.NewL2ELNode(l2BEL) + l2BCLDSL := dsl.NewL2CLNode(l2BCL) + + preset := &TwoL2{ + Log: t.Logger(), + T: t, + L1Network: dsl.NewL1Network(l1Network, l1ELDSL, l1CLDSL), + L1EL: l1ELDSL, + L1CL: l1CLDSL, + L2A: dsl.NewL2Network(l2A, l2AELDSL, l2ACLDSL, l1ELDSL, nil, nil), + L2B: dsl.NewL2Network(l2B, l2BELDSL, l2BCLDSL, l1ELDSL, nil, nil), + L2ACL: l2ACLDSL, + L2BCL: l2BCLDSL, + } + return preset, &twoL2RuntimeComponents{ + l2AEL: l2AEL, + l2BEL: l2BEL, + l2ABatcher: l2ABatcher, + l2BBatcher: l2BBatcher, + faucetA: faucetA, + faucetB: faucetB, + } +} + +func twoL2SupernodeInteropFromRuntime(t devtest.T, runtime *sysgo.MultiChainRuntime) *TwoL2SupernodeInterop { + twoL2, components := twoL2FromRuntime(t, runtime) + + supernode := newSupernodeFrontend(t, "supernode-two-l2-system", runtime.Supernode.UserRPC()) + testSequencer := newTestSequencerFrontend( + t, + runtime.TestSequencer.Name, + runtime.TestSequencer.AdminRPC, + runtime.TestSequencer.ControlRPC, + runtime.TestSequencer.JWTSecret, + ) + + genesisTime := twoL2.L2A.Escape().RollupConfig().Genesis.L2Time + preset := &TwoL2SupernodeInterop{ + TwoL2: TwoL2{ + Log: twoL2.Log, + T: twoL2.T, + L1Network: twoL2.L1Network, + L1EL: twoL2.L1EL, + L1CL: twoL2.L1CL, + L2A: twoL2.L2A, + L2B: twoL2.L2B, + L2ACL: twoL2.L2ACL, + L2BCL: twoL2.L2BCL, + }, + Supernode: dsl.NewSupernodeWithTestControl(supernode, runtime.Supernode), + TestSequencer: dsl.NewTestSequencer(testSequencer), + L2ELA: dsl.NewL2ELNode(components.l2AEL), + L2ELB: dsl.NewL2ELNode(components.l2BEL), + L2BatcherA: dsl.NewL2Batcher(components.l2ABatcher), + L2BatcherB: dsl.NewL2Batcher(components.l2BBatcher), + FaucetA: components.faucetA, + FaucetB: components.faucetB, + Wallet: dsl.NewRandomHDWallet(t, 30), + GenesisTime: genesisTime, + InteropActivationTime: genesisTime + runtime.DelaySeconds, + DelaySeconds: runtime.DelaySeconds, + InteropFilter: runtime.InteropFilter, + timeTravel: runtime.TimeTravel, + } + preset.FunderA = dsl.NewFunder(preset.Wallet, preset.FaucetA, preset.L2ELA) + preset.FunderB = dsl.NewFunder(preset.Wallet, preset.FaucetB, preset.L2ELB) + return preset +} + +func twoL2SupernodeFollowL2FromRuntime(t devtest.T, runtime *sysgo.MultiChainRuntime) *TwoL2SupernodeFollowL2 { + base := twoL2SupernodeInteropFromRuntime(t, runtime) + chainA := runtime.Chains["l2a"] + chainB := runtime.Chains["l2b"] + t.Require().NotNil(chainA, "missing l2a supernode chain") + t.Require().NotNil(chainB, "missing l2b supernode chain") + t.Require().NotNil(chainA.Followers, "missing l2a followers") + t.Require().NotNil(chainB.Followers, "missing l2b followers") + followerA := chainA.Followers["follower"] + followerB := chainB.Followers["follower"] + t.Require().NotNil(followerA, "missing l2a follower") + t.Require().NotNil(followerB, "missing l2b follower") + + l2AFollowEL := newL2ELFrontend( + t, + followerA.Name, + chainA.Network.ChainID(), + followerA.EL.UserRPC(), + followerA.EL.EngineRPC(), + followerA.EL.JWTPath(), + chainA.Network.RollupConfig(), + followerA.EL, + ) + l2AFollowCL := newL2CLFrontend(t, followerA.Name, chainA.Network.ChainID(), followerA.CL.UserRPC(), followerA.CL) + l2AFollowCL.attachEL(l2AFollowEL) + + l2BFollowEL := newL2ELFrontend( + t, + followerB.Name, + chainB.Network.ChainID(), + followerB.EL.UserRPC(), + followerB.EL.EngineRPC(), + followerB.EL.JWTPath(), + chainB.Network.RollupConfig(), + followerB.EL, + ) + l2BFollowCL := newL2CLFrontend(t, followerB.Name, chainB.Network.ChainID(), followerB.CL.UserRPC(), followerB.CL) + l2BFollowCL.attachEL(l2BFollowEL) + + l2ANet, ok := base.L2A.Escape().(*presetL2Network) + t.Require().True(ok, "expected preset L2 network A") + l2ANet.AddL2ELNode(l2AFollowEL) + l2ANet.AddL2CLNode(l2AFollowCL) + + l2BNet, ok := base.L2B.Escape().(*presetL2Network) + t.Require().True(ok, "expected preset L2 network B") + l2BNet.AddL2ELNode(l2BFollowEL) + l2BNet.AddL2CLNode(l2BFollowCL) + + return &TwoL2SupernodeFollowL2{ + TwoL2SupernodeInterop: *base, + L2AFollowEL: dsl.NewL2ELNode(l2AFollowEL), + L2AFollowCL: dsl.NewL2CLNode(l2AFollowCL), + L2BFollowEL: dsl.NewL2ELNode(l2BFollowEL), + L2BFollowCL: dsl.NewL2CLNode(l2BFollowCL), + } +} diff --git a/op-devstack/shared/challenger/challenger.go b/op-devstack/shared/challenger/challenger.go index 65e59be5d61..0775d7431d3 100644 --- a/op-devstack/shared/challenger/challenger.go +++ b/op-devstack/shared/challenger/challenger.go @@ -216,18 +216,7 @@ func WithFastGames() Option { } } -// WithExperimentalWitnessEndpoint enables kona's experimental witness endpoint feature. -// This uses debug_executePayload to collect execution witnesses, reducing proof generation -// time by avoiding full block re-derivation and re-execution. -// Requires op-reth or execution client with debug_executePayload support. -func WithExperimentalWitnessEndpoint() Option { - return func(c *config.Config) error { - c.CannonKona.EnableExperimentalWitnessEndpoint = true - return nil - } -} - -func NewInteropChallengerConfig(dir string, l1Endpoint string, l1Beacon string, supervisorEndpoint string, l2Endpoints []string, options ...Option) (*config.Config, error) { +func NewInteropChallengerConfig(ctx context.Context, dir string, l1Endpoint string, l1Beacon string, supervisorEndpoint string, l2Endpoints []string, options ...Option) (*config.Config, error) { cfg := config.NewInteropConfig(common.Address{}, l1Endpoint, l1Beacon, supervisorEndpoint, l2Endpoints, dir) if err := applyCommonChallengerOpts(&cfg, options...); err != nil { return nil, err diff --git a/op-devstack/shared/rustbin/kona_host_native.go b/op-devstack/shared/rustbin/kona_host_native.go new file mode 100644 index 00000000000..0d0611812fd --- /dev/null +++ b/op-devstack/shared/rustbin/kona_host_native.go @@ -0,0 +1,42 @@ +package rustbin + +import ( + "errors" + "os" + "os/exec" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" + "github.com/ethereum-optimism/optimism/op-service/logpipe" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +// RunKonaNative runs kona-host in --native mode. Returns false if kona disagrees and true +// otherwise. +func RunKonaNative(t require.TestingT, logger log.Logger, vmConfig *vm.Config, dir string, inputs *utils.LocalGameInputs) bool { + require.NotNil(t, vmConfig) + require.NotNil(t, inputs) + args, err := vm.NewNativeKonaExecutor().OracleCommand(*vmConfig, dir, *inputs) + require.NoError(t, err, "build kona oracle command") + + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = dir + cmd.Env = append(os.Environ(), "KONA_LOG_STDOUT_FORMAT=json", "NO_COLOR=1") + + logOut := logpipe.ToLoggerWithMinLevel(logger.New("component", "kona-host", "src", "stdout"), log.LevelWarn) + logErr := logpipe.ToLoggerWithMinLevel(logger.New("component", "kona-host", "src", "stderr"), log.LevelWarn) + cmd.Stdout = logpipe.NewLineBuffer(logpipe.LogCallback(func(line []byte) { + logOut(logpipe.ParseRustStructuredLogs(line)) + })) + cmd.Stderr = logpipe.NewLineBuffer(logpipe.LogCallback(func(line []byte) { + logErr(logpipe.ParseRustStructuredLogs(line)) + })) + + var exitErr *exec.ExitError + if runErr := cmd.Run(); errors.As(runErr, &exitErr) && exitErr.ExitCode() == 1 { + return false + } + require.NoError(t, err) + return true +} diff --git a/op-devstack/shared/rustbin/rust_binary.go b/op-devstack/shared/rustbin/rust_binary.go new file mode 100644 index 00000000000..ca625ebc375 --- /dev/null +++ b/op-devstack/shared/rustbin/rust_binary.go @@ -0,0 +1,158 @@ +package rustbin + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "github.com/ethereum/go-ethereum/log" + + opservice "github.com/ethereum-optimism/optimism/op-service" +) + +// Spec describes a Rust binary to be built and located. +type Spec struct { + SrcDir string // directory name relative to monorepo root, e.g. "rollup-boost" + Package string // cargo package name, e.g. "rollup-boost" + Binary string // binary name, e.g. "rollup-boost" +} + +// EnsureExists locates or builds a Rust binary as needed. +// +// Env var overrides (suffix derived from binary name, e.g. "rollup-boost" -> "ROLLUP_BOOST"): +// - RUST_BINARY_PATH_: absolute path to pre-built binary (skips build, must exist) +// - RUST_SRC_DIR_: overrides SrcDir (absolute path to cargo project root) +// +// Build behavior: +// - RUST_JIT_BUILD=1: runs cargo build in debug mode (letting cargo handle rebuild detection) +// - Otherwise: only checks binary exists, errors if missing +func (s Spec) EnsureExists(ctx context.Context, logger log.Logger) (string, error) { + envSuffix := toEnvVarSuffix(s.Binary) + + // Check for explicit binary path override + if pathOverride := os.Getenv("RUST_BINARY_PATH_" + envSuffix); pathOverride != "" { + if _, err := os.Stat(pathOverride); os.IsNotExist(err) { + return "", fmt.Errorf("%s binary not found at overridden path %s", s.Binary, pathOverride) + } + logger.Info("Using overridden binary path", "binary", s.Binary, "path", pathOverride) + return pathOverride, nil + } + + // Determine source root + srcRoot, err := resolveSrcRoot(s.SrcDir, envSuffix) + if err != nil { + return "", err + } + + jitBuild := os.Getenv("RUST_JIT_BUILD") != "" + + if jitBuild { + logger.Info("Building Rust binary (JIT)", "binary", s.Binary, "dir", srcRoot) + if err := buildRustBinary(ctx, srcRoot, s.Package, s.Binary); err != nil { + return "", err + } + } + + binaryPath, err := resolveBuiltRustBinaryPath(srcRoot, s.Binary) + if err != nil { + return "", fmt.Errorf("%s binary not found; run 'cd %s && just build-%s-debug' (or just build-%s for release) or set RUST_JIT_BUILD=1: %w", + s.Binary, s.SrcDir, s.Binary, s.Binary, err) + } + return binaryPath, nil +} + +// resolveSrcRoot determines the cargo project root, checking for env var override first. +func resolveSrcRoot(defaultSrcDir, envSuffix string) (string, error) { + if srcOverride := os.Getenv("RUST_SRC_DIR_" + envSuffix); srcOverride != "" { + return srcOverride, nil + } + + rootDir, err := os.Getwd() + if err != nil { + return "", err + } + monorepoRoot, err := opservice.FindMonorepoRoot(rootDir) + if err != nil { + return "", err + } + return filepath.Join(monorepoRoot, defaultSrcDir), nil +} + +// toEnvVarSuffix converts a binary name to an env var suffix. +// e.g. "rollup-boost" -> "ROLLUP_BOOST" +func toEnvVarSuffix(binary string) string { + return strings.ToUpper(strings.ReplaceAll(binary, "-", "_")) +} + +func buildRustBinary(ctx context.Context, root, pkg, bin string) error { + cmd := exec.CommandContext(ctx, "cargo", "build", "-p", pkg, "--bin", bin) + cmd.Dir = root + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +type cargoMetadata struct { + TargetDirectory string `json:"target_directory"` +} + +func resolveBuiltRustBinaryPath(srcRoot, binary string) (string, error) { + targetDir, err := cargoTargetDirectory(srcRoot) + if err != nil { + return "", err + } + + candidates := []string{ + filepath.Join(targetDir, "release", binary), + filepath.Join(targetDir, "debug", binary), + } + globMatches, err := filepath.Glob(filepath.Join(targetDir, "*", "release", binary)) + if err == nil { + candidates = append(candidates, globMatches...) + } + + seen := make(map[string]struct{}, len(candidates)) + var existing []string + for _, candidate := range candidates { + if _, dup := seen[candidate]; dup { + continue + } + seen[candidate] = struct{}{} + if _, err := os.Stat(candidate); err == nil { + existing = append(existing, candidate) + } + } + + switch len(existing) { + case 0: + return "", fmt.Errorf("no built binary found under target dir %s", targetDir) + case 1: + return existing[0], nil + default: + sort.Strings(existing) + return existing[0], nil + } +} + +func cargoTargetDirectory(srcRoot string) (string, error) { + cmd := exec.Command("cargo", "metadata", "--no-deps", "--format-version", "1") + cmd.Dir = srcRoot + out, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("cargo metadata: %w", err) + } + + var meta cargoMetadata + if err := json.Unmarshal(out, &meta); err != nil { + return "", fmt.Errorf("parse cargo metadata: %w", err) + } + if meta.TargetDirectory == "" { + return "", fmt.Errorf("cargo metadata returned empty target directory") + } + return meta.TargetDirectory, nil +} diff --git a/op-devstack/stack/common.go b/op-devstack/stack/common.go new file mode 100644 index 00000000000..a4408fbefee --- /dev/null +++ b/op-devstack/stack/common.go @@ -0,0 +1,21 @@ +package stack + +import ( + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" +) + +type Common interface { + T() devtest.T + Logger() log.Logger + Name() string + + // Label retrieves a label by key. + // If the label does not exist, it returns an empty string. + Label(key string) string + + // SetLabel sets a label by key. + // Note that labels added by tests are not visible to other tests against the same backend. + SetLabel(key, value string) +} diff --git a/op-devstack/stack/conductor.go b/op-devstack/stack/conductor.go new file mode 100644 index 00000000000..e92aa62840a --- /dev/null +++ b/op-devstack/stack/conductor.go @@ -0,0 +1,13 @@ +package stack + +import ( + conductorRpc "github.com/ethereum-optimism/optimism/op-conductor/rpc" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type Conductor interface { + Common + ChainID() eth.ChainID + + RpcAPI() conductorRpc.API +} diff --git a/op-devstack/stack/context.go b/op-devstack/stack/context.go new file mode 100644 index 00000000000..ab46b35f90c --- /dev/null +++ b/op-devstack/stack/context.go @@ -0,0 +1,26 @@ +package stack + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/log/logfilter" +) + +// ContextWithChainID annotates the context with the given chainID of service +func ContextWithChainID(ctx context.Context, chainID eth.ChainID) context.Context { + return logfilter.AddLogAttrToContext(ctx, "chainID", chainID) +} + +// ChainIDFromContext extracts the chain ID from the context. +func ChainIDFromContext(ctx context.Context) eth.ChainID { + v, _ := logfilter.ValueFromContext[eth.ChainID](ctx, "chainID") + return v +} + +// ChainIDSelector creates a log-filter that applies the given inner log-filter only if it matches the given chainID. +// This can be composed with logfilter package utils like logfilter.MuteAll or logfilter.Level +// to adjust logging for a specific chain ID. +func ChainIDSelector(chainID eth.ChainID) logfilter.Selector { + return logfilter.Select("chainID", chainID) +} diff --git a/op-devstack/stack/context_test.go b/op-devstack/stack/context_test.go new file mode 100644 index 00000000000..72f950fc239 --- /dev/null +++ b/op-devstack/stack/context_test.go @@ -0,0 +1,32 @@ +package stack + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/tri" +) + +func TestContext(t *testing.T) { + ctx := context.Background() + chainA := eth.ChainIDFromUInt64(900) + chainB := eth.ChainIDFromUInt64(901) + require.Equal(t, eth.ChainID{}, ChainIDFromContext(ctx), "none") + require.Equal(t, chainA, ChainIDFromContext(ContextWithChainID(ctx, chainA)), "lookup") + require.Equal(t, chainB, ChainIDFromContext(ContextWithChainID(ContextWithChainID(ctx, chainA), chainB)), "priority") +} + +func TestLogFilter(t *testing.T) { + ctx := context.Background() + chainA := eth.ChainIDFromUInt64(900) + chainB := eth.ChainIDFromUInt64(901) + fn := ChainIDSelector(chainA).Mute() + require.Equal(t, tri.Undefined, fn(ctx, log.LevelDebug), "regular context should be false") + require.Equal(t, tri.False, fn(ContextWithChainID(ctx, chainA), log.LevelDebug), "detected chain should be muted") + require.Equal(t, tri.Undefined, fn(ContextWithChainID(ctx, chainB), log.LevelDebug), "different chain should be shown") +} diff --git a/op-devstack/stack/faucet.go b/op-devstack/stack/faucet.go new file mode 100644 index 00000000000..10ebf48e237 --- /dev/null +++ b/op-devstack/stack/faucet.go @@ -0,0 +1,12 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type Faucet interface { + Common + ChainID() eth.ChainID + API() apis.Faucet +} diff --git a/op-devstack/stack/l1_cl.go b/op-devstack/stack/l1_cl.go new file mode 100644 index 00000000000..49face44aa7 --- /dev/null +++ b/op-devstack/stack/l1_cl.go @@ -0,0 +1,15 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// L1CLNode is a L1 ethereum consensus-layer node, aka Beacon node. +// This node may not be a full beacon node, and instead run a mock L1 consensus node. +type L1CLNode interface { + Common + ChainID() eth.ChainID + + BeaconClient() apis.BeaconClient +} diff --git a/op-devstack/stack/l1_el.go b/op-devstack/stack/l1_el.go new file mode 100644 index 00000000000..da4441bbc1a --- /dev/null +++ b/op-devstack/stack/l1_el.go @@ -0,0 +1,6 @@ +package stack + +// L1ELNode is a L1 ethereum execution-layer node +type L1ELNode interface { + ELNode +} diff --git a/op-devstack/stack/l1_network.go b/op-devstack/stack/l1_network.go new file mode 100644 index 00000000000..60b4857f14f --- /dev/null +++ b/op-devstack/stack/l1_network.go @@ -0,0 +1,9 @@ +package stack + +// L1Network represents a L1 chain, a collection of configuration and node resources. +type L1Network interface { + Network + + L1ELNodes() []L1ELNode + L1CLNodes() []L1CLNode +} diff --git a/op-devstack/stack/l2_batcher.go b/op-devstack/stack/l2_batcher.go new file mode 100644 index 00000000000..12ddee248cc --- /dev/null +++ b/op-devstack/stack/l2_batcher.go @@ -0,0 +1,13 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// L2Batcher represents an L2 batch-submission service, posting L2 data of an L2 to L1. +type L2Batcher interface { + Common + ChainID() eth.ChainID + ActivityAPI() apis.BatcherActivity +} diff --git a/op-devstack/stack/l2_challenger.go b/op-devstack/stack/l2_challenger.go new file mode 100644 index 00000000000..c4d524c1245 --- /dev/null +++ b/op-devstack/stack/l2_challenger.go @@ -0,0 +1,12 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type L2Challenger interface { + Common + ChainID() eth.ChainID + Config() *config.Config +} diff --git a/op-devstack/stack/l2_cl.go b/op-devstack/stack/l2_cl.go new file mode 100644 index 00000000000..b4c64fdd9f4 --- /dev/null +++ b/op-devstack/stack/l2_cl.go @@ -0,0 +1,27 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// L2CLNode is a L2 ethereum consensus-layer node +type L2CLNode interface { + Common + ChainID() eth.ChainID + + ClientRPC() client.RPC + RollupAPI() apis.RollupClient + P2PAPI() apis.P2PClient + InteropRPC() (endpoint string, jwtSecret eth.Bytes32) + UserRPC() string + + // ELs returns the engine(s) that this L2CLNode is connected to. + // This may be empty, if the L2CL is not connected to any. + ELs() []L2ELNode + RollupBoostNodes() []RollupBoostNode + OPRBuilderNodes() []OPRBuilderNode + + ELClient() apis.EthClient +} diff --git a/op-devstack/stack/l2_el.go b/op-devstack/stack/l2_el.go new file mode 100644 index 00000000000..861f15b808d --- /dev/null +++ b/op-devstack/stack/l2_el.go @@ -0,0 +1,13 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-service/apis" +) + +// L2ELNode is a L2 ethereum execution-layer node +type L2ELNode interface { + L2EthClient() apis.L2EthClient + L2EngineClient() apis.EngineClient + + ELNode +} diff --git a/op-devstack/stack/l2_network.go b/op-devstack/stack/l2_network.go new file mode 100644 index 00000000000..025d94bcb84 --- /dev/null +++ b/op-devstack/stack/l2_network.go @@ -0,0 +1,41 @@ +package stack + +import ( + "crypto/ecdsa" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-node/rollup" +) + +type L2Deployment interface { + SystemConfigProxyAddr() common.Address + DisputeGameFactoryProxyAddr() common.Address + L1StandardBridgeProxyAddr() common.Address + // Other addresses will be added here later +} + +type Keys interface { + Secret(key devkeys.Key) *ecdsa.PrivateKey + Address(key devkeys.Key) common.Address +} + +// L2Network represents a L2 chain, a collection of configuration and node resources. +type L2Network interface { + Network + RollupConfig() *rollup.Config + Deployment() L2Deployment + Keys() Keys + + L1() L1Network + + L2Batchers() []L2Batcher + L2Proposers() []L2Proposer + L2Challengers() []L2Challenger + L2CLNodes() []L2CLNode + L2ELNodes() []L2ELNode + Conductors() []Conductor + RollupBoostNodes() []RollupBoostNode + OPRBuilderNodes() []OPRBuilderNode +} diff --git a/op-devstack/stack/l2_proposer.go b/op-devstack/stack/l2_proposer.go new file mode 100644 index 00000000000..27eb7fb4c97 --- /dev/null +++ b/op-devstack/stack/l2_proposer.go @@ -0,0 +1,9 @@ +package stack + +import "github.com/ethereum-optimism/optimism/op-service/eth" + +// L2Proposer is a L2 output proposer, posting claims of L2 state to L1. +type L2Proposer interface { + Common + ChainID() eth.ChainID +} diff --git a/op-devstack/stack/lifecycle.go b/op-devstack/stack/lifecycle.go new file mode 100644 index 00000000000..3aee741c34d --- /dev/null +++ b/op-devstack/stack/lifecycle.go @@ -0,0 +1,6 @@ +package stack + +type Lifecycle interface { + Start() + Stop() +} diff --git a/op-devstack/stack/network.go b/op-devstack/stack/network.go new file mode 100644 index 00000000000..aa410ac2b0d --- /dev/null +++ b/op-devstack/stack/network.go @@ -0,0 +1,22 @@ +package stack + +import ( + "github.com/ethereum/go-ethereum/params" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// Network is an interface to an ethereum chain and its resources, with common properties between L1 and L2. +// For L1 or L2 specifics, see L1Network and L2Network extensions. +// A network hosts configuration resources and tracks participating nodes. +type Network interface { + Common + + ChainID() eth.ChainID + + ChainConfig() *params.ChainConfig + + Faucets() []Faucet + + SyncTesters() []SyncTester +} diff --git a/op-devstack/stack/rollup_boost.go b/op-devstack/stack/rollup_boost.go new file mode 100644 index 00000000000..44fd9165e80 --- /dev/null +++ b/op-devstack/stack/rollup_boost.go @@ -0,0 +1,15 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/client" +) + +// RollupBoostNode is a shim service between an L2 consensus-layer node and an L2 ethereum execution-layer node +type RollupBoostNode interface { + L2EthClient() apis.L2EthClient + L2EngineClient() apis.EngineClient + FlashblocksClient() *client.WSClient + + ELNode +} diff --git a/op-devstack/stack/superchain.go b/op-devstack/stack/superchain.go new file mode 100644 index 00000000000..7f90d16dd25 --- /dev/null +++ b/op-devstack/stack/superchain.go @@ -0,0 +1,10 @@ +package stack + +import ( + "github.com/ethereum/go-ethereum/common" +) + +type SuperchainDeployment interface { + ProtocolVersionsAddr() common.Address + SuperchainConfigAddr() common.Address +} diff --git a/op-devstack/stack/supernode.go b/op-devstack/stack/supernode.go new file mode 100644 index 00000000000..d9f2a84c24f --- /dev/null +++ b/op-devstack/stack/supernode.go @@ -0,0 +1,21 @@ +package stack + +import "github.com/ethereum-optimism/optimism/op-service/apis" + +type Supernode interface { + Common + QueryAPI() apis.SupernodeQueryAPI +} + +// InteropTestControl provides integration test control methods for the interop activity. +// This interface is for integration test control only. +type InteropTestControl interface { + // PauseInteropActivity pauses the interop activity at the given timestamp. + // When the interop activity attempts to process this timestamp, it returns early. + // This function is for integration test control only. + PauseInteropActivity(ts uint64) + + // ResumeInteropActivity clears any pause on the interop activity, allowing normal processing. + // This function is for integration test control only. + ResumeInteropActivity() +} diff --git a/op-devstack/stack/sync_tester.go b/op-devstack/stack/sync_tester.go new file mode 100644 index 00000000000..32239ba6f51 --- /dev/null +++ b/op-devstack/stack/sync_tester.go @@ -0,0 +1,14 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type SyncTester interface { + Common + ChainID() eth.ChainID + API() apis.SyncTester + + APIWithSession(sessionID string) apis.SyncTester +} diff --git a/op-devstack/stack/test_sequencer.go b/op-devstack/stack/test_sequencer.go new file mode 100644 index 00000000000..94aae09187f --- /dev/null +++ b/op-devstack/stack/test_sequencer.go @@ -0,0 +1,15 @@ +package stack + +import ( + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// TestSequencer +type TestSequencer interface { + Common + + AdminAPI() apis.TestSequencerAdminAPI + BuildAPI() apis.TestSequencerBuildAPI + ControlAPI(chainID eth.ChainID) apis.TestSequencerControlAPI +} diff --git a/op-devstack/sysgo/add_game_type.go b/op-devstack/sysgo/add_game_type.go new file mode 100644 index 00000000000..9e7fa692abd --- /dev/null +++ b/op-devstack/sysgo/add_game_type.go @@ -0,0 +1,183 @@ +package sysgo + +import ( + "fmt" + "math/big" + "net/url" + "path" + "runtime" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/manage" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" + "github.com/ethereum-optimism/optimism/op-service/txintent/contractio" + "github.com/ethereum-optimism/optimism/op-service/txplan" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" +) + +func setRespectedGameTypeForRuntime( + t devtest.T, + keys devkeys.Keys, + gameType gameTypes.GameType, + l1ChainID eth.ChainID, + l1ELRPC string, + l2Net *L2Network, +) { + require := t.Require() + require.NotNil(l2Net, "l2 network must exist") + require.NotNil(l2Net.rollupCfg, "l2 rollup config must exist") + + portalAddr := l2Net.rollupCfg.DepositContractAddress + + rpcClient, err := rpc.DialContext(t.Ctx(), l1ELRPC) + require.NoError(err) + defer rpcClient.Close() + client := ethclient.NewClient(rpcClient) + + guardianKey, err := keys.Secret(devkeys.SuperchainOperatorKeys(l1ChainID.ToBig())(devkeys.SuperchainConfigGuardianKey)) + require.NoError(err, "failed to get guardian key") + + transactOpts, err := bind.NewKeyedTransactorWithChainID(guardianKey, l1ChainID.ToBig()) + require.NoError(err, "must have transact opts") + transactOpts.Context = t.Ctx() + + portalBindings := bindings.NewBindings[bindings.OptimismPortal2](bindings.WithTo(portalAddr), bindings.WithTest(t)) + f := portalBindings.AnchorStateRegistry() + calldata, err := f.EncodeInput() + require.NoError(err, "failed to encode anchorStateRegistry() calldata") + result, err := client.CallContract(t.Ctx(), ethereum.CallMsg{ + To: &portalAddr, + Data: calldata, + }, nil) + require.NoError(err, "failed to read anchor state registry address from portal") + asrAddr, err := f.DecodeOutput(result) + require.NoError(err, "failed to decode anchor state registry address from portal") + + txOpts := txplan.Combine( + txplan.WithChainID(client), + txplan.WithPrivateKey(guardianKey), + txplan.WithPendingNonce(client), + txplan.WithAgainstLatestBlockEthClient(client), + txplan.WithEstimator(client, true), + txplan.WithRetrySubmission(client, 5, retry.Exponential()), + txplan.WithRetryInclusion(client, 5, retry.Exponential())) + + asrBindings := bindings.NewBindings[bindings.AnchorStateRegistry](bindings.WithTo(asrAddr), bindings.WithTest(t)) + rcpt, err := contractio.Write(asrBindings.SetRespectedGameType(uint32(gameType)), t.Ctx(), txOpts) + require.NoError(err, "failed to set respected game type") + require.Equal(rcpt.Status, gethTypes.ReceiptStatusSuccessful, "set respected game type tx did not execute correctly") +} + +func addGameTypeForRuntime( + t devtest.T, + keys devkeys.Keys, + absolutePrestate common.Hash, + gameType gameTypes.GameType, + l1ChainID eth.ChainID, + l1ELRPC string, + l2Net *L2Network, +) { + require := t.Require() + require.NotNil(l2Net, "l2 network must exist") + require.NotNil(l2Net.deployment, "l2 deployment must exist") + require.NotEqual(common.Address{}, l2Net.opcmImpl, "missing OPCM implementation address") + require.NotEqual(common.Address{}, l2Net.mipsImpl, "missing MIPS implementation address") + + rpcClient, err := rpc.DialContext(t.Ctx(), l1ELRPC) + require.NoError(err) + defer rpcClient.Close() + client := ethclient.NewClient(rpcClient) + + l1PAO, err := keys.Address(devkeys.ChainOperatorKeys(l1ChainID.ToBig())(devkeys.L1ProxyAdminOwnerRole)) + require.NoError(err, "failed to get l1 proxy admin owner address") + + cfg := manage.AddGameTypeConfig{ + L1RPCUrl: l1ELRPC, + Logger: t.Logger(), + ArtifactsLocator: LocalArtifacts(t), + CacheDir: t.TempDir(), + L1ProxyAdminOwner: l1PAO, + OPCMImpl: l2Net.opcmImpl, + SystemConfigProxy: l2Net.deployment.SystemConfigProxyAddr(), + DelayedWETHProxy: l2Net.deployment.PermissionlessDelayedWETHProxyAddr(), + DisputeGameType: uint32(gameType), + DisputeAbsolutePrestate: absolutePrestate, + DisputeMaxGameDepth: big.NewInt(73), + DisputeSplitDepth: big.NewInt(30), + DisputeClockExtension: 10800, + DisputeMaxClockDuration: 302400, + InitialBond: eth.GWei(80_000_000).ToBig(), // 0.08 ETH + VM: l2Net.mipsImpl, + Permissionless: true, + SaltMixer: fmt.Sprintf("devstack-%s-%s", l2Net.ChainID(), absolutePrestate.Hex()), + } + + opChainProxyAdmin := l2Net.deployment.ProxyAdminAddr() + + _, addGameTypeCalldata, err := manage.AddGameType(t.Ctx(), cfg) + require.NoError(err, "failed to create add game type calldata") + require.Len(addGameTypeCalldata, 1, "calldata must contain one entry") + + chainOps := devkeys.ChainOperatorKeys(l1ChainID.ToBig()) + l1PAOKey, err := keys.Secret(chainOps(devkeys.L1ProxyAdminOwnerRole)) + require.NoError(err, "failed to get l1 proxy admin owner key") + transactOpts, err := bind.NewKeyedTransactorWithChainID(l1PAOKey, l1ChainID.ToBig()) + require.NoError(err, "must have transact opts") + transactOpts.Context = t.Ctx() + + t.Log("Deploying delegate call proxy contract") + delegateCallProxy, proxyContract := deployDelegateCallProxy(t, transactOpts, client, l1PAO) + // transfer ownership to the proxy so that we can delegatecall the opcm + transferOwnership(t, l1PAOKey, client, opChainProxyAdmin, delegateCallProxy) + dgf := l2Net.deployment.DisputeGameFactoryProxyAddr() + transferOwnership(t, l1PAOKey, client, dgf, delegateCallProxy) + + t.Log("sending opcm.addGameType transaction") + tx, err := proxyContract.ExecuteDelegateCall(transactOpts, l2Net.opcmImpl, addGameTypeCalldata[0].Data) + require.NoError(err, "failed to send add game type tx") + _, err = wait.ForReceiptOK(t.Ctx(), client, tx.Hash()) + require.NoError(err, "failed to wait for add game type receipt") + + // reset ProxyAdmin ownership transfers + transferOwnershipForDelegateCallProxy(t, l1ChainID.ToBig(), l1PAOKey, client, delegateCallProxy, opChainProxyAdmin, l1PAO) + transferOwnershipForDelegateCallProxy(t, l1ChainID.ToBig(), l1PAOKey, client, delegateCallProxy, dgf, l1PAO) +} + +func PrestateForGameType(t devtest.CommonT, gameType gameTypes.GameType) common.Hash { + switch gameType { + case gameTypes.CannonGameType: + return getAbsolutePrestate(t, "op-program/bin/prestate-proof-mt64.json") + case gameTypes.CannonKonaGameType: + return getCannonKonaAbsolutePrestate(t) + default: + t.Require().Fail("no prestate available for game type", gameType) + return common.Hash{} + } +} + +func LocalArtifacts(t devtest.T) *artifacts.Locator { + require := t.Require() + _, testFilename, _, ok := runtime.Caller(0) + require.Truef(ok, "failed to get test filename") + monorepoDir, err := op_service.FindMonorepoRoot(testFilename) + require.NoError(err, "failed to find monorepo root") + artifactsDir := path.Join(monorepoDir, "packages", "contracts-bedrock", "forge-artifacts") + artifactsURL, err := url.Parse(fmt.Sprintf("file://%s", artifactsDir)) + require.NoError(err, "failed to parse artifacts dir url") + loc := &artifacts.Locator{ + URL: artifactsURL, + } + return loc +} diff --git a/op-devstack/sysgo/component_target.go b/op-devstack/sysgo/component_target.go new file mode 100644 index 00000000000..4300176f935 --- /dev/null +++ b/op-devstack/sysgo/component_target.go @@ -0,0 +1,26 @@ +package sysgo + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type ComponentTarget struct { + Name string + ChainID eth.ChainID +} + +func NewComponentTarget(name string, chainID eth.ChainID) ComponentTarget { + return ComponentTarget{ + Name: name, + ChainID: chainID, + } +} + +func (t ComponentTarget) String() string { + if t.ChainID == (eth.ChainID{}) { + return t.Name + } + return fmt.Sprintf("%s-%s", t.Name, t.ChainID) +} diff --git a/op-devstack/sysgo/default_chain_ids.go b/op-devstack/sysgo/default_chain_ids.go new file mode 100644 index 00000000000..f7f63acbc8c --- /dev/null +++ b/op-devstack/sysgo/default_chain_ids.go @@ -0,0 +1,9 @@ +package sysgo + +import "github.com/ethereum-optimism/optimism/op-service/eth" + +var ( + DefaultL1ID = eth.ChainIDFromUInt64(900) + DefaultL2AID = eth.ChainIDFromUInt64(901) + DefaultL2BID = eth.ChainIDFromUInt64(902) +) diff --git a/op-devstack/sysgo/deployer.go b/op-devstack/sysgo/deployer.go new file mode 100644 index 00000000000..71650f01973 --- /dev/null +++ b/op-devstack/sysgo/deployer.go @@ -0,0 +1,440 @@ +package sysgo + +import ( + "math/big" + "os" + "path/filepath" + "slices" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + opforks "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/inspect" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/intentbuilder" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testreq" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" + "github.com/holiman/uint256" +) + +// funderMnemonicIndex the funding account is not one of the 30 standard account, but still derived from a user-key. +const funderMnemonicIndex = 10_000 +const devFeatureBitmapKey = "devFeatureBitmap" + +type DeployerOption func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) + +func WithForkAtL1Genesis(fork forks.Fork) DeployerOption { + return func(_ devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { + builder.L1().WithL1ForkAtGenesis(fork) + } +} + +func WithForkAtL1Offset(fork forks.Fork, offset uint64) DeployerOption { + return func(_ devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { + builder.L1().WithL1ForkAtOffset(fork, &offset) + } +} + +func WithDefaultBPOBlobSchedule(_ devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { + // Once we get the latest changes from op-geth we can change this to + // params.DefaultBlobSchedule. + builder.L1().WithL1BlobSchedule(¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + Prague: params.DefaultPragueBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + BPO2: params.DefaultBPO2BlobConfig, + BPO3: params.DefaultBPO3BlobConfig, + BPO4: params.DefaultBPO4BlobConfig, + }) +} + +func WithKarstAtOffset(offset *uint64) DeployerOption { + return func(p devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithForkAtOffset(opforks.Karst, offset) + } + } +} + +func WithJovianAtGenesis(p devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithForkAtGenesis(opforks.Jovian) + } +} + +type DeployerPipelineOption func(wb *worldBuilder, intent *state.Intent, cfg *deployer.ApplyPipelineOpts) + +func WithDeployerCacheDir(dirPath string) DeployerPipelineOption { + return func(_ *worldBuilder, _ *state.Intent, cfg *deployer.ApplyPipelineOpts) { + cfg.CacheDir = dirPath + } +} + +// WithDAFootprintGasScalar sets the DA footprint gas scalar with which the networks identified by +// l2ChainIDs will be launched. If there are no l2ChainIDs provided, all L2 networks are set with scalar. +func WithDAFootprintGasScalar(scalar uint16, l2ChainIDs ...eth.ChainID) DeployerOption { + return func(p devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { + for _, l2 := range builder.L2s() { + if len(l2ChainIDs) == 0 || slices.Contains(l2ChainIDs, l2.ChainID()) { + l2.WithDAFootprintGasScalar(scalar) + } + } + } +} + +type L2Deployment struct { + systemConfigProxyAddr common.Address + disputeGameFactoryProxy common.Address + l1StandardBridgeProxy common.Address + proxyAdmin common.Address + permissionlessDelayedWETHProxy common.Address +} + +var _ stack.L2Deployment = &L2Deployment{} + +func (d *L2Deployment) SystemConfigProxyAddr() common.Address { + return d.systemConfigProxyAddr +} + +func (d *L2Deployment) DisputeGameFactoryProxyAddr() common.Address { + return d.disputeGameFactoryProxy +} + +func (d *L2Deployment) L1StandardBridgeProxyAddr() common.Address { + return d.l1StandardBridgeProxy +} + +func (d *L2Deployment) ProxyAdminAddr() common.Address { + return d.proxyAdmin +} + +func (d *L2Deployment) PermissionlessDelayedWETHProxyAddr() common.Address { + return d.permissionlessDelayedWETHProxy +} + +type worldBuilder struct { + p devtest.CommonT + + logger log.Logger + require *testreq.Assertions + keys devkeys.Keys + + // options + deployerPipelineOptions []DeployerPipelineOption + + builder intentbuilder.Builder + + output *state.State + outL1Genesis *core.Genesis + l2Chains []eth.ChainID + outL2Genesis map[eth.ChainID]*core.Genesis + outL2RollupCfg map[eth.ChainID]*rollup.Config + outL2Deployment map[eth.ChainID]*L2Deployment + + outFullCfgSet depset.FullConfigSetMerged + + outSuperchainDeployment *SuperchainDeployment +} + +var ( + oneEth = uint256.NewInt(1e18) + millionEth = new(uint256.Int).Mul(uint256.NewInt(1e6), oneEth) +) + +func WithEmbeddedContractSources() DeployerOption { + return func(_ devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { + builder.WithL1ContractsLocator(artifacts.EmbeddedLocator) + builder.WithL2ContractsLocator(artifacts.EmbeddedLocator) + } +} + +func WithLocalContractSources() DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + paths, err := contractPaths() + p.Require().NoError(err) + wd, err := os.Getwd() + p.Require().NoError(err) + artifactsPath := filepath.Join(wd, paths.FoundryArtifacts) + p.Require().NoError(ensureDir(artifactsPath)) + contractArtifacts, err := artifacts.NewFileLocator(artifactsPath) + p.Require().NoError(err) + builder.WithL1ContractsLocator(contractArtifacts) + builder.WithL2ContractsLocator(contractArtifacts) + } +} + +func WithCommons(l1ChainID eth.ChainID) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + _, l1Config := builder.WithL1(l1ChainID) + + l1StartTimestamp := uint64(time.Now().Unix()) + 1 + l1Config.WithTimestamp(l1StartTimestamp) + + l1Config.WithL1ForkAtGenesis(forks.Prague) // activate pectra on L1 + + faucetFunderAddr, err := keys.Address(devkeys.UserKey(funderMnemonicIndex)) + p.Require().NoError(err, "need funder addr") + l1Config.WithPrefundedAccount(faucetFunderAddr, *eth.BillionEther.ToU256()) + + // We use the L1 chain ID to identify the superchain-wide roles. + addrFor := intentbuilder.RoleToAddrProvider(p, keys, l1ChainID) + _, superCfg := builder.WithSuperchain() + intentbuilder.WithDevkeySuperRoles(p, keys, l1ChainID, superCfg) + l1Config.WithPrefundedAccount(addrFor(devkeys.SuperchainProxyAdminOwner), *millionEth) + l1Config.WithPrefundedAccount(addrFor(devkeys.SuperchainProtocolVersionsOwner), *millionEth) + l1Config.WithPrefundedAccount(addrFor(devkeys.SuperchainConfigGuardianKey), *millionEth) + l1Config.WithPrefundedAccount(addrFor(devkeys.L1ProxyAdminOwnerRole), *millionEth) + } +} + +func WithGuardianMatchL1PAO() DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + _, superCfg := builder.WithSuperchain() + intentbuilder.WithOverrideGuardianToL1PAO(p, keys, superCfg.L1ChainID(), superCfg) + } +} + +func WithPrefundedL2(l1ChainID, l2ChainID eth.ChainID) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + _, l2Config := builder.WithL2(l2ChainID) + intentbuilder.WithDevkeyVaults(p, keys, l2Config) + intentbuilder.WithDevkeyL2Roles(p, keys, l2Config) + // l2configurator L1ProxyAdminOwner must be also populated + intentbuilder.WithDevkeyL1Roles(p, keys, l2Config, l1ChainID) + { + faucetFunderAddr, err := keys.Address(devkeys.UserKey(funderMnemonicIndex)) + p.Require().NoError(err, "need funder addr") + l2Config.WithPrefundedAccount(faucetFunderAddr, *eth.BillionEther.ToU256()) + } + { + addrFor := intentbuilder.RoleToAddrProvider(p, keys, l2ChainID) + l1Config := l2Config.L1Config() + l1Config.WithPrefundedAccount(addrFor(devkeys.BatcherRole), *millionEth) + l1Config.WithPrefundedAccount(addrFor(devkeys.ProposerRole), *millionEth) + l1Config.WithPrefundedAccount(addrFor(devkeys.ChallengerRole), *millionEth) + l1Config.WithPrefundedAccount(addrFor(devkeys.SystemConfigOwner), *millionEth) + } + } +} + +// WithDevFeatureEnabled adds a feature as enabled in the dev feature bitmap +func WithDevFeatureEnabled(flag common.Hash) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + currentValue := builder.GlobalOverride(devFeatureBitmapKey) + var bitmap common.Hash + if currentValue != nil { + bitmap = currentValue.(common.Hash) + } + builder.WithGlobalOverride(devFeatureBitmapKey, deployer.EnableDevFeature(bitmap, flag)) + } +} + +// WithInteropAtGenesis activates interop at genesis for all known L2s +func WithInteropAtGenesis() DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithForkAtGenesis(opforks.Interop) + } + } +} + +// WithHardforkSequentialActivation configures a deployment such that L2 chains +// activate hardforks sequentially, starting from startFork and continuing +// until (including) endFork. Each successive fork is scheduled at +// an increasing offset. +func WithHardforkSequentialActivation(startFork, endFork opforks.Name, delta *uint64) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithForkAtGenesis(startFork) + activateWithOffset := false + deactivate := false + for idx, refFork := range opforks.All { + if deactivate { + l2Cfg.WithForkAtOffset(refFork, nil) + deactivate = true + continue + } + if activateWithOffset { + offset := *delta * uint64(idx) + l2Cfg.WithForkAtOffset(refFork, &offset) + } + if startFork == refFork { + activateWithOffset = true + } + if endFork == refFork { + deactivate = true + } + } + } + } +} + +// WithSequencingWindow overrides the number of L1 blocks in a sequencing window, applied to all L2s. +func WithSequencingWindow(n uint64) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + builder.WithGlobalOverride("sequencerWindowSize", uint64(n)) + } +} + +func WithDeployerMatchL1PAO() DeployerPipelineOption { + return func(wb *worldBuilder, intent *state.Intent, cfg *deployer.ApplyPipelineOpts) { + l1ChainID := new(big.Int).SetUint64(intent.L1ChainID) + deployerKey, err := wb.keys.Secret(devkeys.L1ProxyAdminOwnerRole.Key(l1ChainID)) + wb.require.NoError(err) + cfg.DeployerPrivateKey = deployerKey + } +} + +// WithFinalizationPeriodSeconds overrides the number of L1 blocks in a sequencing window, applied to all L2s. +func WithFinalizationPeriodSeconds(n uint64) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithFinalizationPeriodSeconds(n) + } + } +} + +func WithProofMaturityDelaySeconds(n uint64) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + builder.WithGlobalOverride("proofMaturityDelaySeconds", uint64(n)) + } +} + +func WithDisputeGameFinalityDelaySeconds(seconds uint64) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + builder.WithGlobalOverride("disputeGameFinalityDelaySeconds", seconds) + } +} + +func WithCustomGasToken(name, symbol string, initialLiquidity *big.Int, liquidityControllerOwner common.Address) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithCustomGasToken(name, symbol, initialLiquidity, liquidityControllerOwner) + } + } +} + +func (wb *worldBuilder) buildL1Genesis() { + wb.require.NotNil(wb.output.L1DevGenesis, "must have L1 genesis outer config") + wb.require.NotNil(wb.output.L1StateDump, "must have L1 genesis alloc") + + genesisOuter := wb.output.L1DevGenesis + genesisAlloc := wb.output.L1StateDump.Data.Accounts + genesisCfg := *genesisOuter + genesisCfg.StateHash = nil + genesisCfg.Alloc = genesisAlloc + + wb.outL1Genesis = &genesisCfg +} + +func (wb *worldBuilder) buildL2Genesis() { + wb.outL2Genesis = make(map[eth.ChainID]*core.Genesis) + wb.outL2RollupCfg = make(map[eth.ChainID]*rollup.Config) + for _, ch := range wb.output.Chains { + l2Genesis, l2RollupCfg, err := inspect.GenesisAndRollup(wb.output, ch.ID) + wb.require.NoError(err, "need L2 genesis and rollup") + id := eth.ChainIDFromBytes32(ch.ID) + wb.outL2Genesis[id] = l2Genesis + wb.outL2RollupCfg[id] = l2RollupCfg + } +} + +func (wb *worldBuilder) buildL2DeploymentOutputs() { + wb.outL2Deployment = make(map[eth.ChainID]*L2Deployment) + for _, ch := range wb.output.Chains { + chainID := eth.ChainIDFromBytes32(ch.ID) + wb.outL2Deployment[chainID] = &L2Deployment{ + systemConfigProxyAddr: ch.SystemConfigProxy, + disputeGameFactoryProxy: ch.DisputeGameFactoryProxy, + l1StandardBridgeProxy: ch.L1StandardBridgeProxy, + proxyAdmin: ch.OpChainProxyAdminImpl, + permissionlessDelayedWETHProxy: ch.DelayedWethPermissionlessGameProxy, + } + } + wb.outSuperchainDeployment = &SuperchainDeployment{ + protocolVersionsAddr: wb.output.SuperchainDeployment.ProtocolVersionsProxy, + superchainConfigAddr: wb.output.SuperchainDeployment.SuperchainConfigProxy, + } +} + +func WithRevenueShare(enabled bool, chainFeesRecipient common.Address) DeployerOption { + return func(p devtest.T, keys devkeys.Keys, builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithRevenueShare(enabled, chainFeesRecipient) + } + } +} + +func (wb *worldBuilder) buildFullConfigSet() { + // If no chain has interop active, the dep set will be nil here, + // so we should skip building the full config set. + if wb.output.InteropDepSet == nil { + return + } + + rollupConfigSet := depset.StaticRollupConfigSetFromRollupConfigMap(wb.outL2RollupCfg, + depset.StaticTimestamp(wb.outL1Genesis.Timestamp)) + fullCfgSet, err := depset.NewFullConfigSetMerged(rollupConfigSet, wb.output.InteropDepSet) + wb.require.NoError(err) + wb.outFullCfgSet = fullCfgSet +} + +func (wb *worldBuilder) Build() { + st := &state.State{ + Version: 1, + } + + // Work-around of op-deployer design issue. + // We use the same deployer key for all L1 and L2 chains we deploy here. + deployerKey, err := wb.keys.Secret(devkeys.DeployerRole.Key(big.NewInt(0))) + wb.require.NoError(err, "need deployer key") + + intent, err := wb.builder.Build() + wb.require.NoError(err) + + pipelineOpts := deployer.ApplyPipelineOpts{ + DeploymentTarget: deployer.DeploymentTargetGenesis, + L1RPCUrl: "", + DeployerPrivateKey: deployerKey, + Intent: intent, + State: st, + Logger: wb.logger, + StateWriter: wb, // direct output back here + } + for _, opt := range wb.deployerPipelineOptions { + opt(wb, intent, &pipelineOpts) + } + + err = deployer.ApplyPipeline(wb.p.Ctx(), pipelineOpts) + wb.require.NoError(err) + + wb.require.NotNil(wb.output, "expected state-write to output") + + for _, id := range wb.output.Chains { + chainID := eth.ChainIDFromBytes32(id.ID) + wb.l2Chains = append(wb.l2Chains, chainID) + } + + wb.buildL1Genesis() + wb.buildL2Genesis() + wb.buildL2DeploymentOutputs() + wb.buildFullConfigSet() +} + +// WriteState is a callback used by deployer.ApplyPipeline to write the output +func (wb *worldBuilder) WriteState(st *state.State) error { + wb.output = st + return nil +} diff --git a/op-devstack/sysgo/fakepos.go b/op-devstack/sysgo/fakepos.go new file mode 100644 index 00000000000..be920e37945 --- /dev/null +++ b/op-devstack/sysgo/fakepos.go @@ -0,0 +1,19 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" +) + +type FakePoS struct { + p devtest.CommonT + fakepos *geth.FakePoS +} + +func (f *FakePoS) Start() { + f.p.Require().NoError(f.fakepos.Start(), "fakePoS failed to start") +} + +func (f *FakePoS) Stop() { + f.p.Require().NoError(f.fakepos.Stop(), "fakePoS failed to stop") +} diff --git a/op-devstack/sysgo/interop_filter.go b/op-devstack/sysgo/interop_filter.go new file mode 100644 index 00000000000..17e9d2a331a --- /dev/null +++ b/op-devstack/sysgo/interop_filter.go @@ -0,0 +1,131 @@ +package sysgo + +import ( + "context" + "sync" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-interop-filter/filter" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/log" +) + +// InteropFilter wraps an in-process op-interop-filter service for devstack. +type InteropFilter struct { + mu sync.Mutex + name string + logger log.Logger + service *filter.Service +} + +// HTTPEndpoint returns the service's actual RPC endpoint (e.g. "http://127.0.0.1:12345"). +// The caller is responsible for proxying if needed. +func (f *InteropFilter) HTTPEndpoint() string { + f.mu.Lock() + defer f.mu.Unlock() + return f.service.HTTPEndpoint() +} + +// Ready returns true once all chain ingesters have backfilled. +func (f *InteropFilter) Ready() bool { + f.mu.Lock() + defer f.mu.Unlock() + if f.service == nil { + return false + } + return f.service.Ready() +} + +// WaitForReady blocks until all chain ingesters have backfilled or the timeout expires. +// Call this after the supernode/CL layer has started so that blocks are being produced. +func (f *InteropFilter) WaitForReady(t devtest.T, timeout time.Duration) { + waitCtx, waitCancel := context.WithTimeout(t.Ctx(), timeout) + defer waitCancel() + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + for !f.Ready() { + select { + case <-waitCtx.Done(): + t.Require().Fail("interop filter did not become ready within timeout") + case <-ticker.C: + } + } + f.logger.Info("Interop filter ready") +} + +// SetFailsafeEnabled toggles failsafe mode directly on the backend. +// No admin RPC or JWT needed — this is the in-process advantage. +func (f *InteropFilter) SetFailsafeEnabled(enabled bool) { + f.mu.Lock() + defer f.mu.Unlock() + f.service.SetFailsafeEnabled(enabled) +} + +// FailsafeEnabled returns the current failsafe state. +func (f *InteropFilter) FailsafeEnabled() bool { + f.mu.Lock() + defer f.mu.Unlock() + return f.service.FailsafeEnabled() +} + +// Stop gracefully shuts down the interop filter service. +func (f *InteropFilter) Stop() { + f.mu.Lock() + defer f.mu.Unlock() + if f.service == nil { + f.logger.Warn("InteropFilter already stopped") + return + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + f.logger.Info("Closing interop filter") + closeErr := f.service.Stop(ctx) + f.logger.Info("Closed interop filter", "err", closeErr) + f.service = nil +} + +// startInteropFilter creates and starts an in-process interop filter. +// Rollup configs are passed as Go structs — no file serialization needed. +// The filter connects to EL nodes via the provided RPC URLs. +func startInteropFilter( + t devtest.T, + name string, + l2RPCs []string, + rollupConfigs map[eth.ChainID]*rollup.Config, +) *InteropFilter { + logger := t.Logger().New("component", name) + + cfg := &filter.Config{ + L2RPCs: l2RPCs, + RollupConfigs: rollupConfigs, + DataDir: t.TempDir(), + BackfillDuration: 30 * time.Second, + MessageExpiryWindow: 7 * 24 * 3600, // 7 days in seconds + PollInterval: 500 * time.Millisecond, + ValidationInterval: 200 * time.Millisecond, + RPCAddr: "127.0.0.1", + RPCPort: 0, // Auto-assign + Version: "devstack", + } + + t.Require().NoError(cfg.Check(), "invalid interop filter config") + + service, err := filter.NewService(context.Background(), cfg, logger) + t.Require().NoError(err, "failed to create interop filter service") + + f := &InteropFilter{ + name: name, + logger: logger, + service: service, + } + + logger.Info("Starting interop filter") + err = service.Start(context.Background()) + t.Require().NoError(err, "failed to start interop filter") + t.Cleanup(func() { f.Stop() }) + logger.Info("Started interop filter", "endpoint", service.HTTPEndpoint()) + + return f +} diff --git a/op-devstack/sysgo/interop_helpers.go b/op-devstack/sysgo/interop_helpers.go new file mode 100644 index 00000000000..775a0350c3b --- /dev/null +++ b/op-devstack/sysgo/interop_helpers.go @@ -0,0 +1,42 @@ +package sysgo + +import ( + "os" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" +) + +func validateSimpleInteropPresetConfig(t devtest.T, cfg PresetConfig, l2Nets ...*L2Network) { + require := t.Require() + if cfg.MaxSequencingWindow != nil { + for _, l2Net := range l2Nets { + require.LessOrEqualf( + l2Net.rollupCfg.SeqWindowSize, + *cfg.MaxSequencingWindow, + "sequencing window of chain %s must fit in max sequencing window size", + l2Net.ChainID(), + ) + } + } + if cfg.RequireInteropNotAtGen { + for _, l2Net := range l2Nets { + interopTime := l2Net.genesis.Config.InteropTime + require.NotNilf(interopTime, "chain %s must have interop", l2Net.ChainID()) + require.NotZerof(*interopTime, "chain %s interop must not be at genesis", l2Net.ChainID()) + } + } +} + +func readJWTSecretFromPath(t devtest.T, jwtPath string) [32]byte { + content, err := os.ReadFile(jwtPath) + t.Require().NoError(err, "failed to read jwt path %s", jwtPath) + raw, err := hexutil.Decode(strings.TrimSpace(string(content))) + t.Require().NoError(err, "failed to decode jwt secret from %s", jwtPath) + t.Require().Len(raw, 32, "invalid jwt secret length from %s", jwtPath) + var secret [32]byte + copy(secret[:], raw) + return secret +} diff --git a/op-devstack/sysgo/l1_network.go b/op-devstack/sysgo/l1_network.go new file mode 100644 index 00000000000..56b3ad202ad --- /dev/null +++ b/op-devstack/sysgo/l1_network.go @@ -0,0 +1,27 @@ +package sysgo + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/params" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type L1Network struct { + name string + chainID eth.ChainID + genesis *core.Genesis + blockTime uint64 +} + +func (n *L1Network) Name() string { + return n.name +} + +func (n *L1Network) ChainID() eth.ChainID { + return n.chainID +} + +func (n *L1Network) ChainConfig() *params.ChainConfig { + return n.genesis.Config +} diff --git a/op-devstack/sysgo/l1_nodes.go b/op-devstack/sysgo/l1_nodes.go new file mode 100644 index 00000000000..eaf20820c29 --- /dev/null +++ b/op-devstack/sysgo/l1_nodes.go @@ -0,0 +1,49 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/fakebeacon" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type L1ELNode interface { + l1ELNode() + UserRPC() string + AuthRPC() string +} + +type L1Geth struct { + name string + chainID eth.ChainID + userRPC string + authRPC string + l1Geth *geth.GethInstance + blobPath string +} + +func (*L1Geth) l1ELNode() {} + +func (g *L1Geth) UserRPC() string { + return g.userRPC +} + +func (g *L1Geth) AuthRPC() string { + return g.authRPC +} + +type L1CLNode struct { + name string + chainID eth.ChainID + beaconHTTPAddr string + beacon *fakebeacon.FakeBeacon + fakepos *FakePoS +} + +func (n *L1CLNode) BeaconHTTPAddr() string { + return n.beaconHTTPAddr +} + +func (n *L1CLNode) FakePoS() stack.Lifecycle { + return n.fakepos +} diff --git a/op-devstack/sysgo/l2_batcher.go b/op-devstack/sysgo/l2_batcher.go new file mode 100644 index 00000000000..3bf6fdbb25f --- /dev/null +++ b/op-devstack/sysgo/l2_batcher.go @@ -0,0 +1,22 @@ +package sysgo + +import ( + bss "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type L2Batcher struct { + name string + chainID eth.ChainID + service *bss.BatcherService + rpc string + l1RPC string + l2CLRPC string + l2ELRPC string +} + +func (b *L2Batcher) UserRPC() string { + return b.rpc +} + +type BatcherOption func(target ComponentTarget, cfg *bss.CLIConfig) diff --git a/op-devstack/sysgo/l2_challenger.go b/op-devstack/sysgo/l2_challenger.go new file mode 100644 index 00000000000..76dc6d5d57d --- /dev/null +++ b/op-devstack/sysgo/l2_challenger.go @@ -0,0 +1,18 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type L2Challenger struct { + name string + chainIDs []eth.ChainID + service cliapp.Lifecycle + config *config.Config +} + +func (p *L2Challenger) Config() *config.Config { + return p.config +} diff --git a/op-devstack/sysgo/l2_cl.go b/op-devstack/sysgo/l2_cl.go new file mode 100644 index 00000000000..009e55d85c3 --- /dev/null +++ b/op-devstack/sysgo/l2_cl.go @@ -0,0 +1,94 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + nodeSync "github.com/ethereum-optimism/optimism/op-node/rollup/sync" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type L2CLNode interface { + stack.Lifecycle + UserRPC() string + InteropRPC() (endpoint string, jwtSecret eth.Bytes32) +} + +type L2CLConfig struct { + // SyncMode to run, if this is a sequencer + SequencerSyncMode nodeSync.Mode + // SyncMode to run, if this is a verifier + VerifierSyncMode nodeSync.Mode + + // SafeDBPath is the path to the safe DB to use. Disabled if empty. + SafeDBPath string + + IsSequencer bool + IndexingMode bool + + // EnableReqRespSync is the flag to enable/disable req-resp sync. + EnableReqRespSync bool + + // UseReqRespSync controls whether to use the req-resp sync protocol. EnableReqRespSync == false && UseReqRespSync == true is not allowed, and node will fail to start. + UseReqRespSync bool + + // NoDiscovery is the flag to enable/disable discovery + NoDiscovery bool + + FollowSource string +} + +func L2CLSequencer() L2CLOption { + return L2CLOptionFn(func(p devtest.T, _ ComponentTarget, cfg *L2CLConfig) { + cfg.IsSequencer = true + }) +} + +func L2CLIndexing() L2CLOption { + return L2CLOptionFn(func(p devtest.T, _ ComponentTarget, cfg *L2CLConfig) { + cfg.IndexingMode = true + }) +} + +func L2CLFollowSource(source string) L2CLOption { + return L2CLOptionFn(func(p devtest.T, _ ComponentTarget, cfg *L2CLConfig) { + cfg.FollowSource = source + }) +} + +func DefaultL2CLConfig() *L2CLConfig { + return &L2CLConfig{ + SequencerSyncMode: nodeSync.CLSync, + VerifierSyncMode: nodeSync.CLSync, + SafeDBPath: "", + IsSequencer: false, + IndexingMode: false, + EnableReqRespSync: true, + UseReqRespSync: true, + NoDiscovery: false, + FollowSource: "", + } +} + +type L2CLOption interface { + Apply(p devtest.T, target ComponentTarget, cfg *L2CLConfig) +} + +type L2CLOptionFn func(p devtest.T, target ComponentTarget, cfg *L2CLConfig) + +var _ L2CLOption = L2CLOptionFn(nil) + +func (fn L2CLOptionFn) Apply(p devtest.T, target ComponentTarget, cfg *L2CLConfig) { + fn(p, target, cfg) +} + +// L2CLOptionBundle a list of multiple L2CLOption, to all be applied in order. +type L2CLOptionBundle []L2CLOption + +var _ L2CLOption = L2CLOptionBundle(nil) + +func (l L2CLOptionBundle) Apply(p devtest.T, target ComponentTarget, cfg *L2CLConfig) { + for _, opt := range l { + p.Require().NotNil(opt, "cannot Apply nil L2CLOption") + opt.Apply(p, target, cfg) + } +} diff --git a/op-devstack/sysgo/l2_cl_kona.go b/op-devstack/sysgo/l2_cl_kona.go new file mode 100644 index 00000000000..80350c2e431 --- /dev/null +++ b/op-devstack/sysgo/l2_cl_kona.go @@ -0,0 +1,132 @@ +package sysgo + +import ( + "fmt" + "net/url" + "strings" + "sync" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/logpipe" + "github.com/ethereum-optimism/optimism/op-service/tasks" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" + "github.com/ethereum/go-ethereum/log" +) + +type KonaNode struct { + mu sync.Mutex + + name string + chainID eth.ChainID + + userRPC string + interopEndpoint string // warning: currently not fully supported + interopJwtSecret eth.Bytes32 + + userProxy *tcpproxy.Proxy + + execPath string + args []string + // Each entry is of the form "key=value". + env []string + + p devtest.T + + sub *SubProcess + + l2MetricsRegistrar L2MetricsRegistrar +} + +func (k *KonaNode) Start() { + k.mu.Lock() + defer k.mu.Unlock() + if k.sub != nil { + k.p.Logger().Warn("Kona-node already started") + return + } + // Create a proxy for the user RPC, + // so other services can connect, and stay connected, across restarts. + if k.userProxy == nil { + k.userProxy = tcpproxy.New(k.p.Logger()) + k.p.Require().NoError(k.userProxy.Start()) + k.p.Cleanup(func() { + k.userProxy.Close() + }) + k.userRPC = "http://" + k.userProxy.Addr() + } + // Create the sub-process. + // We pipe sub-process logs to the test-logger. + // And inspect them along the way, to get the RPC server address. + logOut := logpipe.ToLoggerWithMinLevel(k.p.Logger().New("component", "kona-node", "src", "stdout"), log.LevelWarn) + logErr := logpipe.ToLoggerWithMinLevel(k.p.Logger().New("component", "kona-node", "src", "stderr"), log.LevelWarn) + userRPCChan := make(chan string, 1) + defer close(userRPCChan) + + metricsTargetChan := make(chan PrometheusMetricsTarget, 1) + defer close(metricsTargetChan) + + onLogEntry := func(e logpipe.LogEntry) { + msg := e.LogMessage() + if msg == "RPC server bound to address" { + userRPCChan <- "http://" + e.FieldValue("addr").(string) + } else if metricsUrl, found := strings.CutPrefix(msg, "Serving metrics at: "); found { + // Matching messages like "Serving metrics at: http://0.0.0.0:9091" + if !strings.HasPrefix(metricsUrl, "http") { + metricsUrl = fmt.Sprintf("http://%s", metricsUrl) + } + parsedUrl, err := url.Parse(metricsUrl) + k.p.Require().NoError(err, "invalid metrics url output to logs", "log", msg) + k.p.Require().NotEmpty(parsedUrl.Port(), "empty port in logged metrics url", "log", msg) + metricsTargetChan <- NewPrometheusMetricsTarget(parsedUrl.Hostname(), parsedUrl.Port(), false) + } + } + stdOutLogs := logpipe.LogCallback(func(line []byte) { + e := logpipe.ParseRustStructuredLogs(line) + logOut(e) + onLogEntry(e) + }) + stdErrLogs := logpipe.LogCallback(func(line []byte) { + e := logpipe.ParseRustStructuredLogs(line) + logErr(e) + }) + k.sub = NewSubProcess(k.p, stdOutLogs, stdErrLogs) + + err := k.sub.Start(k.execPath, k.args, k.env) + k.p.Require().NoError(err, "Must start") + + var userRPCAddr string + k.p.Require().NoError(tasks.Await(k.p.Ctx(), userRPCChan, &userRPCAddr), "need user RPC") + + if areMetricsEnabled() { + var metricsTarget PrometheusMetricsTarget + k.p.Require().NoError(tasks.Await(k.p.Ctx(), metricsTargetChan, &metricsTarget), "need metrics endpoint") + k.l2MetricsRegistrar.RegisterL2MetricsTargets(k.name, metricsTarget) + } + + k.userProxy.SetUpstream(ProxyAddr(k.p.Require(), userRPCAddr)) +} + +// Stop stops the kona node. +// warning: no restarts supported yet, since the RPC port is not remembered. +func (k *KonaNode) Stop() { + k.mu.Lock() + defer k.mu.Unlock() + if k.sub == nil { + k.p.Logger().Warn("kona-node already stopped") + return + } + err := k.sub.Stop(true) + k.p.Require().NoError(err, "Must stop") + k.sub = nil +} + +func (k *KonaNode) UserRPC() string { + return k.userRPC +} + +func (k *KonaNode) InteropRPC() (endpoint string, jwtSecret eth.Bytes32) { + return k.interopEndpoint, k.interopJwtSecret +} + +var _ L2CLNode = (*KonaNode)(nil) diff --git a/op-devstack/sysgo/l2_cl_opnode.go b/op-devstack/sysgo/l2_cl_opnode.go new file mode 100644 index 00000000000..07b8bdbf466 --- /dev/null +++ b/op-devstack/sysgo/l2_cl_opnode.go @@ -0,0 +1,96 @@ +package sysgo + +import ( + "context" + "sync" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/opnode" + "github.com/ethereum-optimism/optimism/op-node/config" + "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" + "github.com/ethereum/go-ethereum/log" +) + +type OpNode struct { + mu sync.Mutex + + name string + opNode *opnode.Opnode + userRPC string + interopEndpoint string + interopJwtSecret eth.Bytes32 + cfg *config.Config + p devtest.CommonT + logger log.Logger + userProxy *tcpproxy.Proxy + interopProxy *tcpproxy.Proxy + clock clock.Clock +} + +var _ L2CLNode = (*OpNode)(nil) + +func (n *OpNode) UserRPC() string { + return n.userRPC +} + +func (n *OpNode) InteropRPC() (endpoint string, jwtSecret eth.Bytes32) { + // Make sure to use the proxied interop endpoint + return n.interopEndpoint, n.interopJwtSecret +} + +func (n *OpNode) Start() { + n.mu.Lock() + defer n.mu.Unlock() + if n.opNode != nil { + n.logger.Warn("Op-node already started") + return + } + + if n.userProxy == nil { + n.userProxy = tcpproxy.New(n.logger.New("proxy", "l2cl-user")) + n.p.Require().NoError(n.userProxy.Start()) + n.p.Cleanup(func() { + n.userProxy.Close() + }) + n.userRPC = "http://" + n.userProxy.Addr() + } + if n.interopProxy == nil { + n.interopProxy = tcpproxy.New(n.logger.New("proxy", "l2cl-interop")) + n.p.Require().NoError(n.interopProxy.Start()) + n.p.Cleanup(func() { + n.interopProxy.Close() + }) + n.interopEndpoint = "ws://" + n.interopProxy.Addr() + } + n.logger.Info("Starting op-node") + opNode, err := opnode.NewOpnode(n.logger, n.cfg, n.clock, func(err error) { + n.p.Require().NoError(err, "op-node critical error") + }) + n.p.Require().NoError(err, "op-node failed to start") + n.logger.Info("Started op-node") + n.opNode = opNode + + n.userProxy.SetUpstream(ProxyAddr(n.p.Require(), opNode.UserRPC().RPC())) + + interopEndpoint, interopJwtSecret := opNode.InteropRPC() + n.interopProxy.SetUpstream(ProxyAddr(n.p.Require(), interopEndpoint)) + n.interopJwtSecret = interopJwtSecret +} + +func (n *OpNode) Stop() { + n.mu.Lock() + defer n.mu.Unlock() + if n.opNode == nil { + n.logger.Warn("Op-node already stopped") + return + } + ctx, cancel := context.WithCancel(context.Background()) + cancel() // force-quit + n.logger.Info("Closing op-node") + closeErr := n.opNode.Stop(ctx) + n.logger.Info("Closed op-node", "err", closeErr) + + n.opNode = nil +} diff --git a/op-devstack/sysgo/l2_cl_p2p_config.go b/op-devstack/sysgo/l2_cl_p2p_config.go new file mode 100644 index 00000000000..fe94889d540 --- /dev/null +++ b/op-devstack/sysgo/l2_cl_p2p_config.go @@ -0,0 +1,74 @@ +package sysgo + +import ( + "encoding/hex" + "flag" + + "github.com/urfave/cli/v2" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + opNodeFlags "github.com/ethereum-optimism/optimism/op-node/flags" + "github.com/ethereum-optimism/optimism/op-node/p2p" + p2pcli "github.com/ethereum-optimism/optimism/op-node/p2p/cli" +) + +func newDevstackP2PConfig( + p devtest.CommonT, + logger log.Logger, + blockTime uint64, + noDiscovery bool, + enableReqRespSync bool, + sequencerP2PKeyHex string, +) (*p2p.Config, p2p.SignerSetup) { + require := p.Require() + + // make a dummy flagset since p2p config initialization helpers only input cli context + fs := flag.NewFlagSet("", flag.ContinueOnError) + // use default flags + for _, f := range opNodeFlags.P2PFlags(opNodeFlags.EnvVarPrefix) { + require.NoError(f.Apply(fs)) + } + // mandatory P2P flags + require.NoError(fs.Set(opNodeFlags.AdvertiseIPName, "127.0.0.1")) + require.NoError(fs.Set(opNodeFlags.AdvertiseTCPPortName, "0")) + require.NoError(fs.Set(opNodeFlags.AdvertiseUDPPortName, "0")) + require.NoError(fs.Set(opNodeFlags.ListenIPName, "127.0.0.1")) + require.NoError(fs.Set(opNodeFlags.ListenTCPPortName, "0")) + require.NoError(fs.Set(opNodeFlags.ListenUDPPortName, "0")) + // avoid resource unavailable error by using memorydb + require.NoError(fs.Set(opNodeFlags.DiscoveryPathName, "memory")) + require.NoError(fs.Set(opNodeFlags.PeerstorePathName, "memory")) + // Explicitly set to empty; do not default to resolving DNS of external bootnodes + require.NoError(fs.Set(opNodeFlags.BootnodesName, "")) + // For peer ID + networkPrivKey, err := crypto.GenerateKey() + networkPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(networkPrivKey)) + require.NoError(err) + require.NoError(fs.Set(opNodeFlags.P2PPrivRawName, networkPrivKeyHex)) + if noDiscovery { + require.NoError(fs.Set(opNodeFlags.NoDiscoveryName, "true")) + } + if enableReqRespSync { + require.NoError(fs.Set(opNodeFlags.SyncReqRespName, "true")) + } + + cliCtx := cli.NewContext(&cli.App{}, fs, nil) + + var p2pSignerSetup p2p.SignerSetup + if sequencerP2PKeyHex != "" { + require.NoError(fs.Set(opNodeFlags.SequencerP2PKeyName, sequencerP2PKeyHex)) + p2pSignerSetup, err = p2pcli.LoadSignerSetup(cliCtx, logger) + require.NoError(err, "failed to load p2p signer") + logger.Info("Sequencer key acquired") + } + + p2pConfig, err := p2pcli.NewConfig(cliCtx, blockTime) + require.NoError(err, "failed to load p2p config") + p2pConfig.NoDiscovery = noDiscovery + p2pConfig.EnableReqRespSync = enableReqRespSync + + return p2pConfig, p2pSignerSetup +} diff --git a/op-devstack/sysgo/l2_cl_p2p_util.go b/op-devstack/sysgo/l2_cl_p2p_util.go new file mode 100644 index 00000000000..b5fda39a510 --- /dev/null +++ b/op-devstack/sysgo/l2_cl_p2p_util.go @@ -0,0 +1,82 @@ +package sysgo + +import ( + "context" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-service/testreq" +) + +func GetP2PClient(ctx context.Context, logger log.Logger, l2CLNode L2CLNode) (*sources.P2PClient, error) { + rpcClient, err := client.NewRPC(ctx, logger, l2CLNode.UserRPC(), client.WithLazyDial()) + if err != nil { + return nil, fmt.Errorf("failed to initialize rpc client for p2p client: %w", err) + } + return sources.NewP2PClient(rpcClient), nil +} + +func GetPeerInfo(ctx context.Context, p2pClient *sources.P2PClient) (*apis.PeerInfo, error) { + peerInfo, err := retry.Do(ctx, 3, retry.Exponential(), func() (*apis.PeerInfo, error) { + self, err := p2pClient.Self(ctx) + if err != nil { + return nil, err + } + if len(self.Addresses) == 0 { + return nil, fmt.Errorf("no address found for peer") + } + if strings.HasPrefix(self.Addresses[0], "/p2p/") { + return nil, fmt.Errorf("malformed multiaddr which starts with /p2p/") + } + return self, nil + }) + if err != nil { + return nil, fmt.Errorf("failed to get peer info: %w", err) + } + return peerInfo, nil +} + +func GetPeers(ctx context.Context, p2pClient *sources.P2PClient) (*apis.PeerDump, error) { + peerDump, err := retry.Do(ctx, 3, retry.Exponential(), func() (*apis.PeerDump, error) { + return p2pClient.Peers(ctx, true) + }) + if err != nil { + return nil, fmt.Errorf("failed to get peers: %w", err) + } + return peerDump, nil +} + +type p2pClientsAndPeers struct { + client1 *sources.P2PClient + client2 *sources.P2PClient + peerInfo1 *apis.PeerInfo + peerInfo2 *apis.PeerInfo +} + +func getP2PClientsAndPeers(ctx context.Context, logger log.Logger, + require *testreq.Assertions, l2CL1, l2CL2 L2CLNode) *p2pClientsAndPeers { + p2pClient1, err := GetP2PClient(ctx, logger, l2CL1) + require.NoError(err) + p2pClient2, err := GetP2PClient(ctx, logger, l2CL2) + require.NoError(err) + + peerInfo1, err := GetPeerInfo(ctx, p2pClient1) + require.NoError(err) + peerInfo2, err := GetPeerInfo(ctx, p2pClient2) + require.NoError(err) + + require.True(len(peerInfo1.Addresses) > 0 && len(peerInfo2.Addresses) > 0, "malformed peer info") + + return &p2pClientsAndPeers{ + client1: p2pClient1, + client2: p2pClient2, + peerInfo1: peerInfo1, + peerInfo2: peerInfo2, + } +} diff --git a/op-devstack/sysgo/l2_cl_supernode.go b/op-devstack/sysgo/l2_cl_supernode.go new file mode 100644 index 00000000000..650ce7fc103 --- /dev/null +++ b/op-devstack/sysgo/l2_cl_supernode.go @@ -0,0 +1,156 @@ +package sysgo + +import ( + "context" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-node/config" + "github.com/ethereum-optimism/optimism/op-service/eth" + snconfig "github.com/ethereum-optimism/optimism/op-supernode/config" + "github.com/ethereum-optimism/optimism/op-supernode/supernode" +) + +type SuperNode struct { + mu sync.Mutex + sn *supernode.Supernode + cancel context.CancelFunc + userRPC string + interopEndpoint string + interopJwtSecret eth.Bytes32 + p devtest.CommonT + logger log.Logger + chains []eth.ChainID + l1UserRPC string + l1BeaconAddr string + + // Configs stored for Start()/restart. + snCfg *snconfig.CLIConfig + vnCfgs map[eth.ChainID]*config.Config +} + +var _ L2CLNode = (*SuperNode)(nil) + +func (n *SuperNode) UserRPC() string { + return n.userRPC +} + +func (n *SuperNode) InteropRPC() (endpoint string, jwtSecret eth.Bytes32) { + return n.interopEndpoint, n.interopJwtSecret +} + +func (n *SuperNode) Start() { + n.mu.Lock() + defer n.mu.Unlock() + if n.sn != nil { + n.logger.Warn("Supernode already started") + return + } + + n.p.Require().NotNil(n.snCfg, "supernode CLI config required") + + ctx, cancel := context.WithCancel(n.p.Ctx()) + exitFn := func(err error) { n.p.Require().NoError(err, "supernode critical error") } + sn, err := supernode.New(ctx, n.logger, "devstack", exitFn, n.snCfg, n.vnCfgs) + n.p.Require().NoError(err, "supernode failed to create") + n.sn = sn + n.cancel = cancel + + n.p.Require().NoError(n.sn.Start(ctx)) + + // Wait for the RPC addr and save userRPC/interop endpoints + addr, err := n.sn.WaitRPCAddr(ctx) + n.p.Require().NoError(err, "supernode failed to bind RPC address") + base := "http://" + addr + n.userRPC = base + n.interopEndpoint = base +} + +func (n *SuperNode) Stop() { + n.mu.Lock() + defer n.mu.Unlock() + if n.sn == nil { + n.logger.Warn("Supernode already stopped") + return + } + if n.cancel != nil { + n.cancel() + } + // Attempt graceful stop + stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = n.sn.Stop(stopCtx) + n.sn = nil +} + +// PauseInteropActivity pauses the interop activity at the given timestamp. +// This function is for integration test control only. +func (n *SuperNode) PauseInteropActivity(ts uint64) { + n.mu.Lock() + defer n.mu.Unlock() + if n.sn != nil { + n.sn.PauseInteropActivity(ts) + } +} + +// ResumeInteropActivity clears any pause on the interop activity. +// This function is for integration test control only. +func (n *SuperNode) ResumeInteropActivity() { + n.mu.Lock() + defer n.mu.Unlock() + if n.sn != nil { + n.sn.ResumeInteropActivity() + } +} + +// SuperNodeProxy is a thin wrapper that points to a shared supernode instance. +type SuperNodeProxy struct { + p devtest.CommonT + logger log.Logger + userRPC string + interopEndpoint string + interopJwtSecret eth.Bytes32 +} + +var _ L2CLNode = (*SuperNodeProxy)(nil) + +func (n *SuperNodeProxy) Start() {} +func (n *SuperNodeProxy) Stop() {} +func (n *SuperNodeProxy) UserRPC() string { return n.userRPC } +func (n *SuperNodeProxy) InteropRPC() (endpoint string, jwtSecret eth.Bytes32) { + return n.interopEndpoint, n.interopJwtSecret +} + +// SupernodeConfig holds configuration options for the shared supernode. +type SupernodeConfig struct { + // InteropActivationTimestamp enables the interop activity at the given timestamp. + // Set to nil to disable interop (default). Non-nil (including 0) enables interop. + InteropActivationTimestamp *uint64 + + // UseGenesisInterop, when true, sets InteropActivationTimestamp to the genesis + // timestamp of the first configured chain at deploy time. Takes effect inside + // withSharedSupernodeCLsImpl after deployment, when the genesis time is known. + UseGenesisInterop bool +} + +// SupernodeOption is a functional option for configuring the supernode. +type SupernodeOption func(*SupernodeConfig) + +// WithSupernodeInterop enables the interop activity with the given activation timestamp. +func WithSupernodeInterop(activationTimestamp uint64) SupernodeOption { + return func(cfg *SupernodeConfig) { + ts := activationTimestamp + cfg.InteropActivationTimestamp = &ts + } +} + +// WithSupernodeInteropAtGenesis enables interop at the genesis timestamp of the first +// configured chain. The timestamp is resolved after deployment, when genesis is known. +func WithSupernodeInteropAtGenesis() SupernodeOption { + return func(cfg *SupernodeConfig) { + cfg.UseGenesisInterop = true + } +} diff --git a/op-devstack/sysgo/l2_conductor.go b/op-devstack/sysgo/l2_conductor.go new file mode 100644 index 00000000000..d28fafbbaa6 --- /dev/null +++ b/op-devstack/sysgo/l2_conductor.go @@ -0,0 +1,28 @@ +package sysgo + +import ( + opconductor "github.com/ethereum-optimism/optimism/op-conductor/conductor" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type Conductor struct { + name string + chainID eth.ChainID + + serverID string + consensusEndpoint string + rpcEndpoint string + service *opconductor.OpConductor +} + +func (c *Conductor) ServerID() string { + return c.serverID +} + +func (c *Conductor) ConsensusEndpoint() string { + return c.consensusEndpoint +} + +func (c *Conductor) HTTPEndpoint() string { + return c.rpcEndpoint +} diff --git a/op-devstack/sysgo/l2_el.go b/op-devstack/sysgo/l2_el.go new file mode 100644 index 00000000000..3aa7acf8286 --- /dev/null +++ b/op-devstack/sysgo/l2_el.go @@ -0,0 +1,74 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/stack" +) + +type L2ELNode interface { + stack.Lifecycle + UserRPC() string + EngineRPC() string + JWTPath() string +} + +type L2ELConfig struct { + P2PAddr string + P2PPort int + P2PNodeKeyHex string + StaticPeers []string + TrustedPeers []string + ProofHistory bool +} + +func L2ELWithProofHistory(enable bool) L2ELOption { + return L2ELOptionFn(func(p devtest.T, _ ComponentTarget, cfg *L2ELConfig) { + cfg.ProofHistory = enable + }) +} + +// L2ELWithP2PConfig sets deterministic P2P identity and static peers for the L2 EL. +func L2ELWithP2PConfig(addr string, port int, nodeKeyHex string, staticPeers, trustedPeers []string) L2ELOption { + return L2ELOptionFn(func(p devtest.T, _ ComponentTarget, cfg *L2ELConfig) { + cfg.P2PAddr = addr + cfg.P2PPort = port + cfg.P2PNodeKeyHex = nodeKeyHex + cfg.StaticPeers = staticPeers + cfg.TrustedPeers = trustedPeers + }) +} + +func DefaultL2ELConfig() *L2ELConfig { + return &L2ELConfig{ + P2PAddr: "127.0.0.1", + P2PPort: 0, + P2PNodeKeyHex: "", + StaticPeers: nil, + TrustedPeers: nil, + ProofHistory: false, + } +} + +type L2ELOption interface { + Apply(p devtest.T, target ComponentTarget, cfg *L2ELConfig) +} + +type L2ELOptionFn func(p devtest.T, target ComponentTarget, cfg *L2ELConfig) + +var _ L2ELOption = L2ELOptionFn(nil) + +func (fn L2ELOptionFn) Apply(p devtest.T, target ComponentTarget, cfg *L2ELConfig) { + fn(p, target, cfg) +} + +// L2ELOptionBundle a list of multiple L2ELOption, to all be applied in order. +type L2ELOptionBundle []L2ELOption + +var _ L2ELOption = L2ELOptionBundle(nil) + +func (l L2ELOptionBundle) Apply(p devtest.T, target ComponentTarget, cfg *L2ELConfig) { + for _, opt := range l { + p.Require().NotNil(opt, "cannot Apply nil L2ELOption") + opt.Apply(p, target, cfg) + } +} diff --git a/op-devstack/sysgo/l2_el_opgeth.go b/op-devstack/sysgo/l2_el_opgeth.go new file mode 100644 index 00000000000..44702840950 --- /dev/null +++ b/op-devstack/sysgo/l2_el_opgeth.go @@ -0,0 +1,144 @@ +package sysgo + +import ( + "strconv" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/log" + gn "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" +) + +type OpGeth struct { + mu sync.Mutex + + p devtest.CommonT + logger log.Logger + name string + l2Net *L2Network + jwtPath string + jwtSecret [32]byte + supervisorRPC string + l2Geth *geth.GethInstance + cfg *L2ELConfig + + authRPC string + userRPC string + + authProxy *tcpproxy.Proxy + userProxy *tcpproxy.Proxy +} + +var _ L2ELNode = (*OpGeth)(nil) + +func (n *OpGeth) UserRPC() string { + return n.userRPC +} + +func (n *OpGeth) EngineRPC() string { + return n.authRPC +} + +func (n *OpGeth) JWTPath() string { + return n.jwtPath +} + +func (n *OpGeth) Start() { + n.mu.Lock() + defer n.mu.Unlock() + if n.l2Geth != nil { + n.logger.Warn("op-geth already started") + return + } + + if n.authProxy == nil { + n.authProxy = tcpproxy.New(n.logger.New("proxy", "l2el-auth")) + n.p.Require().NoError(n.authProxy.Start()) + n.p.Cleanup(func() { + n.authProxy.Close() + }) + n.authRPC = "ws://" + n.authProxy.Addr() + } + if n.userProxy == nil { + n.userProxy = tcpproxy.New(n.logger.New("proxy", "l2el-user")) + n.p.Require().NoError(n.userProxy.Start()) + n.p.Cleanup(func() { + n.userProxy.Close() + }) + n.userRPC = "ws://" + n.userProxy.Addr() + } + + require := n.p.Require() + l2Geth, err := geth.InitL2(NewComponentTarget(n.name, n.l2Net.ChainID()).String(), n.l2Net.genesis, n.jwtPath, + func(ethCfg *ethconfig.Config, nodeCfg *gn.Config) error { + ethCfg.InteropMessageRPC = n.supervisorRPC + ethCfg.InteropMempoolFiltering = true + + listenAddr := n.cfg.P2PAddr + port := n.cfg.P2PPort + listenAddr = listenAddr + ":" + strconv.Itoa(port) + + nodeCfg.P2P = p2p.Config{ + NoDiscovery: true, + ListenAddr: listenAddr, + MaxPeers: 10, + } + + if n.cfg.P2PNodeKeyHex != "" { + priv, err := crypto.HexToECDSA(strings.TrimPrefix(n.cfg.P2PNodeKeyHex, "0x")) + if err != nil { + return err + } + nodeCfg.P2P.PrivateKey = priv + } + if len(n.cfg.StaticPeers) > 0 { + nodes := make([]*enode.Node, 0, len(n.cfg.StaticPeers)) + for _, p := range n.cfg.StaticPeers { + nn, err := enode.Parse(enode.ValidSchemes, p) + if err != nil { + return err + } + nodes = append(nodes, nn) + } + nodeCfg.P2P.StaticNodes = nodes + } + if len(n.cfg.TrustedPeers) > 0 { + nodes := make([]*enode.Node, 0, len(n.cfg.TrustedPeers)) + for _, p := range n.cfg.TrustedPeers { + nn, err := enode.Parse(enode.ValidSchemes, p) + if err != nil { + return err + } + nodes = append(nodes, nn) + } + nodeCfg.P2P.TrustedNodes = nodes + } + return nil + }) + require.NoError(err) + require.NoError(l2Geth.Node.Start()) + n.l2Geth = l2Geth + n.authProxy.SetUpstream(ProxyAddr(require, l2Geth.AuthRPC().RPC())) + n.userProxy.SetUpstream(ProxyAddr(require, l2Geth.UserRPC().RPC())) +} + +func (n *OpGeth) Stop() { + n.mu.Lock() + defer n.mu.Unlock() + if n.l2Geth == nil { + n.logger.Warn("op-geth already stopped") + return + } + n.logger.Info("Closing op-geth", "name", n.name, "chain", n.l2Net.ChainID()) + closeErr := n.l2Geth.Close() + n.logger.Info("Closed op-geth", "name", n.name, "chain", n.l2Net.ChainID(), "err", closeErr) + n.l2Geth = nil +} diff --git a/op-devstack/sysgo/l2_el_opreth.go b/op-devstack/sysgo/l2_el_opreth.go new file mode 100644 index 00000000000..4fce659cd9f --- /dev/null +++ b/op-devstack/sysgo/l2_el_opreth.go @@ -0,0 +1,149 @@ +package sysgo + +import ( + "fmt" + "net/url" + "strings" + "sync" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/logpipe" + "github.com/ethereum-optimism/optimism/op-service/tasks" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" + "github.com/ethereum/go-ethereum/log" +) + +type OpReth struct { + mu sync.Mutex + + name string + chainID eth.ChainID + jwtPath string + jwtSecret [32]byte + authRPC string + userRPC string + + authProxy *tcpproxy.Proxy + userProxy *tcpproxy.Proxy + + execPath string + args []string + // Each entry is of the form "key=value". + env []string + + p devtest.T + + sub *SubProcess + + l2MetricsRegistrar L2MetricsRegistrar +} + +var _ L2ELNode = (*OpReth)(nil) + +func (n *OpReth) Start() { + n.mu.Lock() + defer n.mu.Unlock() + if n.sub != nil { + n.p.Logger().Warn("op-reth already started") + return + } + if n.authProxy == nil { + n.authProxy = tcpproxy.New(n.p.Logger()) + n.p.Require().NoError(n.authProxy.Start()) + n.p.Cleanup(func() { + n.authProxy.Close() + }) + n.authRPC = "ws://" + n.authProxy.Addr() + } + if n.userProxy == nil { + n.userProxy = tcpproxy.New(n.p.Logger()) + n.p.Require().NoError(n.userProxy.Start()) + n.p.Cleanup(func() { + n.userProxy.Close() + }) + n.userRPC = "ws://" + n.userProxy.Addr() + } + logOut := logpipe.ToLoggerWithMinLevel(n.p.Logger().New("component", "op-reth", "src", "stdout", "name", n.name, "chain", n.chainID), log.LevelInfo) + logErr := logpipe.ToLoggerWithMinLevel(n.p.Logger().New("component", "op-reth", "src", "stderr", "name", n.name, "chain", n.chainID), log.LevelWarn) + + authRPCChan := make(chan string, 1) + defer close(authRPCChan) + + metricsTargetChan := make(chan PrometheusMetricsTarget, 1) + defer close(metricsTargetChan) + + userRPCChan := make(chan string, 1) + defer close(userRPCChan) + onLogEntry := func(e logpipe.LogEntry) { + msg := e.LogMessage() + if msg == "RPC WS server started" { + select { + case userRPCChan <- "ws://" + e.FieldValue("url").(string): + default: + } + } else if msg == "RPC auth server started" { + select { + case authRPCChan <- "ws://" + e.FieldValue("url").(string): + default: + } + } else if metricsUrl, found := strings.CutPrefix(msg, "Starting metrics endpoint at "); found { + // expected format: "Starting metrics endpoint at 127.0.0.1:9091" + if !strings.HasPrefix(metricsUrl, "http") { + metricsUrl = fmt.Sprintf("http://%s", metricsUrl) + } + parsedUrl, err := url.Parse(metricsUrl) + n.p.Require().NoError(err, "invalid metrics url output to logs", "log", msg) + n.p.Require().NotEmpty(parsedUrl.Port(), "empty port in logged metrics url", "log", msg) + metricsTargetChan <- NewPrometheusMetricsTarget(parsedUrl.Hostname(), parsedUrl.Port(), false) + } + } + stdOutLogs := logpipe.LogCallback(func(line []byte) { + e := logpipe.ParseRustStructuredLogs(line) + logOut(e) + onLogEntry(e) + }) + stdErrLogs := logpipe.LogCallback(func(line []byte) { + e := logpipe.ParseRustStructuredLogs(line) + logErr(e) + }) + n.sub = NewSubProcess(n.p, stdOutLogs, stdErrLogs) + + err := n.sub.Start(n.execPath, n.args, n.env) + n.p.Require().NoError(err, "Must start") + + var userRPCAddr, authRPCAddr string + n.p.Require().NoError(tasks.Await(n.p.Ctx(), userRPCChan, &userRPCAddr), "need user RPC") + n.p.Require().NoError(tasks.Await(n.p.Ctx(), authRPCChan, &authRPCAddr), "need auth RPC") + + if areMetricsEnabled() { + var metricsTarget PrometheusMetricsTarget + n.p.Require().NoError(tasks.Await(n.p.Ctx(), metricsTargetChan, &metricsTarget), "need metrics endpoint") + n.l2MetricsRegistrar.RegisterL2MetricsTargets(n.name, metricsTarget) + } + + n.userProxy.SetUpstream(ProxyAddr(n.p.Require(), userRPCAddr)) + n.authProxy.SetUpstream(ProxyAddr(n.p.Require(), authRPCAddr)) +} + +// Stop stops the op-reth node. +// warning: no restarts supported yet, since the RPC port is not remembered. +func (n *OpReth) Stop() { + n.mu.Lock() + defer n.mu.Unlock() + err := n.sub.Stop(true) + n.p.Require().NoError(err, "Must stop") + n.sub = nil +} + +func (n *OpReth) UserRPC() string { + return n.userRPC +} + +func (n *OpReth) EngineRPC() string { + return n.authRPC +} + +func (n *OpReth) JWTPath() string { + return n.jwtPath +} diff --git a/op-devstack/sysgo/l2_el_p2p_util.go b/op-devstack/sysgo/l2_el_p2p_util.go new file mode 100644 index 00000000000..fc010e9a6cc --- /dev/null +++ b/op-devstack/sysgo/l2_el_p2p_util.go @@ -0,0 +1,93 @@ +package sysgo + +import ( + "context" + "os" + "slices" + "strings" + "time" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-service/testreq" +) + +type RpcCaller interface { + CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error +} + +// ConnectP2P creates a p2p peer connection between node1 and node2. +func ConnectP2P(ctx context.Context, require *testreq.Assertions, initiator RpcCaller, acceptor RpcCaller, trusted bool) { + var targetInfo p2p.NodeInfo + require.NoError(acceptor.CallContext(ctx, &targetInfo, "admin_nodeInfo"), "get node info") + targetNode, err := enode.ParseV4(targetInfo.Enode) + require.NoError(err, "failed to parse target node") + expectedID := targetNode.ID().String() + + var initiatorInfo p2p.NodeInfo + require.NoError(initiator.CallContext(ctx, &initiatorInfo, "admin_nodeInfo"), "get initiator node info") + + var peerAdded bool + require.NoError(initiator.CallContext(ctx, &peerAdded, "admin_addPeer", targetInfo.Enode), "add peer") + require.True(peerAdded, "should have added peer successfully") + + if trusted { + var peerAddedTrusted bool + require.NoError(initiator.CallContext(ctx, &peerAddedTrusted, "admin_addTrustedPeer", targetInfo.Enode), "add trusted peer") + require.True(peerAddedTrusted, "should have added trusted peer successfully") + } + + // Skip P2P connection verification if SKIP_P2P_CONNECTION_CHECK is set + // FIXME(#18570): it seems we have some issues getting op-reth to connect to op-geth. This is a temporary workaround to ensure we can still run the + // devstack tests. + if os.Getenv("SKIP_P2P_CONNECTION_CHECK") != "" { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + err = wait.For(ctx, time.Second, func() (bool, error) { + var peers []peer + if err := initiator.CallContext(ctx, &peers, "admin_peers"); err != nil { + return false, err + } + return slices.ContainsFunc(peers, func(p peer) bool { + peerID := strings.TrimPrefix(strings.ToLower(p.ID), "0x") + return peerID == strings.ToLower(expectedID) + }), nil + }) + require.NoError(err, "The peer was not connected") +} + +// DisconnectP2P disconnects a p2p peer connection between node1 and node2. +func DisconnectP2P(ctx context.Context, require *testreq.Assertions, initiator RpcCaller, acceptor RpcCaller) { + var targetInfo p2p.NodeInfo + require.NoError(acceptor.CallContext(ctx, &targetInfo, "admin_nodeInfo"), "get node info") + targetNode, err := enode.ParseV4(targetInfo.Enode) + require.NoError(err, "failed to parse target node") + expectedID := targetNode.ID().String() + + var peerRemoved bool + require.NoError(initiator.CallContext(ctx, &peerRemoved, "admin_removePeer", targetInfo.ENR), "add peer") + require.True(peerRemoved, "should have removed peer successfully") + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + err = wait.For(ctx, time.Second, func() (bool, error) { + var peers []peer + if err := initiator.CallContext(ctx, &peers, "admin_peers"); err != nil { + return false, err + } + return !slices.ContainsFunc(peers, func(p peer) bool { + peerID := strings.TrimPrefix(strings.ToLower(p.ID), "0x") + return peerID == strings.ToLower(expectedID) + }), nil + }) + require.NoError(err, "The peer was not removed") +} + +type peer struct { + ID string `json:"id"` +} diff --git a/op-devstack/sysgo/l2_el_synctester.go b/op-devstack/sysgo/l2_el_synctester.go new file mode 100644 index 00000000000..0c9d6e106e8 --- /dev/null +++ b/op-devstack/sysgo/l2_el_synctester.go @@ -0,0 +1,133 @@ +package sysgo + +import ( + "fmt" + "sync" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" +) + +// SyncTesterEL is an L2ELNode implementation that runs a sync tester service. +// It provides RPC endpoints that can be used by CL nodes for testing sync functionality. +type SyncTesterEL struct { + mu sync.Mutex + + target ComponentTarget + jwtPath string + + authRPC string + userRPC string + + authProxy *tcpproxy.Proxy + userProxy *tcpproxy.Proxy + + config *SyncTesterELConfig + p devtest.CommonT + + syncTester *SyncTesterService +} + +type SyncTesterELConfig struct { + FCUState eth.FCUState + ELSyncActive bool +} + +func (cfg *SyncTesterELConfig) Path() string { + path := fmt.Sprintf("?latest=%d&safe=%d&finalized=%d", cfg.FCUState.Latest, cfg.FCUState.Safe, cfg.FCUState.Finalized) + if cfg.ELSyncActive { + path += "&el_sync_active=true" + } + return path +} + +func DefaultSyncTesterELConfig() *SyncTesterELConfig { + return &SyncTesterELConfig{ + FCUState: eth.FCUState{Latest: 0, Safe: 0, Finalized: 0}, + ELSyncActive: false, + } +} + +type SyncTesterELOption interface { + Apply(p devtest.T, target ComponentTarget, cfg *SyncTesterELConfig) +} + +type SyncTesterELOptionFn func(p devtest.T, target ComponentTarget, cfg *SyncTesterELConfig) + +var _ SyncTesterELOption = SyncTesterELOptionFn(nil) + +func (fn SyncTesterELOptionFn) Apply(p devtest.T, target ComponentTarget, cfg *SyncTesterELConfig) { + fn(p, target, cfg) +} + +// SyncTesterELOptionBundle a list of multiple SyncTesterELOption, to all be applied in order. +type SyncTesterELOptionBundle []SyncTesterELOption + +var _ SyncTesterELOptionBundle = SyncTesterELOptionBundle(nil) + +func (l SyncTesterELOptionBundle) Apply(p devtest.T, target ComponentTarget, cfg *SyncTesterELConfig) { + for _, opt := range l { + p.Require().NotNil(opt, "cannot Apply nil SyncTesterELOption") + opt.Apply(p, target, cfg) + } +} + +var _ L2ELNode = (*SyncTesterEL)(nil) + +func (n *SyncTesterEL) Start() { + n.mu.Lock() + defer n.mu.Unlock() + + // The SyncTesterEL should connect to the existing sync tester service. + if n.syncTester == nil || n.syncTester.service == nil { + n.p.Logger().Error("syncTester service not available") + return + } + + endpoint := n.syncTester.SyncTesterRPCPath(n.target.ChainID, true) + + path := endpoint + n.config.Path() + + if n.authProxy == nil { + n.authProxy = tcpproxy.New(n.p.Logger().New("proxy", "l2el-synctester-auth")) + n.p.Require().NoError(n.authProxy.Start()) + n.p.Cleanup(func() { + n.authProxy.Close() + }) + + rpc := "http://" + n.authProxy.Addr() + n.authRPC = rpc + path + } + if n.userProxy == nil { + n.userProxy = tcpproxy.New(n.p.Logger().New("proxy", "l2el-synctester-user")) + n.p.Require().NoError(n.userProxy.Start()) + n.p.Cleanup(func() { + n.userProxy.Close() + }) + + rpc := "http://" + n.userProxy.Addr() + n.userRPC = rpc + path + } + + sessionURL := n.syncTester.RPC() + path + + n.authProxy.SetUpstream(ProxyAddr(n.p.Require(), sessionURL)) + n.userProxy.SetUpstream(ProxyAddr(n.p.Require(), sessionURL)) +} + +func (n *SyncTesterEL) Stop() { + // The SyncTesterEL is just a proxy, so there's nothing to stop +} + +func (n *SyncTesterEL) UserRPC() string { + return n.userRPC +} + +func (n *SyncTesterEL) EngineRPC() string { + return n.authRPC +} + +func (n *SyncTesterEL) JWTPath() string { + return n.jwtPath +} diff --git a/op-devstack/sysgo/l2_metrics_dashboard.go b/op-devstack/sysgo/l2_metrics_dashboard.go new file mode 100644 index 00000000000..9acac9ac99e --- /dev/null +++ b/op-devstack/sysgo/l2_metrics_dashboard.go @@ -0,0 +1,18 @@ +package sysgo + +import "fmt" + +const dockerToLocalHost = "host.docker.internal" + +type L2MetricsRegistrar interface { + RegisterL2MetricsTargets(serviceName string, endpoints ...PrometheusMetricsTarget) +} + +type PrometheusMetricsTarget string + +func NewPrometheusMetricsTarget(host string, port string, isRunningInDocker bool) PrometheusMetricsTarget { + if !isRunningInDocker { + host = dockerToLocalHost + } + return PrometheusMetricsTarget(fmt.Sprintf("%s:%s", host, port)) +} diff --git a/op-devstack/sysgo/l2_network.go b/op-devstack/sysgo/l2_network.go new file mode 100644 index 00000000000..20163f3b824 --- /dev/null +++ b/op-devstack/sysgo/l2_network.go @@ -0,0 +1,52 @@ +package sysgo + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/params" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type L2Network struct { + name string + chainID eth.ChainID + l1ChainID eth.ChainID + genesis *core.Genesis + rollupCfg *rollup.Config + deployment *L2Deployment + opcmImpl common.Address + mipsImpl common.Address + keys devkeys.Keys +} + +func (c *L2Network) Name() string { + return c.name +} + +func (c *L2Network) ChainID() eth.ChainID { + return c.chainID +} + +func (c *L2Network) L1ChainID() eth.ChainID { + return c.l1ChainID +} + +func (c *L2Network) ChainConfig() *params.ChainConfig { + return c.genesis.Config +} + +func (c *L2Network) RollupConfig() *rollup.Config { + return c.rollupCfg +} + +func (c *L2Network) Deployment() stack.L2Deployment { + return c.deployment +} + +func (c *L2Network) Keys() devkeys.Keys { + return c.keys +} diff --git a/op-devstack/sysgo/l2_proposer.go b/op-devstack/sysgo/l2_proposer.go new file mode 100644 index 00000000000..bb6e7c31cb5 --- /dev/null +++ b/op-devstack/sysgo/l2_proposer.go @@ -0,0 +1,15 @@ +package sysgo + +import ( + ps "github.com/ethereum-optimism/optimism/op-proposer/proposer" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +type L2Proposer struct { + name string + chainID eth.ChainID + service *ps.ProposerService + userRPC string +} + +type ProposerOption func(target ComponentTarget, cfg *ps.CLIConfig) diff --git a/op-devstack/sysgo/mixed_runtime.go b/op-devstack/sysgo/mixed_runtime.go index 3e096dab764..456ea0e15dc 100644 --- a/op-devstack/sysgo/mixed_runtime.go +++ b/op-devstack/sysgo/mixed_runtime.go @@ -42,27 +42,65 @@ import ( type MixedL2ELKind string -const DevstackL2ELKindEnvVar = "DEVSTACK_L2EL_KIND" +const ( + MixedL2ELOpGeth MixedL2ELKind = "op-geth" + MixedL2ELOpReth MixedL2ELKind = "op-reth" + MixedL2ELOpRethV2 MixedL2ELKind = "op-reth-proof-v2" +) + +type MixedL2CLKind string const ( - MixedL2ELOpGeth MixedL2ELKind = "op-geth" - MixedL2ELOpReth MixedL2ELKind = "op-reth" + MixedL2CLOpNode MixedL2CLKind = "op-node" + MixedL2CLKona MixedL2CLKind = "kona-node" ) +// SkipOnOpGeth skips the test when the L2 execution layer is op-geth +func SkipOnOpGeth(t devtest.T, reason string) { + if devstackL2ELKind() == MixedL2ELOpGeth { + t.Skipf("skipping on op-geth: %s", reason) + } +} + // SkipOnOpReth skips the test when the L2 execution layer is op-reth -// (i.e. DEVSTACK_L2EL_KIND is not "op-geth"). func SkipOnOpReth(t devtest.T, reason string) { - if MixedL2ELKind(os.Getenv(DevstackL2ELKindEnvVar)) == MixedL2ELOpReth { + if devstackL2ELKind() == MixedL2ELOpReth { t.Skipf("skipping on op-reth: %s", reason) } } -type MixedL2CLKind string +// SkipOnKonaNode skips the test when the L2 consensus layer is kona-node +func SkipOnKonaNode(t devtest.T, reason string) { + if devstackL2CLKind() == MixedL2CLKona { + t.Skipf("skipping on kona-node: %s", reason) + } +} -const ( - MixedL2CLOpNode MixedL2CLKind = "op-node" - MixedL2CLKona MixedL2CLKind = "kona-node" -) +func FlakyOnOpReth(t devtest.T, reason string) { + if devstackL2ELKind() == MixedL2ELOpReth { + t.MarkFlaky(reason) + } +} + +func FlakyOnKonaNode(t devtest.T, reason string) { + if devstackL2CLKind() == MixedL2CLKona { + t.MarkFlaky(reason) + } +} + +// devstackL2ELKind returns the L2 EL kind requested via the DEVSTACK_L2EL_KIND +// environment variable. Returns the empty string when the variable is unset, +// meaning "use the runtime's default". +func devstackL2ELKind() MixedL2ELKind { + return MixedL2ELKind(os.Getenv("DEVSTACK_L2EL_KIND")) +} + +// devstackL2CLKind returns the L2 CL kind requested via the DEVSTACK_L2CL_KIND +// environment variable. Returns the empty string when the variable is unset, +// meaning "use the runtime's default". +func devstackL2CLKind() MixedL2CLKind { + return MixedL2CLKind(os.Getenv("DEVSTACK_L2CL_KIND")) +} type MixedSingleChainNodeSpec struct { ELKey string @@ -73,11 +111,10 @@ type MixedSingleChainNodeSpec struct { } type MixedSingleChainPresetConfig struct { - NodeSpecs []MixedSingleChainNodeSpec - WithTestSequencer bool - TestSequencerName string - LocalContractArtifactsPath string - DeployerOptions []DeployerOption + NodeSpecs []MixedSingleChainNodeSpec + WithTestSequencer bool + TestSequencerName string + DeployerOptions []DeployerOption } type mixedSingleChainNode struct { @@ -115,7 +152,7 @@ func NewMixedSingleChainRuntime(t devtest.T, cfg MixedSingleChainPresetConfig) * keys, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) require.NoError(err, "failed to derive dev keys from mnemonic") - l1Net, l2Net := buildSingleChainWorld(t, keys, cfg.LocalContractArtifactsPath, cfg.DeployerOptions...) + l1Net, l2Net := buildSingleChainWorld(t, keys, cfg.DeployerOptions...) jwtPath, jwtSecret := writeJWTSecret(t) l1EL, l1CL := startInProcessL1(t, l1Net, jwtPath) @@ -130,7 +167,9 @@ func NewMixedSingleChainRuntime(t devtest.T, cfg MixedSingleChainPresetConfig) * case MixedL2ELOpGeth: el = startL2ELNode(t, l2Net, jwtPath, jwtSecret, spec.ELKey, identity) case MixedL2ELOpReth: - el = startMixedOpRethNode(t, l2Net, spec.ELKey, jwtPath, jwtSecret, metricsRegistrar) + el = startMixedOpRethNode(t, l2Net, spec.ELKey, jwtPath, jwtSecret, metricsRegistrar, "v1") + case MixedL2ELOpRethV2: + el = startMixedOpRethNode(t, l2Net, spec.ELKey, jwtPath, jwtSecret, metricsRegistrar, "v2") default: require.FailNowf("unsupported EL kind", "unsupported mixed EL kind %q", spec.ELKind) } @@ -234,13 +273,16 @@ func mixedNodeRefs(nodes []mixedSingleChainNode) []MixedSingleChainNodeRefs { return out } -func startMixedOpRethNode( +// buildMixedOpRethNode constructs an OpReth node without starting it. +// Use this when you need to customize args (e.g. --rollup.supervisor-http) before starting. +func buildMixedOpRethNode( t devtest.T, l2Net *L2Network, key string, jwtPath string, jwtSecret [32]byte, metricsRegistrar L2MetricsRegistrar, + storageVersion string, ) *OpReth { tempDir := t.TempDir() @@ -316,19 +358,20 @@ func startMixedOpRethNode( "--datadir=" + dataDirPath, "--chain=" + chainConfigPath, "--proofs-history.storage-path=" + proofHistoryDir, + "--proofs-history.storage-version=" + storageVersion, } - err = exec.Command(execPath, initProofsArgs...).Run() - t.Require().NoError(err, "must init op-reth proof history") + initOut, initErr := exec.Command(execPath, initProofsArgs...).CombinedOutput() + t.Require().NoError(initErr, "must init op-reth proof history: %s", string(initOut)) args = append( args, "--proofs-history", "--proofs-history.window=10000", - "--proofs-history.prune-interval=1m", "--proofs-history.storage-path="+proofHistoryDir, + "--proofs-history.storage-version="+storageVersion, ) - l2EL := &OpReth{ + return &OpReth{ name: key, chainID: l2Net.ChainID(), jwtPath: jwtPath, @@ -341,12 +384,47 @@ func startMixedOpRethNode( p: t, l2MetricsRegistrar: metricsRegistrar, } +} +func startMixedOpRethNode( + t devtest.T, + l2Net *L2Network, + key string, + jwtPath string, + jwtSecret [32]byte, + metricsRegistrar L2MetricsRegistrar, + storageVersion string, +) *OpReth { + node := buildMixedOpRethNode(t, l2Net, key, jwtPath, jwtSecret, metricsRegistrar, storageVersion) t.Logger().Info("Starting op-reth", "name", key, "chain", l2Net.ChainID()) - l2EL.Start() - t.Cleanup(l2EL.Stop) - t.Logger().Info("op-reth is ready", "name", key, "chain", l2Net.ChainID(), "userRPC", l2EL.userRPC, "authRPC", l2EL.authRPC) - return l2EL + node.Start() + t.Cleanup(node.Stop) + t.Logger().Info("op-reth is ready", "name", key, "chain", l2Net.ChainID(), "userRPC", node.userRPC, "authRPC", node.authRPC) + return node +} + +// startMixedOpRethNodeWithSupervisorURL builds and starts an OpReth node +// with --rollup.supervisor-http pointing at the given URL. +func startMixedOpRethNodeWithSupervisorURL( + t devtest.T, + l2Net *L2Network, + key string, + jwtPath string, + jwtSecret [32]byte, + metricsRegistrar L2MetricsRegistrar, + supervisorURL string, + storageVersion string, +) *OpReth { + node := buildMixedOpRethNode(t, l2Net, key, jwtPath, jwtSecret, metricsRegistrar, storageVersion) + if supervisorURL != "" { + node.args = append(node.args, "--rollup.supervisor-http="+supervisorURL) + } + t.Logger().Info("Starting op-reth with supervisor URL", + "name", key, "chain", l2Net.ChainID(), "supervisorURL", supervisorURL) + node.Start() + t.Cleanup(node.Stop) + t.Logger().Info("op-reth is ready", "name", key, "chain", l2Net.ChainID(), "userRPC", node.userRPC, "authRPC", node.authRPC) + return node } func startMixedKonaNode( diff --git a/op-devstack/sysgo/multichain_proofs.go b/op-devstack/sysgo/multichain_proofs.go index db14e6f3eb7..df7af8273d8 100644 --- a/op-devstack/sysgo/multichain_proofs.go +++ b/op-devstack/sysgo/multichain_proofs.go @@ -279,14 +279,8 @@ func startInteropChallenger( sharedchallenger.WithCannonConfig(rollupCfgs, l1Net.genesis, l2Geneses, sharedchallenger.InteropVariant), sharedchallenger.WithSuperCannonGameType(), sharedchallenger.WithSuperPermissionedGameType(), - } - if enableCannonKona { - t.Log("Enabling cannon-kona for super challenger") - options = append(options, - sharedchallenger.WithCannonKonaInteropConfig(rollupCfgs, l1Net.genesis, l2Geneses), - sharedchallenger.WithSuperCannonKonaGameType(), - sharedchallenger.WithExperimentalWitnessEndpoint(), - ) + sharedchallenger.WithCannonKonaInteropConfig(rollupCfgs, l1Net.genesis, l2Geneses), + sharedchallenger.WithSuperCannonKonaGameType(), } cfg, err := sharedchallenger.NewInteropChallengerConfig( t.TempDir(), diff --git a/op-devstack/sysgo/multichain_supernode_runtime.go b/op-devstack/sysgo/multichain_supernode_runtime.go index f886856122f..b68e999f797 100644 --- a/op-devstack/sysgo/multichain_supernode_runtime.go +++ b/op-devstack/sysgo/multichain_supernode_runtime.go @@ -4,7 +4,6 @@ import ( "context" "encoding/hex" "fmt" - "os" "sort" "strconv" "time" @@ -23,6 +22,7 @@ import ( fconf "github.com/ethereum-optimism/optimism/op-faucet/faucet/backend/config" ftypes "github.com/ethereum-optimism/optimism/op-faucet/faucet/backend/types" opnodeconfig "github.com/ethereum-optimism/optimism/op-node/config" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/rollup/interop" nodeSync "github.com/ethereum-optimism/optimism/op-node/rollup/sync" @@ -35,6 +35,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/oppprof" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" snconfig "github.com/ethereum-optimism/optimism/op-supernode/config" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" sequencerConfig "github.com/ethereum-optimism/optimism/op-test-sequencer/config" @@ -97,13 +98,43 @@ func NewTwoL2SupernodeRuntimeWithConfig(t devtest.T, cfg PresetConfig) *MultiCha return runtime } -// startSupernodeEL starts an L2 EL node for the supernode runtime. -// It respects the DEVSTACK_L2EL_KIND env var: "op-geth" uses op-geth, otherwise op-reth is used. +// startSupernodeEL starts an L2 EL node for the supernode runtime, +// respecting DEVSTACK_L2EL_KIND (defaults to op-geth when unset). func startSupernodeEL(t devtest.T, l2Net *L2Network, jwtPath string, jwtSecret [32]byte) L2ELNode { - if MixedL2ELKind(os.Getenv(DevstackL2ELKindEnvVar)) == MixedL2ELOpGeth { - return startL2ELNode(t, l2Net, jwtPath, jwtSecret, "sequencer", NewELNodeIdentity(0)) + return startL2ELForKey(t, l2Net, jwtPath, jwtSecret, "sequencer", NewELNodeIdentity(0)) +} + +// startSupernodeELWithSupervisorURL starts an L2 EL node with --rollup.supervisor-http +// pointing at the given URL. Used by supernode interop presets to connect ELs +// to the interop filter for tx pool validation. +func startSupernodeELWithSupervisorURL( + t devtest.T, + l2Net *L2Network, + key string, + jwtPath string, + jwtSecret [32]byte, + supervisorURL string, +) L2ELNode { + switch devstackL2ELKind() { + case MixedL2ELOpGeth: + cfg := DefaultL2ELConfig() + l2EL := &OpGeth{ + name: key, + p: t, + logger: t.Logger().New("component", "l2el-"+key), + l2Net: l2Net, + jwtPath: jwtPath, + jwtSecret: jwtSecret, + supervisorRPC: supervisorURL, + cfg: cfg, + } + l2EL.Start() + t.Cleanup(l2EL.Stop) + return l2EL + default: // op-reth + return startMixedOpRethNodeWithSupervisorURL( + t, l2Net, key, jwtPath, jwtSecret, nil, supervisorURL, "v1") } - return startMixedOpRethNode(t, l2Net, "sequencer", jwtPath, jwtSecret, nil) } func newSingleChainSupernodeRuntimeWithConfig(t devtest.T, interopAtGenesis bool, cfg PresetConfig) *MultiChainRuntime { @@ -112,7 +143,7 @@ func newSingleChainSupernodeRuntimeWithConfig(t devtest.T, interopAtGenesis bool keys, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) require.NoError(err, "failed to derive dev keys from mnemonic") - migration, l1Net, l2Net, depSet, _ := buildSingleChainWorldWithInteropAndState(t, keys, interopAtGenesis, cfg.LocalContractArtifactsPath, cfg.DeployerOptions...) + migration, l1Net, l2Net, depSet, _ := buildSingleChainWorldWithInteropAndState(t, keys, interopAtGenesis, cfg.DeployerOptions...) validateSimpleInteropPresetConfig(t, cfg, l2Net) jwtPath, jwtSecret := writeJWTSecret(t) @@ -166,7 +197,7 @@ func newTwoL2SupernodeRuntimeWithConfig(t devtest.T, enableInterop bool, delaySe keys, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) require.NoError(err, "failed to derive dev keys from mnemonic") - wb, l1Net, l2ANet, l2BNet := buildTwoL2RuntimeWorld(t, keys, enableInterop, cfg.LocalContractArtifactsPath, cfg.DeployerOptions...) + wb, l1Net, l2ANet, l2BNet := buildTwoL2RuntimeWorld(t, keys, enableInterop, cfg.DeployerOptions...) jwtPath, jwtSecret := writeJWTSecret(t) l1Clock := clock.SystemClock var timeTravelClock *clock.AdvancingClock @@ -176,8 +207,38 @@ func newTwoL2SupernodeRuntimeWithConfig(t devtest.T, enableInterop bool, delaySe } l1EL, l1CL := startInProcessL1WithClockConfig(t, l1Net, jwtPath, l1Clock, cfg) - l2AEL := startSupernodeEL(t, l2ANet, jwtPath, jwtSecret) - l2BEL := startSupernodeEL(t, l2BNet, jwtPath, jwtSecret) + var l2AEL, l2BEL L2ELNode + var interopFilter *InteropFilter + + if cfg.UseInteropFilter { + // Proxy pattern: allocate stable address before ELs start + filterProxy := tcpproxy.New(t.Logger().New("proxy", "interop-filter")) + require.NoError(filterProxy.Start()) + t.Cleanup(func() { filterProxy.Close() }) + filterRPC := "http://" + filterProxy.Addr() + + // Start ELs with filter proxy URL + l2AEL = startSupernodeELWithSupervisorURL(t, l2ANet, "sequencer", jwtPath, jwtSecret, filterRPC) + l2BEL = startSupernodeELWithSupervisorURL(t, l2BNet, "sequencer", jwtPath, jwtSecret, filterRPC) + + // Build rollup config map from L2 networks (Go structs, no file I/O) + rollupConfigs := map[eth.ChainID]*rollup.Config{ + eth.ChainIDFromBig(l2ANet.RollupConfig().L2ChainID): l2ANet.RollupConfig(), + eth.ChainIDFromBig(l2BNet.RollupConfig().L2ChainID): l2BNet.RollupConfig(), + } + + // Create and start interop filter in-process + interopFilter = startInteropFilter(t, "interop-filter", + []string{l2AEL.UserRPC(), l2BEL.UserRPC()}, + rollupConfigs) + + // Connect proxy to the filter's actual RPC endpoint + filterProxy.SetUpstream(ProxyAddr(require, interopFilter.HTTPEndpoint())) + } else { + // No interop filter — ELs start without supervisor/filter URL (existing behavior) + l2AEL = startSupernodeEL(t, l2ANet, jwtPath, jwtSecret) + l2BEL = startSupernodeEL(t, l2BNet, jwtPath, jwtSecret) + } var activationTime uint64 var interopActivationTimestamp *uint64 @@ -218,6 +279,21 @@ func newTwoL2SupernodeRuntimeWithConfig(t devtest.T, enableInterop bool, delaySe l2BNet.ChainID(): l2BEL.UserRPC(), }) + // Use the potentially-overridden depSet (e.g. with custom message expiry window) + // if available; otherwise fall back to the original from the world builder. + var runtimeDepSet depset.DependencySet + if depSet != nil { + runtimeDepSet = depSet + } else { + runtimeDepSet = wb.outFullCfgSet.DependencySet + } + + // Wait for interop filter readiness now that the supernode and batchers are running. + // The filter needs blocks to be produced before its chain ingesters can backfill. + if interopFilter != nil { + interopFilter.WaitForReady(t, 120*time.Second) + } + return &MultiChainRuntime{ Keys: keys, Migration: newInteropMigrationState(wb), @@ -246,10 +322,11 @@ func newTwoL2SupernodeRuntimeWithConfig(t devtest.T, enableInterop bool, delaySe Supernode: supernode, FaucetService: faucetService, TimeTravel: timeTravelClock, + InteropFilter: interopFilter, }, activationTime } -func buildTwoL2RuntimeWorld(t devtest.T, keys devkeys.Keys, enableInterop bool, localContractArtifactsPath string, deployerOpts ...DeployerOption) (*worldBuilder, *L1Network, *L2Network, *L2Network) { +func buildTwoL2RuntimeWorld(t devtest.T, keys devkeys.Keys, enableInterop bool, deployerOpts ...DeployerOption) (*worldBuilder, *L1Network, *L2Network, *L2Network) { wb := &worldBuilder{ p: t, logger: t.Logger(), @@ -258,7 +335,7 @@ func buildTwoL2RuntimeWorld(t devtest.T, keys devkeys.Keys, enableInterop bool, builder: intentbuilder.New(), } - applyConfigLocalContractSources(t, keys, wb.builder, localContractArtifactsPath) + applyConfigLocalContractSources(t, keys, wb.builder) applyConfigCommons(t, keys, DefaultL1ID, wb.builder) applyConfigPrefundedL2(t, keys, DefaultL1ID, DefaultL2AID, wb.builder) applyConfigPrefundedL2(t, keys, DefaultL1ID, DefaultL2BID, wb.builder) diff --git a/op-devstack/sysgo/multichain_supervisor_runtime.go b/op-devstack/sysgo/multichain_supervisor_runtime.go deleted file mode 100644 index 899e0c3d2d4..00000000000 --- a/op-devstack/sysgo/multichain_supervisor_runtime.go +++ /dev/null @@ -1,472 +0,0 @@ -package sysgo - -import ( - "encoding/json" - "os" - "path/filepath" - "strings" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" - - "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" - "github.com/ethereum-optimism/optimism/op-devstack/devtest" - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/clock" - "github.com/ethereum-optimism/optimism/op-service/dial" - "github.com/ethereum-optimism/optimism/op-service/eth" - oplog "github.com/ethereum-optimism/optimism/op-service/log" - opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" - "github.com/ethereum-optimism/optimism/op-service/oppprof" - "github.com/ethereum-optimism/optimism/op-service/retry" - oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" - supervisorConfig "github.com/ethereum-optimism/optimism/op-supervisor/config" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/syncnode" -) - -func NewSingleChainInteropRuntime(t devtest.T) *MultiChainRuntime { - return NewSingleChainInteropRuntimeWithConfig(t, PresetConfig{}) -} - -func NewSingleChainInteropRuntimeWithConfig(t devtest.T, cfg PresetConfig) *MultiChainRuntime { - require := t.Require() - - keys, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) - require.NoError(err, "failed to derive dev keys from mnemonic") - - migration, l1Net, l2Net, depSet, fullCfgSet := buildSingleChainWorldWithInteropAndState(t, keys, true, cfg.LocalContractArtifactsPath, cfg.DeployerOptions...) - validateSimpleInteropPresetConfig(t, cfg, l2Net) - - jwtPath, jwtSecret := writeJWTSecret(t) - l1Clock := clock.SystemClock - var timeTravelClock *clock.AdvancingClock - if cfg.EnableTimeTravel { - timeTravelClock = clock.NewAdvancingClock(100 * time.Millisecond) - l1Clock = timeTravelClock - } - l1EL, l1CL := startInProcessL1WithClockConfig(t, l1Net, jwtPath, l1Clock, cfg) - supervisor := startSupervisor(t, "1-primary", l1EL, fullCfgSet, map[eth.ChainID]*rollup.Config{ - l2Net.ChainID(): l2Net.rollupCfg, - }) - - l2EL := startL2ELNodeWithSupervisor(t, l2Net, jwtPath, jwtSecret, "sequencer", NewELNodeIdentity(0), supervisor.UserRPC()) - l2CL := startL2CLNode(t, keys, l1Net, l2Net, l1EL, l1CL, l2EL, jwtSecret, l2CLNodeStartConfig{ - Key: "sequencer", - IsSequencer: true, - NoDiscovery: true, - EnableReqResp: true, - UseReqResp: true, - IndexingMode: true, - DependencySet: depSet, - L2FollowSource: "", - L2CLOptions: cfg.GlobalL2CLOptions, - }) - connectManagedL2CLToSupervisor(t, supervisor, l2CL) - - l2Batcher := startMinimalBatcher(t, keys, l2Net, l1EL, l2CL, l2EL, cfg.BatcherOptions...) - l2Proposer := startMinimalProposer(t, keys, l2Net, l1EL, l2CL, cfg.ProposerOptions...) - applyMinimalGameTypeOptions(t, keys, l1Net, l2Net, l1EL, cfg.AddedGameTypes, cfg.RespectedGameTypes) - testSequencer := startTestSequencer(t, keys, jwtPath, jwtSecret, l1Net, l1EL, l1CL, l2EL, l2Net, l2CL) - faucetService := startFaucets(t, keys, l1Net.ChainID(), l2Net.ChainID(), l1EL.UserRPC(), l2EL.UserRPC()) - - chainA := &MultiChainNodeRuntime{ - Name: "l2a", - Network: l2Net, - EL: l2EL, - CL: l2CL, - Batcher: l2Batcher, - Proposer: l2Proposer, - } - - return &MultiChainRuntime{ - Keys: keys, - FullConfigSet: fullCfgSet, - DependencySet: depSet, - Migration: migration, - L1Network: l1Net, - L1EL: l1EL, - L1CL: l1CL, - Chains: map[string]*MultiChainNodeRuntime{"l2a": chainA}, - PrimarySupervisor: supervisor, - FaucetService: faucetService, - TimeTravel: timeTravelClock, - TestSequencer: newTestSequencerRuntime(testSequencer, "dev"), - } -} - -func NewSimpleInteropRuntime(t devtest.T) *MultiChainRuntime { - return NewSimpleInteropRuntimeWithConfig(t, PresetConfig{}) -} - -func NewSimpleInteropRuntimeWithConfig(t devtest.T, cfg PresetConfig) *MultiChainRuntime { - require := t.Require() - - keys, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) - require.NoError(err, "failed to derive dev keys from mnemonic") - - migration, l1Net, l2ANet, l2BNet, fullCfgSet := buildTwoL2WorldWithState(t, keys, true, cfg.LocalContractArtifactsPath, cfg.DeployerOptions...) - validateSimpleInteropPresetConfig(t, cfg, l2ANet, l2BNet) - depSet := fullCfgSet.DependencySet - - jwtPath, jwtSecret := writeJWTSecret(t) - l1Clock := clock.SystemClock - var timeTravelClock *clock.AdvancingClock - if cfg.EnableTimeTravel { - timeTravelClock = clock.NewAdvancingClock(100 * time.Millisecond) - l1Clock = timeTravelClock - } - l1EL, l1CL := startInProcessL1WithClockConfig(t, l1Net, jwtPath, l1Clock, cfg) - supervisor := startSupervisor(t, "1-primary", l1EL, fullCfgSet, map[eth.ChainID]*rollup.Config{ - l2ANet.ChainID(): l2ANet.rollupCfg, - l2BNet.ChainID(): l2BNet.rollupCfg, - }) - - l2AEL := startL2ELNodeWithSupervisor(t, l2ANet, jwtPath, jwtSecret, "sequencer", NewELNodeIdentity(0), supervisor.UserRPC()) - l2BEL := startL2ELNodeWithSupervisor(t, l2BNet, jwtPath, jwtSecret, "sequencer", NewELNodeIdentity(0), supervisor.UserRPC()) - l2ACL := startL2CLNode(t, keys, l1Net, l2ANet, l1EL, l1CL, l2AEL, jwtSecret, l2CLNodeStartConfig{ - Key: "sequencer", - IsSequencer: true, - NoDiscovery: true, - EnableReqResp: true, - UseReqResp: true, - IndexingMode: true, - DependencySet: depSet, - L2FollowSource: "", - L2CLOptions: cfg.GlobalL2CLOptions, - }) - l2BCL := startL2CLNode(t, keys, l1Net, l2BNet, l1EL, l1CL, l2BEL, jwtSecret, l2CLNodeStartConfig{ - Key: "sequencer", - IsSequencer: true, - NoDiscovery: true, - EnableReqResp: true, - UseReqResp: true, - IndexingMode: true, - DependencySet: depSet, - L2FollowSource: "", - L2CLOptions: cfg.GlobalL2CLOptions, - }) - connectManagedL2CLToSupervisor(t, supervisor, l2ACL) - connectManagedL2CLToSupervisor(t, supervisor, l2BCL) - - l2ABatcher := startMinimalBatcher(t, keys, l2ANet, l1EL, l2ACL, l2AEL, cfg.BatcherOptions...) - l2AProposer := startMinimalProposer(t, keys, l2ANet, l1EL, l2ACL, cfg.ProposerOptions...) - l2BBatcher := startMinimalBatcher(t, keys, l2BNet, l1EL, l2BCL, l2BEL, cfg.BatcherOptions...) - l2BProposer := startMinimalProposer(t, keys, l2BNet, l1EL, l2BCL, cfg.ProposerOptions...) - testSequencer := startTestSequencer(t, keys, jwtPath, jwtSecret, l1Net, l1EL, l1CL, l2AEL, l2ANet, l2ACL) - faucetService := startFaucetsForRPCs(t, keys, map[eth.ChainID]string{ - l1Net.ChainID(): l1EL.UserRPC(), - l2ANet.ChainID(): l2AEL.UserRPC(), - l2BNet.ChainID(): l2BEL.UserRPC(), - }) - - return &MultiChainRuntime{ - Keys: keys, - FullConfigSet: fullCfgSet, - DependencySet: depSet, - Migration: migration, - L1Network: l1Net, - L1EL: l1EL, - L1CL: l1CL, - Chains: map[string]*MultiChainNodeRuntime{ - "l2a": { - Name: "l2a", - Network: l2ANet, - EL: l2AEL, - CL: l2ACL, - Batcher: l2ABatcher, - Proposer: l2AProposer, - }, - "l2b": { - Name: "l2b", - Network: l2BNet, - EL: l2BEL, - CL: l2BCL, - Batcher: l2BBatcher, - Proposer: l2BProposer, - }, - }, - PrimarySupervisor: supervisor, - FaucetService: faucetService, - TimeTravel: timeTravelClock, - TestSequencer: newTestSequencerRuntime(testSequencer, "dev"), - } -} - -func validateSimpleInteropPresetConfig(t devtest.T, cfg PresetConfig, l2Nets ...*L2Network) { - require := t.Require() - if cfg.MaxSequencingWindow != nil { - for _, l2Net := range l2Nets { - require.LessOrEqualf( - l2Net.rollupCfg.SeqWindowSize, - *cfg.MaxSequencingWindow, - "sequencing window of chain %s must fit in max sequencing window size", - l2Net.ChainID(), - ) - } - } - if cfg.RequireInteropNotAtGen { - for _, l2Net := range l2Nets { - interopTime := l2Net.genesis.Config.InteropTime - require.NotNilf(interopTime, "chain %s must have interop", l2Net.ChainID()) - require.NotZerof(*interopTime, "chain %s interop must not be at genesis", l2Net.ChainID()) - } - } -} - -func NewMultiSupervisorInteropRuntime(t devtest.T) *MultiChainRuntime { - runtime := NewSimpleInteropRuntime(t) - chainA := runtime.Chains["l2a"] - chainB := runtime.Chains["l2b"] - t.Require().NotNil(chainA, "missing l2a interop chain") - t.Require().NotNil(chainB, "missing l2b interop chain") - - supervisorSecondary := startSupervisor( - t, - "2-secondary", - runtime.L1EL, - runtime.FullConfigSet, - map[eth.ChainID]*rollup.Config{ - chainA.Network.ChainID(): chainA.Network.rollupCfg, - chainB.Network.ChainID(): chainB.Network.rollupCfg, - }, - ) - - l2A2EL := startL2ELNodeWithSupervisor( - t, - chainA.Network, - chainA.EL.JWTPath(), - readJWTSecretFromPath(t, chainA.EL.JWTPath()), - "verifier", - NewELNodeIdentity(0), - supervisorSecondary.UserRPC(), - ) - l2A2CL := startL2CLNode(t, runtime.Keys, runtime.L1Network, chainA.Network, runtime.L1EL, runtime.L1CL, l2A2EL, readJWTSecretFromPath(t, chainA.EL.JWTPath()), l2CLNodeStartConfig{ - Key: "verifier", - IsSequencer: false, - NoDiscovery: true, - EnableReqResp: true, - UseReqResp: true, - IndexingMode: true, - DependencySet: runtime.DependencySet, - L2FollowSource: "", - }) - - l2B2EL := startL2ELNodeWithSupervisor( - t, - chainB.Network, - chainB.EL.JWTPath(), - readJWTSecretFromPath(t, chainB.EL.JWTPath()), - "verifier", - NewELNodeIdentity(0), - supervisorSecondary.UserRPC(), - ) - l2B2CL := startL2CLNode(t, runtime.Keys, runtime.L1Network, chainB.Network, runtime.L1EL, runtime.L1CL, l2B2EL, readJWTSecretFromPath(t, chainB.EL.JWTPath()), l2CLNodeStartConfig{ - Key: "verifier", - IsSequencer: false, - NoDiscovery: true, - EnableReqResp: true, - UseReqResp: true, - IndexingMode: true, - DependencySet: runtime.DependencySet, - L2FollowSource: "", - }) - - connectL2CLPeers(t, t.Logger(), chainA.CL, l2A2CL) - connectL2CLPeers(t, t.Logger(), chainB.CL, l2B2CL) - - connectManagedL2CLToSupervisor(t, supervisorSecondary, l2A2CL) - connectManagedL2CLToSupervisor(t, supervisorSecondary, l2B2CL) - - if chainA.Followers == nil { - chainA.Followers = make(map[string]*SingleChainNodeRuntime) - } - if chainB.Followers == nil { - chainB.Followers = make(map[string]*SingleChainNodeRuntime) - } - chainA.Followers["verifier"] = &SingleChainNodeRuntime{Name: "verifier", EL: l2A2EL, CL: l2A2CL} - chainB.Followers["verifier"] = &SingleChainNodeRuntime{Name: "verifier", EL: l2B2EL, CL: l2B2CL} - runtime.SecondarySupervisor = supervisorSecondary - return runtime -} - -func startSupervisor( - t devtest.T, - supervisorName string, - l1EL *L1Geth, - fullCfgSet depset.FullConfigSetMerged, - rollupCfgs map[eth.ChainID]*rollup.Config, -) Supervisor { - switch os.Getenv("DEVSTACK_SUPERVISOR_KIND") { - case "kona": - return startKonaSupervisor(t, supervisorName, l1EL, fullCfgSet, rollupCfgs) - default: - return startOPSupervisor(t, supervisorName, l1EL, fullCfgSet) - } -} - -func startOPSupervisor( - t devtest.T, - supervisorName string, - l1EL *L1Geth, - fullCfgSet depset.FullConfigSetMerged, -) *OpSupervisor { - cfg := &supervisorConfig.Config{ - MetricsConfig: opmetrics.CLIConfig{ - Enabled: false, - }, - PprofConfig: oppprof.CLIConfig{ - ListenEnabled: false, - }, - LogConfig: oplog.CLIConfig{ - Level: log.LevelDebug, - Format: oplog.FormatText, - }, - RPC: oprpc.CLIConfig{ - ListenAddr: "127.0.0.1", - ListenPort: 0, - EnableAdmin: true, - }, - SyncSources: &syncnode.CLISyncNodes{}, - L1RPC: l1EL.UserRPC(), - Datadir: t.TempDir(), - Version: "dev", - FullConfigSetSource: fullCfgSet, - MockRun: false, - SynchronousProcessors: false, - DatadirSyncEndpoint: "", - } - supervisorNode := &OpSupervisor{ - name: supervisorName, - userRPC: "", - cfg: cfg, - p: t, - logger: t.Logger().New("component", "supervisor"), - service: nil, - } - supervisorNode.Start() - t.Cleanup(supervisorNode.Stop) - return supervisorNode -} - -func startKonaSupervisor( - t devtest.T, - supervisorName string, - l1EL *L1Geth, - fullCfgSet depset.FullConfigSetMerged, - rollupCfgs map[eth.ChainID]*rollup.Config, -) *KonaSupervisor { - require := t.Require() - - cfgDir := t.TempDir() - depSetJSON, err := json.Marshal(fullCfgSet.DependencySet) - require.NoError(err, "failed to marshal dependency set") - depSetCfgPath := filepath.Join(cfgDir, "depset.json") - require.NoError(os.WriteFile(depSetCfgPath, depSetJSON, 0o644)) - - rollupCfgPath := filepath.Join(cfgDir, "rollup-config-*.json") - for chainID, cfg := range rollupCfgs { - rollupData, err := json.Marshal(cfg) - require.NoError(err, "failed to marshal rollup config for chain %s", chainID) - filePath := filepath.Join(cfgDir, "rollup-config-"+chainID.String()+".json") - require.NoError(os.WriteFile(filePath, rollupData, 0o644)) - } - - execPath, err := EnsureRustBinary(t, RustBinarySpec{ - SrcDir: "rust/kona", - Package: "kona-supervisor", - Binary: "kona-supervisor", - }) - require.NoError(err, "prepare kona-supervisor binary") - require.NotEmpty(execPath, "kona-supervisor binary path resolved") - - envVars := []string{ - "RPC_ADDR=127.0.0.1", - "DATADIR=" + t.TempDir(), - "DEPENDENCY_SET=" + depSetCfgPath, - "ROLLUP_CONFIG_PATHS=" + rollupCfgPath, - "L1_RPC=" + l1EL.UserRPC(), - "RPC_ENABLE_ADMIN=true", - "L2_CONSENSUS_NODES=", - "L2_CONSENSUS_JWT_SECRET=", - "KONA_LOG_LEVEL=3", - "KONA_LOG_STDOUT_FORMAT=json", - } - - konaSupervisor := &KonaSupervisor{ - name: supervisorName, - userRPC: "", - execPath: execPath, - args: []string{}, - env: envVars, - p: t, - } - konaSupervisor.Start() - t.Cleanup(konaSupervisor.Stop) - return konaSupervisor -} - -func connectManagedL2CLToSupervisor(t devtest.T, supervisor Supervisor, l2CL L2CLNode) { - interopEndpoint, secret := l2CL.InteropRPC() - supClient, err := dial.DialSupervisorClientWithTimeout(t.Ctx(), t.Logger(), supervisor.UserRPC(), client.WithLazyDial()) - t.Require().NoError(err) - t.Cleanup(supClient.Close) - - err = retry.Do0(t.Ctx(), 10, retry.Exponential(), func() error { - return supClient.AddL2RPC(t.Ctx(), interopEndpoint, secret) - }) - t.Require().NoErrorf(err, "must connect CL node %s to supervisor %s", l2CL, supervisorIDString(supervisor)) -} - -func supervisorIDString(supervisor Supervisor) string { - switch s := supervisor.(type) { - case *OpSupervisor: - return s.name - case *KonaSupervisor: - return s.name - default: - return "" - } -} - -func startL2ELNodeWithSupervisor( - t devtest.T, - l2Net *L2Network, - jwtPath string, - jwtSecret [32]byte, - key string, - identity *ELNodeIdentity, - supervisorRPC string, -) L2ELNode { - cfg := DefaultL2ELConfig() - cfg.P2PAddr = "127.0.0.1" - cfg.P2PPort = identity.Port - cfg.P2PNodeKeyHex = identity.KeyHex() - - l2EL := &OpGeth{ - name: key, - p: t, - logger: t.Logger().New("component", "l2el-"+key), - l2Net: l2Net, - jwtPath: jwtPath, - jwtSecret: jwtSecret, - supervisorRPC: supervisorRPC, - cfg: cfg, - } - l2EL.Start() - t.Cleanup(l2EL.Stop) - return l2EL -} - -func readJWTSecretFromPath(t devtest.T, jwtPath string) [32]byte { - content, err := os.ReadFile(jwtPath) - t.Require().NoError(err, "failed to read jwt path %s", jwtPath) - raw, err := hexutil.Decode(strings.TrimSpace(string(content))) - t.Require().NoError(err, "failed to decode jwt secret from %s", jwtPath) - t.Require().Len(raw, 32, "invalid jwt secret length from %s", jwtPath) - var secret [32]byte - copy(secret[:], raw) - return secret -} diff --git a/op-devstack/sysgo/op_rbuilder.go b/op-devstack/sysgo/op_rbuilder.go index 0692f96743a..986ff090eff 100644 --- a/op-devstack/sysgo/op_rbuilder.go +++ b/op-devstack/sysgo/op_rbuilder.go @@ -3,7 +3,6 @@ package sysgo import ( "encoding/hex" - "fmt" "os" "path/filepath" "strconv" @@ -402,8 +401,8 @@ func (b *OPRBuilderNode) Start() { b.sub = NewSubProcess(b.p, stdOut, stdErr) - execPath, err := EnsureRustBinary(b.p, RustBinarySpec{ - SrcDir: "op-rbuilder", + execPath, err := rustbin.Spec{ + SrcDir: "rust/op-rbuilder", Package: "op-rbuilder", Binary: "op-rbuilder", }) @@ -463,33 +462,3 @@ func (b *OPRBuilderNode) UserRPC() string { func (b *OPRBuilderNode) FlashblocksWSURL() string { return b.wsProxyURL } - -type RulesConfig struct { - File []struct { - Path string `yaml:"path"` - } `yaml:"file"` - RefreshInterval int `yaml:"refresh_interval"` -} - -func (b *OPRBuilderNode) UpdateRuleSet(rulesYaml string) error { - if b.cfg.RulesConfigPath == "" { - return fmt.Errorf("rules config path is not configured (rules not enabled?)") - } - rulesConfigContent, err := os.ReadFile(b.cfg.RulesConfigPath) - if err != nil { - return fmt.Errorf("failed to open rules config yaml: %w", err) - } - var rulesConfig RulesConfig - if err := yaml.Unmarshal(rulesConfigContent, &rulesConfig); err != nil { - return fmt.Errorf("failed to parse rules config yaml: %w", err) - } - if len(rulesConfig.File) == 0 { - return fmt.Errorf("no file entries found") - } - // Use only the first file entry for simplicity - rulesPath := rulesConfig.File[0].Path - if err := os.WriteFile(rulesPath, []byte(rulesYaml), 0644); err != nil { - return fmt.Errorf("failed to update rules file: %w", err) - } - return nil -} diff --git a/op-devstack/sysgo/preset_config.go b/op-devstack/sysgo/preset_config.go index 19f91a11fac..b905749490f 100644 --- a/op-devstack/sysgo/preset_config.go +++ b/op-devstack/sysgo/preset_config.go @@ -20,6 +20,8 @@ type PresetConfig struct { EnableTimeTravel bool MaxSequencingWindow *uint64 RequireInteropNotAtGen bool + MessageExpiryWindow *uint64 + UseInteropFilter bool } type PresetOption interface { diff --git a/op-devstack/sysgo/rollup_boost.go b/op-devstack/sysgo/rollup_boost.go new file mode 100644 index 00000000000..cda425ff8cd --- /dev/null +++ b/op-devstack/sysgo/rollup_boost.go @@ -0,0 +1,303 @@ +package sysgo + +import ( + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/logpipe" + "github.com/ethereum-optimism/optimism/op-service/tasks" + "github.com/ethereum-optimism/optimism/op-service/testutils/tcpproxy" +) + +// RollupBoostNode is a lightweight sysgo-managed process wrapper around a rollup-boost +// WebSocket stream source. It exposes a stable proxied ws URL and hydrates the L2 +// network with a shared WSClient that points at it. +type RollupBoostNode struct { + mu sync.Mutex + + name string + chainID eth.ChainID + wsProxyURL string + wsProxy *tcpproxy.Proxy + + rpcProxyURL string + rpcProxy *tcpproxy.Proxy + + header http.Header + + logger log.Logger + p devtest.CommonT + + sub *SubProcess + + cfg *RollupBoostConfig +} + +var _ stack.Lifecycle = (*RollupBoostNode)(nil) +var _ L2ELNode = (*RollupBoostNode)(nil) + +func (r *RollupBoostNode) Start() { + r.mu.Lock() + defer r.mu.Unlock() + if r.sub != nil { + r.logger.Warn("rollup-boost already started") + return + } + + cfg := r.cfg + r.p.Require().NotNil(cfg, "rollup-boost config not initialized") + + if r.wsProxy == nil { + r.wsProxy = tcpproxy.New(r.p.Logger()) + r.p.Require().NoError(r.wsProxy.Start()) + r.wsProxyURL = "ws://" + r.wsProxy.Addr() + r.p.Cleanup(func() { r.wsProxy.Close() }) + } + + if r.rpcProxy == nil { + r.rpcProxy = tcpproxy.New(r.p.Logger()) + r.p.Require().NoError(r.rpcProxy.Start()) + r.rpcProxyURL = "http://" + r.rpcProxy.Addr() + r.p.Cleanup(func() { r.rpcProxy.Close() }) + } + + args, env := cfg.LaunchSpec(r.p) + + // Create channel for discovering flashblocks WS port from process logs. + // When using port 0, the OS assigns the port at bind time and the process logs it. + flashblocksWSChan := make(chan string, 1) + defer close(flashblocksWSChan) + + // Parse Rust-structured logs and forward into Go logger with attributes + logOut := logpipe.ToLoggerWithMinLevel(r.logger.New("stream", "stdout"), log.LevelWarn) + logErr := logpipe.ToLoggerWithMinLevel(r.logger.New("stream", "stderr"), log.LevelWarn) + + // Log parsing callback to extract bound addresses from process output + onLogEntry := func(e logpipe.LogEntry) { + msg := e.LogMessage() + // Flashblocks WS - custom log message from outbound.rs + if strings.HasPrefix(msg, "Flashblocks WebSocketPublisher listening on ") { + addr := strings.TrimPrefix(msg, "Flashblocks WebSocketPublisher listening on ") + select { + case flashblocksWSChan <- "ws://" + addr: + default: + } + } + } + + stdOut := logpipe.LogCallback(func(line []byte) { + e := logpipe.ParseRustStructuredLogs(line) + logOut(e) + onLogEntry(e) + }) + stdErr := logpipe.LogCallback(func(line []byte) { + logErr(logpipe.ParseRustStructuredLogs(line)) + }) + + r.sub = NewSubProcess(r.p, stdOut, stdErr) + + execPath, err := rustbin.Spec{ + SrcDir: "rust/rollup-boost", + Package: "rollup-boost", + Binary: "rollup-boost", + }) + r.p.Require().NoError(err, "prepare rollup-boost binary") + r.p.Require().NotEmpty(execPath, "rollup-boost binary path resolved") + + err = r.sub.Start(execPath, args, env) + r.p.Require().NoError(err, "start rollup-boost") + + // RPC port: still uses pre-allocation because rollup-boost doesn't log the actual + // bound RPC address when using port 0. This requires a Rust change to fix. + // TODO: Update rollup-boost to log "RPC server listening on {addr}" and parse it here. + rpcUpstreamURL := "http://" + cfg.RPCHost + ":" + strconv.Itoa(int(cfg.RPCPort)) + waitTCPReady(r.p, rpcUpstreamURL, 5*time.Second) + r.logger.Info("rollup-boost upstream RPC ready", "rpc", rpcUpstreamURL) + r.rpcProxy.SetUpstream(ProxyAddr(r.p.Require(), rpcUpstreamURL)) + waitTCPReady(r.p, r.rpcProxyURL, 10*time.Second) + r.logger.Info("rollup-boost proxy RPC ready", "proxy_rpc", r.rpcProxyURL) + + // Flashblocks WS: discover port from logs, then configure proxy + if cfg.EnableFlashblocks { + var flashblocksAddr string + r.p.Require().NoError(tasks.Await(r.p.Ctx(), flashblocksWSChan, &flashblocksAddr), "need Flashblocks WS address from logs") + r.logger.Info("rollup-boost upstream WS ready", "upstream_ws", flashblocksAddr) + + r.wsProxy.SetUpstream(ProxyAddr(r.p.Require(), flashblocksAddr)) + r.logger.Info("rollup-boost proxy WS ready", "proxy_ws", r.wsProxyURL) + } +} + +func (r *RollupBoostNode) Stop() { + r.mu.Lock() + defer r.mu.Unlock() + if r.sub == nil { + r.logger.Warn("rollup-boost already stopped") + return + } + r.p.Require().NoError(r.sub.Stop(true)) + r.sub = nil +} + +// RollupBoostConfig configures the rollup-boost process CLI and environment. +type RollupBoostConfig struct { + // RPC endpoint for rollup-boost itself + RPCHost string + RPCPort uint16 + + // Flashblocks proxy WebSocket exposure + EnableFlashblocks bool + FlashblocksHost string + FlashblocksPort int + + // L2 engine connection details (HTTP(S)) + L2EngineURL string + L2JWTPath string + + // Builder engine connection details (HTTP(S)) + BuilderURL string + BuilderJWTPath string + FlashblocksBuilderURL string // upstream builder WS url (e.g. op-rbuilder ws) + + // Other settings + ExecutionMode string // e.g. "enabled" + LogFormat string // e.g. "json" + + // Debug server + DebugHost string + DebugPort int + + // Optional WS headers to expose to clients through the proxy + Headers http.Header + + // Env variables for the subprocess + Env []string + // ExtraArgs appended to the generated CLI (last-flag-wins semantics) + ExtraArgs []string +} + +func DefaultRollupBoostConfig() *RollupBoostConfig { + return &RollupBoostConfig{ + RPCHost: "127.0.0.1", + RPCPort: 0, + EnableFlashblocks: true, + FlashblocksHost: "127.0.0.1", + FlashblocksPort: 0, + FlashblocksBuilderURL: "", + L2EngineURL: "", + L2JWTPath: "", + BuilderURL: "127.0.0.1:8551", // normalized to http:// later + BuilderJWTPath: "", + ExecutionMode: "enabled", + LogFormat: "json", + DebugHost: "127.0.0.1", + DebugPort: 0, + Headers: http.Header{}, + Env: nil, + ExtraArgs: nil, + } +} + +func (cfg *RollupBoostConfig) LaunchSpec(p devtest.CommonT) (args []string, env []string) { + p.Require().NotNil(cfg, "nil RollupBoostConfig") + + env = append([]string(nil), cfg.Env...) + args = make([]string, 0, len(cfg.ExtraArgs)+16) + + if cfg.EnableFlashblocks { + if cfg.FlashblocksHost == "" { + cfg.FlashblocksHost = "127.0.0.1" + } + args = append(args, "--flashblocks", "--flashblocks-host="+cfg.FlashblocksHost) + if cfg.FlashblocksPort > 0 { + // Use explicitly configured port + args = append(args, "--flashblocks-port="+strconv.Itoa(cfg.FlashblocksPort)) + } else { + // Use port 0 to let the OS assign a port atomically at bind time. + // The actual port will be discovered by parsing the process logs. + args = append(args, "--flashblocks-port=0") + } + if cfg.FlashblocksBuilderURL != "" { + args = append(args, "--flashblocks-builder-url="+cfg.FlashblocksBuilderURL) + } + } + + if cfg.RPCPort <= 0 { + portStr, err := getAvailableLocalPort() + p.Require().NoError(err, "allocate rollup-boost rpc port") + portVal, err := strconv.ParseUint(portStr, 10, 16) + p.Require().NoError(err, "parse rollup-boost rpc port") + cfg.RPCPort = uint16(portVal) + } + p.Require().True(cfg.RPCPort > 0, "RPCPort must be > 0") + args = append(args, "--rpc-host="+cfg.RPCHost, "--rpc-port="+strconv.Itoa(int(cfg.RPCPort))) + + if cfg.L2EngineURL != "" { + args = append(args, "--l2-url="+ensureHTTPURL(cfg.L2EngineURL)) + } + if cfg.L2JWTPath != "" { + args = append(args, "--l2-jwt-path="+cfg.L2JWTPath) + } + if cfg.BuilderURL != "" { + args = append(args, "--builder-url="+ensureHTTPURL(cfg.BuilderURL)) + } + if cfg.BuilderJWTPath != "" { + args = append(args, "--builder-jwt-path="+cfg.BuilderJWTPath) + } + + if cfg.ExecutionMode != "" { + args = append(args, "--execution-mode="+cfg.ExecutionMode) + } + if cfg.LogFormat != "" { + args = append(args, "--log-format="+cfg.LogFormat) + } + + if cfg.DebugHost == "" { + cfg.DebugHost = "127.0.0.1" + } + args = append(args, "--debug-host="+cfg.DebugHost) + if cfg.DebugPort > 0 { + // Use explicitly configured port + args = append(args, "--debug-server-port="+strconv.Itoa(cfg.DebugPort)) + } else { + // Use port 0 to let the OS assign a port atomically at bind time. + // The debug server logs its bound address, but we don't need to parse it + // since the debug port is only used for manual debugging. + args = append(args, "--debug-server-port=0") + } + + args = append(args, cfg.ExtraArgs...) + + return args, env +} + +func ensureHTTPURL(u string) string { + if strings.Contains(u, "://") { + return u + } + return "http://" + u +} + +func (r *RollupBoostNode) EngineRPC() string { + return r.rpcProxyURL +} + +func (r *RollupBoostNode) JWTPath() string { + return r.cfg.L2JWTPath +} + +func (r *RollupBoostNode) UserRPC() string { + return r.rpcProxyURL +} + +func (r *RollupBoostNode) FlashblocksWSURL() string { + return r.wsProxyURL +} diff --git a/op-devstack/sysgo/runtime_state.go b/op-devstack/sysgo/runtime_state.go new file mode 100644 index 00000000000..b7d7806db71 --- /dev/null +++ b/op-devstack/sysgo/runtime_state.go @@ -0,0 +1,118 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + challengerconfig "github.com/ethereum-optimism/optimism/op-challenger/config" + "github.com/ethereum-optimism/optimism/op-faucet/faucet" + "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer" +) + +type TestSequencerRuntime struct { + Name string + AdminRPC string + JWTSecret [32]byte + ControlRPC map[eth.ChainID]string + Service *sequencer.Service +} + +func newTestSequencerRuntime(ts *testSequencer, name string) *TestSequencerRuntime { + if ts == nil { + return nil + } + if name == "" { + name = ts.name + } + return &TestSequencerRuntime{ + Name: name, + AdminRPC: ts.adminRPC, + JWTSecret: ts.jwtSecret, + ControlRPC: copyControlRPCMap(ts.controlRPC), + Service: ts.service, + } +} + +type SingleChainNodeRuntime struct { + Name string + IsSequencer bool + EL L2ELNode + CL L2CLNode +} + +type SyncTesterRuntime struct { + Service *SyncTesterService + Node *SingleChainNodeRuntime +} + +type FlashblocksRuntimeSupport struct { + Builder *OPRBuilderNode + RollupBoost *RollupBoostNode +} + +type SingleChainInteropSupport struct { + Migration *interopMigrationState + FullConfigSet depset.FullConfigSetMerged + DependencySet depset.DependencySet +} + +type SingleChainRuntime struct { + Keys devkeys.Keys + + L1Network *L1Network + L2Network *L2Network + + L1EL *L1Geth + L1CL *L1CLNode + + L2EL L2ELNode + L2CL L2CLNode + + L2Batcher *L2Batcher + L2Proposer *L2Proposer + L2Challenger *L2Challenger + + FaucetService *faucet.Service + TimeTravel *clock.AdvancingClock + TestSequencer *TestSequencerRuntime + + Nodes map[string]*SingleChainNodeRuntime + SyncTester *SyncTesterRuntime + Conductors map[string]*Conductor + Flashblocks *FlashblocksRuntimeSupport + Interop *SingleChainInteropSupport + P2PEnabled bool +} + +type MultiChainNodeRuntime struct { + Name string + Network *L2Network + EL L2ELNode + CL L2CLNode + Batcher *L2Batcher + Proposer *L2Proposer + Followers map[string]*SingleChainNodeRuntime +} + +type MultiChainRuntime struct { + Keys devkeys.Keys + Migration *interopMigrationState + FullConfigSet depset.FullConfigSetMerged + DependencySet depset.DependencySet + + L1Network *L1Network + L1EL *L1Geth + L1CL *L1CLNode + + Chains map[string]*MultiChainNodeRuntime + + Supernode *SuperNode + + FaucetService *faucet.Service + TimeTravel *clock.AdvancingClock + TestSequencer *TestSequencerRuntime + L2ChallengerConfig *challengerconfig.Config + DelaySeconds uint64 + InteropFilter *InteropFilter // nil if not using interop filter +} diff --git a/op-devstack/sysgo/rust_binary.go b/op-devstack/sysgo/rust_binary.go new file mode 100644 index 00000000000..92710a22b0d --- /dev/null +++ b/op-devstack/sysgo/rust_binary.go @@ -0,0 +1,157 @@ +package sysgo + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + opservice "github.com/ethereum-optimism/optimism/op-service" +) + +// RustBinarySpec describes a Rust binary to be built and located. +type RustBinarySpec struct { + SrcDir string // directory name relative to monorepo root, e.g. "rollup-boost" + Package string // cargo package name, e.g. "rollup-boost" + Binary string // binary name, e.g. "rollup-boost" +} + +// EnsureRustBinary locates or builds a Rust binary as needed. +// +// Env var overrides (suffix derived from binary name, e.g. "rollup-boost" -> "ROLLUP_BOOST"): +// - RUST_BINARY_PATH_: absolute path to pre-built binary (skips build, must exist) +// - RUST_SRC_DIR_: overrides SrcDir (absolute path to cargo project root) +// +// Build behavior: +// - RUST_JIT_BUILD=1: runs cargo build in debug mode (letting cargo handle rebuild detection) +// - Otherwise: only checks binary exists, errors if missing +func EnsureRustBinary(p devtest.CommonT, spec RustBinarySpec) (string, error) { + envSuffix := toEnvVarSuffix(spec.Binary) + + // Check for explicit binary path override + if pathOverride := os.Getenv("RUST_BINARY_PATH_" + envSuffix); pathOverride != "" { + if _, err := os.Stat(pathOverride); os.IsNotExist(err) { + return "", fmt.Errorf("%s binary not found at overridden path %s", spec.Binary, pathOverride) + } + p.Logger().Info("Using overridden binary path", "binary", spec.Binary, "path", pathOverride) + return pathOverride, nil + } + + // Determine source root + srcRoot, err := resolveSrcRoot(spec.SrcDir, envSuffix) + if err != nil { + return "", err + } + + jitBuild := os.Getenv("RUST_JIT_BUILD") != "" + + if jitBuild { + p.Logger().Info("Building Rust binary (JIT)", "binary", spec.Binary, "dir", srcRoot) + if err := buildRustBinary(p.Ctx(), srcRoot, spec.Package, spec.Binary); err != nil { + return "", err + } + } + + binaryPath, err := resolveBuiltRustBinaryPath(srcRoot, spec.Binary) + if err != nil { + return "", fmt.Errorf("%s binary not found; run 'cd %s && just build-%s-debug' (or just build-%s for release) or set RUST_JIT_BUILD=1: %w", + spec.Binary, spec.SrcDir, spec.Binary, spec.Binary, err) + } + return binaryPath, nil +} + +// resolveSrcRoot determines the cargo project root, checking for env var override first. +func resolveSrcRoot(defaultSrcDir, envSuffix string) (string, error) { + if srcOverride := os.Getenv("RUST_SRC_DIR_" + envSuffix); srcOverride != "" { + return srcOverride, nil + } + + rootDir, err := os.Getwd() + if err != nil { + return "", err + } + monorepoRoot, err := opservice.FindMonorepoRoot(rootDir) + if err != nil { + return "", err + } + return filepath.Join(monorepoRoot, defaultSrcDir), nil +} + +// toEnvVarSuffix converts a binary name to an env var suffix. +// e.g. "rollup-boost" -> "ROLLUP_BOOST" +func toEnvVarSuffix(binary string) string { + return strings.ToUpper(strings.ReplaceAll(binary, "-", "_")) +} + +func buildRustBinary(ctx context.Context, root, pkg, bin string) error { + cmd := exec.CommandContext(ctx, "cargo", "build", "-p", pkg, "--bin", bin) + cmd.Dir = root + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +type cargoMetadata struct { + TargetDirectory string `json:"target_directory"` +} + +func resolveBuiltRustBinaryPath(srcRoot, binary string) (string, error) { + targetDir, err := cargoTargetDirectory(srcRoot) + if err != nil { + return "", err + } + + candidates := []string{ + filepath.Join(targetDir, "release", binary), + filepath.Join(targetDir, "debug", binary), + } + globMatches, err := filepath.Glob(filepath.Join(targetDir, "*", "release", binary)) + if err == nil { + candidates = append(candidates, globMatches...) + } + + seen := make(map[string]struct{}, len(candidates)) + var existing []string + for _, candidate := range candidates { + if _, dup := seen[candidate]; dup { + continue + } + seen[candidate] = struct{}{} + if _, err := os.Stat(candidate); err == nil { + existing = append(existing, candidate) + } + } + + switch len(existing) { + case 0: + return "", fmt.Errorf("no built binary found under target dir %s", targetDir) + case 1: + return existing[0], nil + default: + sort.Strings(existing) + return existing[0], nil + } +} + +func cargoTargetDirectory(srcRoot string) (string, error) { + cmd := exec.Command("cargo", "metadata", "--no-deps", "--format-version", "1") + cmd.Dir = srcRoot + out, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("cargo metadata: %w", err) + } + + var meta cargoMetadata + if err := json.Unmarshal(out, &meta); err != nil { + return "", fmt.Errorf("parse cargo metadata: %w", err) + } + if meta.TargetDirectory == "" { + return "", fmt.Errorf("cargo metadata returned empty target directory") + } + return meta.TargetDirectory, nil +} diff --git a/op-devstack/sysgo/singlechain_build.go b/op-devstack/sysgo/singlechain_build.go index e7a8e19a531..856dbe11e8a 100644 --- a/op-devstack/sysgo/singlechain_build.go +++ b/op-devstack/sysgo/singlechain_build.go @@ -5,6 +5,8 @@ import ( "encoding/hex" "flag" "fmt" + "os" + "path/filepath" "time" "github.com/urfave/cli/v2" @@ -17,6 +19,7 @@ import ( "github.com/ethereum/go-ethereum/params/forks" "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/intentbuilder" faucetConfig "github.com/ethereum-optimism/optimism/op-faucet/config" @@ -66,7 +69,7 @@ type testSequencer struct { service *sequencer.Service } -func buildSingleChainWorld(t devtest.T, keys devkeys.Keys, localContractArtifactsPath string, deployerOpts ...DeployerOption) (*L1Network, *L2Network) { +func buildSingleChainWorld(t devtest.T, keys devkeys.Keys, deployerOpts ...DeployerOption) (*L1Network, *L2Network) { wb := &worldBuilder{ p: t, logger: t.Logger(), @@ -75,7 +78,7 @@ func buildSingleChainWorld(t devtest.T, keys devkeys.Keys, localContractArtifact builder: intentbuilder.New(), } - applyConfigLocalContractSources(t, keys, wb.builder, localContractArtifactsPath) + applyConfigLocalContractSources(t, keys, wb.builder) applyConfigCommons(t, keys, DefaultL1ID, wb.builder) applyConfigPrefundedL2(t, keys, DefaultL1ID, DefaultL2AID, wb.builder) applyConfigDeployerOptions(t, keys, wb.builder, deployerOpts) @@ -105,8 +108,14 @@ func buildSingleChainWorld(t devtest.T, keys devkeys.Keys, localContractArtifact return l1Net, l2Net } -func applyConfigLocalContractSources(t devtest.T, _ devkeys.Keys, builder intentbuilder.Builder, artifactsPath string) { - contractArtifacts, err := localContractSourcesLocator(artifactsPath) +func applyConfigLocalContractSources(t devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { + paths, err := contractPaths() + t.Require().NoError(err) + wd, err := os.Getwd() + t.Require().NoError(err) + artifactsPath := filepath.Join(wd, paths.FoundryArtifacts) + t.Require().NoError(ensureDir(artifactsPath)) + contractArtifacts, err := artifacts.NewFileLocator(artifactsPath) t.Require().NoError(err) builder.WithL1ContractsLocator(contractArtifacts) builder.WithL2ContractsLocator(contractArtifacts) @@ -150,8 +159,53 @@ func applyConfigPrefundedL2(t devtest.T, keys devkeys.Keys, l1ChainID, l2ChainID l1Config.WithPrefundedAccount(addrFor(devkeys.SystemConfigOwner), *millionEth) } -func startSequencerEL(t devtest.T, l2Net *L2Network, jwtPath string, jwtSecret [32]byte, identity *ELNodeIdentity) *OpGeth { - return startL2ELNode(t, l2Net, jwtPath, jwtSecret, "sequencer", identity) +// startL2ELForKey starts an L2 EL node for the given key, respecting DEVSTACK_L2EL_KIND. +// This is the single env-aware dispatch point for L2 EL selection. +func startL2ELForKey(t devtest.T, l2Net *L2Network, jwtPath string, jwtSecret [32]byte, key string, identity *ELNodeIdentity) L2ELNode { + switch devstackL2ELKind() { + case MixedL2ELOpGeth: + return startL2ELNode(t, l2Net, jwtPath, jwtSecret, key, identity) + case MixedL2ELOpRethV2: + return startMixedOpRethNode(t, l2Net, key, jwtPath, jwtSecret, nil, "v2") + default: // op-reth v1 + return startMixedOpRethNode(t, l2Net, key, jwtPath, jwtSecret, nil, "v1") + } +} + +// startL2CLForKey starts an L2 CL node for the given key, respecting DEVSTACK_L2CL_KIND. +// This is the single env-aware dispatch point for L2 CL selection. +func startL2CLForKey( + t devtest.T, + keys devkeys.Keys, + l1Net *L1Network, + l2Net *L2Network, + l1EL L1ELNode, + l1CL *L1CLNode, + l2EL L2ELNode, + jwtSecret [32]byte, + clKey, elKey string, + isSequencer bool, + followSource string, + l2CLOpts []L2CLOption, +) L2CLNode { + switch devstackL2CLKind() { + case MixedL2CLKona: + return startMixedKonaNode(t, keys, l1Net, l2Net, l1EL, l1CL, l2EL, clKey, elKey, isSequencer, nil) + default: // op-node + return startL2CLNode(t, keys, l1Net, l2Net, l1EL, l1CL, l2EL, jwtSecret, l2CLNodeStartConfig{ + Key: clKey, + IsSequencer: isSequencer, + NoDiscovery: true, + EnableReqResp: true, + UseReqResp: true, + L2FollowSource: followSource, + L2CLOptions: l2CLOpts, + }) + } +} + +func startSequencerEL(t devtest.T, l2Net *L2Network, jwtPath string, jwtSecret [32]byte, identity *ELNodeIdentity) L2ELNode { + return startL2ELForKey(t, l2Net, jwtPath, jwtSecret, "sequencer", identity) } func startL2ELNode( @@ -229,17 +283,8 @@ func startSequencerCL( l2EL L2ELNode, jwtSecret [32]byte, l2CLOpts []L2CLOption, -) *OpNode { - return startL2CLNode(t, keys, l1Net, l2Net, l1EL, l1CL, l2EL, jwtSecret, l2CLNodeStartConfig{ - Key: "sequencer", - IsSequencer: true, - NoDiscovery: true, - EnableReqResp: true, - UseReqResp: true, - IndexingMode: false, - L2FollowSource: "", - L2CLOptions: l2CLOpts, - }) +) L2CLNode { + return startL2CLForKey(t, keys, l1Net, l2Net, l1EL, l1CL, l2EL, jwtSecret, "sequencer", "sequencer", true, "", l2CLOpts) } type l2CLNodeStartConfig struct { @@ -386,7 +431,6 @@ func startL2CLNode( Metrics: opmetrics.CLIConfig{}, Pprof: oppprof.CLIConfig{}, SafeDBPath: cfg.SafeDBPath, - RollupHalt: "", Cancel: nil, ConductorEnabled: false, ConductorRpc: nil, diff --git a/op-devstack/sysgo/singlechain_flashblocks.go b/op-devstack/sysgo/singlechain_flashblocks.go new file mode 100644 index 00000000000..c184d06a5c3 --- /dev/null +++ b/op-devstack/sysgo/singlechain_flashblocks.go @@ -0,0 +1,127 @@ +package sysgo + +import ( + "encoding/json" + "os" + "path/filepath" + "strings" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +func startFlashblocksSingleChainPrimary( + t devtest.T, + keys devkeys.Keys, + world singleChainRuntimeWorld, + l1EL *L1Geth, + l1CL *L1CLNode, + jwtPath string, + jwtSecret [32]byte, + cfg PresetConfig, +) singleChainPrimaryRuntime { + logger := t.Logger() + + sequencerIdentity := NewELNodeIdentity(0) + builderIdentity := NewELNodeIdentity(0) + + l2EL := startSequencerEL(t, world.L2Network, jwtPath, jwtSecret, sequencerIdentity) + l2Builder := startBuilderEL(t, world.L2Network, jwtPath, builderIdentity, cfg.OPRBuilderOptions...) + + connectL2ELPeers(t, logger, l2EL.UserRPC(), l2Builder.UserRPC(), false) + connectL2ELPeers(t, logger, l2Builder.UserRPC(), l2EL.UserRPC(), true) + + rollupBoost := startRollupBoostNode(t, world.L2Network.ChainID(), l2EL, l2Builder) + l2CL := startSequencerCL(t, keys, world.L1Network, world.L2Network, l1EL, l1CL, rollupBoost, jwtSecret, nil) + + return singleChainPrimaryRuntime{ + EL: l2EL, + CL: l2CL, + Flashblocks: &FlashblocksRuntimeSupport{ + Builder: l2Builder, + RollupBoost: rollupBoost, + }, + } +} + +func NewFlashblocksRuntime(t devtest.T) *SingleChainRuntime { + return NewFlashblocksRuntimeWithConfig(t, PresetConfig{}) +} + +func NewFlashblocksRuntimeWithConfig(t devtest.T, cfg PresetConfig) *SingleChainRuntime { + return newSingleChainRuntimeWithConfig(t, cfg, singleChainRuntimeSpec{ + BuildWorld: newDefaultSingleChainWorld, + StartPrimary: startFlashblocksSingleChainPrimary, + StartBatcher: false, + StartProposer: false, + StartChallenger: false, + }) +} + +func startBuilderEL(t devtest.T, l2Net *L2Network, jwtPath string, identity *ELNodeIdentity, opts ...OPRBuilderNodeOption) *OPRBuilderNode { + require := t.Require() + + data, err := json.Marshal(l2Net.genesis) + require.NoError(err, "must json-encode L2 genesis") + chainConfigPath := filepath.Join(t.TempDir(), "op-rbuilder-genesis.json") + require.NoError(os.WriteFile(chainConfigPath, data, 0o644), "must write op-rbuilder genesis file") + + cfg := DefaultOPRbuilderNodeConfig() + cfg.AuthRPCJWTPath = jwtPath + cfg.Chain = chainConfigPath + cfg.P2PAddr = "127.0.0.1" + cfg.P2PPort = identity.Port + cfg.P2PNodeKeyHex = identity.KeyHex() + cfg.StaticPeers = nil + cfg.TrustedPeers = nil + if len(opts) > 0 { + target := NewComponentTarget("sequencer-builder", l2Net.ChainID()) + for _, opt := range opts { + if opt == nil { + continue + } + opt.Apply(t, target, cfg) + } + } + + builder := &OPRBuilderNode{ + name: "sequencer-builder", + chainID: l2Net.ChainID(), + logger: t.Logger().New("component", "op-rbuilder"), + p: t, + rollupCfg: l2Net.rollupCfg, + cfg: cfg, + } + builder.Start() + t.Cleanup(builder.Stop) + return builder +} + +func startRollupBoostNode(t devtest.T, chainID eth.ChainID, l2EL L2ELNode, builder *OPRBuilderNode) *RollupBoostNode { + cfg := DefaultRollupBoostConfig() + engineRPC := l2EL.EngineRPC() + switch { + case strings.HasPrefix(engineRPC, "ws://"): + engineRPC = "http://" + strings.TrimPrefix(engineRPC, "ws://") + case strings.HasPrefix(engineRPC, "wss://"): + engineRPC = "https://" + strings.TrimPrefix(engineRPC, "wss://") + } + cfg.L2EngineURL = engineRPC + cfg.L2JWTPath = l2EL.JWTPath() + cfg.BuilderURL = ensureHTTPURL(builder.authProxyURL) + cfg.BuilderJWTPath = builder.cfg.AuthRPCJWTPath + cfg.FlashblocksBuilderURL = builder.wsProxyURL + + rollupBoost := &RollupBoostNode{ + name: "rollup-boost", + chainID: chainID, + logger: t.Logger().New("component", "rollup-boost"), + p: t, + cfg: cfg, + header: cfg.Headers, + } + rollupBoost.Start() + t.Cleanup(rollupBoost.Stop) + return rollupBoost +} diff --git a/op-devstack/sysgo/singlechain_interop.go b/op-devstack/sysgo/singlechain_interop.go new file mode 100644 index 00000000000..ec94ffdef47 --- /dev/null +++ b/op-devstack/sysgo/singlechain_interop.go @@ -0,0 +1,60 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" +) + +func newSingleChainInteropWorldNoSupervisor(t devtest.T, keys devkeys.Keys, cfg PresetConfig) singleChainRuntimeWorld { + l1Net, l2Net, depSet, fullCfgSet := buildSingleChainWorldWithInterop(t, keys, true, cfg.DeployerOptions...) + return singleChainRuntimeWorld{ + L1Network: l1Net, + L2Network: l2Net, + Interop: &SingleChainInteropSupport{ + DependencySet: depSet, + FullConfigSet: fullCfgSet, + }, + } +} + +func startSingleChainInteropPrimaryNoSupervisor( + t devtest.T, + keys devkeys.Keys, + world singleChainRuntimeWorld, + l1EL *L1Geth, + l1CL *L1CLNode, + jwtPath string, + jwtSecret [32]byte, + cfg PresetConfig, +) singleChainPrimaryRuntime { + t.Require().NotNil(world.Interop, "single-chain interop runtime requires interop support") + + sequencerIdentity := NewELNodeIdentity(0) + l2EL := startSequencerEL(t, world.L2Network, jwtPath, jwtSecret, sequencerIdentity) + l2CL := startL2CLNode(t, keys, world.L1Network, world.L2Network, l1EL, l1CL, l2EL, jwtSecret, l2CLNodeStartConfig{ + Key: "sequencer", + IsSequencer: true, + NoDiscovery: true, + EnableReqResp: true, + UseReqResp: true, + DependencySet: world.Interop.DependencySet, + L2FollowSource: "", + L2CLOptions: cfg.GlobalL2CLOptions, + }) + return singleChainPrimaryRuntime{ + EL: l2EL, + CL: l2CL, + } +} + +// NewMinimalInteropNoSupervisorRuntime constructs the single-chain interop world +// without supervisor wiring. +func NewMinimalInteropNoSupervisorRuntime(t devtest.T) *SingleChainRuntime { + return newSingleChainRuntimeWithConfig(t, PresetConfig{}, singleChainRuntimeSpec{ + BuildWorld: newSingleChainInteropWorldNoSupervisor, + StartPrimary: startSingleChainInteropPrimaryNoSupervisor, + StartBatcher: true, + StartProposer: true, + StartChallenger: false, + }) +} diff --git a/op-devstack/sysgo/singlechain_runtime.go b/op-devstack/sysgo/singlechain_runtime.go index aa7ed075f96..5e35599dd36 100644 --- a/op-devstack/sysgo/singlechain_runtime.go +++ b/op-devstack/sysgo/singlechain_runtime.go @@ -60,7 +60,7 @@ func newSingleChainNodeRuntime(name string, isSequencer bool, el L2ELNode, cl L2 } func newDefaultSingleChainWorld(t devtest.T, keys devkeys.Keys, cfg PresetConfig) singleChainRuntimeWorld { - l1Net, l2Net := buildSingleChainWorld(t, keys, cfg.LocalContractArtifactsPath, cfg.DeployerOptions...) + l1Net, l2Net := buildSingleChainWorld(t, keys, cfg.DeployerOptions...) return singleChainRuntimeWorld{ L1Network: l1Net, L2Network: l2Net, @@ -77,8 +77,7 @@ func startDefaultSingleChainPrimary( jwtSecret [32]byte, cfg PresetConfig, ) singleChainPrimaryRuntime { - sequencerIdentity := NewELNodeIdentity(0) - l2EL := startSequencerEL(t, world.L2Network, jwtPath, jwtSecret, sequencerIdentity) + l2EL := startSequencerEL(t, world.L2Network, jwtPath, jwtSecret, NewELNodeIdentity(0)) l2CL := startSequencerCL(t, keys, world.L1Network, world.L2Network, l1EL, l1CL, l2EL, jwtSecret, cfg.GlobalL2CLOptions) return singleChainPrimaryRuntime{ EL: l2EL, @@ -123,9 +122,7 @@ func newSingleChainRuntimeWithConfig(t devtest.T, cfg PresetConfig, spec singleC applyMinimalGameTypeOptions(t, keys, world.L1Network, world.L2Network, l1EL, cfg.AddedGameTypes, cfg.RespectedGameTypes) - sequencerCL, ok := primary.CL.(*OpNode) - require.True(ok, "single-chain runtime primary CL must be op-node for test sequencer") - testSequencer := startTestSequencer(t, keys, jwtPath, jwtSecret, world.L1Network, l1EL, l1CL, primary.EL, world.L2Network, sequencerCL) + testSequencer := startTestSequencerForRPCs(t, keys, "test-sequencer", jwtPath, jwtSecret, world.L1Network, l1EL, l1CL, world.L2Network.ChainID(), primary.EL.UserRPC(), primary.CL.UserRPC()) testSequencerRuntime := newTestSequencerRuntime(testSequencer, spec.TestSequencer) faucetService := startFaucets(t, keys, world.L1Network.ChainID(), world.L2Network.ChainID(), l1EL.UserRPC(), primary.EL.UserRPC()) @@ -341,14 +338,8 @@ func startMinimalChallenger( sharedchallenger.WithCannonGameType(), sharedchallenger.WithPermissionedGameType(), sharedchallenger.WithFastGames(), - } - if enableCannonKona { - t.Log("Enabling cannon-kona for challenger") - options = append(options, - sharedchallenger.WithCannonKonaConfig(rollupCfgs, l1Net.genesis, l2Geneses), - sharedchallenger.WithCannonKonaGameType(), - sharedchallenger.WithExperimentalWitnessEndpoint(), - ) + sharedchallenger.WithCannonKonaConfig(rollupCfgs, l1Net.genesis, l2Geneses), + sharedchallenger.WithCannonKonaGameType(), } cfg, err := sharedchallenger.NewPreInteropChallengerConfig( t.TempDir(), diff --git a/op-devstack/sysgo/singlechain_variants.go b/op-devstack/sysgo/singlechain_variants.go index 3945ad15b75..53b8050a227 100644 --- a/op-devstack/sysgo/singlechain_variants.go +++ b/op-devstack/sysgo/singlechain_variants.go @@ -166,17 +166,8 @@ func addSingleChainOpNode( ) *SingleChainNodeRuntime { jwtPath := runtime.L2EL.JWTPath() jwtSecret := readJWTSecretFromPath(t, jwtPath) - identity := NewELNodeIdentity(0) - l2EL := startL2ELNode(t, runtime.L2Network, jwtPath, jwtSecret, name, identity) - l2CL := startL2CLNode(t, runtime.Keys, runtime.L1Network, runtime.L2Network, runtime.L1EL, runtime.L1CL, l2EL, jwtSecret, l2CLNodeStartConfig{ - Key: name, - IsSequencer: isSequencer, - NoDiscovery: true, - EnableReqResp: true, - UseReqResp: true, - L2FollowSource: followSource, - L2CLOptions: l2Opts, - }) + l2EL := startL2ELForKey(t, runtime.L2Network, jwtPath, jwtSecret, name, NewELNodeIdentity(0)) + l2CL := startL2CLForKey(t, runtime.Keys, runtime.L1Network, runtime.L2Network, runtime.L1EL, runtime.L1CL, l2EL, jwtSecret, name, name, isSequencer, followSource, l2Opts) node := newSingleChainNodeRuntime(name, isSequencer, l2EL, l2CL) runtime.Nodes[name] = node return node diff --git a/op-devstack/sysgo/subproc.go b/op-devstack/sysgo/subproc.go new file mode 100644 index 00000000000..148fa031fad --- /dev/null +++ b/op-devstack/sysgo/subproc.go @@ -0,0 +1,98 @@ +package sysgo + +import ( + "fmt" + "os" + "os/exec" + "sync" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-service/logpipe" +) + +// SubProcess is a process that can be started and stopped. +type SubProcess struct { + p devtest.CommonT + cmd *exec.Cmd + + stdOutCallback logpipe.LogCallback + stdErrCallback logpipe.LogCallback + + stdOutProc *logpipe.LineBuffer + stdErrProc *logpipe.LineBuffer + + mu sync.Mutex +} + +func NewSubProcess(p devtest.CommonT, stdOutCallback, stdErrCallback logpipe.LogCallback) *SubProcess { + return &SubProcess{ + p: p, + stdOutCallback: stdOutCallback, + stdErrCallback: stdErrCallback, + } +} + +func (sp *SubProcess) Start(cmdPath string, args []string, env []string) error { + sp.mu.Lock() + defer sp.mu.Unlock() + if sp.cmd != nil { + return fmt.Errorf("process is still running (PID: %d)", sp.cmd.Process.Pid) + } + sp.p.Logger().Info("Starting subprocess", "cmd", cmdPath, "args", args) + + stdOutProc := logpipe.NewLineBuffer(sp.stdOutCallback) + stdErrProc := logpipe.NewLineBuffer(sp.stdErrCallback) + + cmd := exec.Command(cmdPath, args...) + cmd.Env = append(os.Environ(), env...) + cmd.Stdout = stdOutProc + cmd.Stderr = stdErrProc + if err := cmd.Start(); err != nil { + return err + } + sp.cmd = cmd + sp.stdOutProc = stdOutProc + sp.stdErrProc = stdErrProc + sp.p.Cleanup(func() { + err := sp.Stop(true) + if err != nil { + sp.p.Logger().Error("Shutdown error", "err", err) + } + }) + return nil +} + +// Stop waits for the process to stop, interrupting the process if it has not completed and +// interrupt is true. +func (sp *SubProcess) Stop(interrupt bool) error { + sp.mu.Lock() + defer sp.mu.Unlock() + if sp.cmd == nil { + return nil // already stopped gracefully + } + + // If not already done, then try an interrupt first as requested. + if sp.cmd.ProcessState == nil && interrupt { + sp.p.Logger().Info("Sending interrupt") + if err := sp.cmd.Process.Signal(os.Interrupt); err != nil { + return err + } + } + + if _, err := sp.cmd.Process.Wait(); err != nil { + sp.p.Logger().Warn("Sub-process exited with error", "err", err) + } else { + sp.p.Logger().Info("Sub-process gracefully exited") + } + + if sp.stdOutProc != nil { + _ = sp.stdOutProc.Close() + sp.stdOutProc = nil + } + if sp.stdErrProc != nil { + _ = sp.stdErrProc.Close() + sp.stdErrProc = nil + } + sp.cmd = nil + return nil +} diff --git a/op-devstack/sysgo/superchain.go b/op-devstack/sysgo/superchain.go new file mode 100644 index 00000000000..85e82f539ea --- /dev/null +++ b/op-devstack/sysgo/superchain.go @@ -0,0 +1,21 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum/go-ethereum/common" +) + +type SuperchainDeployment struct { + protocolVersionsAddr common.Address + superchainConfigAddr common.Address +} + +var _ stack.SuperchainDeployment = &SuperchainDeployment{} + +func (d *SuperchainDeployment) SuperchainConfigAddr() common.Address { + return d.superchainConfigAddr +} + +func (d *SuperchainDeployment) ProtocolVersionsAddr() common.Address { + return d.protocolVersionsAddr +} diff --git a/op-devstack/sysgo/superroot.go b/op-devstack/sysgo/superroot.go new file mode 100644 index 00000000000..5d6fd83e0a1 --- /dev/null +++ b/op-devstack/sysgo/superroot.go @@ -0,0 +1,547 @@ +package sysgo + +import ( + "context" + "crypto/ecdsa" + "encoding/json" + "math/big" + "os" + "path" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/gameargs" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/contracts/bindings/delegatecallproxy" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-service/dial" + "github.com/ethereum-optimism/optimism/op-service/errutil" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/lmittmann/w3" + w3eth "github.com/lmittmann/w3/module/eth" +) + +// V2 structs for OPCM >= 7.0.0 (using IOPContractsManagerMigrator interface) +type DisputeGameConfigV2 struct { + Enabled bool + InitBond *big.Int + GameType uint32 + GameArgs []byte +} + +type MigrateInputV2 struct { + ChainSystemConfigs []common.Address + DisputeGameConfigs []DisputeGameConfigV2 + StartingAnchorRoot bindings.Proposal + StartingRespectedGameType uint32 +} + +func deployDelegateCallProxy(t devtest.CommonT, transactOpts *bind.TransactOpts, client *ethclient.Client, owner common.Address) (common.Address, *delegatecallproxy.Delegatecallproxy) { + deployAddress, tx, proxyContract, err := delegatecallproxy.DeployDelegatecallproxy(transactOpts, client, owner) + t.Require().NoError(err, "DelegateCallProxy deployment failed") + // Make sure the transaction actually got included rather than just being sent + _, err = wait.ForReceiptOK(t.Ctx(), client, tx.Hash()) + t.Require().NoError(err, "DelegateCallProxy deployment tx was not included successfully") + return deployAddress, proxyContract +} + +func awaitSuperrootTime(t devtest.T, cls ...L2CLNode) uint64 { + t.Require().NotEmpty(cls, "at least one L2 CL is required") + + var superrootTime uint64 + for _, l2CL := range cls { + rollupClient, err := dial.DialRollupClientWithTimeout(t.Ctx(), t.Logger(), l2CL.UserRPC()) + t.Require().NoError(err) + defer rollupClient.Close() + + ctx, cancel := context.WithTimeout(t.Ctx(), 2*time.Minute) + err = wait.For(ctx, time.Second, func() (bool, error) { + status, err := rollupClient.SyncStatus(ctx) + if err != nil { + return false, err + } + if status == nil || status.SafeL2.Number == 0 { + return false, nil + } + superrootTime = status.SafeL2.Time + return true, nil + }) + cancel() + t.Require().NoError(err, "waiting for chain safe head to advance failed") + } + return superrootTime +} + +func getSupervisorSuperRoot(t devtest.T, supervisor Supervisor, timestamp uint64) eth.Bytes32 { + client, err := dial.DialSupervisorClientWithTimeout(t.Ctx(), t.Logger(), supervisor.UserRPC()) + t.Require().NoError(err) + + ctx, cancel := context.WithTimeout(t.Ctx(), 2*time.Minute) + err = wait.For(ctx, time.Second, func() (bool, error) { + status, err := client.SyncStatus(ctx) + if err != nil { + return false, err + } + return timestamp < status.MinSyncedL1.Time, nil + }) + cancel() + t.Require().NoError(err, "waiting for supervisor to sync failed") + + super, err := client.SuperRootAtTimestamp(t.Ctx(), hexutil.Uint64(timestamp)) + t.Require().NoError(err, "super root at timestamp failed") + return super.SuperRoot +} + +func getSupernodeSuperRoot(t devtest.T, supernode *SuperNode, timestamp uint64) eth.Bytes32 { + client, err := dial.DialSuperNodeClientWithTimeout(t.Ctx(), t.Logger(), supernode.UserRPC()) + t.Require().NoError(err) + + ctx, cancel := context.WithTimeout(t.Ctx(), 2*time.Minute) + err = wait.For(ctx, time.Second, func() (bool, error) { + resp, err := client.SuperRootAtTimestamp(ctx, timestamp) + if err != nil { + t.Logf("DEBUG: Failed to get super root at timestamp %d: err: %v", timestamp, err) + return false, err + } + return resp.Data != nil, nil + }) + cancel() + t.Require().NoError(err, "waiting for supernode superroot to be ready failed") + + resp, err := client.SuperRootAtTimestamp(t.Ctx(), timestamp) + t.Require().NoError(err, "super root at timestamp failed") + t.Require().NotNil(resp.Data, "super root data must be present") + return resp.Data.SuperRoot +} + +func migrateSuperRoots( + t devtest.T, + keys devkeys.Keys, + migration *interopMigrationState, + l1ChainID eth.ChainID, + l1EL L1ELNode, + superRoot eth.Bytes32, + superrootTime uint64, + primaryL2 eth.ChainID, +) common.Address { + require := t.Require() + require.NotNil(migration, "interop migration state is required") + require.NotEmpty(migration.opcmImpl, "must have an OPCM implementation") + require.NotEmpty(migration.superchainConfigAddr, "must have a superchain deployment") + require.NotEmpty(migration.l2Deployments, "must have L2 deployments for interop migration") + + rpcClient, err := rpc.DialContext(t.Ctx(), l1EL.UserRPC()) + require.NoError(err) + client := ethclient.NewClient(rpcClient) + w3Client := w3.NewClient(rpcClient) + + l1pao, err := keys.Address(devkeys.ChainOperatorKeys(l1ChainID.ToBig())(devkeys.L1ProxyAdminOwnerRole)) + require.NoError(err, "must have L1 proxy admin owner private key") + + superchainProxyAdmin := getProxyAdmin(t, w3Client, migration.superchainConfigAddr) + require.NotEmpty(superchainProxyAdmin, "superchain proxy admin address is empty") + + useV2 := isOPCMV2(t, w3Client, migration.opcmImpl) + absoluteCannonPrestate := getInteropCannonAbsolutePrestate(t) + absoluteCannonKonaPrestate := getInteropCannonKonaAbsolutePrestate(t) + + permissionedChainOps := devkeys.ChainOperatorKeys(primaryL2.ToBig()) + proposer, err := keys.Address(permissionedChainOps(devkeys.ProposerRole)) + require.NoError(err, "must have configured proposer") + challenger, err := keys.Address(permissionedChainOps(devkeys.ChallengerRole)) + require.NoError(err, "must have configured challenger") + + var opChainConfigs []bindings.OPContractsManagerOpChainConfig + var l2ChainIDs []eth.ChainID + for l2ChainID, l2Deployment := range migration.l2Deployments { + l2ChainIDs = append(l2ChainIDs, l2ChainID) + opChainConfigs = append(opChainConfigs, bindings.OPContractsManagerOpChainConfig{ + SystemConfigProxy: l2Deployment.SystemConfigProxyAddr(), + CannonPrestate: absoluteCannonPrestate, + CannonKonaPrestate: absoluteCannonKonaPrestate, + }) + } + + opcmABI, err := bindings.OPContractsManagerMetaData.GetAbi() + require.NoError(err, "invalid OPCM ABI") + contract := batching.NewBoundContract(opcmABI, migration.opcmImpl) + + var migrateCallData []byte + if useV2 { + var chainSystemConfigs []common.Address + for _, cfg := range opChainConfigs { + chainSystemConfigs = append(chainSystemConfigs, cfg.SystemConfigProxy) + } + migrateInputV2 := MigrateInputV2{ + ChainSystemConfigs: chainSystemConfigs, + DisputeGameConfigs: []DisputeGameConfigV2{ + { + Enabled: true, + InitBond: big.NewInt(0), + GameType: superCannonGameType, + GameArgs: absoluteCannonPrestate[:], + }, + }, + StartingAnchorRoot: bindings.Proposal{ + Root: common.Hash(superRoot), + L2SequenceNumber: big.NewInt(int64(superrootTime)), + }, + StartingRespectedGameType: superCannonGameType, + } + migrateCall := contract.Call("migrate", migrateInputV2) + migrateCallData, err = migrateCall.Pack() + require.NoError(err) + } else { + migrateInputV1 := bindings.OPContractsManagerInteropMigratorMigrateInput{ + UsePermissionlessGame: true, + StartingAnchorRoot: bindings.Proposal{ + Root: common.Hash(superRoot), + L2SequenceNumber: big.NewInt(int64(superrootTime)), + }, + GameParameters: bindings.OPContractsManagerInteropMigratorGameParameters{ + Proposer: proposer, + Challenger: challenger, + MaxGameDepth: big.NewInt(73), + SplitDepth: big.NewInt(30), + InitBond: big.NewInt(0), + ClockExtension: 10800, + MaxClockDuration: 302400, + }, + OpChainConfigs: opChainConfigs, + } + migrateCall := contract.Call("migrate", migrateInputV1) + migrateCallData, err = migrateCall.Pack() + require.NoError(err) + } + + l1PAOKey, err := keys.Secret(devkeys.ChainOperatorKeys(l1ChainID.ToBig())(devkeys.L1ProxyAdminOwnerRole)) + require.NoError(err, "must have configured L1 proxy admin owner") + transactOpts, err := bind.NewKeyedTransactorWithChainID(l1PAOKey, l1ChainID.ToBig()) + require.NoError(err, "must have transact opts") + transactOpts.Context = t.Ctx() + + t.Log("Deploying delegate call proxy contract") + delegateCallProxy, proxyContract := deployDelegateCallProxy(t, transactOpts, client, l1pao) + oldSuperchainProxyAdminOwner := getOwner(t, w3Client, superchainProxyAdmin) + transferOwnership(t, l1PAOKey, client, superchainProxyAdmin, delegateCallProxy) + + oldDisputeGameFactories := make(map[eth.ChainID]common.Address) + for i, opChainConfig := range opChainConfigs { + var portal common.Address + require.NoError(w3Client.Call(w3eth.CallFunc(opChainConfig.SystemConfigProxy, optimismPortalFn).Returns(&portal))) + portalProxyAdmin := getProxyAdmin(t, w3Client, portal) + transferOwnership(t, l1PAOKey, client, portalProxyAdmin, delegateCallProxy) + + dgf := getDisputeGameFactory(t, w3Client, portal) + transferOwnership(t, l1PAOKey, client, dgf, delegateCallProxy) + oldDisputeGameFactories[l2ChainIDs[i]] = dgf + } + + t.Log("Executing delegate call") + migrateTx, err := proxyContract.ExecuteDelegateCall(transactOpts, migration.opcmImpl, migrateCallData) + require.NoErrorf(err, "migrate delegatecall failed: %v", errutil.TryAddRevertReason(err)) + _, err = wait.ForReceiptOK(t.Ctx(), client, migrateTx.Hash()) + require.NoError(err) + + var sharedDGF common.Address + for _, l2Deployment := range migration.l2Deployments { + portal := getOptimismPortal(t, w3Client, l2Deployment.SystemConfigProxyAddr()) + addr := getDisputeGameFactory(t, w3Client, portal) + if sharedDGF == (common.Address{}) { + sharedDGF = addr + } else { + require.Equal(sharedDGF, addr, "dispute game factory address is not the same for all deployments") + } + } + require.NotEmpty(getSuperGameImpl(t, w3Client, sharedDGF)) + + resetOwnershipAfterMigration( + t, + keys, + l1ChainID.ToBig(), + l1PAOKey, + w3Client, + client, + delegateCallProxy, + opChainConfigs, + ) + resetOldDisputeGameFactoriesAfterMigration( + t, + keys, + l1ChainID.ToBig(), + l1PAOKey, + client, + delegateCallProxy, + oldDisputeGameFactories, + ) + transferOwnershipForDelegateCallProxy(t, l1ChainID.ToBig(), l1PAOKey, client, delegateCallProxy, superchainProxyAdmin, oldSuperchainProxyAdminOwner) + + superchainProxyAdminOwner := getOwner(t, w3Client, superchainProxyAdmin) + require.Equal(oldSuperchainProxyAdminOwner, superchainProxyAdminOwner, "superchain proxy admin owner is not the L1PAO") + + for chainID, l2Deployment := range migration.l2Deployments { + l2Deployment.disputeGameFactoryProxy = sharedDGF + migration.l2Deployments[chainID] = l2Deployment + } + t.Log("Interop migration complete") + return sharedDGF +} + +func getInteropCannonAbsolutePrestate(t devtest.CommonT) common.Hash { + return getAbsolutePrestate(t, "op-program/bin/prestate-proof-interop.json") +} + +func getInteropCannonKonaAbsolutePrestate(t devtest.CommonT) common.Hash { + return getAbsolutePrestate(t, "rust/kona/prestate-artifacts-cannon-interop/prestate-proof.json") +} + +func getCannonKonaAbsolutePrestate(t devtest.CommonT) common.Hash { + return getAbsolutePrestate(t, "rust/kona/prestate-artifacts-cannon/prestate-proof.json") +} + +func getAbsolutePrestate(t devtest.CommonT, prestatePath string) common.Hash { + root, err := findMonorepoRoot(prestatePath) + t.Require().NoError(err) + p := path.Join(root, prestatePath) + file, err := os.Open(p) + t.Require().NoError(err) + decoder := json.NewDecoder(file) + var prestate map[string]interface{} + err = decoder.Decode(&prestate) + t.Require().NoError(err) + t.Require().NotEmpty(prestate, "prestate is empty") + return common.HexToHash(prestate["pre"].(string)) +} + +const ( + superCannonGameType = 4 + superPermissionedGameType = 5 +) + +var ( + optimismPortalFn = w3.MustNewFunc("optimismPortal()", "address") + disputeGameFactoryFn = w3.MustNewFunc("disputeGameFactory()", "address") + gameImplsFn = w3.MustNewFunc("gameImpls(uint32)", "address") + gameArgsFn = w3.MustNewFunc("gameArgs(uint32)", "bytes") + ownerFn = w3.MustNewFunc("owner()", "address") + proxyAdminFn = w3.MustNewFunc("proxyAdmin()", "address") + adminFn = w3.MustNewFunc("admin()", "address") + proxyAdminOwnerFn = w3.MustNewFunc("proxyAdminOwner()", "address") + ethLockboxFn = w3.MustNewFunc("ethLockbox()", "address") + anchorStateRegistryFn = w3.MustNewFunc("anchorStateRegistry()", "address") + transferOwnershipFn = w3.MustNewFunc("transferOwnership(address)", "") + versionFn = w3.MustNewFunc("version()", "string") +) + +// isOPCMV2 is a helper function that checks the OPCM version and returns true if it is at least 7.0.0 +func isOPCMV2(t devtest.CommonT, client *w3.Client, opcmAddr common.Address) bool { + var version string + err := client.Call(w3eth.CallFunc(opcmAddr, versionFn).Returns(&version)) + t.Require().NoError(err, "failed to get OPCM version") + + isVersionAtLeast, err := deployer.IsVersionAtLeast(version, 7, 0, 0) + t.Require().NoError(err, "failed to check OPCM version") + return isVersionAtLeast +} + +func getOptimismPortal(t devtest.CommonT, client *w3.Client, systemConfigProxy common.Address) common.Address { + var addr common.Address + err := client.Call(w3eth.CallFunc(systemConfigProxy, optimismPortalFn).Returns(&addr)) + t.Require().NoError(err) + return addr +} + +func getDisputeGameFactory(t devtest.CommonT, client *w3.Client, portal common.Address) common.Address { + var addr common.Address + err := client.Call(w3eth.CallFunc(portal, disputeGameFactoryFn).Returns(&addr)) + t.Require().NoError(err) + return addr +} + +func getSuperGameImpl(t devtest.CommonT, client *w3.Client, dgf common.Address) common.Address { + var addr common.Address + err := client.Call(w3eth.CallFunc(dgf, gameImplsFn, uint32(superCannonGameType)).Returns(&addr)) + t.Require().NoError(err) + return addr +} + +func getOwner(t devtest.CommonT, client *w3.Client, addr common.Address) common.Address { + var owner common.Address + err := client.Call(w3eth.CallFunc(addr, ownerFn).Returns(&owner)) + t.Require().NoError(err) + return owner +} + +func getAdmin(t devtest.CommonT, client *w3.Client, addr common.Address) common.Address { + var admin common.Address + err := client.Call(w3eth.CallFunc(addr, adminFn).Returns(&admin)) + t.Require().NoError(err) + return admin +} + +func getProxyAdminOwner(t devtest.CommonT, client *w3.Client, addr common.Address) common.Address { + var proxyAdminOwner common.Address + err := client.Call(w3eth.CallFunc(addr, proxyAdminOwnerFn).Returns(&proxyAdminOwner)) + t.Require().NoError(err) + return proxyAdminOwner +} + +func getProxyAdmin(t devtest.CommonT, client *w3.Client, addr common.Address) common.Address { + var proxyAdmin common.Address + err := client.Call(w3eth.CallFunc(addr, proxyAdminFn).Returns(&proxyAdmin)) + t.Require().NoError(err) + return proxyAdmin +} + +func transferOwnership(t devtest.CommonT, privateKey *ecdsa.PrivateKey, client *ethclient.Client, l1ProxyAdmin common.Address, newOwner common.Address) { + data, err := transferOwnershipFn.EncodeArgs(newOwner) + t.Require().NoError(err) + + candidate := txmgr.TxCandidate{ + To: &l1ProxyAdmin, + TxData: data, + GasLimit: 1_000_000, + } + _, receipt, err := transactions.SendTx(t.Ctx(), client, candidate, privateKey) + t.Require().NoErrorf(err, "transferOwnership failed: %v", errutil.TryAddRevertReason(err)) + t.Require().Equal(receipt.Status, types.ReceiptStatusSuccessful, "transferOwnership failed") +} + +func transferOwnershipForDelegateCallProxy( + t devtest.CommonT, + transactChainID *big.Int, + privateKey *ecdsa.PrivateKey, + client *ethclient.Client, + delegateCallProxy common.Address, + proxyAdminOwned common.Address, + newOwner common.Address, +) { + transactOpts, err := bind.NewKeyedTransactorWithChainID(privateKey, transactChainID) + t.Require().NoError(err, "must have transact opts") + transactOpts.Context = t.Ctx() + + abi, err := delegatecallproxy.DelegatecallproxyMetaData.GetAbi() + t.Require().NoError(err, "failed to get abi") + contract := batching.NewBoundContract(abi, delegateCallProxy) + call := contract.Call("transferOwnership", proxyAdminOwned, newOwner) + data, err := call.Pack() + t.Require().NoError(err) + + candidate := txmgr.TxCandidate{ + To: &delegateCallProxy, + TxData: data, + GasLimit: 1_000_000, + } + _, receipt, err := transactions.SendTx(t.Ctx(), client, candidate, privateKey) + t.Require().NoErrorf(err, "transferOwnership failed: %v", errutil.TryAddRevertReason(err)) + t.Require().Equal(receipt.Status, types.ReceiptStatusSuccessful, "transferOwnership failed") +} + +func resetOwnershipAfterMigration( + t devtest.CommonT, + keys devkeys.Keys, + l1ChainID *big.Int, + ownerPrivateKey *ecdsa.PrivateKey, + w3Client *w3.Client, + client *ethclient.Client, + delegateCallProxy common.Address, + opChainConfigs []bindings.OPContractsManagerOpChainConfig, +) { + l1PAO, err := keys.Address(devkeys.ChainOperatorKeys(l1ChainID)(devkeys.L1ProxyAdminOwnerRole)) + t.Require().NoError(err, "must have L1 proxy admin owner private key") + + portal0 := getOptimismPortal(t, w3Client, opChainConfigs[0].SystemConfigProxy) + sharedDGF := getDisputeGameFactory(t, w3Client, portal0) + transferOwnershipForDelegateCallProxy( + t, + l1ChainID, + ownerPrivateKey, + client, + delegateCallProxy, + sharedDGF, + l1PAO, + ) + + var sharedEthLockboxProxy common.Address + err = w3Client.Call(w3eth.CallFunc(portal0, ethLockboxFn).Returns(&sharedEthLockboxProxy)) + t.Require().NoError(err) + proxyAdmin := getAdmin(t, w3Client, sharedEthLockboxProxy) + transferOwnershipForDelegateCallProxy( + t, + l1ChainID, + ownerPrivateKey, + client, + delegateCallProxy, + proxyAdmin, + l1PAO, + ) + + for _, cfg := range opChainConfigs { + portal := getOptimismPortal(t, w3Client, cfg.SystemConfigProxy) + portalProxyAdmin := getProxyAdmin(t, w3Client, portal) + if getOwner(t, w3Client, portalProxyAdmin) == delegateCallProxy { + transferOwnershipForDelegateCallProxy( + t, + l1ChainID, + ownerPrivateKey, + client, + delegateCallProxy, + portalProxyAdmin, + l1PAO, + ) + } + } + + var sharedAnchorStateRegistryProxy common.Address + err = w3Client.Call(w3eth.CallFunc(portal0, anchorStateRegistryFn).Returns(&sharedAnchorStateRegistryProxy)) + t.Require().NoError(err) + asrAAdminOwner := getProxyAdminOwner(t, w3Client, sharedAnchorStateRegistryProxy) + t.Require().Equal(l1PAO, asrAAdminOwner, "sharedAnchorStateRegistryProxy proxy admin owner is not the L1PAO") + + gameTypes := []uint32{superPermissionedGameType, superCannonGameType} + for _, gameType := range gameTypes { + var gameArgsBytes []byte + err = w3Client.Call(w3eth.CallFunc(sharedDGF, gameArgsFn, gameType).Returns(&gameArgsBytes)) + t.Require().NoError(err) + gameArgs, err := gameargs.Parse(gameArgsBytes) + t.Require().NoErrorf(err, "invalid game args for gameType %d", gameType) + wethAdminOwner := getProxyAdminOwner(t, w3Client, gameArgs.Weth) + t.Require().Equal(l1PAO, wethAdminOwner, "wethProxy proxy admin owner is not the L1PAO") + } +} + +func resetOldDisputeGameFactoriesAfterMigration( + t devtest.CommonT, + keys devkeys.Keys, + l1ChainID *big.Int, + ownerPrivateKey *ecdsa.PrivateKey, + client *ethclient.Client, + delegateCallProxy common.Address, + oldDisputeGameFactories map[eth.ChainID]common.Address, +) { + for l2ChainID, oldDGF := range oldDisputeGameFactories { + chainOpsForL2 := devkeys.ChainOperatorKeys(l2ChainID.ToBig()) + l1PAOForL2, err := keys.Address(chainOpsForL2(devkeys.L1ProxyAdminOwnerRole)) + t.Require().NoError(err, "must have configured L1 proxy admin owner private key") + transferOwnershipForDelegateCallProxy( + t, + l1ChainID, + ownerPrivateKey, + client, + delegateCallProxy, + oldDGF, + l1PAOForL2, + ) + } +} diff --git a/op-devstack/sysgo/sync_tester.go b/op-devstack/sysgo/sync_tester.go new file mode 100644 index 00000000000..b34c6c89882 --- /dev/null +++ b/op-devstack/sysgo/sync_tester.go @@ -0,0 +1,38 @@ +package sysgo + +import ( + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-sync-tester/synctester" +) + +// Caveat: id is binded by a single EL(chainID), but service can support multiple ELs +type SyncTesterService struct { + service *synctester.Service +} + +func (n *SyncTesterService) DefaultEndpoint(chainID eth.ChainID) (string, string, bool) { + if n == nil || n.service == nil { + return "", "", false + } + for syncTesterID, mappedChainID := range n.service.SyncTesters() { + if mappedChainID != chainID { + continue + } + return syncTesterID.String(), n.service.SyncTesterRPC(chainID, false), true + } + return "", "", false +} + +func (n *SyncTesterService) RPC() string { + if n == nil || n.service == nil { + return "" + } + return n.service.RPC() +} + +func (n *SyncTesterService) SyncTesterRPCPath(chainID eth.ChainID, withSessionID bool) string { + if n == nil || n.service == nil { + return "" + } + return n.service.SyncTesterRPCPath(chainID, withSessionID) +} diff --git a/op-devstack/sysgo/util.go b/op-devstack/sysgo/util.go new file mode 100644 index 00000000000..f177364bf8b --- /dev/null +++ b/op-devstack/sysgo/util.go @@ -0,0 +1,112 @@ +package sysgo + +import ( + "errors" + "fmt" + "net" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/stretchr/testify/assert" +) + +// getEnvVarOrDefault returns the value of the provided env var or the provided default value if unset. +func getEnvVarOrDefault(envVarName string, defaultValue string) string { + val, found := os.LookupEnv(envVarName) + if !found { + val = defaultValue + } + return val +} + +// propagateEnvVarOrDefault returns a string in the format "ENV_VAR_NAME=VALUE", with the ENV_VAR_NAME being +// the provided env var name and the value being the value of that env var, or the provided default +// value if that env var is unset. +func propagateEnvVarOrDefault(envVarName string, defaultValue string) string { + if val := getEnvVarOrDefault(envVarName, defaultValue); val == "" { + return "" + } else { + return fmt.Sprintf("%s=%s", envVarName, val) + } +} + +var availableLocalPortMutex sync.Mutex +var recentlyAllocatedPorts = make(map[int]struct{}) + +// getAvailableLocalPort searches for and returns a currently unused local port. +// Tracks recently allocated ports to avoid returning the same port twice +// (the OS may recycle a port immediately after we release it). +// Note: this function is threadsafe. +func getAvailableLocalPort() (string, error) { + availableLocalPortMutex.Lock() + defer availableLocalPortMutex.Unlock() + + // Keep listeners open while looping so the OS won't return the same port twice + var heldListeners []net.Listener + defer func() { + for _, ln := range heldListeners { + ln.Close() + } + }() + + const maxAttempts = 100 + for range maxAttempts { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return "", fmt.Errorf("could not listen on ephemeral port: %w", err) + } + heldListeners = append(heldListeners, ln) + + addr, ok := ln.Addr().(*net.TCPAddr) + if !ok { + return "", errors.New("listener did not return a TCP addr") + } + port := addr.Port + + if _, used := recentlyAllocatedPorts[port]; used { + continue + } + recentlyAllocatedPorts[port] = struct{}{} + return strconv.Itoa(port), nil + } + return "", errors.New("failed to allocate unique port after max attempts") +} + +// waitTCPReady parses a URL and waits for its TCP endpoint to become ready using EventuallyWithT. +func waitTCPReady(p devtest.CommonT, rawURL string, timeout time.Duration) { + p.Helper() + u, err := url.Parse(rawURL) + p.Require().NoError(err, "parse URL: %s", rawURL) + p.Require().NotEmpty(u.Host, "URL has no host: %s", rawURL) + waitMsg := fmt.Sprintf("TCP endpoint %s not ready within %v", u.Host, timeout) + p.Require().EventuallyWithT(func(c *assert.CollectT) { + conn, err := net.DialTimeout("tcp", u.Host, 300*time.Millisecond) + if err == nil { + _ = conn.Close() + } + assert.NoError(c, err, "TCP connection to %s should succeed", u.Host) + }, timeout, 100*time.Millisecond, waitMsg) +} + +// parseAndValidateAddr ensures the address has a scheme and is a valid URL. +// Returns the validated URL string or empty string if invalid. +// This is used to parse addresses from process (e.g. op-rbuilder) log output. +func parseAndValidateAddr(addr, defaultScheme string) string { + if addr == "" { + return "" + } + // Add scheme if not present + if !strings.Contains(addr, "://") { + addr = defaultScheme + "://" + addr + } + u, err := url.Parse(addr) + if err != nil || u.Host == "" || u.Hostname() == "" { + return "" + } + return u.String() +} diff --git a/op-devstack/sysgo/world.go b/op-devstack/sysgo/world.go new file mode 100644 index 00000000000..817b917016a --- /dev/null +++ b/op-devstack/sysgo/world.go @@ -0,0 +1,105 @@ +package sysgo + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/intentbuilder" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" +) + +func newWorldBuilder(t devtest.T, keys devkeys.Keys) *worldBuilder { + return &worldBuilder{ + p: t, + logger: t.Logger(), + require: t.Require(), + keys: keys, + builder: intentbuilder.New(), + } +} + +func applyConfigInteropAtGenesis(builder intentbuilder.Builder) { + for _, l2Cfg := range builder.L2s() { + l2Cfg.WithForkAtGenesis(forks.Interop) + } +} + +func applyConfigDeployerOptions(t devtest.T, keys devkeys.Keys, builder intentbuilder.Builder, opts []DeployerOption) { + if len(opts) == 0 { + return + } + for _, opt := range opts { + if opt == nil { + continue + } + opt(t, keys, builder) + } +} + +func buildSingleChainWorldWithInterop(t devtest.T, keys devkeys.Keys, interopAtGenesis bool, deployerOpts ...DeployerOption) (*L1Network, *L2Network, depset.DependencySet, depset.FullConfigSetMerged) { + _, l1Net, l2Net, depSet, fullCfgSet := buildSingleChainWorldWithInteropAndState(t, keys, interopAtGenesis, deployerOpts...) + return l1Net, l2Net, depSet, fullCfgSet +} + +type interopMigrationState struct { + opcmImpl common.Address + superchainConfigAddr common.Address + l2Deployments map[eth.ChainID]*L2Deployment +} + +func newInteropMigrationState(wb *worldBuilder) *interopMigrationState { + if wb == nil || wb.output == nil || wb.outSuperchainDeployment == nil { + return nil + } + state := &interopMigrationState{ + opcmImpl: wb.output.ImplementationsDeployment.OpcmImpl, + superchainConfigAddr: wb.outSuperchainDeployment.SuperchainConfigAddr(), + l2Deployments: make(map[eth.ChainID]*L2Deployment, len(wb.outL2Deployment)), + } + for chainID, deployment := range wb.outL2Deployment { + state.l2Deployments[chainID] = deployment + } + return state +} + +func buildSingleChainWorldWithInteropAndState(t devtest.T, keys devkeys.Keys, interopAtGenesis bool, deployerOpts ...DeployerOption) (*interopMigrationState, *L1Network, *L2Network, depset.DependencySet, depset.FullConfigSetMerged) { + wb := newWorldBuilder(t, keys) + applyConfigLocalContractSources(t, keys, wb.builder) + applyConfigCommons(t, keys, DefaultL1ID, wb.builder) + applyConfigPrefundedL2(t, keys, DefaultL1ID, DefaultL2AID, wb.builder) + if interopAtGenesis { + applyConfigInteropAtGenesis(wb.builder) + } + applyConfigDeployerOptions(t, keys, wb.builder, deployerOpts) + wb.Build() + + t.Require().Len(wb.l2Chains, 1, "expected exactly one L2 chain") + l2ID := wb.l2Chains[0] + l1ID := eth.ChainIDFromUInt64(wb.output.AppliedIntent.L1ChainID) + + l1Net := &L1Network{ + name: "l1", + chainID: l1ID, + genesis: wb.outL1Genesis, + blockTime: 6, + } + l2Net := &L2Network{ + name: "l2a", + chainID: l2ID, + l1ChainID: l1ID, + genesis: wb.outL2Genesis[l2ID], + rollupCfg: wb.outL2RollupCfg[l2ID], + deployment: wb.outL2Deployment[l2ID], + opcmImpl: wb.output.ImplementationsDeployment.OpcmImpl, + mipsImpl: wb.output.ImplementationsDeployment.MipsImpl, + keys: keys, + } + var depSet depset.DependencySet + if wb.outFullCfgSet.DependencySet != nil { + depSet = wb.outFullCfgSet.DependencySet + } + return newInteropMigrationState(wb), l1Net, l2Net, depSet, wb.outFullCfgSet +} diff --git a/op-e2e/actions/interop/interop_fork_test.go b/op-e2e/actions/interop/interop_fork_test.go index e2cf0fd513e..f1fc864ab5b 100644 --- a/op-e2e/actions/interop/interop_fork_test.go +++ b/op-e2e/actions/interop/interop_fork_test.go @@ -219,6 +219,31 @@ func TestInteropUpgrade(gt *testing.T) { assertProgramOutputMatchesDerivationForBlockTimestamp(gt, system, system.Actors.ChainA.Sequencer.L2Safe().Time) } +func TestInteropGenesisActivation(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + system := dsl.NewInteropDSL(t) // default offset 0 = genesis activation + + interopProxies := []common.Address{ + predeploys.CrossL2InboxAddr, + predeploys.L2toL2CrossDomainMessengerAddr, + predeploys.SuperchainETHBridgeAddr, + predeploys.ETHLiquidityAddr, + } + + for _, c := range []*dsl.Chain{system.Actors.ChainA, system.Actors.ChainB} { + genesis := new(big.Int) + for _, addr := range interopProxies { + code, err := c.SequencerEngine.EthClient().CodeAt(t.Ctx(), addr, genesis) + require.NoError(gt, err) + require.NotEmpty(gt, code, "contract at %s should have code at genesis", addr) + } + + bal, err := c.SequencerEngine.EthClient().BalanceAt(t.Ctx(), predeploys.ETHLiquidityAddr, genesis) + require.NoError(gt, err) + require.Equal(gt, derive.InteropETHLiquidityFundingAmount(), bal, "ETHLiquidity balance should match funding amount") + } +} + func VerifyContractsDeployedCorrectly(t helpers.Testing, chain *dsl.Chain, activationBlockTxs []*types.Transaction, activationBlockID eth.BlockID) { require.Len(t, activationBlockTxs, 5) // 4 upgrade txs + 1 system deposit tx upgradeTransactions := activationBlockTxs[1:] diff --git a/op-e2e/actions/proofs/bad_tx_in_batch_test.go b/op-e2e/actions/proofs/bad_tx_in_batch_test.go index 4d71bb95713..e51023963aa 100644 --- a/op-e2e/actions/proofs/bad_tx_in_batch_test.go +++ b/op-e2e/actions/proofs/bad_tx_in_batch_test.go @@ -181,10 +181,8 @@ func runBadTxInBatch_ResubmitBadFirstFrame_Test(gt *testing.T, testCfg *helpers. env.RunFaultProofProgramFromGenesis(t, l2SafeHead.Number.Uint64(), testCfg.CheckResult, testCfg.InputParams...) } -func Test_ProgramAction_BadTxInBatch(gt *testing.T) { +func TestBadTxInBatch(gt *testing.T) { matrix := helpers.NewMatrix[int64]() - defer matrix.Run(gt) - matrix.AddDefaultTestCasesWithName( "BadSignature", BadSignature, @@ -209,4 +207,5 @@ func Test_ProgramAction_BadTxInBatch(gt *testing.T) { helpers.NewForkMatrix(helpers.Granite, helpers.Holocene, helpers.Isthmus), runBadTxInBatch_ResubmitBadFirstFrame_Test, ) + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/batcher_change_test.go b/op-e2e/actions/proofs/batcher_change_test.go new file mode 100644 index 00000000000..5f961e4fbca --- /dev/null +++ b/op-e2e/actions/proofs/batcher_change_test.go @@ -0,0 +1,64 @@ +package proofs_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" +) + +// TestBatcherChangeWithinChannelTimeout verifies that after a +// pipeline reset, the system config is loaded from the walked-back L2 block +// (channel_timeout L1 blocks behind the safe head's L1 origin), not from the +// safe head itself. +// +// Scenario: +// 1. Batcher A submits a batch. +// 2. The batcher address is changed from A to B on L1. +// 3. Batcher B submits a batch. +// 4. The fault proof program re-derives the chain from scratch. During reset, +// the pipeline walks back by channel_timeout and should find the old system +// config (batcher A). If it incorrectly uses the safe head's config +// (batcher B), it rejects batcher A's batch and derivation diverges. +func TestBatcherChangeWithinChannelTimeout(gt *testing.T) { + matrix := helpers.NewMatrix[any]() + matrix.AddDefaultTestCases( + nil, + helpers.NewForkMatrix(helpers.Granite, helpers.Jovian), + testBatcherChangeWithinChannelTimeout, + ) + matrix.Run(gt) +} + +func testBatcherChangeWithinChannelTimeout(gt *testing.T, testCfg *helpers.TestCfg[any]) { + t := actionsHelpers.NewDefaultTesting(gt) + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + + miner := env.Miner + sequencer := env.Sequencer + + // Step 1: Batcher A (default) submits a batch. + miner.ActEmptyBlock(t) + sequencer.ActL1HeadSignal(t) + sequencer.ActBuildToL1Head(t) + safeAfterA := env.BatchMineAndSync(t) + require.Greater(t, safeAfterA.Number, uint64(0), "safe head should advance after batcher A's batch") + + // Step 2: Change batcher from A to B (Bob) on L1 and replace env.Batcher. + env.RotateBatcher(t, env.Dp.Secrets.Bob) + + // Step 3: Build L2 blocks adopting the batcher change, submit with batcher B. + sequencer.ActL1HeadSignal(t) + sequencer.ActBuildToL1Head(t) + safeAfterB := env.BatchMineAndSync(t) + require.Greater(t, safeAfterB.Number, safeAfterA.Number, "safe head should advance after batcher B's batch") + + // Step 4: Run the fault proof program. This re-derives the chain from + // scratch, triggering a pipeline reset. The pipeline must walk back by + // channel_timeout and use batcher A's system config for the initial + // derivation window. If it uses batcher B's config, batcher A's batch + // is rejected and derivation produces a different (shorter) safe chain. + env.RunFaultProofProgram(t, safeAfterB.Number, testCfg.CheckResult, testCfg.InputParams...) +} diff --git a/op-e2e/actions/proofs/block_data_hint_test.go b/op-e2e/actions/proofs/block_data_hint_test.go index 5a63a7abdaa..d6219cd8443 100644 --- a/op-e2e/actions/proofs/block_data_hint_test.go +++ b/op-e2e/actions/proofs/block_data_hint_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ProgramAction_BlockDataHint(gt *testing.T) { +func TestBlockDataHint(gt *testing.T) { testCfg := &helpers.TestCfg[any]{ Hardfork: helpers.LatestFork, } diff --git a/op-e2e/actions/proofs/channel_timeout_test.go b/op-e2e/actions/proofs/channel_timeout_test.go index f7464bfc529..9d43d94303a 100644 --- a/op-e2e/actions/proofs/channel_timeout_test.go +++ b/op-e2e/actions/proofs/channel_timeout_test.go @@ -213,10 +213,8 @@ func runChannelTimeoutTest_CloseChannelLate(gt *testing.T, testCfg *helpers.Test env.RunFaultProofProgramFromGenesis(t, l2SafeHead.Number.Uint64(), testCfg.CheckResult, testCfg.InputParams...) } -func Test_ProgramAction_ChannelTimeout(gt *testing.T) { +func TestChannelTimeout(gt *testing.T) { matrix := helpers.NewMatrix[any]() - defer matrix.Run(gt) - matrix.AddTestCase( "HonestClaim", nil, @@ -247,4 +245,5 @@ func Test_ProgramAction_ChannelTimeout(gt *testing.T) { helpers.ExpectError(claim.ErrClaimNotValid), helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/end_of_source_test.go b/op-e2e/actions/proofs/end_of_source_test.go new file mode 100644 index 00000000000..ded283d992f --- /dev/null +++ b/op-e2e/actions/proofs/end_of_source_test.go @@ -0,0 +1,81 @@ +package proofs + +import ( + "testing" + + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-program/client/claim" + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +// runEndOfSourceOutputRootTest tests FPP behavior when the kona derivation pipeline hits EndOfSource +// before deriving any blocks beyond the agreed prestate. The FPP should return the agreed prestate's +// output root (i.e. the safe head output root), not a zero output root. +func runEndOfSourceOutputRootTest(gt *testing.T, testCfg *helpers.TestCfg[any]) { + t := actionsHelpers.NewDefaultTesting(gt) + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + + // Build 1 L2 block, batch it to L1, and sync + env.Sequencer.ActL2StartBlock(t) + env.Sequencer.ActL2EndBlock(t) + env.BatchMineAndSync(t) + + l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock() + safeHeadNum := bigs.Uint64Strict(l2SafeHead.Number) + require.Equal(t, uint64(1), safeHeadNum) + + // Get the output root at the safe head via the rollup client + rollupClient := env.Sequencer.RollupClient() + safeHeadOutput, err := rollupClient.OutputAtBlock(t.Ctx(), safeHeadNum) + require.NoError(t, err) + + safeHeadOutputRoot := common.Hash(safeHeadOutput.OutputRoot) + safeHeadHash := safeHeadOutput.BlockRef.Hash + + params := []helpers.FixtureInputParam{ + func(f *helpers.FixtureInputs) { + f.L2OutputRoot = safeHeadOutputRoot + }, + func(f *helpers.FixtureInputs) { + f.L2Head = safeHeadHash + }, + helpers.WithL2BlockNumber(safeHeadNum + 1), + helpers.WithL2Claim(safeHeadOutputRoot), + } + params = append(params, testCfg.InputParams...) + + env.RunFaultProofProgram(t, safeHeadNum, testCfg.CheckResult, params...) +} + +// TestEndOfSourceOutputRoot verifies that the FPP correctly returns the agreed +// prestate output root when EndOfSource is reached before deriving any new blocks. +func TestEndOfSourceOutputRoot(gt *testing.T) { + matrix := helpers.NewMatrix[any]() + matrix.AddTestCase( + "HonestClaim", + nil, + helpers.LatestForkOnly, + runEndOfSourceOutputRootTest, + helpers.ExpectNoError(), + ) + matrix.AddTestCase( + "JunkClaim", + nil, + helpers.LatestForkOnly, + runEndOfSourceOutputRootTest, + helpers.ExpectError(claim.ErrClaimNotValid), + helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), + ) + matrix.AddTestCase( + "ZeroClaim", + nil, + helpers.LatestForkOnly, + runEndOfSourceOutputRootTest, + helpers.ExpectError(claim.ErrClaimNotValid), + helpers.WithL2Claim(common.Hash{}), + ) + matrix.Run(gt) +} diff --git a/op-e2e/actions/proofs/garbage_channel_test.go b/op-e2e/actions/proofs/garbage_channel_test.go index 3624cf858c7..bae8c40508e 100644 --- a/op-e2e/actions/proofs/garbage_channel_test.go +++ b/op-e2e/actions/proofs/garbage_channel_test.go @@ -99,10 +99,8 @@ func runGarbageChannelTest(gt *testing.T, testCfg *helpers.TestCfg[actionsHelper env.RunFaultProofProgramFromGenesis(t, l2SafeHead.Number.Uint64(), testCfg.CheckResult, testCfg.InputParams...) } -func Test_ProgramAction_GarbageChannel(gt *testing.T) { +func TestGarbageChannel(gt *testing.T) { matrix := helpers.NewMatrix[actionsHelpers.GarbageKind]() - defer matrix.Run(gt) - for _, garbageKind := range garbageKinds { matrix.AddTestCase( fmt.Sprintf("HonestClaim-%s", garbageKind.String()), @@ -120,4 +118,5 @@ func Test_ProgramAction_GarbageChannel(gt *testing.T) { helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) } + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/helpers/kona.go b/op-e2e/actions/proofs/helpers/kona.go index 7d5996ab14a..34465b4c608 100644 --- a/op-e2e/actions/proofs/helpers/kona.go +++ b/op-e2e/actions/proofs/helpers/kona.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "testing" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" @@ -28,6 +29,16 @@ func IsKonaConfigured() bool { return konaHostPath != "" } +// SkipIfKona skips the test when kona-host is configured (KONA_HOST_PATH set). +// Use it at the top of action-proof tests that exercise op-program-only +// behavior and aren't expected to pass under kona-host. +func SkipIfKona(t *testing.T) { + t.Helper() + if IsKonaConfigured() { + t.Skip("op-program-only test; skipping under kona-host (KONA_HOST_PATH set)") + } +} + func writeConfigs[T any](t helpers.Testing, workDir string, name string, cfg []*T, cfgPaths []string) { for i, cfg := range cfg { cfgPath := filepath.Join(workDir, fmt.Sprintf("%s_%d.json", name, i)) diff --git a/op-e2e/actions/proofs/holocene_activation_test.go b/op-e2e/actions/proofs/holocene_activation_test.go index 96ddda59891..b77d8a82d0d 100644 --- a/op-e2e/actions/proofs/holocene_activation_test.go +++ b/op-e2e/actions/proofs/holocene_activation_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ProgramAction_HoloceneActivation(gt *testing.T) { +func TestHoloceneActivation(gt *testing.T) { runHoloceneDerivationTest := func(gt *testing.T, testCfg *helpers.TestCfg[any]) { t := actionsHelpers.NewDefaultTesting(gt) diff --git a/op-e2e/actions/proofs/holocene_batches_test.go b/op-e2e/actions/proofs/holocene_batches_test.go index 7a77a65cbd9..fca3ac4834f 100644 --- a/op-e2e/actions/proofs/holocene_batches_test.go +++ b/op-e2e/actions/proofs/holocene_batches_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ProgramAction_HoloceneBatches(gt *testing.T) { +func TestHoloceneBatches(gt *testing.T) { type testCase struct { name string blocks []uint // blocks is an ordered list of blocks (by number) to add to a single channel. @@ -133,8 +133,6 @@ func Test_ProgramAction_HoloceneBatches(gt *testing.T) { } matrix := helpers.NewMatrix[testCase]() - defer matrix.Run(gt) - for _, ordering := range testCases { matrix.AddTestCase( fmt.Sprintf("HonestClaim-%s", ordering.name), @@ -152,4 +150,5 @@ func Test_ProgramAction_HoloceneBatches(gt *testing.T) { helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) } + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/holocene_frame_test.go b/op-e2e/actions/proofs/holocene_frame_test.go index bae16af19e3..a309674414b 100644 --- a/op-e2e/actions/proofs/holocene_frame_test.go +++ b/op-e2e/actions/proofs/holocene_frame_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ProgramAction_HoloceneFrames(gt *testing.T) { +func TestHoloceneFrames(gt *testing.T) { type testCase struct { name string frames []uint @@ -116,8 +116,6 @@ func Test_ProgramAction_HoloceneFrames(gt *testing.T) { } matrix := helpers.NewMatrix[testCase]() - defer matrix.Run(gt) - for _, ordering := range testCases { matrix.AddTestCase( fmt.Sprintf("HonestClaim-%s", ordering.name), @@ -135,4 +133,5 @@ func Test_ProgramAction_HoloceneFrames(gt *testing.T) { helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) } + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/holocene_invalid_batch_test.go b/op-e2e/actions/proofs/holocene_invalid_batch_test.go index 16fea615180..701bb8d8e92 100644 --- a/op-e2e/actions/proofs/holocene_invalid_batch_test.go +++ b/op-e2e/actions/proofs/holocene_invalid_batch_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ProgramAction_HoloceneInvalidBatch(gt *testing.T) { +func TestHoloceneInvalidBatch(gt *testing.T) { type testCase struct { name string blocks []uint // An ordered list of blocks (by number) to add to a single channel. @@ -244,8 +244,6 @@ func Test_ProgramAction_HoloceneInvalidBatch(gt *testing.T) { } matrix := helpers.NewMatrix[testCase]() - defer matrix.Run(gt) - for _, ordering := range testCases { matrix.AddTestCase( fmt.Sprintf("HonestClaim-%s", ordering.name), @@ -263,4 +261,5 @@ func Test_ProgramAction_HoloceneInvalidBatch(gt *testing.T) { helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) } + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/isthmus_fork_test.go b/op-e2e/actions/proofs/isthmus_fork_test.go index 7688989edb0..b63aa3c2b81 100644 --- a/op-e2e/actions/proofs/isthmus_fork_test.go +++ b/op-e2e/actions/proofs/isthmus_fork_test.go @@ -38,11 +38,11 @@ var ( var zeroHex64 = hexutil.Uint64(0) -// Test_ProgramAction_IsthmusActivationAtGenesis tests the Isthmus activation at genesis. +// TestIsthmusActivationAtGenesis tests the Isthmus activation at genesis. // It verifies that the Isthmus is active at genesis and that the genesis block // has the correct withdrawals root and requests hash. It runs the fault proof // program. -func Test_ProgramAction_IsthmusActivationAtGenesis(gt *testing.T) { +func TestIsthmusActivationAtGenesis(gt *testing.T) { matrix := helpers.NewMatrix[any]() matrix.AddDefaultTestCases( @@ -108,13 +108,13 @@ func testIsthmusActivationAtGenesis(gt *testing.T, testCfg *helpers.TestCfg[any] env.RunFaultProofProgramFromGenesis(t, safeBlock.Number, testCfg.CheckResult, testCfg.InputParams...) } -// Test_ProgramAction_IsthmusWithdrawalsRoot tests the withdrawals root in the header: +// TestIsthmusWithdrawalsRoot tests the withdrawals root in the header: // - post canyon but pre Isthmus // - post Isthmus // We do not include pre canyon behaviour (nil withdrawals root) since Canyon does not support Cancun L1. // It does this by activating the relevant forks at genesis. // It runs the fault proof program. -func Test_ProgramAction_IsthmusWithdrawalsRoot(gt *testing.T) { +func TestIsthmusWithdrawalsRoot(gt *testing.T) { matrix := helpers.NewMatrix[any]() matrix.AddDefaultTestCases( @@ -163,7 +163,7 @@ func testWithdrawalsRoot(gt *testing.T, testCfg *helpers.TestCfg[any]) { env.RunFaultProofProgramFromGenesis(t, l2Safe.Number, testCfg.CheckResult, testCfg.InputParams...) } -// Test_ProgramAction_WithdrawalsRootBeforeAtAndAfterIsthmus tests the withdrawals root +// TestWithdrawalsRootBeforeAtAndAfterIsthmus tests the withdrawals root // - before isthmus // - at isthmus // - after isthmus @@ -171,7 +171,7 @@ func testWithdrawalsRoot(gt *testing.T, testCfg *helpers.TestCfg[any]) { // It verifies that the withdrawals root is set correctly in the header // and that the withdrawal transaction is included in the block. // It runs the fault proof program. -func Test_ProgramAction_WithdrawalsRootBeforeAtAndAfterIsthmus(gt *testing.T) { +func TestWithdrawalsRootBeforeAtAndAfterIsthmus(gt *testing.T) { type testCase struct { name string @@ -298,7 +298,7 @@ func checkContractVersion(gt *testing.T, client *ethclient.Client, addr common.A require.Equal(gt, expectedVersion, version) } -func Test_ProgramAction_IsthmusNetworkUpgradeTransactions(gt *testing.T) { +func TestIsthmusNetworkUpgradeTransactions(gt *testing.T) { matrix := helpers.NewMatrix[any]() matrix.AddDefaultTestCases( diff --git a/op-e2e/actions/proofs/isthmus_requests_test.go b/op-e2e/actions/proofs/isthmus_requests_test.go index 176e9435600..9d4376b32ab 100644 --- a/op-e2e/actions/proofs/isthmus_requests_test.go +++ b/op-e2e/actions/proofs/isthmus_requests_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ProgramAction_IsthmusExcludedPredeploys(gt *testing.T) { +func TestIsthmusExcludedPredeploys(gt *testing.T) { // Ensures that if EIP-7251, or EIP-7002 predeploys are deployed manually after the fork, // Isthmus block processing still works correctly. Also ensures that if requests are sent to these // contracts, they are not processed and do not show up in the block body or requests hash. diff --git a/op-e2e/actions/proofs/isthmus_setcode_tx_test.go b/op-e2e/actions/proofs/isthmus_setcode_tx_test.go index d05694ec8cd..6d2f081afc9 100644 --- a/op-e2e/actions/proofs/isthmus_setcode_tx_test.go +++ b/op-e2e/actions/proofs/isthmus_setcode_tx_test.go @@ -19,7 +19,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testutils" ) -func Test_ProgramAction_SetCodeTx(gt *testing.T) { +func TestSetCodeTx(gt *testing.T) { matrix := helpers.NewMatrix[any]() matrix.AddDefaultTestCases( @@ -145,7 +145,7 @@ func runSetCodeTxTypeTest(gt *testing.T, testCfg *helpers.TestCfg[any]) { } // TestInvalidSetCodeTxBatch tests that batches that include SetCodeTxs are dropped before Isthmus -func Test_ProgramAction_InvalidSetCodeTxBatch(gt *testing.T) { +func TestInvalidSetCodeTxBatch(gt *testing.T) { matrix := helpers.NewMatrix[any]() matrix.AddDefaultTestCases( nil, @@ -193,7 +193,7 @@ func testInvalidSetCodeTxBatch(gt *testing.T, testCfg *helpers.TestCfg[any]) { env.RunFaultProofProgramFromGenesis(t, l2safe.Number, testCfg.CheckResult, testCfg.InputParams...) } -func Test_ProgramAction_SetCodeTxWithContractCreationBitSet(gt *testing.T) { +func TestSetCodeTxWithContractCreationBitSet(gt *testing.T) { matrix := helpers.NewMatrix[any]() matrix.AddDefaultTestCases( diff --git a/op-e2e/actions/proofs/jovian_dafootprint_test.go b/op-e2e/actions/proofs/jovian_dafootprint_test.go index 0171167a269..6bd2b2883f1 100644 --- a/op-e2e/actions/proofs/jovian_dafootprint_test.go +++ b/op-e2e/actions/proofs/jovian_dafootprint_test.go @@ -44,7 +44,7 @@ func requireL1BlockDAFootprintGasScalarEquals(t actionsHelpers.Testing, env *hel require.Equal(t, scalar, l1BlockScalar, "L1 block DA footprint gas scalar mismatch") } -func Test_ProgramAction_JovianDAFootprint(gt *testing.T) { +func TestJovianDAFootprint(gt *testing.T) { const lowDAFootprintGasScalar = 30 // Builds a block whose DA footprint is high (near but below gas limit), // then verifies header BlobGasUsed and next block basefee calculation. diff --git a/op-e2e/actions/proofs/jovian_minbasefee_test.go b/op-e2e/actions/proofs/jovian_minbasefee_test.go index 8a76211cb64..4b4062c1697 100644 --- a/op-e2e/actions/proofs/jovian_minbasefee_test.go +++ b/op-e2e/actions/proofs/jovian_minbasefee_test.go @@ -35,7 +35,7 @@ func setMinBaseFeeViaSystemConfig(t actionsHelpers.Testing, env *helpers.L2Fault env.Miner.ActL1EndBlock(t) } -func Test_ProgramAction_JovianMinBaseFee(gt *testing.T) { +func TestJovianMinBaseFee(gt *testing.T) { runJovianDerivationTest := func(gt *testing.T, testCfg *helpers.TestCfg[any], genesisConfigFn func(*genesis.DeployConfig), minBaseFee uint64) { t := actionsHelpers.NewDefaultTesting(gt) env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg(), genesisConfigFn) diff --git a/op-e2e/actions/proofs/l1_blob_parameter_forks_test.go b/op-e2e/actions/proofs/l1_blob_parameter_forks_test.go index db3315fd097..4716063961b 100644 --- a/op-e2e/actions/proofs/l1_blob_parameter_forks_test.go +++ b/op-e2e/actions/proofs/l1_blob_parameter_forks_test.go @@ -19,8 +19,8 @@ import ( "github.com/stretchr/testify/require" ) -// Test_ProgramAction_BlobParameterForks tests the blob base fee calculation for different forks. -func Test_ProgramAction_BlobParameterForks(gt *testing.T) { +// TestBlobParameterForks tests the blob base fee calculation for different forks. +func TestBlobParameterForks(gt *testing.T) { runBlobParameterForksTest := func(gt *testing.T, testCfg *helpers.TestCfg[any]) { t := actionsHelpers.NewDefaultTesting(gt) diff --git a/op-e2e/actions/proofs/l1_lookback_test.go b/op-e2e/actions/proofs/l1_lookback_test.go index 1466261faef..69cbebe26a0 100644 --- a/op-e2e/actions/proofs/l1_lookback_test.go +++ b/op-e2e/actions/proofs/l1_lookback_test.go @@ -125,10 +125,8 @@ func runL1LookbackTest_ReopenChannel(gt *testing.T, testCfg *helpers.TestCfg[any env.RunFaultProofProgramFromGenesis(t, numL2Blocks/2, testCfg.CheckResult, testCfg.InputParams...) } -func Test_ProgramAction_L1Lookback(gt *testing.T) { +func TestL1Lookback(gt *testing.T) { matrix := helpers.NewMatrix[any]() - defer matrix.Run(gt) - matrix.AddTestCase( "HonestClaim", nil, @@ -159,4 +157,5 @@ func Test_ProgramAction_L1Lookback(gt *testing.T) { helpers.ExpectError(claim.ErrClaimNotValid), helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/l1_prague_fork_test.go b/op-e2e/actions/proofs/l1_prague_fork_test.go index f200fb3c1b1..d4f9aa74d14 100644 --- a/op-e2e/actions/proofs/l1_prague_fork_test.go +++ b/op-e2e/actions/proofs/l1_prague_fork_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) { +func TestPragueForkAfterGenesis(gt *testing.T) { type testCase struct { name string useSetCodeTx bool @@ -160,8 +160,8 @@ func Test_ProgramAction_PragueForkAfterGenesis(gt *testing.T) { } matrix := helpers.NewMatrix[testCase]() - defer matrix.Run(gt) matrix. AddDefaultTestCasesWithName(dynamiceFeeCase.name, dynamiceFeeCase, helpers.NewForkMatrix(helpers.Holocene, helpers.LatestFork), runL1PragueTest). AddDefaultTestCasesWithName(setCodeCase.name, setCodeCase, helpers.NewForkMatrix(helpers.LatestFork), runL1PragueTest) + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/nut_bundle_activation_test.go b/op-e2e/actions/proofs/nut_bundle_activation_test.go new file mode 100644 index 00000000000..3dad9640ab5 --- /dev/null +++ b/op-e2e/actions/proofs/nut_bundle_activation_test.go @@ -0,0 +1,200 @@ +package proofs + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-core/predeploys" + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +// forksWithoutNUTBundle lists forks from Karst onward that are activated via +// the legacy hardcoded upgrade-transactions path rather than a JSON NUT +// bundle. Adding a NUT bundle for one of these forks WILL fail this test — +// remove the entry here as part of the same PR. +var forksWithoutNUTBundle = map[forks.Name]bool{ + forks.Interop: true, +} + +// TestActivationBlockNUTBundle verifies that, for every fork from Karst onward +// that uses the JSON NUT bundle system, the fork's activation block contains +// exactly the bundle's deposit transactions in order, every upgrade tx executes +// successfully, and the fault-proof program can prove the result. +// +// Discovery runs through [forks.From]([forks.Karst]), so any future fork is +// covered automatically. Forks that are still on the legacy hardcoded +// upgrade-transactions path are listed in [forksWithoutNUTBundle]; the test +// asserts in BOTH directions — a fork without a bundle that isn't on that list +// fails, and a fork on the list that gains a bundle also fails (so the +// exception list cannot silently go stale). +// +// The per-fork requirement beyond a JSON bundle is that the fork immediately +// preceding it is registered in [helpers.Hardforks] — a one-line entry needed +// for any fork-parametrized test in this package. +// +// Fork-specific state assertions (e.g. Karst's proxy implementation swap) are +// dispatched via the switch in [testActivationBlockNUTBundle]. Future forks +// with their own post-activation invariants register a case there. +func TestActivationBlockNUTBundle(gt *testing.T) { + matrix := helpers.NewMatrix[forks.Name]() + + for _, fork := range forks.From(forks.Karst) { + _, _, err := derive.UpgradeTransactions(fork) + excepted := forksWithoutNUTBundle[fork] + + if err != nil { + require.Truef(gt, excepted, + "fork %s has no NUT bundle and is not on the forksWithoutNUTBundle exception list", fork) + gt.Logf("skipping %s: no JSON NUT bundle (legacy hardcoded upgrade-tx path)", fork) + continue + } + require.Falsef(gt, excepted, + "fork %s now has a NUT bundle; remove it from forksWithoutNUTBundle", fork) + + preFork := forks.Prev(fork) + require.NotEqual(gt, forks.None, preFork, "fork %s has no preceding fork in forks.All", fork) + preHelper := lookupHardforkHelper(preFork) + require.NotNil(gt, preHelper, + "no pre-fork helper registered for NUT-bundle fork %s (prior fork: %s); add %s to helpers.Hardforks", + fork, preFork, preFork) + + matrix.AddDefaultTestCasesWithName( + string(fork), + fork, + helpers.NewForkMatrix(preHelper), + testActivationBlockNUTBundle, + ) + } + + matrix.Run(gt) +} + +func testActivationBlockNUTBundle(gt *testing.T, testCfg *helpers.TestCfg[forks.Name]) { + fork := testCfg.Custom + t := actionsHelpers.NewDefaultTesting(gt) + + offset := uint64(4) + testSetup := func(dc *genesis.DeployConfig) { + dc.L1PragueTimeOffset = ptr(hexutil.Uint64(0)) + dc.SetForkTimeOffset(fork, &offset) + } + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg(), testSetup) + + expectedTxs, expectedGas, err := derive.UpgradeTransactions(fork) + require.NoError(t, err, "load NUT bundle for %s", fork) + require.NotEmpty(t, expectedTxs, "bundle for %s must contain at least one upgrade tx", fork) + + env.Miner.ActEmptyBlock(t) + env.Sequencer.ActL1HeadSignal(t) + for i := 0; i < int(offset); i++ { + env.Sequencer.ActL2EmptyBlock(t) + } + + engine := env.Engine + actHeader := engine.L2Chain().CurrentHeader() + require.True(t, + env.Sd.RollupCfg.IsActivationBlockForFork(actHeader.Time, fork), + "expected activation block for %s at time %d", fork, actHeader.Time) + + actBlock := engine.L2Chain().GetBlockByHash(actHeader.Hash()) + txs := actBlock.Transactions() + // Index 0 is the L1 info deposit; indices 1.. are the NUT upgrade deposits. + require.Len(t, txs, 1+len(expectedTxs), + "activation block should have 1 L1 info deposit + %d NUT upgrade txs", len(expectedTxs)) + + var totalUpgradeGas uint64 + for i, rawExpected := range expectedTxs { + actualBytes, err := txs[1+i].MarshalBinary() + require.NoError(t, err) + require.Equal(t, []byte(rawExpected), actualBytes, "NUT tx %d byte mismatch", i) + + var expected types.Transaction + require.NoError(t, expected.UnmarshalBinary(rawExpected)) + totalUpgradeGas += expected.Gas() + } + require.Equal(t, expectedGas, totalUpgradeGas, "total NUT gas must equal bundle total") + + // Every tx in the activation block — the L1 info deposit and all NUT upgrade + // deposits — must execute successfully. A reverted upgrade tx would leave the + // chain in a broken fork-activation state. + receipts := engine.L2Chain().GetReceiptsByHash(actHeader.Hash()) + require.Len(t, receipts, len(txs), "receipt count must match tx count") + for i, r := range receipts { + require.Equal(t, types.ReceiptStatusSuccessful, r.Status, + "activation-block tx %d reverted", i) + } + + // Fork-specific post-activation assertions. Future forks register cases here. + switch fork { + case forks.Karst: + assertKarstActivation(t, env, actHeader) + } + + // Advance the safe head across the activation boundary so the fault-proof + // program verifies a non-trivial span including the upgrade block. No new + // L2 blocks are produced past the activation block, so the safe head should + // land exactly on it. + env.BatchMineAndSync(t) + l2SafeHead := env.Sequencer.L2Safe() + require.Equal(t, bigs.Uint64Strict(actHeader.Number), l2SafeHead.Number, + "safe head must be exactly the %s activation block", fork) + + env.RunFaultProofProgram(t, l2SafeHead.Number, testCfg.CheckResult, testCfg.InputParams...) +} + +// assertKarstActivation verifies Karst-specific state changes: representative +// predeploy proxies' EIP-1967 implementation slots must change across the +// activation block and the new implementations must have code. This is a +// smoke test that the bundle's upgrade transactions actually rewrote proxy +// implementation pointers, not a check of what the new implementations do. +func assertKarstActivation(t actionsHelpers.StatefulTesting, env *helpers.L2FaultProofEnv, actHeader *types.Header) { + ethCl := env.Engine.EthClient() + postBlock := actHeader.Number + preBlock := new(big.Int).Sub(postBlock, big.NewInt(1)) + + // L1Block and GasPriceOracle mirror the proxies asserted by earlier fork + // tests (ecotone, isthmus); covering them keeps vocabulary consistent + // across fork tests. + proxies := []struct { + name string + addr common.Address + }{ + {"L1Block", predeploys.L1BlockAddr}, + {"GasPriceOracle", predeploys.GasPriceOracleAddr}, + } + for _, p := range proxies { + preImpl, err := ethCl.StorageAt(context.Background(), p.addr, genesis.ImplementationSlot, preBlock) + require.NoError(t, err, "read %s impl slot pre-activation", p.name) + postImpl, err := ethCl.StorageAt(context.Background(), p.addr, genesis.ImplementationSlot, postBlock) + require.NoError(t, err, "read %s impl slot post-activation", p.name) + + require.NotEqualf(t, preImpl, postImpl, + "%s (%s) implementation slot must change across Karst activation", p.name, p.addr) + + newImplAddr := common.BytesToAddress(postImpl) + code, err := ethCl.CodeAt(context.Background(), newImplAddr, postBlock) + require.NoError(t, err, "read code at new %s impl", p.name) + require.NotEmptyf(t, code, "new %s impl %s must have code", p.name, newImplAddr) + } +} + +// lookupHardforkHelper resolves a fork name to its [helpers.Hardfork] entry by +// scanning [helpers.Hardforks]. Returns nil when the fork isn't registered. +func lookupHardforkHelper(name forks.Name) *helpers.Hardfork { + for _, hf := range helpers.Hardforks { + if forks.Name(hf.Name) == name { + return hf + } + } + return nil +} diff --git a/op-e2e/actions/proofs/operator_fee_fix_transition_test.go b/op-e2e/actions/proofs/operator_fee_fix_transition_test.go index 7652428c3f9..6218fc0dcd8 100644 --- a/op-e2e/actions/proofs/operator_fee_fix_transition_test.go +++ b/op-e2e/actions/proofs/operator_fee_fix_transition_test.go @@ -22,7 +22,7 @@ import ( // It establishes that no special logic is in place to automatically reset // the scalars, and fees are therefore expected to vastly increase // under the new Jovian formula. -func Test_ProgramAction_OperatorFeeFixTransition(gt *testing.T) { +func TestOperatorFeeFixTransition(gt *testing.T) { run := func(gt *testing.T, testCfg *helpers.TestCfg[any]) { t := actionsHelpers.NewDefaultTesting(gt) diff --git a/op-e2e/actions/proofs/operator_fee_test.go b/op-e2e/actions/proofs/operator_fee_test.go index 6b431b2a0ec..483b439d435 100644 --- a/op-e2e/actions/proofs/operator_fee_test.go +++ b/op-e2e/actions/proofs/operator_fee_test.go @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_ProgramAction_OperatorFeeConsistency(gt *testing.T) { +func TestOperatorFeeConsistency(gt *testing.T) { type testCase int64 const ( diff --git a/op-e2e/actions/proofs/pectra_blob_schedule_test.go b/op-e2e/actions/proofs/pectra_blob_schedule_test.go index 6511fcd3e72..ac84bfba318 100644 --- a/op-e2e/actions/proofs/pectra_blob_schedule_test.go +++ b/op-e2e/actions/proofs/pectra_blob_schedule_test.go @@ -21,10 +21,8 @@ type pectraBlobScheduleTestCfg struct { expectCancunBBF bool } -func Test_ProgramAction_PectraBlobSchedule(gt *testing.T) { +func TestPectraBlobSchedule(gt *testing.T) { matrix := helpers.NewMatrix[any]() - defer matrix.Run(gt) - matrix.AddDefaultTestCases( // aligned with an L1 timestamp pectraBlobScheduleTestCfg{ptr(uint64(24)), true}, @@ -40,6 +38,7 @@ func Test_ProgramAction_PectraBlobSchedule(gt *testing.T) { helpers.NewForkMatrix(helpers.Holocene, helpers.Isthmus), testPectraBlobSchedule, ) + matrix.Run(gt) } func testPectraBlobSchedule(gt *testing.T, testCfg *helpers.TestCfg[any]) { diff --git a/op-e2e/actions/proofs/precompile_test.go b/op-e2e/actions/proofs/precompile_test.go index 04a4d0987dc..739e17049cc 100644 --- a/op-e2e/actions/proofs/precompile_test.go +++ b/op-e2e/actions/proofs/precompile_test.go @@ -24,10 +24,10 @@ import ( "github.com/stretchr/testify/require" ) -func Test_OPProgramAction_PrecompileHint(gt *testing.T) { - matrix := helpers.NewMatrix[any]() - defer matrix.Run(gt) +func TestOPProgram_PrecompileHint(gt *testing.T) { + helpers.SkipIfKona(gt) + matrix := helpers.NewMatrix[any]() for _, test := range PrecompileTestFixtures { testCase := test matrix.AddTestCase( @@ -40,6 +40,7 @@ func Test_OPProgramAction_PrecompileHint(gt *testing.T) { helpers.ExpectNoError(), ) } + matrix.Run(gt) } func runPrecompileHintTest(gt *testing.T, testCase PrecompileTestFixture, testCfg *helpers.TestCfg[any]) { @@ -152,9 +153,8 @@ func (p *precompileHintCounter) GetCount() int { return p.count } -func Test_ProgramAction_Precompiles(gt *testing.T) { +func TestPrecompiles(gt *testing.T) { matrix := helpers.NewMatrix[PrecompileTestFixture]() - defer matrix.Run(gt) for _, test := range PrecompileTestFixtures { testCase := test matrix.AddTestCase( @@ -165,6 +165,7 @@ func Test_ProgramAction_Precompiles(gt *testing.T) { helpers.ExpectNoError(), ) } + matrix.Run(gt) } func runPrecompileTest(gt *testing.T, testCfg *helpers.TestCfg[PrecompileTestFixture]) { diff --git a/op-e2e/actions/proofs/sequence_window_expiry_test.go b/op-e2e/actions/proofs/sequence_window_expiry_test.go index cb702fe8eb6..001fa32a2b4 100644 --- a/op-e2e/actions/proofs/sequence_window_expiry_test.go +++ b/op-e2e/actions/proofs/sequence_window_expiry_test.go @@ -128,10 +128,8 @@ func runSequenceWindowExpire_ChannelCloseAfterWindowExpiry_Test(gt *testing.T, t env.RunFaultProofProgram(t, l2SafeHead.Number.Uint64()/2, testCfg.CheckResult, testCfg.InputParams...) } -func Test_ProgramAction_SequenceWindowExpired(gt *testing.T) { +func TestSequenceWindowExpired(gt *testing.T) { matrix := helpers.NewMatrix[any]() - defer matrix.Run(gt) - forks := helpers.ForkMatrix{helpers.Granite, helpers.LatestFork} matrix.AddTestCase( "HonestClaim", @@ -163,4 +161,5 @@ func Test_ProgramAction_SequenceWindowExpired(gt *testing.T) { helpers.ExpectError(claim.ErrClaimNotValid), helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/simple_program_test.go b/op-e2e/actions/proofs/simple_program_test.go index c744ba3bfda..651212476e0 100644 --- a/op-e2e/actions/proofs/simple_program_test.go +++ b/op-e2e/actions/proofs/simple_program_test.go @@ -53,13 +53,12 @@ func runSimpleProgramTest(gt *testing.T, testCfg *helpers.TestCfg[any]) { env.RunFaultProofProgramFromGenesis(t, l2SafeHead.Number.Uint64(), testCfg.CheckResult, testCfg.InputParams...) } -func Test_ProgramAction_SimpleEmptyChain(gt *testing.T) { +func TestSimpleEmptyChain(gt *testing.T) { matrix := helpers.NewMatrix[any]() - defer matrix.Run(gt) - matrix.AddDefaultTestCases( nil, helpers.LatestForkOnly, runSimpleProgramTest, ) + matrix.Run(gt) } diff --git a/op-e2e/actions/proofs/system_config_test.go b/op-e2e/actions/proofs/system_config_test.go index f24578c0bfb..a86958ea37b 100644 --- a/op-e2e/actions/proofs/system_config_test.go +++ b/op-e2e/actions/proofs/system_config_test.go @@ -16,9 +16,9 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" ) -// Test_ProgramAction_SystemConfigEarlyIsthmusUpgrade tests that setting the operator +// TestSystemConfigEarlyIsthmusUpgrade tests that setting the operator // fee parameters pre-Isthmus is ignored and doesn't cause problems during derivation. -func Test_ProgramAction_SystemConfigEarlyIsthmusUpgrade(gt *testing.T) { +func TestSystemConfigEarlyIsthmusUpgrade(gt *testing.T) { matrix := helpers.NewMatrix[any]() matrix.AddDefaultTestCases( nil, diff --git a/op-e2e/actions/proofs/trace_extension_test.go b/op-e2e/actions/proofs/trace_extension_test.go index e31975c00bf..31e6f5f6a54 100644 --- a/op-e2e/actions/proofs/trace_extension_test.go +++ b/op-e2e/actions/proofs/trace_extension_test.go @@ -34,14 +34,73 @@ func runSafeHeadTraceExtensionTest(gt *testing.T, testCfg *helpers.TestCfg[any]) env.RunFaultProofProgram(t, l2SafeHead.Number.Uint64(), testCfg.CheckResult, params...) } -// Test_ProgramAction_SafeHeadTraceExtension checks that op-program correctly handles the trace extension case where +func runTraceExtensionLeafTest(gt *testing.T, testCfg *helpers.TestCfg[any]) { + t := actionsHelpers.NewDefaultTesting(gt) + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + + // Build two empty blocks on L2. + // We want a transition where the agreed output root is at block N, but the claim targets block N+1, + // and maliciously repeats the output root from block N. This is invalid, and used to be accepted + // by kona due to faulty trace-extension short-circuiting based only on root equality. + env.Sequencer.ActL2StartBlock(t) + env.Sequencer.ActL2EndBlock(t) + env.Sequencer.ActL2StartBlock(t) + env.Sequencer.ActL2EndBlock(t) + + env.BatchMineAndSync(t) + + l1Head := env.Miner.L1Chain().CurrentBlock() + l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock() + + // Ensure there is only 1 block on L1. + require.Equal(t, uint64(1), bigs.Uint64Strict(l1Head.Number)) + // Ensure the block is marked as safe before we attempt to fault prove it. + require.Equal(t, uint64(2), bigs.Uint64Strict(l2SafeHead.Number)) + + // Prove the transition 1 -> 2. This means the agreed output root is at block 1, and the claim + // targets block 2. + env.RunFaultProofProgram(t, 2, testCfg.CheckResult, testCfg.InputParams...) +} + +func runTraceExtensionRepeatedRootAtNextBlockTest(gt *testing.T, testCfg *helpers.TestCfg[any]) { + t := actionsHelpers.NewDefaultTesting(gt) + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + + // Build two empty blocks on L2. + env.Sequencer.ActL2StartBlock(t) + env.Sequencer.ActL2EndBlock(t) + env.Sequencer.ActL2StartBlock(t) + env.Sequencer.ActL2EndBlock(t) + + env.BatchMineAndSync(t) + + l1Head := env.Miner.L1Chain().CurrentBlock() + l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock() + + // Ensure there is only 1 block on L1. + require.Equal(t, uint64(1), bigs.Uint64Strict(l1Head.Number)) + // Ensure the block is marked as safe before we attempt to fault prove it. + require.Equal(t, uint64(2), bigs.Uint64Strict(l2SafeHead.Number)) + + rollupClient := env.Sequencer.L2Verifier.RollupClient() + out1, err := rollupClient.OutputAtBlock(t.Ctx(), 1) + require.NoError(t, err) + + params := []helpers.FixtureInputParam{ + helpers.WithL2Claim(common.Hash(out1.OutputRoot)), + } + params = append(params, testCfg.InputParams...) + + // Prove the transition 1 -> 2, but maliciously repeat the output root from block 1 at block 2. + env.RunFaultProofProgram(t, 2, testCfg.CheckResult, params...) +} + +// TestSafeHeadTraceExtension checks that op-program correctly handles the trace extension case where // the claimed l2 block number is after the safe head. The honest actor should repeat the output root from the safe head // and op-program should consider it valid even though the claimed l2 block number is not reached. // Output roots other than from the safe head should be invalid if the claimed l2 block number is not reached. -func Test_ProgramAction_SafeHeadTraceExtension(gt *testing.T) { +func TestSafeHeadTraceExtension(gt *testing.T) { matrix := helpers.NewMatrix[any]() - defer matrix.Run(gt) - matrix.AddTestCase( "HonestClaim", nil, @@ -57,4 +116,26 @@ func Test_ProgramAction_SafeHeadTraceExtension(gt *testing.T) { helpers.ExpectError(claim.ErrClaimNotValid), helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) + matrix.Run(gt) +} + +// TestTraceExtensionLeaf checks that both op-program and kona reject a claim that +// maliciously repeats the agreed output root at a later (reachable) block number. +func TestTraceExtensionLeaf(gt *testing.T) { + matrix := helpers.NewMatrix[any]() + matrix.AddTestCase( + "HonestTransition", + nil, + helpers.LatestForkOnly, + runTraceExtensionLeafTest, + helpers.ExpectNoError(), + ) + matrix.AddTestCase( + "RepeatedRootAtNextBlock", + nil, + helpers.LatestForkOnly, + runTraceExtensionRepeatedRootAtNextBlockTest, + helpers.ExpectError(claim.ErrClaimNotValid), + ) + matrix.Run(gt) } diff --git a/op-e2e/e2eutils/setup.go b/op-e2e/e2eutils/setup.go index d46831b63ef..8865798a7fc 100644 --- a/op-e2e/e2eutils/setup.go +++ b/op-e2e/e2eutils/setup.go @@ -219,6 +219,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * PectraBlobScheduleTime: deployConf.PectraBlobScheduleTime(uint64(deployConf.L1GenesisBlockTimestamp)), IsthmusTime: deployConf.IsthmusTime(uint64(deployConf.L1GenesisBlockTimestamp)), JovianTime: deployConf.JovianTime(uint64(deployConf.L1GenesisBlockTimestamp)), + KarstTime: deployConf.KarstTime(uint64(deployConf.L1GenesisBlockTimestamp)), InteropTime: deployConf.InteropTime(uint64(deployConf.L1GenesisBlockTimestamp)), AltDAConfig: pcfg, ChainOpConfig: ¶ms.OptimismConfig{ diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index 265e14c6ed0..23d3c3206da 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -699,28 +699,28 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, L2Time: uint64(cfg.DeployConfig.L1GenesisBlockTimestamp), SystemConfig: e2eutils.SystemConfigFromDeployConfig(cfg.DeployConfig), }, - BlockTime: cfg.DeployConfig.L2BlockTime, - MaxSequencerDrift: cfg.DeployConfig.MaxSequencerDrift, - SeqWindowSize: cfg.DeployConfig.SequencerWindowSize, - ChannelTimeoutBedrock: cfg.DeployConfig.ChannelTimeoutBedrock, - L1ChainID: cfg.L1ChainIDBig(), - L2ChainID: cfg.L2ChainIDBig(), - BatchInboxAddress: cfg.DeployConfig.BatchInboxAddress, - DepositContractAddress: cfg.DeployConfig.OptimismPortalProxy, - L1SystemConfigAddress: cfg.DeployConfig.SystemConfigProxy, - RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - CanyonTime: cfg.DeployConfig.CanyonTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - DeltaTime: cfg.DeployConfig.DeltaTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - EcotoneTime: cfg.DeployConfig.EcotoneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - GraniteTime: cfg.DeployConfig.GraniteTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - HoloceneTime: cfg.DeployConfig.HoloceneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - PectraBlobScheduleTime: cfg.DeployConfig.PectraBlobScheduleTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - IsthmusTime: cfg.DeployConfig.IsthmusTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - JovianTime: cfg.DeployConfig.JovianTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), - ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, - AltDAConfig: rollupAltDAConfig, + BlockTime: cfg.DeployConfig.L2BlockTime, + MaxSequencerDrift: cfg.DeployConfig.MaxSequencerDrift, + SeqWindowSize: cfg.DeployConfig.SequencerWindowSize, + ChannelTimeoutBedrock: cfg.DeployConfig.ChannelTimeoutBedrock, + L1ChainID: cfg.L1ChainIDBig(), + L2ChainID: cfg.L2ChainIDBig(), + BatchInboxAddress: cfg.DeployConfig.BatchInboxAddress, + DepositContractAddress: cfg.DeployConfig.OptimismPortalProxy, + L1SystemConfigAddress: cfg.DeployConfig.SystemConfigProxy, + RegolithTime: cfg.DeployConfig.RegolithTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + CanyonTime: cfg.DeployConfig.CanyonTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + DeltaTime: cfg.DeployConfig.DeltaTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + EcotoneTime: cfg.DeployConfig.EcotoneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + GraniteTime: cfg.DeployConfig.GraniteTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + HoloceneTime: cfg.DeployConfig.HoloceneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + PectraBlobScheduleTime: cfg.DeployConfig.PectraBlobScheduleTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + IsthmusTime: cfg.DeployConfig.IsthmusTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + JovianTime: cfg.DeployConfig.JovianTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + KarstTime: cfg.DeployConfig.KarstTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + AltDAConfig: rollupAltDAConfig, ChainOpConfig: ¶ms.OptimismConfig{ EIP1559Elasticity: cfg.DeployConfig.EIP1559Elasticity, EIP1559Denominator: cfg.DeployConfig.EIP1559Denominator, diff --git a/op-e2e/system/runcfg/protocol_versions_test.go b/op-e2e/system/runcfg/protocol_versions_test.go deleted file mode 100644 index 85daeff6c43..00000000000 --- a/op-e2e/system/runcfg/protocol_versions_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package runcfg - -import ( - "context" - "errors" - "fmt" - "math/big" - "testing" - "time" - - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" - - "github.com/ethereum-optimism/optimism/op-e2e/bindings" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum-optimism/optimism/op-service/client" - "github.com/ethereum-optimism/optimism/op-service/endpoint" - "github.com/ethereum-optimism/optimism/op-service/retry" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/params" - "github.com/stretchr/testify/require" -) - -func TestRecommendedProtocolVersionChange(t *testing.T) { - op_e2e.InitParallel(t) - - cfg := e2esys.DefaultSystemConfig(t) - require.NotEqual(t, common.Address{}, cfg.L1Deployments.ProtocolVersions, "need ProtocolVersions contract deployment") - // to speed up the test, make it reload the config more often, and do not impose a long conf depth - cfg.Nodes["verifier"].RuntimeConfigReloadInterval = time.Second * 5 - cfg.Nodes["verifier"].Driver.VerifierConfDepth = 1 - - sys, err := cfg.Start(t) - require.Nil(t, err, "Error starting up system") - - runtimeConfig := sys.RollupNodes["verifier"].RuntimeConfig() - - // Change the superchain-config via L1 - l1 := sys.NodeClient("l1") - - _, build, major, minor, patch, preRelease := params.OPStackSupport.Parse() - newRecommendedProtocolVersion := params.ProtocolVersionV0{Build: build, Major: major + 1, Minor: minor, Patch: patch, PreRelease: preRelease}.Encode() - require.NotEqual(t, runtimeConfig.RecommendedProtocolVersion(), newRecommendedProtocolVersion, "changing to a different protocol version") - - protVersions, err := bindings.NewProtocolVersions(cfg.L1Deployments.ProtocolVersionsProxy, l1) - require.NoError(t, err) - - // ProtocolVersions contract is owned by the deployer in devnet - opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Deployer, cfg.L1ChainIDBig()) - require.NoError(t, err) - - // Change recommended protocol version - tx, err := protVersions.SetRecommended(opts, new(big.Int).SetBytes(newRecommendedProtocolVersion[:])) - require.NoError(t, err) - - // wait for the change to confirm - _, err = wait.ForReceiptOK(context.Background(), l1, tx.Hash()) - require.NoError(t, err) - - // wait for the recommended protocol version to change - _, err = retry.Do(context.Background(), 10, retry.Fixed(time.Second*10), func() (struct{}, error) { - v := sys.RollupNodes["verifier"].RuntimeConfig().RecommendedProtocolVersion() - if v == newRecommendedProtocolVersion { - return struct{}{}, nil - } - return struct{}{}, fmt.Errorf("no change yet, seeing %s but looking for %s", v, newRecommendedProtocolVersion) - }) - require.NoError(t, err) -} - -func TestRequiredProtocolVersionChangeAndHalt(t *testing.T) { - op_e2e.InitParallel(t) - - cfg := e2esys.DefaultSystemConfig(t) - // to speed up the test, make it reload the config more often, and do not impose a long conf depth - cfg.Nodes["verifier"].RuntimeConfigReloadInterval = time.Second * 5 - cfg.Nodes["verifier"].Driver.VerifierConfDepth = 1 - // configure halt in verifier op-node - cfg.Nodes["verifier"].RollupHalt = "major" - // configure halt in verifier op-geth node - cfg.GethOptions["verifier"] = append(cfg.GethOptions["verifier"], []geth.GethOption{ - func(ethCfg *ethconfig.Config, nodeCfg *node.Config) error { - ethCfg.RollupHaltOnIncompatibleProtocolVersion = "major" - return nil - }, - }...) - - sys, err := cfg.Start(t) - require.Nil(t, err, "Error starting up system") - - runtimeConfig := sys.RollupNodes["verifier"].RuntimeConfig() - - // Change the superchain-config via L1 - l1 := sys.NodeClient("l1") - - _, build, major, minor, patch, preRelease := params.OPStackSupport.Parse() - newRequiredProtocolVersion := params.ProtocolVersionV0{Build: build, Major: major + 1, Minor: minor, Patch: patch, PreRelease: preRelease}.Encode() - require.NotEqual(t, runtimeConfig.RequiredProtocolVersion(), newRequiredProtocolVersion, "changing to a different protocol version") - - protVersions, err := bindings.NewProtocolVersions(cfg.L1Deployments.ProtocolVersionsProxy, l1) - require.NoError(t, err) - - // ProtocolVersions contract is owned by same key as SystemConfig in devnet - opts, err := bind.NewKeyedTransactorWithChainID(cfg.Secrets.Deployer, cfg.L1ChainIDBig()) - require.NoError(t, err) - - // Change required protocol version - tx, err := protVersions.SetRequired(opts, new(big.Int).SetBytes(newRequiredProtocolVersion[:])) - require.NoError(t, err) - - // wait for the change to confirm - _, err = wait.ForReceiptOK(context.Background(), l1, tx.Hash()) - require.NoError(t, err) - - // wait for the required protocol version to take effect by halting the verifier that opted in, and halting the op-geth node that opted in. - _, err = retry.Do(context.Background(), 10, retry.Fixed(time.Second*10), func() (struct{}, error) { - if !sys.RollupNodes["verifier"].Stopped() { - return struct{}{}, errors.New("verifier rollup node is not closed yet") - } - return struct{}{}, nil - }) - require.NoError(t, err) - t.Log("verified that op-node closed!") - // Checking if the engine is down is not trivial in op-e2e. - // In op-geth we have halting tests covering the Engine API, in op-e2e we instead check if the API stops. - _, err = retry.Do(context.Background(), 10, retry.Fixed(time.Second*10), func() (struct{}, error) { - available := client.IsURLAvailable(context.Background(), sys.NodeEndpoint("verifier").(endpoint.HttpRPC).HttpRPC(), 5*time.Second) - if !available { // waiting for client to stop responding to RPC requests (slow dials with timeout don't count) - return struct{}{}, nil - } - return struct{}{}, errors.New("verifier EL node is not closed yet") - }) - require.NoError(t, err) - t.Log("verified that op-geth closed!") -} diff --git a/op-interop-filter/filter/backend.go b/op-interop-filter/filter/backend.go index c98a197f8f6..9afb83e75bb 100644 --- a/op-interop-filter/filter/backend.go +++ b/op-interop-filter/filter/backend.go @@ -8,6 +8,9 @@ import ( "github.com/ethereum-optimism/optimism/op-interop-filter/metrics" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) // Backend coordinates chain ingesters and handles the failsafe state. @@ -43,14 +46,144 @@ func (b *Backend) Stop(ctx context.Context) error { // FailsafeEnabled returns whether failsafe is enabled func (b *Backend) FailsafeEnabled() bool { - return false + return b.manualFailsafe.Load() || len(b.GetChainErrors()) > 0 || b.crossValidator.Error() != nil +} + +// SetFailsafeEnabled sets the manual failsafe override. +func (b *Backend) SetFailsafeEnabled(enabled bool) { + b.manualFailsafe.Store(enabled) + b.metrics.RecordFailsafeEnabled(b.FailsafeEnabled()) +} + +// GetChainErrors returns all chains that are in an error state +func (b *Backend) GetChainErrors() map[eth.ChainID]*IngesterError { + errs := make(map[eth.ChainID]*IngesterError) + for chainID, ingester := range b.chains { + if err := ingester.Error(); err != nil { + errs[chainID] = err + } + } + return errs +} + +// Ready returns true if all chains have completed backfill +func (b *Backend) Ready() bool { + for _, ingester := range b.chains { + if !ingester.Ready() { + return false + } + } + + return len(b.chains) > 0 +} + +// supportedSafetyLevel returns true if the safety level is supported for access list checks. +func supportedSafetyLevel(level safety.Level) bool { + return level == safety.LocalUnsafe || level == safety.CrossUnsafe +} + +// classifyRejectionReason categorizes an error from CheckAccessList into a rejection reason label. +func classifyRejectionReason(err error) string { + switch { + case errors.Is(err, types.ErrFailsafeEnabled): + return "failsafe" + case errors.Is(err, types.ErrUnknownChain): + return "unknown_chain" + case errors.Is(err, types.ErrConflict): + return "expired_message" + default: + return "invalid_executing_message" + } } // CheckAccessList validates the given access list entries. // This is a stub implementation that always returns ErrUninitialized. func (b *Backend) CheckAccessList(ctx context.Context, inboxEntries []common.Hash, - minSafety types.SafetyLevel, execDescriptor types.ExecutingDescriptor) error { + minSafety safety.Level, execDescriptor messages.ExecutingDescriptor) error { + + start := time.Now() + defer func() { + b.metrics.RecordCheckAccessListDuration(time.Since(start).Seconds()) + }() + + if b.passthrough { + b.metrics.RecordCheckAccessList(true) + return nil + } + + if b.FailsafeEnabled() { + b.metrics.RecordCheckAccessList(false) + b.metrics.RecordCheckAccessListRejection("failsafe") + return types.ErrFailsafeEnabled + } + + if !b.Ready() { + b.metrics.RecordCheckAccessList(false) + b.metrics.RecordCheckAccessListRejection("failsafe") + b.log.Debug("Backend not ready; rejecting access list check") + return types.ErrUninitialized + } + + if !supportedSafetyLevel(minSafety) { + b.metrics.RecordCheckAccessList(false) + b.metrics.RecordCheckAccessListRejection("invalid_executing_message") + return fmt.Errorf("unsupported safety level %s: only %s and %s are supported", + minSafety, safety.LocalUnsafe, safety.CrossUnsafe) + } + + if _, ok := b.chains[execDescriptor.ChainID]; !ok { + if !b.legacyCheckAccessListFormat { + b.metrics.RecordCheckAccessList(false) + b.metrics.RecordCheckAccessListRejection("unknown_chain") + return fmt.Errorf("executing chain %s: %w", execDescriptor.ChainID, types.ErrUnknownChain) + } + b.log.Debug("Supporting legacy check access list format", "executing_chain", execDescriptor.ChainID) + } + + remaining := inboxEntries + for len(remaining) > 0 { + var access messages.Access + var err error + remaining, access, err = messages.ParseAccess(remaining) + if err != nil { + b.metrics.RecordCheckAccessList(false) + b.metrics.RecordCheckAccessListRejection("parse_error") + return fmt.Errorf("failed to parse access entry: %w", err) + } + + if err := b.crossValidator.ValidateAccessEntry(access, minSafety, execDescriptor); err != nil { + b.metrics.RecordCheckAccessList(false) + b.metrics.RecordCheckAccessListRejection(classifyRejectionReason(err)) + return err + } + } - b.metrics.RecordCheckAccessList(false) - return types.ErrUninitialized + b.metrics.RecordCheckAccessList(true) + return nil +} + +// GetBlockHashByNumber returns the latest block hash or the block hash at a specific height for the given chain. +// Accepts rpc.BlockNumber: "latest" or a numeric block number. Other named tags are not supported. +func (b *Backend) GetBlockHashByNumber(chainID eth.ChainID, blockNum rpc.BlockNumber) (common.Hash, error) { + ingester, ok := b.chains[chainID] + if !ok { + return common.Hash{}, fmt.Errorf("chain %s: %w", chainID, types.ErrUnknownChain) + } + + if blockNum == rpc.LatestBlockNumber { + block, ok := ingester.LatestBlock() + if !ok { + return common.Hash{}, fmt.Errorf("latest block for chain %s: %w", chainID, ethereum.NotFound) + } + return block.Hash, nil + } + if blockNum < 0 { + return common.Hash{}, fmt.Errorf("unsupported block tag %q: only \"latest\" and block numbers are supported", blockNum) + } + + blockHash, ok := ingester.BlockHashByNumber(uint64(blockNum)) + if !ok { + return common.Hash{}, fmt.Errorf("block %d for chain %s: %w", blockNum, chainID, ethereum.NotFound) + } + return blockHash, nil } diff --git a/op-interop-filter/filter/backend_test.go b/op-interop-filter/filter/backend_test.go new file mode 100644 index 00000000000..6762d2b30ea --- /dev/null +++ b/op-interop-filter/filter/backend_test.go @@ -0,0 +1,183 @@ +package filter + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-interop-filter/metrics" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +// Test constants +const ( + testExpiryWindow = uint64(100) + testChainA = uint64(900) +) + +// ============================================================================= +// Test Helpers +// ============================================================================= + +func newTestBackend() *Backend { + return NewBackend(context.Background(), BackendParams{ + Logger: testlog.Logger(&testing.T{}, log.LevelCrit), + Metrics: metrics.NoopMetrics, + Chains: make(map[eth.ChainID]ChainIngester), + CrossValidator: &mockCrossValidator{}, + }) +} + +func newTestBackendWithMockChain(chainID uint64) (*Backend, *mockChainIngester) { + mock := newMockChainIngester() + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(chainID): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + return NewBackend(context.Background(), BackendParams{ + Logger: testlog.Logger(&testing.T{}, log.LevelCrit), + Metrics: metrics.NoopMetrics, + Chains: chains, + CrossValidator: cv, + }), mock +} + +func newTestCrossValidator(chains map[eth.ChainID]ChainIngester, expiryWindow uint64, startTimestamp uint64) *LockstepCrossValidator { + return NewLockstepCrossValidator( + context.Background(), + testlog.Logger(&testing.T{}, log.LevelCrit), + metrics.NoopMetrics, + expiryWindow, + startTimestamp, + time.Hour, // Long interval - won't tick in tests + chains, + ) +} + +// makeAccess creates a test access entry +func makeAccess(chainID, timestamp, blockNum uint64, logIdx uint32, checksum messages.MessageChecksum) messages.Access { + return messages.Access{ + ChainID: eth.ChainIDFromUInt64(chainID), + Timestamp: timestamp, + BlockNumber: blockNum, + LogIndex: logIdx, + Checksum: checksum, + } +} + +// makeExecDescriptor creates a test executing descriptor +func makeExecDescriptor(chainID, timestamp, timeout uint64) messages.ExecutingDescriptor { + return messages.ExecutingDescriptor{ + ChainID: eth.ChainIDFromUInt64(chainID), + Timestamp: timestamp, + Timeout: timeout, + } +} + +// ============================================================================= +// Backend Failsafe Tests +// ============================================================================= + +func TestBackend_Failsafe_CrossValidatorError(t *testing.T) { + mock := newMockChainIngester() + mock.SetReady(true) + mock.SetLatestTimestamp(100) // Lazy init will set crossValidatedTs=100 + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + backend := NewBackend(context.Background(), BackendParams{ + Logger: testlog.Logger(t, log.LevelCrit), + Metrics: metrics.NoopMetrics, + Chains: chains, + CrossValidator: cv, + }) + + // Initially not enabled + require.False(t, backend.FailsafeEnabled()) + + // Trigger lazy init (sets crossValidatedTs=100) + cv.advanceValidation() + + // Add an invalid exec message at timestamp 101 (which we'll validate next) + mock.AddExecMsg(IncludedMessage{ + ExecutingMessage: &messages.ExecutingMessage{ + ChainID: eth.ChainIDFromUInt64(testChainA), + BlockNum: 999, // Non-existent + LogIdx: 0, + Timestamp: 50, + Checksum: messages.MessageChecksum{0xFF}, + }, + InclusionBlockNum: 10, + InclusionTimestamp: 101, // Will be validated when advancing from 100 to 101 + }) + mock.SetLatestTimestamp(101) + + // This should try to validate timestamp 101 and fail + cv.advanceValidation() + + // Cross-validator error should enable failsafe + require.NotNil(t, cv.Error()) + require.True(t, backend.FailsafeEnabled()) +} + +func TestBackend_Failsafe_AllClear(t *testing.T) { + backend, mock := newTestBackendWithMockChain(testChainA) + + // Set everything to a good state + mock.SetReady(true) + mock.SetLatestTimestamp(200) + + // Failsafe should be off + require.False(t, backend.FailsafeEnabled()) + + // Even after some operations, failsafe stays off + mock.SetLatestTimestamp(300) + require.False(t, backend.FailsafeEnabled()) +} + +func TestBackend_ReorgRecovery_NoErrorIsNotResolvable(t *testing.T) { + mock := newMockChainIngester() + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + backend := NewBackend(context.Background(), BackendParams{Logger: testlog.Logger(t, log.LevelCrit), Metrics: metrics.NoopMetrics, Chains: chains, CrossValidator: &mockCrossValidator{}}) + + _, _, err := backend.recoverChainReorg(context.Background(), eth.ChainIDFromUInt64(testChainA), mock) + require.Error(t, err) + require.Contains(t, err.Error(), "no ingester error") + require.Equal(t, 0, mock.rewindToFinalizedCount) +} + +func TestBackend_Ready(t *testing.T) { + // No chains = not ready + backend := newTestBackend() + require.False(t, backend.Ready(), "should not be ready with no chains") + + // With chains + backend, mock := newTestBackendWithMockChain(testChainA) + mock.SetReady(true) + require.True(t, backend.Ready(), "should be ready when chains are ready") + + mock.SetReady(false) + require.False(t, backend.Ready(), "should not be ready when chains are not ready") +} + +func TestBackend_CheckAccessList_SupportLegacyCheckAccessListFormat(t *testing.T) { + backend, mock := newTestBackendWithMockChain(testChainA) + backend.legacyCheckAccessListFormat = true + mock.SetReady(true) + mock.SetLatestTimestamp(200) + + err := backend.CheckAccessList(context.Background(), nil, safety.LocalUnsafe, makeExecDescriptor(0, 150, 0)) + require.NoError(t, err) +} diff --git a/op-interop-filter/filter/config.go b/op-interop-filter/filter/config.go index 2fee5c6283d..d1f1cf6e320 100644 --- a/op-interop-filter/filter/config.go +++ b/op-interop-filter/filter/config.go @@ -60,3 +60,48 @@ func NewConfig(ctx *cli.Context, version string) (*Config, error) { RPC: oprpc.ReadCLIConfig(ctx), }, nil } + +// loadRollupConfigs loads rollup configs from networks (superchain registry) and custom JSON files. +func loadRollupConfigs(networks []string, configPaths []string) (map[eth.ChainID]*rollup.Config, error) { + configs := make(map[eth.ChainID]*rollup.Config) + + // Load from superchain registry by network name + for _, network := range networks { + cfg, err := chaincfg.GetRollupConfig(network) + if err != nil { + return nil, fmt.Errorf("failed to load rollup config for network %q: %w", network, err) + } + chainID := eth.ChainIDFromBig(cfg.L2ChainID) + if _, exists := configs[chainID]; exists { + return nil, fmt.Errorf("duplicate chain ID %s: network %q conflicts with another config", chainID, network) + } + configs[chainID] = cfg + } + + // Load from custom JSON files + for _, path := range configPaths { + cfg, err := loadRollupConfigFromFile(path) + if err != nil { + return nil, fmt.Errorf("failed to load rollup config from %q: %w", path, err) + } + chainID := eth.ChainIDFromBig(cfg.L2ChainID) + if _, exists := configs[chainID]; exists { + return nil, fmt.Errorf("duplicate chain ID %s: file %q conflicts with another config", chainID, path) + } + configs[chainID] = cfg + } + + return configs, nil +} + +// loadRollupConfigFromFile loads a rollup config from a JSON file. +func loadRollupConfigFromFile(path string) (*rollup.Config, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + var rollupConfig rollup.Config + return &rollupConfig, rollupConfig.ParseRollupConfig(file) +} diff --git a/op-interop-filter/filter/frontend.go b/op-interop-filter/filter/frontend.go index 7e5a1ee6624..8ae443b9cf9 100644 --- a/op-interop-filter/filter/frontend.go +++ b/op-interop-filter/filter/frontend.go @@ -9,6 +9,9 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) // QueryFrontend handles supervisor query RPC methods @@ -18,7 +21,7 @@ type QueryFrontend struct { // CheckAccessList validates interop executing messages func (f *QueryFrontend) CheckAccessList(ctx context.Context, inboxEntries []common.Hash, - minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { + minSafety safety.Level, executingDescriptor messages.ExecutingDescriptor) error { err := f.backend.CheckAccessList(ctx, inboxEntries, minSafety, executingDescriptor) if err != nil { diff --git a/op-interop-filter/filter/interfaces.go b/op-interop-filter/filter/interfaces.go new file mode 100644 index 00000000000..5e2d8d923a9 --- /dev/null +++ b/op-interop-filter/filter/interfaces.go @@ -0,0 +1,104 @@ +package filter + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-service/eth" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +// LogsDB is the subset of an op-supervisor logs DB that +// LogsDBChainIngester depends on. Capturing it as an interface lets tests +// substitute a fake when exercising dispatch paths the real DB cannot +// produce under correct ingester control flow. +type LogsDB interface { + Close() error + Contains(query messages.ContainsQuery) (messages.BlockSeal, error) + LatestSealedBlock() (eth.BlockID, bool) + FindSealedBlock(number uint64) (messages.BlockSeal, error) + FirstSealedBlock() (messages.BlockSeal, error) + OpenBlock(blockNum uint64) (eth.BlockRef, uint32, map[uint32]*messages.ExecutingMessage, error) + AddLog(logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *messages.ExecutingMessage) error + SealBlock(parentHash common.Hash, block eth.BlockID, timestamp uint64) error + Rewind(newHead eth.BlockID) error +} + +// IncludedMessage wraps an executing message with its inclusion context. +// The ExecutingMessage contains the initiating message's data (source chain), +// while InclusionBlockNum/Timestamp indicate when it was executed (this chain). +type IncludedMessage struct { + *messages.ExecutingMessage + InclusionBlockNum uint64 + InclusionTimestamp uint64 +} + +// ChainIngester provides access to chain logs and state. +// Implementations include: +// - mockChainIngester: in-memory for testing +// - LogsDBChainIngester: RPC-backed with logsdb for production +type ChainIngester interface { + // Start begins the ingester's background processing. + Start() error + + // Stop halts the ingester's background processing. + Stop() error + + // Contains checks if a log exists in the chain's database. + Contains(query messages.ContainsQuery) (messages.BlockSeal, error) + + // LatestBlock returns the latest ingested block. + LatestBlock() (eth.BlockID, bool) + + // BlockHashByNumber returns the hash of the ingested block at the given height. + BlockHashByNumber(number uint64) (common.Hash, bool) + + // LatestTimestamp returns the timestamp of the latest ingested block. + LatestTimestamp() (uint64, bool) + + // GetExecMsgsAtTimestamp returns executing messages with the given inclusion timestamp. + GetExecMsgsAtTimestamp(timestamp uint64) ([]IncludedMessage, error) + + // Ready returns true if the ingester has completed initial sync. + Ready() bool + + // Error returns the current error state, if any. + Error() *IngesterError + + // SetError sets an error state on the ingester. + SetError(reason IngesterErrorReason, msg string) + + // ClearError clears the error state. + ClearError() + + // RewindToFinalized rewinds durable log state to finalized. + RewindToFinalized(ctx context.Context) (eth.BlockID, uint64, error) +} + +// CrossValidator validates cross-chain messages. +// Implementations: +// - LockstepCrossValidator: waits for all chains to align before advancing +type CrossValidator interface { + // Start begins the validator's background processing. + Start() error + + // Stop halts the validator's background processing. + Stop() error + + // ValidateAccessEntry validates a single access list entry. + ValidateAccessEntry(access messages.Access, minSafety safety.Level, execDescriptor messages.ExecutingDescriptor) error + + // CrossValidatedTimestamp returns the global cross-validated timestamp. + CrossValidatedTimestamp() (uint64, bool) + + // Error returns the current error state, if any. + // Validation errors (invalid executing messages) are tracked here. + Error() *ValidatorError + + // ResetCrossValidatedTimestamp rewinds in-memory validation progress after + // the underlying log DB has been rewound. + ResetCrossValidatedTimestamp(timestamp uint64) +} diff --git a/op-interop-filter/filter/jwt_auth_test.go b/op-interop-filter/filter/jwt_auth_test.go new file mode 100644 index 00000000000..6771c740d7c --- /dev/null +++ b/op-interop-filter/filter/jwt_auth_test.go @@ -0,0 +1,199 @@ +package filter + +import ( + "context" + "crypto/rand" + "io" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/eth" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-service/testlog" + gn "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum/go-ethereum/log" +) + +// testSupervisorAPI is a mock supervisor API for testing +type testSupervisorAPI struct{} + +func (t *testSupervisorAPI) Ping(_ context.Context) string { + return "pong" +} + +// testAdminAPI is a mock admin API for testing +type testAdminAPI struct{} + +func (t *testAdminAPI) GetFailsafeEnabled(_ context.Context) (bool, error) { + return false, nil +} + +func TestDedicatedAdminRPCServer(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + + // Generate JWT secret + var jwtSecret eth.Bytes32 + _, err := io.ReadFull(rand.Reader, jwtSecret[:]) + require.NoError(t, err) + + // Create filter server (public, no JWT) + filterServer := oprpc.NewServer( + "127.0.0.1", + 0, + "test", + oprpc.WithLogger(logger), + ) + filterServer.AddAPI(rpc.API{ + Namespace: "interop", + Service: new(testSupervisorAPI), + }) + + // Create admin server (JWT-protected) + adminServer := oprpc.NewServer( + "127.0.0.1", + 0, + "test", + oprpc.WithLogger(logger), + oprpc.WithJWTSecret(jwtSecret[:]), + ) + adminServer.AddAPI(rpc.API{ + Namespace: "admin", + Service: new(testAdminAPI), + Authenticated: true, + }) + + require.NoError(t, filterServer.Start()) + t.Cleanup(func() { + _ = filterServer.Stop() + }) + + require.NoError(t, adminServer.Start()) + t.Cleanup(func() { + _ = adminServer.Stop() + }) + + filterEndpoint := "http://" + filterServer.Endpoint() + adminEndpoint := "http://" + adminServer.Endpoint() + + // Create clients + filterClient, err := rpc.Dial(filterEndpoint) + require.NoError(t, err) + t.Cleanup(filterClient.Close) + + adminUnauthClient, err := rpc.Dial(adminEndpoint) + require.NoError(t, err) + t.Cleanup(adminUnauthClient.Close) + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + adminAuthClient, err := client.NewRPC( + ctx, + logger, + adminEndpoint, + client.WithGethRPCOptions(rpc.WithHTTPAuth(gn.NewJWTAuth(jwtSecret))), + ) + require.NoError(t, err) + t.Cleanup(adminAuthClient.Close) + + t.Run("filter API works without JWT on its dedicated port", func(t *testing.T) { + var res string + err := filterClient.Call(&res, "interop_ping") + require.NoError(t, err) + require.Equal(t, "pong", res) + }) + + t.Run("admin API requires JWT on dedicated port - fails without auth", func(t *testing.T) { + var res bool + err := adminUnauthClient.Call(&res, "admin_getFailsafeEnabled") + require.ErrorContains(t, err, "missing token") + }) + + t.Run("admin API works with valid JWT on dedicated port", func(t *testing.T) { + var res bool + err := adminAuthClient.CallContext(ctx, &res, "admin_getFailsafeEnabled") + require.NoError(t, err) + require.Equal(t, false, res) + }) +} + +func TestPublicAdminGetFailsafe(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + + filterServer := oprpc.NewServer( + "127.0.0.1", + 0, + "test", + oprpc.WithLogger(logger), + ) + filterServer.AddAPI(rpc.API{ + Namespace: "interop", + Service: new(testSupervisorAPI), + }) + filterServer.AddAPI(rpc.API{ + Namespace: "admin", + Service: new(testAdminAPI), + }) + + require.NoError(t, filterServer.Start()) + t.Cleanup(func() { + _ = filterServer.Stop() + }) + + endpoint := "http://" + filterServer.Endpoint() + filterClient, err := rpc.Dial(endpoint) + require.NoError(t, err) + t.Cleanup(filterClient.Close) + + t.Run("admin_getFailsafeEnabled works on public port without JWT", func(t *testing.T) { + var res bool + err := filterClient.Call(&res, "admin_getFailsafeEnabled") + require.NoError(t, err) + require.Equal(t, false, res) + }) + + t.Run("supervisor API still works alongside public admin", func(t *testing.T) { + var res string + err := filterClient.Call(&res, "interop_ping") + require.NoError(t, err) + require.Equal(t, "pong", res) + }) +} + +func TestFilterAPIWithoutAdminServer(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + + // Create filter server WITHOUT admin server (simulates admin.rpc.addr not set) + filterServer := oprpc.NewServer( + "127.0.0.1", + 0, + "test", + oprpc.WithLogger(logger), + ) + filterServer.AddAPI(rpc.API{ + Namespace: "interop", + Service: new(testSupervisorAPI), + }) + + require.NoError(t, filterServer.Start()) + t.Cleanup(func() { + _ = filterServer.Stop() + }) + + endpoint := "http://" + filterServer.Endpoint() + filterClient, err := rpc.Dial(endpoint) + require.NoError(t, err) + t.Cleanup(filterClient.Close) + + t.Run("filter API works without admin server configured", func(t *testing.T) { + var res string + err := filterClient.Call(&res, "interop_ping") + require.NoError(t, err) + require.Equal(t, "pong", res) + }) +} diff --git a/op-interop-filter/filter/lockstep_cross_validator.go b/op-interop-filter/filter/lockstep_cross_validator.go new file mode 100644 index 00000000000..e6aa7b3bbe5 --- /dev/null +++ b/op-interop-filter/filter/lockstep_cross_validator.go @@ -0,0 +1,387 @@ +package filter + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-interop-filter/metrics" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +// LockstepCrossValidator validates cross-chain executing messages and tracks +// the cross-validated timestamp. +// +// "Lockstep" refers to its synchronization model: all chains must reach the same +// timestamp before validation can advance. This is simpler but means a slow chain +// holds back validation for all chains. +// +// Simplifications in this implementation: +// - No cycle detection: same-block executing messages are not supported +// - Lockstep advancement: waits for ALL chains to reach timestamp T before +// validating T, rather than validating each chain independently +// +// Future improvement: per-chain validation that tracks cross-validated timestamp +// independently for each chain, allowing faster chains to advance without waiting. +type LockstepCrossValidator struct { + log log.Logger + metrics metrics.Metricer + + messageExpiryWindow uint64 + startTimestamp uint64 // Initial timestamp to start validation from + validationInterval time.Duration + + // Chain ingesters keyed by chain ID (read-only after construction) + chains map[eth.ChainID]ChainIngester + + // Single global cross-validated timestamp + crossValidatedTs atomic.Uint64 + crossValidatedOK atomic.Bool + + // Error state for validation failures + errMu sync.RWMutex + err *ValidatorError + + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup +} + +// NewLockstepCrossValidator creates a new LockstepCrossValidator. +// The startTimestamp is used to initialize the cross-validated timestamp on first run. +func NewLockstepCrossValidator( + parentCtx context.Context, + logger log.Logger, + m metrics.Metricer, + messageExpiryWindow uint64, + startTimestamp uint64, + validationInterval time.Duration, + chains map[eth.ChainID]ChainIngester, +) *LockstepCrossValidator { + ctx, cancel := context.WithCancel(parentCtx) + + return &LockstepCrossValidator{ + log: logger.New("component", "cross-validator"), + metrics: m, + messageExpiryWindow: messageExpiryWindow, + startTimestamp: startTimestamp, + validationInterval: validationInterval, + chains: chains, + ctx: ctx, + cancel: cancel, + } +} + +// Start starts the validation loop +func (v *LockstepCrossValidator) Start() error { + v.log.Info("Starting cross-validator", "chains", len(v.chains)) + + v.wg.Add(1) + go v.runValidationLoop() + + return nil +} + +// Stop stops the validation loop +func (v *LockstepCrossValidator) Stop() error { + v.log.Info("Stopping cross-validator") + v.cancel() + v.wg.Wait() + return nil +} + +// Error returns the current validation error state, if any. +func (v *LockstepCrossValidator) Error() *ValidatorError { + v.errMu.RLock() + defer v.errMu.RUnlock() + return v.err +} + +// setError sets the validation error state. +func (v *LockstepCrossValidator) setError(msg string) { + v.errMu.Lock() + defer v.errMu.Unlock() + v.err = &ValidatorError{ + Message: msg, + } +} + +// CrossValidatedTimestamp returns the global cross-validated timestamp. +func (v *LockstepCrossValidator) CrossValidatedTimestamp() (uint64, bool) { + if !v.crossValidatedOK.Load() { + return 0, false + } + return v.crossValidatedTs.Load(), true +} + +// ResetCrossValidatedTimestamp rewinds validation progress after a logs DB rewind. +func (v *LockstepCrossValidator) ResetCrossValidatedTimestamp(timestamp uint64) { + for { + current := v.crossValidatedTs.Load() + if v.crossValidatedOK.Load() && current <= timestamp { + return + } + if v.crossValidatedTs.CompareAndSwap(current, timestamp) { + v.crossValidatedOK.Store(true) + v.log.Info("Reset cross-validated timestamp", "timestamp", timestamp) + return + } + } +} + +// validateMessageTiming is a pure function that validates temporal constraints for cross-chain messages. +// Parameters: +// - initTimestamp: when the initiating message was created +// - inclusionTimestamp: when the executing message is included +// - messageExpiryWindow: how long messages remain valid +// - timeout: optional max execution delay (0 = disabled) +// - execTimestamp: execution timestamp (only used if timeout > 0) +func validateMessageTiming( + initTimestamp, inclusionTimestamp, messageExpiryWindow, timeout, execTimestamp uint64, +) error { + // Rule 1: init must be strictly before inclusion + if initTimestamp >= inclusionTimestamp { + return fmt.Errorf("initiating message timestamp %d not before inclusion timestamp %d: %w", + initTimestamp, inclusionTimestamp, types.ErrConflict) + } + + // Rule 2: compute expiry with overflow check + expiresAt := initTimestamp + messageExpiryWindow + if expiresAt < initTimestamp { + return fmt.Errorf("overflow in expiry calculation: timestamp %d + window %d: %w", + initTimestamp, messageExpiryWindow, types.ErrConflict) + } + + // Rule 3: message must not be expired at inclusion + if expiresAt < inclusionTimestamp { + return fmt.Errorf("initiating message expired: init %d + expiry window %d = %d < inclusion %d: %w", + initTimestamp, messageExpiryWindow, expiresAt, inclusionTimestamp, types.ErrConflict) + } + + // Rule 4: if timeout set, message must not expire before timeout deadline + if timeout > 0 { + maxExecTimestamp := execTimestamp + timeout + if maxExecTimestamp < execTimestamp { + return fmt.Errorf("overflow in max exec timestamp calculation: timestamp %d + timeout %d: %w", + execTimestamp, timeout, types.ErrConflict) + } + if expiresAt < maxExecTimestamp { + return fmt.Errorf("initiating message will expire before timeout: "+ + "init %d + expiry %d = %d < exec %d + timeout %d = %d: %w", + initTimestamp, messageExpiryWindow, expiresAt, + execTimestamp, timeout, maxExecTimestamp, + types.ErrConflict) + } + } + + return nil +} + +// ValidateAccessEntry validates a single access list entry against all message validity rules. +func (v *LockstepCrossValidator) ValidateAccessEntry( + access messages.Access, + minSafety safety.Level, + execDescriptor messages.ExecutingDescriptor, +) error { + // Check that we have ingested data for the requested timestamp + minIngestedTs, ok := v.getMinIngestedTimestamp() + if !ok || access.Timestamp > minIngestedTs { + return fmt.Errorf("timestamp %d not yet ingested (min ingested: %d): %w", + access.Timestamp, minIngestedTs, types.ErrOutOfScope) + } + + // Check cross-unsafe timestamp + if minSafety == safety.CrossUnsafe { + crossValidatedTs, ok := v.CrossValidatedTimestamp() + if !ok { + return fmt.Errorf("cross-validated timestamp not available: %w", types.ErrOutOfScope) + } + if access.Timestamp > crossValidatedTs { + return fmt.Errorf("message at timestamp %d not yet cross-unsafe validated "+ + "(current cross-validated timestamp: %d): %w", + access.Timestamp, crossValidatedTs, types.ErrOutOfScope) + } + } + + // Validate timing constraints (including timeout if set) + if err := validateMessageTiming( + access.Timestamp, + execDescriptor.Timestamp, + v.messageExpiryWindow, + execDescriptor.Timeout, + execDescriptor.Timestamp, + ); err != nil { + return err + } + + // Check that the log exists on the source chain + ingester, ok := v.chains[access.ChainID] + if !ok { + return fmt.Errorf("source chain %s: %w", access.ChainID, types.ErrUnknownChain) + } + + query := messages.ContainsQuery{ + Timestamp: access.Timestamp, + BlockNum: access.BlockNumber, + LogIdx: access.LogIndex, + Checksum: access.Checksum, + } + _, err := ingester.Contains(query) + return err +} + +func (v *LockstepCrossValidator) validateExecutingMessage( + execMsg *messages.ExecutingMessage, + inclusionTimestamp uint64, +) error { + ingester, ok := v.chains[execMsg.ChainID] + if !ok { + return fmt.Errorf("source chain %s: %w", execMsg.ChainID, types.ErrUnknownChain) + } + + // Validate timing constraints (no timeout for background validation) + if err := validateMessageTiming( + execMsg.Timestamp, + inclusionTimestamp, + v.messageExpiryWindow, + 0, 0, // no timeout + ); err != nil { + return err + } + + query := messages.ContainsQuery{ + Timestamp: execMsg.Timestamp, + BlockNum: execMsg.BlockNum, + LogIdx: execMsg.LogIdx, + Checksum: execMsg.Checksum, + } + _, err := ingester.Contains(query) + return err +} + +func (v *LockstepCrossValidator) runValidationLoop() { + defer v.wg.Done() + + ticker := time.NewTicker(v.validationInterval) + defer ticker.Stop() + + for { + select { + case <-v.ctx.Done(): + return + case <-ticker.C: + v.advanceValidation() + } + } +} + +// advanceValidation tries to advance the cross-validated timestamp one step at a time. +func (v *LockstepCrossValidator) advanceValidation() { + // Stop if we've already hit a validation error + if v.Error() != nil { + return + } + + // All chains must be ready and error-free + for _, ingester := range v.chains { + if ingester.Error() != nil { + return + } + if !ingester.Ready() { + return + } + } + + minIngestedTs, ok := v.getMinIngestedTimestamp() + if !ok { + return + } + + // Lazy initialization: start from the configured start timestamp + if !v.crossValidatedOK.Load() { + v.crossValidatedTs.Store(v.startTimestamp) + v.crossValidatedOK.Store(true) + v.log.Info("Cross-validator initialized", "startTimestamp", v.startTimestamp) + return + } + + currentTs := v.crossValidatedTs.Load() + + // Try to advance one timestamp at a time until we catch up or hit an error + for { + nextTs := currentTs + 1 + + // Don't go past what all chains have ingested + if nextTs > minIngestedTs { + return + } + + // Validate all messages at this timestamp across all chains + if err := v.validateTimestamp(nextTs); err != nil { + v.log.Error("Cross-validation failed", "timestamp", nextTs, "err", err) + v.setError(err.Error()) + return + } + + // Advance + v.crossValidatedTs.Store(nextTs) + currentTs = nextTs + + v.log.Debug("Advanced cross-validated timestamp", "timestamp", nextTs) + } +} + +// validateTimestamp validates all executing messages with the given inclusion timestamp +// across all chains. +func (v *LockstepCrossValidator) validateTimestamp(timestamp uint64) error { + for chainID, ingester := range v.chains { + msgs, err := ingester.GetExecMsgsAtTimestamp(timestamp) + if err != nil { + return fmt.Errorf("failed to get messages at timestamp %d from chain %s: %w", + timestamp, chainID, err) + } + + for _, msg := range msgs { + if err := v.validateExecutingMessage(msg.ExecutingMessage, msg.InclusionTimestamp); err != nil { + return fmt.Errorf("validation failed on chain %s at timestamp %d, log %d: %w", + chainID, timestamp, msg.LogIdx, err) + } + } + } + + return nil +} + +func (v *LockstepCrossValidator) getMinIngestedTimestamp() (uint64, bool) { + if len(v.chains) == 0 { + return 0, false + } + + var minTs uint64 + first := true + for _, ingester := range v.chains { + if !ingester.Ready() { + return 0, false + } + ts, ok := ingester.LatestTimestamp() + if !ok { + return 0, false + } + if first || ts < minTs { + minTs = ts + first = false + } + } + return minTs, true +} + +// Ensure LockstepCrossValidator implements CrossValidator +var _ CrossValidator = (*LockstepCrossValidator)(nil) diff --git a/op-interop-filter/filter/lockstep_cross_validator_test.go b/op-interop-filter/filter/lockstep_cross_validator_test.go new file mode 100644 index 00000000000..449220c38de --- /dev/null +++ b/op-interop-filter/filter/lockstep_cross_validator_test.go @@ -0,0 +1,556 @@ +package filter + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-interop-filter/metrics" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +// Note: Test helpers (newTestCrossValidator, makeAccess, makeExecDescriptor) and +// constants (testExpiryWindow, testChainA) are defined in backend_test.go + +// ============================================================================= +// Timeout Expiry Tests +// ============================================================================= + +func TestCrossValidator_TimeoutExceedsExpiry(t *testing.T) { + mock := newMockChainIngester() + checksum := messages.MessageChecksum{0x01} + mock.AddLog(100, 10, 0, checksum, messages.BlockSeal{}) + mock.SetLatestTimestamp(200) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + access := makeAccess(testChainA, 100, 10, 0, checksum) + // init=100, expiry=100, so expiresAt=200 + // exec=150, timeout=51, so maxExecTs=201 + // 200 < 201, so should fail + exec := makeExecDescriptor(testChainA, 150, 51) + + err := cv.ValidateAccessEntry(access, safety.LocalUnsafe, exec) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrConflict) + require.Contains(t, err.Error(), "expire before timeout") +} + +// ============================================================================= +// CrossUnsafe Timestamp Tests +// ============================================================================= + +func TestCrossValidator_CrossUnsafe_Boundary(t *testing.T) { + mock := newMockChainIngester() + checksum := messages.MessageChecksum{0x01} + mock.AddLog(100, 10, 0, checksum, messages.BlockSeal{}) + mock.AddLog(101, 10, 0, checksum, messages.BlockSeal{}) + mock.SetLatestTimestamp(100) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + // Trigger initialization (sets crossValidatedTs=100) + cv.advanceValidation() + + // At boundary: access at timestamp 100 == crossValidatedTs=100 should pass + access := makeAccess(testChainA, 100, 10, 0, checksum) + exec := makeExecDescriptor(testChainA, 150, 0) + err := cv.ValidateAccessEntry(access, safety.CrossUnsafe, exec) + require.NoError(t, err) + + // Beyond boundary: access at timestamp 101 > crossValidatedTs=100 should fail + access = makeAccess(testChainA, 101, 10, 0, checksum) + err = cv.ValidateAccessEntry(access, safety.CrossUnsafe, exec) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrOutOfScope) +} + +// ============================================================================= +// Unknown Chain Tests +// ============================================================================= + +func TestCrossValidator_KnownChain(t *testing.T) { + mock := newMockChainIngester() + checksum := messages.MessageChecksum{0x01} + mock.AddLog(100, 10, 0, checksum, messages.BlockSeal{}) + mock.SetLatestTimestamp(200) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + access := makeAccess(testChainA, 100, 10, 0, checksum) + exec := makeExecDescriptor(testChainA, 150, 0) + + err := cv.ValidateAccessEntry(access, safety.LocalUnsafe, exec) + require.NoError(t, err) +} + +func TestCrossValidator_UnknownChain(t *testing.T) { + mock := newMockChainIngester() + mock.SetLatestTimestamp(200) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + // Access from chain 902 which is not registered + unknownChainID := uint64(902) + access := makeAccess(unknownChainID, 100, 10, 0, messages.MessageChecksum{0x01}) + exec := makeExecDescriptor(testChainA, 150, 0) + + err := cv.ValidateAccessEntry(access, safety.LocalUnsafe, exec) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrUnknownChain) +} + +func TestCrossValidator_InitiatingMessageNotFound(t *testing.T) { + mock := newMockChainIngester() + mock.SetLatestTimestamp(200) + // Don't add any logs - the initiating message won't exist + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + // Valid chain, valid timing, but log doesn't exist + access := makeAccess(testChainA, 100, 10, 0, messages.MessageChecksum{0x01}) + exec := makeExecDescriptor(testChainA, 150, 0) + + err := cv.ValidateAccessEntry(access, safety.LocalUnsafe, exec) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrConflict) +} + +// ============================================================================= +// Validation Failure Propagation Test +// ============================================================================= + +func TestCrossValidator_ValidationFailureSetsError(t *testing.T) { + // Setup two chains + mockA := newMockChainIngester() + mockB := newMockChainIngester() + + checksumA := messages.MessageChecksum{0x01} + + // Add valid log on chain A + mockA.AddLog(100, 10, 0, checksumA, messages.BlockSeal{}) + mockA.SetLatestTimestamp(101) + + // Add INVALID executing message on chain B that references a non-existent log + mockB.AddExecMsg(IncludedMessage{ + ExecutingMessage: &messages.ExecutingMessage{ + ChainID: eth.ChainIDFromUInt64(testChainA), // References chain A + BlockNum: 999, // Non-existent block + LogIdx: 0, + Timestamp: 50, // Init timestamp + Checksum: messages.MessageChecksum{0xFF}, // Non-existent checksum + }, + InclusionBlockNum: 11, + InclusionTimestamp: 101, + }) + mockB.SetLatestTimestamp(101) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mockA, + eth.ChainIDFromUInt64(testChainA + 1): mockB, + } + + cv := NewLockstepCrossValidator( + context.Background(), + testlog.Logger(t, log.LevelCrit), + metrics.NoopMetrics, + testExpiryWindow, + 101, // startTimestamp - matches what chains report + time.Millisecond, // Short interval for test + chains, + ) + + // Both chains should have no errors initially + require.Nil(t, mockA.Error()) + require.Nil(t, mockB.Error()) + + // First call triggers initialization (sets crossValidatedTs to minIngestedTs=101) + cv.advanceValidation() + + // Simulate chains ingesting one more block (timestamp 102) + mockA.SetLatestTimestamp(102) + mockB.SetLatestTimestamp(102) + + // Update the invalid exec msg to have inclusionTimestamp=102 + mockB.ClearExecMsgs() + mockB.AddExecMsg(IncludedMessage{ + ExecutingMessage: &messages.ExecutingMessage{ + ChainID: eth.ChainIDFromUInt64(testChainA), + BlockNum: 999, + LogIdx: 0, + Timestamp: 50, + Checksum: messages.MessageChecksum{0xFF}, + }, + InclusionBlockNum: 12, + InclusionTimestamp: 102, + }) + + // This will try to validate timestamp 102, which will fail + cv.advanceValidation() + + // Chain ingesters should NOT have errors - validation errors are tracked by cross-validator + require.Nil(t, mockA.Error(), "chain ingesters should not have validation errors") + require.Nil(t, mockB.Error(), "chain ingesters should not have validation errors") + + // Cross-validator should have an error + require.NotNil(t, cv.Error(), "cross-validator should have error after validation failure") + require.Contains(t, cv.Error().Message, "validation failed") + + // Cross-validated timestamp should NOT have advanced past 101 + ts, ok := cv.CrossValidatedTimestamp() + require.True(t, ok) + require.Equal(t, uint64(101), ts, "cross-validated timestamp should not advance after failure") +} + +func TestCrossValidator_StartTimestampZeroStillAdvances(t *testing.T) { + mock := newMockChainIngester() + mock.SetLatestTimestamp(1) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 0) + + // First advancement initializes the validator at timestamp 0. + cv.advanceValidation() + + ts, ok := cv.CrossValidatedTimestamp() + require.True(t, ok) + require.Equal(t, uint64(0), ts) + + // Second advancement should move past 0 once chains have ingested timestamp 1. + cv.advanceValidation() + + ts, ok = cv.CrossValidatedTimestamp() + require.True(t, ok) + require.Equal(t, uint64(1), ts) +} + +// ============================================================================= +// ValidateAccessEntry Timestamp Ingestion Tests +// ============================================================================= + +func TestValidateAccessEntry_TimestampNotIngested(t *testing.T) { + mock := newMockChainIngester() + checksum := messages.MessageChecksum{0x01} + mock.AddLog(100, 10, 0, checksum, messages.BlockSeal{}) + mock.SetLatestTimestamp(100) // Only ingested up to 100 + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + // Access at timestamp 150, but we've only ingested up to 100 + access := makeAccess(testChainA, 150, 10, 0, checksum) + exec := makeExecDescriptor(testChainA, 200, 0) + + err := cv.ValidateAccessEntry(access, safety.LocalUnsafe, exec) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrOutOfScope) + require.Contains(t, err.Error(), "not yet ingested") +} + +// ============================================================================= +// validateExecutingMessage Timing Tests +// ============================================================================= + +func TestValidateExecMsg_InitBeforeInclusion(t *testing.T) { + mock := newMockChainIngester() + checksum := messages.MessageChecksum{0x01} + mock.AddLog(100, 10, 0, checksum, messages.BlockSeal{}) + mock.SetLatestTimestamp(200) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + // Init timestamp = 100, Inclusion timestamp = 100 (equal, not before) + access := makeAccess(testChainA, 100, 10, 0, checksum) + exec := makeExecDescriptor(testChainA, 100, 0) // Same as init timestamp + + err := cv.ValidateAccessEntry(access, safety.LocalUnsafe, exec) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrConflict) + require.Contains(t, err.Error(), "not before inclusion") +} + +func TestValidateExecMsg_MessageExpired(t *testing.T) { + mock := newMockChainIngester() + checksum := messages.MessageChecksum{0x01} + mock.AddLog(100, 10, 0, checksum, messages.BlockSeal{}) + mock.SetLatestTimestamp(300) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) // expiry window = 100 + + // Init timestamp = 100, expiry = 100, so expires at 200 + // Inclusion at 250 - message has expired + access := makeAccess(testChainA, 100, 10, 0, checksum) + exec := makeExecDescriptor(testChainA, 250, 0) + + err := cv.ValidateAccessEntry(access, safety.LocalUnsafe, exec) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrConflict) + require.Contains(t, err.Error(), "expired") +} + +// ============================================================================= +// advanceValidation Tests +// ============================================================================= + +func TestAdvanceValidation_WaitsForChainsReady(t *testing.T) { + mock := newMockChainIngester() + mock.SetReady(false) // Not ready + mock.SetLatestTimestamp(100) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + // Should not initialize when chains not ready + cv.advanceValidation() + _, ok := cv.CrossValidatedTimestamp() + require.False(t, ok, "should not initialize when chains not ready") + + // Once ready, should initialize + mock.SetReady(true) + cv.advanceValidation() + ts, ok := cv.CrossValidatedTimestamp() + require.True(t, ok) + require.Equal(t, uint64(100), ts) +} + +func TestAdvanceValidation_InitializesToStartTimestamp(t *testing.T) { + mock := newMockChainIngester() + mock.SetReady(true) + mock.SetLatestTimestamp(150) // Chains have ingested up to 150 + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) // startTimestamp = 100 + + // Before first advance, no cross-validated timestamp + _, ok := cv.CrossValidatedTimestamp() + require.False(t, ok) + + // First advance initializes to startTimestamp + cv.advanceValidation() + ts, ok := cv.CrossValidatedTimestamp() + require.True(t, ok) + require.Equal(t, uint64(100), ts, "should initialize to startTimestamp") +} + +func TestAdvanceValidation_AdvancesTimestamp(t *testing.T) { + mock := newMockChainIngester() + mock.SetReady(true) + mock.SetLatestTimestamp(100) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mock, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + // Initialize + cv.advanceValidation() + ts, _ := cv.CrossValidatedTimestamp() + require.Equal(t, uint64(100), ts) + + // Advance chain + mock.SetLatestTimestamp(103) + + // Should advance to 103 (validates 101, 102, 103) + cv.advanceValidation() + ts, _ = cv.CrossValidatedTimestamp() + require.Equal(t, uint64(103), ts) +} + +func TestAdvanceValidation_StopsAtMinIngested(t *testing.T) { + mockA := newMockChainIngester() + mockB := newMockChainIngester() + mockA.SetReady(true) + mockB.SetReady(true) + mockA.SetLatestTimestamp(100) + mockB.SetLatestTimestamp(100) + + chains := map[eth.ChainID]ChainIngester{ + eth.ChainIDFromUInt64(testChainA): mockA, + eth.ChainIDFromUInt64(testChainA + 1): mockB, + } + cv := newTestCrossValidator(chains, testExpiryWindow, 100) + + // Initialize + cv.advanceValidation() + ts, _ := cv.CrossValidatedTimestamp() + require.Equal(t, uint64(100), ts) + + // Only chain A advances + mockA.SetLatestTimestamp(105) + // Chain B stays at 100 + + // Should not advance past 100 (min of both chains) + cv.advanceValidation() + ts, _ = cv.CrossValidatedTimestamp() + require.Equal(t, uint64(100), ts, "should not advance past slow chain") +} + +// ============================================================================= +// validateMessageTiming Tests +// ============================================================================= + +func TestValidateMessageTiming(t *testing.T) { + tests := []struct { + name string + initTimestamp uint64 + inclusionTimestamp uint64 + messageExpiryWindow uint64 + timeout uint64 + execTimestamp uint64 + wantErr bool + errContains string + }{ + { + name: "valid: basic case without timeout", + initTimestamp: 100, + inclusionTimestamp: 150, + messageExpiryWindow: 100, + timeout: 0, + execTimestamp: 0, + wantErr: false, + }, + { + name: "valid: message expires exactly at inclusion", + initTimestamp: 100, + inclusionTimestamp: 200, + messageExpiryWindow: 100, // expiresAt = 200 + timeout: 0, + execTimestamp: 0, + wantErr: false, + }, + { + name: "valid: with timeout, message expires after deadline", + initTimestamp: 100, + inclusionTimestamp: 150, + messageExpiryWindow: 100, // expiresAt = 200 + timeout: 40, // maxExecTs = 150 + 40 = 190 + execTimestamp: 150, // 200 >= 190, valid + wantErr: false, + }, + { + name: "invalid: init timestamp equals inclusion", + initTimestamp: 100, + inclusionTimestamp: 100, + messageExpiryWindow: 100, + timeout: 0, + execTimestamp: 0, + wantErr: true, + errContains: "not before inclusion", + }, + { + name: "invalid: init timestamp after inclusion", + initTimestamp: 150, + inclusionTimestamp: 100, + messageExpiryWindow: 100, + timeout: 0, + execTimestamp: 0, + wantErr: true, + errContains: "not before inclusion", + }, + { + name: "invalid: overflow in expiry calculation", + initTimestamp: ^uint64(0) - 10, // near max uint64 + inclusionTimestamp: ^uint64(0), + messageExpiryWindow: 100, // will overflow + timeout: 0, + execTimestamp: 0, + wantErr: true, + errContains: "overflow in expiry calculation", + }, + { + name: "invalid: message expired at inclusion", + initTimestamp: 100, + inclusionTimestamp: 250, + messageExpiryWindow: 100, // expiresAt = 200 < 250 + timeout: 0, + execTimestamp: 0, + wantErr: true, + errContains: "expired", + }, + { + name: "invalid: timeout overflow", + initTimestamp: 100, + inclusionTimestamp: 150, + messageExpiryWindow: 100, + timeout: ^uint64(0), // max uint64 + execTimestamp: ^uint64(0) - 10, // will overflow when added to timeout + wantErr: true, + errContains: "overflow in max exec timestamp", + }, + { + name: "invalid: expires before timeout deadline", + initTimestamp: 100, + inclusionTimestamp: 150, + messageExpiryWindow: 100, // expiresAt = 200 + timeout: 51, // maxExecTs = 150 + 51 = 201 + execTimestamp: 150, // 200 < 201, invalid + wantErr: true, + errContains: "expire before timeout", + }, + { + name: "valid: expires exactly at timeout deadline", + initTimestamp: 100, + inclusionTimestamp: 150, + messageExpiryWindow: 100, // expiresAt = 200 + timeout: 50, // maxExecTs = 150 + 50 = 200 + execTimestamp: 150, // 200 >= 200, valid (equal is ok) + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateMessageTiming( + tt.initTimestamp, + tt.inclusionTimestamp, + tt.messageExpiryWindow, + tt.timeout, + tt.execTimestamp, + ) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errContains) + require.ErrorIs(t, err, types.ErrConflict) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/op-interop-filter/filter/logsdb_integration_backend_test.go b/op-interop-filter/filter/logsdb_integration_backend_test.go new file mode 100644 index 00000000000..f144e6a41f1 --- /dev/null +++ b/op-interop-filter/filter/logsdb_integration_backend_test.go @@ -0,0 +1,123 @@ +package filter + +import ( + "context" + "errors" + "testing" + + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +func TestIntegration_Backend_NoChains_FailsafeOn(t *testing.T) { + t.Parallel() + + mtr := newCapturingMetrics() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + logger := testlog.Logger(t, log.LevelError) + bk := NewBackend(ctx, BackendParams{ + Logger: logger, + Metrics: mtr, + Chains: map[eth.ChainID]ChainIngester{}, + CrossValidator: NewLockstepCrossValidator(ctx, logger, mtr, 1<<30, defaultStartTs, 100, map[eth.ChainID]ChainIngester{}), + }) + + require.False(t, bk.Ready(), "empty chain map -> Backend.Ready() is false") + err := bk.CheckAccessList(ctx, nil, safety.LocalUnsafe, messages.ExecutingDescriptor{ChainID: executingChain()}) + require.ErrorIs(t, err, types.ErrUninitialized) +} + +func TestIntegration_Backend_ManualFailsafe_RejectsAll(t *testing.T) { + t.Parallel() + + bk := twoChainBackend(t, 1) + bk.SetFailsafeEnabled(true) + bk.requireRejection(executingChain(), inclusionTs, "failsafe", bk.sourceAccess(100, 0)) +} + +func TestIntegration_Backend_IngesterErrorTripsFailsafe(t *testing.T) { + t.Parallel() + + bk := twoChainBackend(t, 1) + require.False(t, bk.FailsafeEnabled()) + + bk.ingesters[eth.ChainIDFromUInt64(901)].SetError(ErrorConflict, "forced") + require.True(t, bk.FailsafeEnabled()) + bk.requireRejection(executingChain(), inclusionTs, "failsafe", bk.sourceAccess(100, 0)) +} + +func TestIntegration_Backend_RecoverReorg_ClearsFailsafe(t *testing.T) { + t.Parallel() + + bk, si := reorgRecoveryBackend(t) + si.eth.SetLabelBlock(eth.Finalized, si.blockInfo[101]) + + putIntoReorg(t, si, 103, 1206) + require.True(t, bk.FailsafeEnabled()) + + bk.tryResolveReorgs(context.Background()) + require.False(t, bk.FailsafeEnabled()) +} + +func TestIntegration_Backend_RecoverConflict_DoesNotClearFailsafe(t *testing.T) { + t.Parallel() + + bk, si := reorgRecoveryBackend(t) + si.eth.SetLabelBlock(eth.Finalized, si.blockInfo[101]) + + si.SetError(ErrorConflict, "forced") + require.True(t, bk.FailsafeEnabled()) + + bk.tryResolveReorgs(context.Background()) + require.True(t, bk.FailsafeEnabled(), + "ErrorConflict is not auto-recoverable; failsafe must stay on") +} + +func TestIntegration_Backend_UnsupportedSafetyLevel_Rejected(t *testing.T) { + t.Parallel() + + bk := twoChainBackend(t, 1) + err := bk.CheckAccessList(context.Background(), nil, safety.Finalized, + messages.ExecutingDescriptor{ChainID: executingChain(), Timestamp: inclusionTs}) + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported safety level") +} + +func TestIntegration_Backend_EmptyAccessList_LocalUnsafe_Accepted(t *testing.T) { + t.Parallel() + + bk := twoChainBackend(t, 1) + require.NoError(t, bk.CheckAccessList(context.Background(), nil, safety.LocalUnsafe, + messages.ExecutingDescriptor{ChainID: executingChain(), Timestamp: inclusionTs})) +} + +func TestIntegration_Backend_Ready_FalseUntilAllChainsReady(t *testing.T) { + t.Parallel() + + bk := newSeededBackend(t, backendOpts{ + Specs: []seedSpec{ + {ChainID: 901, AnchorNumber: 99, AnchorTime: 1198, + Blocks: []seedBlock{{Num: 100, Ts: 1200, Logs: []seedLog{{}}}}}, + {ChainID: 902, AnchorNumber: 99, AnchorTime: 1198, + StartTimestamp: 1 << 30, // unreachable by seeded timestamps -> Ready=false + NoIngest: true}, + }, + }) + + require.False(t, bk.Ready(), "Backend.Ready requires all ingesters Ready") + + err := bk.CheckAccessList(context.Background(), nil, safety.LocalUnsafe, + messages.ExecutingDescriptor{ChainID: executingChain(), Timestamp: inclusionTs}) + require.Error(t, err) + require.True(t, errors.Is(err, types.ErrUninitialized) || errors.Is(err, types.ErrFailsafeEnabled), + "expected ErrUninitialized or ErrFailsafeEnabled, got %v", err) +} diff --git a/op-interop-filter/filter/logsdb_integration_helpers_test.go b/op-interop-filter/filter/logsdb_integration_helpers_test.go new file mode 100644 index 00000000000..c6a3f553413 --- /dev/null +++ b/op-interop-filter/filter/logsdb_integration_helpers_test.go @@ -0,0 +1,560 @@ +package filter + +import ( + "context" + "encoding/binary" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-interop-filter/metrics" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/processors" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +const ( + defaultChainID uint64 = 901 + defaultBlockTime uint64 = 2 + defaultGenesisTime uint64 = 1000 + defaultAnchorNumber uint64 = 99 + defaultAnchorTime uint64 = 1198 + defaultFirstBlock uint64 = 100 + defaultStartTs uint64 = 1 // below typical seed-block timestamps so Ready() flips true +) + +// seedLog declares one log on a seeded block. +// +// Either Log is set (use as-is) or it's nil and a `simpleLog` placeholder is +// synthesized. ExecMsg, if non-nil, replaces the default with an executing +// message targeting the given coordinates. +type seedLog struct { + Log *gethTypes.Log + ExecMsg *seedExecMsg +} + +type seedExecMsg struct { + TargetChainID eth.ChainID + TargetBlock uint64 + TargetLogIdx uint32 + TargetTs uint64 + Origin common.Address + PayloadHash common.Hash +} + +// seedBlock declares one block to seal into the ingester's logsdb. +type seedBlock struct { + Num uint64 + Ts uint64 + ParentOverride *common.Hash + Logs []seedLog +} + +// seedSpec configures a single-chain seeded ingester. +type seedSpec struct { + ChainID uint64 // defaults to defaultChainID + AnchorNumber uint64 // defaults to defaultAnchorNumber + AnchorTime uint64 // defaults to defaultAnchorTime + Blocks []seedBlock + + // Optional overrides — zero values fall through to test defaults. + DataDir string + StartTimestamp uint64 + BackfillDuration time.Duration + PollInterval time.Duration + FetchConcurrency int + NoSealAnchor bool // omit sealParentBlock; used by init tests + NoIngest bool // omit per-block ingest; used by init tests +} + +// seededIngester bundles a real LogsDBChainIngester with the artefacts a test +// needs to query and assert against it. +type seededIngester struct { + *LogsDBChainIngester + t *testing.T + chainID eth.ChainID + eth *MockEthClient + metrics *capturingMetrics + blockInfo map[uint64]*mockBlockInfo + receipts map[uint64]gethTypes.Receipts +} + +// newSeededIngester builds a LogsDBChainIngester with a real on-disk logsdb in +// t.TempDir(), feeds it the spec's blocks (anchor + each ingestBlock), and +// returns it ready for assertions. Safe under t.Parallel(). +func newSeededIngester(t *testing.T, spec seedSpec) *seededIngester { + t.Helper() + applySpecDefaults(&spec) + + si := &seededIngester{ + t: t, + chainID: eth.ChainIDFromUInt64(spec.ChainID), + eth: NewMockEthClient(), + metrics: newCapturingMetrics(), + blockInfo: map[uint64]*mockBlockInfo{}, + receipts: map[uint64]gethTypes.Receipts{}, + } + + si.seedEthClient(spec) + si.LogsDBChainIngester = buildIngester(t, spec, si.eth, si.metrics) + + require.NoError(t, si.initLogsDB(), "initLogsDB") + t.Cleanup(func() { + if si.logsDB != nil { + _ = si.logsDB.Close() + } + }) + + if !spec.NoSealAnchor { + require.NoError(t, si.sealParentBlock(spec.AnchorNumber), "sealParentBlock") + } + if !spec.NoIngest { + for _, b := range spec.Blocks { + require.NoErrorf(t, si.ingestBlock(b.Num), "ingestBlock %d", b.Num) + } + } + return si +} + +func applySpecDefaults(spec *seedSpec) { + if spec.ChainID == 0 { + spec.ChainID = defaultChainID + } + if spec.AnchorNumber == 0 && spec.AnchorTime == 0 { + spec.AnchorNumber = defaultAnchorNumber + spec.AnchorTime = defaultAnchorTime + } + if spec.StartTimestamp == 0 { + // Explicit zero -> use the default; a test that wants Ready=false should + // override to a value above the seeded block timestamps. + spec.StartTimestamp = defaultStartTs + } + if spec.PollInterval == 0 { + spec.PollInterval = 100 * time.Millisecond + } + if spec.FetchConcurrency == 0 { + spec.FetchConcurrency = 4 + } +} + +func (si *seededIngester) seedEthClient(spec seedSpec) { + anchor := makeBlockInfo(spec.AnchorNumber, spec.AnchorTime, common.Hash{}) + si.blockInfo[anchor.number] = anchor + si.eth.AddBlock(anchor, nil) + + prevHash := anchor.hash + prevNum := spec.AnchorNumber + for _, b := range spec.Blocks { + parent := prevHash + if b.ParentOverride != nil { + parent = *b.ParentOverride + } + info := makeBlockInfo(b.Num, b.Ts, parent) + receipts := materialiseReceipts(b) + si.blockInfo[b.Num] = info + si.receipts[b.Num] = receipts + si.eth.AddBlock(info, receipts) + prevHash = info.hash + prevNum = b.Num + } + if len(spec.Blocks) > 0 { + si.eth.SetHeadBlock(si.blockInfo[prevNum]) + si.eth.SetLabelBlock(eth.Finalized, si.blockInfo[prevNum]) + } else { + si.eth.SetHeadBlock(anchor) + si.eth.SetLabelBlock(eth.Finalized, anchor) + } +} + +func buildIngester(t *testing.T, spec seedSpec, ethClient EthClient, m metrics.Metricer) *LogsDBChainIngester { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + dataDir := spec.DataDir + if dataDir == "" { + dataDir = t.TempDir() + } + + rollupCfg := testRollupConfig(spec.ChainID, 0, defaultGenesisTime) + return &LogsDBChainIngester{ + log: testlog.Logger(t, log.LevelError), + metrics: m, + chainID: eth.ChainIDFromUInt64(spec.ChainID), + ethClient: ethClient, + dataDir: dataDir, + startTimestamp: spec.StartTimestamp, + backfillDuration: spec.BackfillDuration, + pollInterval: spec.PollInterval, + rollupCfg: rollupCfg, + fetchConcurrency: spec.FetchConcurrency, + ctx: ctx, + cancel: cancel, + } +} + +func makeBlockInfo(number, timestamp uint64, parentHash common.Hash) *mockBlockInfo { + var hash common.Hash + binary.BigEndian.PutUint64(hash[24:], number) + // Mix the parent into the hash so reorged branches get distinct hashes. + copy(hash[:24], crypto.Keccak256(parentHash[:], hash[24:])[:24]) + return &mockBlockInfo{ + number: number, + hash: hash, + parentHash: parentHash, + timestamp: timestamp, + } +} + +func materialiseReceipts(b seedBlock) gethTypes.Receipts { + if len(b.Logs) == 0 { + return nil + } + logs := make([]*gethTypes.Log, 0, len(b.Logs)) + for i, sl := range b.Logs { + switch { + case sl.Log != nil: + cp := *sl.Log + cp.Index = uint(i) + logs = append(logs, &cp) + case sl.ExecMsg != nil: + logs = append(logs, buildExecMsgLog(uint(i), *sl.ExecMsg)) + default: + logs = append(logs, plainLog(uint(i), b.Num)) + } + } + return gethTypes.Receipts{{ + TxHash: common.Hash{byte(b.Num)}, + Logs: logs, + }} +} + +func plainLog(index uint, blockNum uint64) *gethTypes.Log { + return &gethTypes.Log{ + Address: common.Address{byte(blockNum), byte(index)}, + Topics: []common.Hash{{0x01, 0x02, 0x03}}, + Data: []byte{0x00}, + Index: index, + } +} + +func buildExecMsgLog(index uint, em seedExecMsg) *gethTypes.Log { + id := messages.Identifier{ + Origin: em.Origin, + BlockNumber: em.TargetBlock, + LogIndex: em.TargetLogIdx, + Timestamp: em.TargetTs, + ChainID: em.TargetChainID, + } + data := encodeExecMsgIdentifier(id) + payloadHash := em.PayloadHash + if payloadHash == (common.Hash{}) { + payloadHash = common.Hash{0xee, byte(index), byte(em.TargetBlock)} + } + return &gethTypes.Log{ + Address: params.InteropCrossL2InboxAddress, + Topics: []common.Hash{ + messages.ExecutingMessageEventTopic, + payloadHash, + }, + Data: data, + Index: index, + } +} + +// encodeExecMsgIdentifier is the inverse of Message.DecodeEvent's data section. +func encodeExecMsgIdentifier(id messages.Identifier) []byte { + out := make([]byte, 32*5) + copy(out[12:32], id.Origin[:]) + binary.BigEndian.PutUint64(out[32+24:64], id.BlockNumber) + binary.BigEndian.PutUint32(out[64+28:96], id.LogIndex) + binary.BigEndian.PutUint64(out[96+24:128], id.Timestamp) + cid := id.ChainID.Bytes32() + copy(out[128:160], cid[:]) + return out +} + +// accessForLog returns an Access referencing the given (blockNum, logIdx) on +// this chain. The checksum is computed from the actual stored log content so +// the access entry is accepted by a happy-path CheckAccessList. +func (si *seededIngester) accessForLog(blockNum uint64, logIdx uint32) messages.Access { + si.t.Helper() + receipts, ok := si.receipts[blockNum] + require.Truef(si.t, ok, "no receipts seeded for block %d", blockNum) + require.Lessf(si.t, int(logIdx), len(receipts[0].Logs), "log index %d out of range for block %d", logIdx, blockNum) + log := receipts[0].Logs[logIdx] + info := si.blockInfo[blockNum] + args := messages.ChecksumArgs{ + BlockNumber: blockNum, + LogIndex: logIdx, + Timestamp: info.timestamp, + ChainID: si.chainID, + LogHash: processors.LogToLogHash(log), + } + return args.Access() +} + +// twoChainBackend builds a backend with a source chain (chainID 901) and an +// executing chain (chainID 902), both seeded with blocks 100 and 101 at +// timestamps 1200 and 1202. The source-chain block 100 carries the given +// number of placeholder logs. +func twoChainBackend(t *testing.T, sourceLogCount int) *seededBackend { + t.Helper() + sourceLogs := make([]seedLog, sourceLogCount) + return newSeededBackend(t, backendOpts{ + Specs: []seedSpec{ + {ChainID: 901, AnchorNumber: 99, AnchorTime: 1198, + Blocks: []seedBlock{ + {Num: 100, Ts: 1200, Logs: sourceLogs}, + {Num: 101, Ts: 1202}, + }}, + {ChainID: 902, AnchorNumber: 99, AnchorTime: 1198, + Blocks: []seedBlock{ + {Num: 100, Ts: 1200}, + {Num: 101, Ts: 1202}, + }}, + }, + }) +} + +// sourceAccess returns an access for the first source-chain block's log. +func (sb *seededBackend) sourceAccess(blockNum uint64, logIdx uint32) messages.Access { + return sb.ingesters[eth.ChainIDFromUInt64(901)].accessForLog(blockNum, logIdx) +} + +const ( + executingChainID uint64 = 902 + sourceLogTs uint64 = 1200 + inclusionTs uint64 = 1300 // safely above latest sealed timestamps on both chains +) + +func executingChain() eth.ChainID { return eth.ChainIDFromUInt64(executingChainID) } + +// reopenSeededIngester builds a fresh ingester sharing the given ingester's +// dataDir and eth client, then runs initIngestion. Use this for restart / +// resume tests. +func reopenSeededIngester(t *testing.T, prev *seededIngester) *seededIngester { + t.Helper() + if prev.logsDB != nil { + require.NoError(t, prev.logsDB.Close()) + prev.logsDB = nil + } + dataDir := prev.LogsDBChainIngester.dataDir + + si := &seededIngester{ + t: t, + chainID: prev.chainID, + eth: prev.eth, + metrics: prev.metrics, + blockInfo: prev.blockInfo, + receipts: prev.receipts, + } + chainIDu64, _ := prev.chainID.Uint64() + si.LogsDBChainIngester = buildIngester(t, seedSpec{ + ChainID: chainIDu64, + DataDir: dataDir, + StartTimestamp: defaultStartTs, + PollInterval: 100 * time.Millisecond, + FetchConcurrency: 4, + }, si.eth, si.metrics) + require.NoError(t, si.initLogsDB()) + t.Cleanup(func() { + if si.logsDB != nil { + _ = si.logsDB.Close() + } + }) + + latest, ok := si.logsDB.LatestSealedBlock() + require.True(t, ok, "reopened DB has no sealed blocks") + require.NoError(t, si.findAndSetEarliestBlock(latest.Number+1)) + return si +} + +// addBlock seeds an additional block on the eth client *after* construction. +// Returns the block info so tests can reference its hash. +func (si *seededIngester) addBlock(num, ts uint64, parent common.Hash, logs []seedLog) *mockBlockInfo { + info := makeBlockInfo(num, ts, parent) + receipts := materialiseReceipts(seedBlock{Num: num, Logs: logs}) + si.blockInfo[num] = info + if receipts != nil { + si.receipts[num] = receipts + } + si.eth.AddBlock(info, receipts) + si.eth.SetHeadBlock(info) + return info +} + +// withChecksum returns a copy of acc with a synthetic checksum (typed prefix +// preserved so it survives access-list encoding). +func withChecksum(acc messages.Access, raw [32]byte) messages.Access { + raw[0] = messages.PrefixChecksum + acc.Checksum = messages.MessageChecksum(raw) + return acc +} + +// seededBackend wires a fully-real Backend (real LockstepCrossValidator, real +// LogsDBChainIngester, real on-disk logsdb) for end-to-end CheckAccessList +// tests. +type seededBackend struct { + *Backend + t *testing.T + ingesters map[eth.ChainID]*seededIngester + metrics *capturingMetrics +} + +type backendOpts struct { + Specs []seedSpec + MessageExpiryWindow uint64 + ValidationInterval time.Duration + Passthrough bool +} + +func newSeededBackend(t *testing.T, opts backendOpts) *seededBackend { + t.Helper() + if len(opts.Specs) == 0 { + opts.Specs = []seedSpec{{Blocks: []seedBlock{{Num: defaultFirstBlock, Ts: defaultAnchorTime + defaultBlockTime}}}} + } + if opts.MessageExpiryWindow == 0 { + opts.MessageExpiryWindow = 1 << 30 + } + if opts.ValidationInterval == 0 { + opts.ValidationInterval = 100 * time.Millisecond + } + + chains := map[eth.ChainID]ChainIngester{} + ingesters := map[eth.ChainID]*seededIngester{} + mtr := newCapturingMetrics() + + for _, spec := range opts.Specs { + si := newSeededIngester(t, spec) + si.metrics = mtr + si.LogsDBChainIngester.metrics = mtr + chains[si.chainID] = si.LogsDBChainIngester + ingesters[si.chainID] = si + } + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + logger := testlog.Logger(t, log.LevelError) + cv := NewLockstepCrossValidator(ctx, logger, mtr, opts.MessageExpiryWindow, defaultStartTs, + opts.ValidationInterval, chains) + backend := NewBackend(ctx, BackendParams{ + Logger: logger, + Metrics: mtr, + Chains: chains, + CrossValidator: cv, + Passthrough: opts.Passthrough, + ReorgRecoveryEnabled: false, + }) + return &seededBackend{Backend: backend, t: t, ingesters: ingesters, metrics: mtr} +} + +// checkAccessList encodes the given access entries into the inbox-entry form +// CheckAccessList expects and forwards the call. +func (sb *seededBackend) checkAccessList(execChain eth.ChainID, execTs uint64, accesses ...messages.Access) error { + entries := messages.EncodeAccessList(accesses) + return sb.CheckAccessList(context.Background(), entries, safety.LocalUnsafe, + messages.ExecutingDescriptor{Timestamp: execTs, ChainID: execChain}) +} + +// requireRejection asserts CheckAccessList rejects the given accesses with the +// expected classification label. +func (sb *seededBackend) requireRejection(execChain eth.ChainID, execTs uint64, expectedReason string, accesses ...messages.Access) { + sb.t.Helper() + before := sb.metrics.rejectionCount(expectedReason) + err := sb.checkAccessList(execChain, execTs, accesses...) + require.Errorf(sb.t, err, "expected rejection (reason=%s) but got nil", expectedReason) + require.Equal(sb.t, expectedReason, classifyRejectionReason(err), + "unexpected classification for err=%v", err) + require.Equal(sb.t, before+1, sb.metrics.rejectionCount(expectedReason), + "rejection metric not incremented for reason=%s", expectedReason) +} + +// requireAccepted asserts CheckAccessList accepts the given accesses. +func (sb *seededBackend) requireAccepted(execChain eth.ChainID, execTs uint64, accesses ...messages.Access) { + sb.t.Helper() + err := sb.checkAccessList(execChain, execTs, accesses...) + require.NoErrorf(sb.t, err, "expected accept, got err=%v", err) +} + +// ============================================================================= +// capturingMetrics — records the calls tests need to assert on. +// ============================================================================= + +type capturingMetrics struct { + mu sync.Mutex + rejections map[string]int + reorgs map[uint64]int + blockSealed map[uint64]int64 + logsAdded map[uint64]int64 + chainHead map[uint64]uint64 +} + +func newCapturingMetrics() *capturingMetrics { + return &capturingMetrics{ + rejections: map[string]int{}, + reorgs: map[uint64]int{}, + blockSealed: map[uint64]int64{}, + logsAdded: map[uint64]int64{}, + chainHead: map[uint64]uint64{}, + } +} + +func (m *capturingMetrics) rejectionCount(reason string) int { + m.mu.Lock() + defer m.mu.Unlock() + return m.rejections[reason] +} + +func (m *capturingMetrics) reorgCount(chainID uint64) int { + m.mu.Lock() + defer m.mu.Unlock() + return m.reorgs[chainID] +} + +func (m *capturingMetrics) sealedCount(chainID uint64) int64 { + m.mu.Lock() + defer m.mu.Unlock() + return m.blockSealed[chainID] +} + +func (m *capturingMetrics) RecordInfo(version string) {} +func (m *capturingMetrics) RecordUp() {} +func (m *capturingMetrics) RecordFailsafeEnabled(enabled bool) {} +func (m *capturingMetrics) RecordChainHead(chainID uint64, blockNum uint64) { + m.locked(func() { m.chainHead[chainID] = blockNum }) +} +func (m *capturingMetrics) RecordCheckAccessList(success bool) {} +func (m *capturingMetrics) RecordCheckAccessListDuration(duration float64) {} +func (m *capturingMetrics) RecordCheckAccessListRejection(reason string) { + m.locked(func() { m.rejections[reason]++ }) +} +func (m *capturingMetrics) RecordBackfillProgress(chainID uint64, p float64) {} +func (m *capturingMetrics) RecordReorgDetected(chainID uint64) { + m.locked(func() { m.reorgs[chainID]++ }) +} +func (m *capturingMetrics) RecordLogsAdded(chainID uint64, count int64) { + m.locked(func() { m.logsAdded[chainID] += count }) +} +func (m *capturingMetrics) RecordBlocksSealed(chainID uint64, count int64) { + m.locked(func() { m.blockSealed[chainID] += count }) +} +func (m *capturingMetrics) RecordCrossUnsafeValidatedTimestamp(timestamp uint64) {} + +func (m *capturingMetrics) locked(fn func()) { + m.mu.Lock() + defer m.mu.Unlock() + fn() +} + +var _ metrics.Metricer = (*capturingMetrics)(nil) diff --git a/op-interop-filter/filter/mock_test.go b/op-interop-filter/filter/mock_test.go new file mode 100644 index 00000000000..09686a7d551 --- /dev/null +++ b/op-interop-filter/filter/mock_test.go @@ -0,0 +1,556 @@ +package filter + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "os" + "sync" + + "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +// mockChainIngester is an in-memory implementation of ChainIngester for testing. +// It stores logs in a map and provides simple state management. +type mockChainIngester struct { + mu sync.RWMutex + + // Logs stored by their identifying query + logs map[logKey]messages.BlockSeal + + // Blocks keyed by block number + blocks map[uint64]eth.BlockID + + // Executing messages with their inclusion context + execMsgs []IncludedMessage + + // State + ready bool + err *IngesterError + latestBlock eth.BlockID + latestTimestamp uint64 + earliestIngestedBlock uint64 + + rewindToFinalizedErr error + rewindToFinalizedCount int +} + +// logKey uniquely identifies a log entry +type logKey struct { + Timestamp uint64 + BlockNum uint64 + LogIdx uint32 + Checksum messages.MessageChecksum +} + +// newMockChainIngester creates a new in-memory chain ingester. +func newMockChainIngester() *mockChainIngester { + return &mockChainIngester{ + logs: make(map[logKey]messages.BlockSeal), + blocks: make(map[uint64]eth.BlockID), + execMsgs: make([]IncludedMessage, 0), + ready: true, // Default to ready for simple tests + } +} + +// Start implements ChainIngester (no-op for in-memory). +func (m *mockChainIngester) Start() error { return nil } + +// Stop implements ChainIngester (no-op for in-memory). +func (m *mockChainIngester) Stop() error { return nil } + +// AddLog adds a log entry to the ingester. +func (m *mockChainIngester) AddLog(timestamp, blockNum uint64, logIdx uint32, checksum messages.MessageChecksum, seal messages.BlockSeal) { + m.mu.Lock() + defer m.mu.Unlock() + + key := logKey{ + Timestamp: timestamp, + BlockNum: blockNum, + LogIdx: logIdx, + Checksum: checksum, + } + m.logs[key] = seal + m.blocks[blockNum] = eth.BlockID{Hash: seal.Hash, Number: blockNum} + + // Update latest block/timestamp if needed + if blockNum > m.latestBlock.Number { + m.latestBlock = eth.BlockID{Hash: seal.Hash, Number: blockNum} + m.latestTimestamp = timestamp + } + if m.earliestIngestedBlock == 0 || blockNum < m.earliestIngestedBlock { + m.earliestIngestedBlock = blockNum + } +} + +// AddExecMsg adds an executing message with its inclusion context. +func (m *mockChainIngester) AddExecMsg(msg IncludedMessage) { + m.mu.Lock() + defer m.mu.Unlock() + + m.execMsgs = append(m.execMsgs, msg) + + // Update latest block/timestamp if needed + if msg.InclusionBlockNum > m.latestBlock.Number { + m.latestBlock = eth.BlockID{Number: msg.InclusionBlockNum} + m.latestTimestamp = msg.InclusionTimestamp + } + if m.earliestIngestedBlock == 0 || msg.InclusionBlockNum < m.earliestIngestedBlock { + m.earliestIngestedBlock = msg.InclusionBlockNum + } +} + +// AddBlock adds a block directly to the ingester. +func (m *mockChainIngester) AddBlock(block eth.BlockID) { + m.mu.Lock() + defer m.mu.Unlock() + + m.blocks[block.Number] = block + if block.Number >= m.latestBlock.Number { + m.latestBlock = block + } +} + +// SetReady sets the ready state. +func (m *mockChainIngester) SetReady(ready bool) { + m.mu.Lock() + defer m.mu.Unlock() + m.ready = ready +} + +// Contains implements ChainIngester. +func (m *mockChainIngester) Contains(query messages.ContainsQuery) (messages.BlockSeal, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + key := logKey{ + Timestamp: query.Timestamp, + BlockNum: query.BlockNum, + LogIdx: query.LogIdx, + Checksum: query.Checksum, + } + + seal, ok := m.logs[key] + if !ok { + return messages.BlockSeal{}, types.ErrConflict + } + return seal, nil +} + +// LatestBlock implements ChainIngester. +func (m *mockChainIngester) LatestBlock() (eth.BlockID, bool) { + m.mu.RLock() + defer m.mu.RUnlock() + + if m.latestBlock.Number == 0 { + return eth.BlockID{}, false + } + return m.latestBlock, true +} + +// BlockHashByNumber implements ChainIngester. +func (m *mockChainIngester) BlockHashByNumber(number uint64) (common.Hash, bool) { + m.mu.RLock() + defer m.mu.RUnlock() + + block, ok := m.blocks[number] + if !ok { + return common.Hash{}, false + } + return block.Hash, true +} + +// LatestTimestamp implements ChainIngester. +func (m *mockChainIngester) LatestTimestamp() (uint64, bool) { + m.mu.RLock() + defer m.mu.RUnlock() + + if m.latestTimestamp == 0 { + return 0, false + } + return m.latestTimestamp, true +} + +// GetExecMsgsAtTimestamp implements ChainIngester. +func (m *mockChainIngester) GetExecMsgsAtTimestamp(timestamp uint64) ([]IncludedMessage, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + var result []IncludedMessage + for _, msg := range m.execMsgs { + if msg.InclusionTimestamp == timestamp { + result = append(result, msg) + } + } + return result, nil +} + +// Ready implements ChainIngester. +func (m *mockChainIngester) Ready() bool { + m.mu.RLock() + defer m.mu.RUnlock() + return m.ready +} + +// Error implements ChainIngester. +func (m *mockChainIngester) Error() *IngesterError { + m.mu.RLock() + defer m.mu.RUnlock() + return m.err +} + +// SetError implements ChainIngester. +func (m *mockChainIngester) SetError(reason IngesterErrorReason, msg string) { + m.mu.Lock() + defer m.mu.Unlock() + m.err = &IngesterError{ + Reason: reason, + Message: msg, + } +} + +// ClearError implements ChainIngester. +func (m *mockChainIngester) ClearError() { + m.mu.Lock() + defer m.mu.Unlock() + m.err = nil +} + +// RewindToFinalized implements ChainIngester. +func (m *mockChainIngester) RewindToFinalized(ctx context.Context) (eth.BlockID, uint64, error) { + m.mu.Lock() + defer m.mu.Unlock() + m.rewindToFinalizedCount++ + if m.rewindToFinalizedErr != nil { + return eth.BlockID{}, 0, m.rewindToFinalizedErr + } + return m.latestBlock, m.latestTimestamp, nil +} + +// SetLatestTimestamp sets the latest ingested timestamp (for testing). +func (m *mockChainIngester) SetLatestTimestamp(ts uint64) { + m.mu.Lock() + defer m.mu.Unlock() + m.latestTimestamp = ts +} + +// ClearExecMsgs removes all executing messages (for testing). +func (m *mockChainIngester) ClearExecMsgs() { + m.mu.Lock() + defer m.mu.Unlock() + m.execMsgs = nil +} + +// Ensure mockChainIngester implements ChainIngester +var _ ChainIngester = (*mockChainIngester)(nil) + +// ============================================================================= +// MockCrossValidator +// ============================================================================= + +// mockCrossValidator is a minimal mock for backend tests that don't need real validation. +type mockCrossValidator struct { + validateErr error + errState *ValidatorError + resetTs uint64 + resetOK bool +} + +func (m *mockCrossValidator) Start() error { return nil } +func (m *mockCrossValidator) Stop() error { return nil } +func (m *mockCrossValidator) ValidateAccessEntry(access messages.Access, minSafety safety.Level, execDescriptor messages.ExecutingDescriptor) error { + return m.validateErr +} +func (m *mockCrossValidator) CrossValidatedTimestamp() (uint64, bool) { return 0, false } +func (m *mockCrossValidator) Error() *ValidatorError { return m.errState } +func (m *mockCrossValidator) ResetCrossValidatedTimestamp(timestamp uint64) { + m.resetTs = timestamp + m.resetOK = true +} + +// SetError sets the error state for the mock validator. +func (m *mockCrossValidator) SetError(msg string) { + m.errState = &ValidatorError{Message: msg} +} + +// Ensure mockCrossValidator implements CrossValidator +var _ CrossValidator = (*mockCrossValidator)(nil) + +// ============================================================================= +// MockEthClient +// ============================================================================= + +// MockEthClient is an in-memory implementation of EthClient for testing. +// It can be populated manually or loaded from captured JSON data. +type MockEthClient struct { + mu sync.RWMutex + + // Block info keyed by block number + blocksByNumber map[uint64]eth.BlockInfo + + // Block info keyed by label (e.g., "unsafe") + blocksByLabel map[eth.BlockLabel]eth.BlockInfo + + // Receipts keyed by block hash + receiptsByHash map[common.Hash]gethTypes.Receipts + + // Error injection + infoByNumberErr error + infoByLabelErr error + fetchReceiptsErr error +} + +// NewMockEthClient creates a new mock eth client. +func NewMockEthClient() *MockEthClient { + return &MockEthClient{ + blocksByNumber: make(map[uint64]eth.BlockInfo), + blocksByLabel: make(map[eth.BlockLabel]eth.BlockInfo), + receiptsByHash: make(map[common.Hash]gethTypes.Receipts), + } +} + +// InfoByLabel implements EthClient. +func (m *MockEthClient) InfoByLabel(ctx context.Context, label eth.BlockLabel) (eth.BlockInfo, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + if m.infoByLabelErr != nil { + return nil, m.infoByLabelErr + } + + block, ok := m.blocksByLabel[label] + if !ok { + return nil, fmt.Errorf("block not found for label %s", label) + } + return block, nil +} + +// InfoByNumber implements EthClient. +func (m *MockEthClient) InfoByNumber(ctx context.Context, number uint64) (eth.BlockInfo, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + if m.infoByNumberErr != nil { + return nil, m.infoByNumberErr + } + + block, ok := m.blocksByNumber[number] + if !ok { + return nil, fmt.Errorf("block %d not found", number) + } + return block, nil +} + +// FetchReceipts implements EthClient. +func (m *MockEthClient) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, gethTypes.Receipts, error) { + m.mu.RLock() + defer m.mu.RUnlock() + + if m.fetchReceiptsErr != nil { + return nil, nil, m.fetchReceiptsErr + } + + receipts, ok := m.receiptsByHash[blockHash] + if !ok { + return nil, nil, fmt.Errorf("receipts not found for block %s", blockHash.Hex()) + } + + // Find the block info for this hash + for _, block := range m.blocksByNumber { + if block.Hash() == blockHash { + return block, receipts, nil + } + } + + return nil, receipts, nil +} + +// Close implements EthClient. +func (m *MockEthClient) Close() {} + +// AddBlock adds a block to the mock. +func (m *MockEthClient) AddBlock(block eth.BlockInfo, receipts gethTypes.Receipts) { + m.mu.Lock() + defer m.mu.Unlock() + + m.blocksByNumber[block.NumberU64()] = block + m.receiptsByHash[block.Hash()] = receipts +} + +// SetHeadBlock sets a block as the head (unsafe label). +func (m *MockEthClient) SetHeadBlock(block eth.BlockInfo) { + m.mu.Lock() + defer m.mu.Unlock() + + m.blocksByLabel[eth.Unsafe] = block +} + +// SetLabelBlock sets a block for the given label. +func (m *MockEthClient) SetLabelBlock(label eth.BlockLabel, block eth.BlockInfo) { + m.mu.Lock() + defer m.mu.Unlock() + + m.blocksByLabel[label] = block +} + +// SetInfoByNumberErr sets an error to return from InfoByNumber. +func (m *MockEthClient) SetInfoByNumberErr(err error) { + m.mu.Lock() + defer m.mu.Unlock() + m.infoByNumberErr = err +} + +// SetInfoByLabelErr sets an error to return from InfoByLabel. +func (m *MockEthClient) SetInfoByLabelErr(err error) { + m.mu.Lock() + defer m.mu.Unlock() + m.infoByLabelErr = err +} + +// SetFetchReceiptsErr sets an error to return from FetchReceipts. +func (m *MockEthClient) SetFetchReceiptsErr(err error) { + m.mu.Lock() + defer m.mu.Unlock() + m.fetchReceiptsErr = err +} + +// Ensure MockEthClient implements EthClient +var _ EthClient = (*MockEthClient)(nil) + +// ============================================================================= +// Captured Data Types (for loading test fixtures) +// ============================================================================= + +// CapturedData is the format output by spammer --capture +type CapturedData struct { + ChainID uint64 `json:"chain_id"` + Blocks []*CapturedBlock `json:"blocks"` +} + +// CapturedBlock holds a block's info and receipts +type CapturedBlock struct { + Number uint64 `json:"number"` + Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parent_hash"` + Timestamp uint64 `json:"timestamp"` + Receipts []*CapturedReceipt `json:"receipts"` +} + +// CapturedReceipt holds receipt data with logs +type CapturedReceipt struct { + TxHash common.Hash `json:"tx_hash"` + Logs []*CapturedLog `json:"logs"` +} + +// CapturedLog holds log data needed for testing +type CapturedLog struct { + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data []byte `json:"data"` + Index uint `json:"index"` +} + +// mockBlockInfo implements eth.BlockInfo for test data +type mockBlockInfo struct { + number uint64 + hash common.Hash + parentHash common.Hash + timestamp uint64 +} + +func (b *mockBlockInfo) Hash() common.Hash { return b.hash } +func (b *mockBlockInfo) ParentHash() common.Hash { return b.parentHash } +func (b *mockBlockInfo) NumberU64() uint64 { return b.number } +func (b *mockBlockInfo) Time() uint64 { return b.timestamp } +func (b *mockBlockInfo) Coinbase() common.Address { return common.Address{} } +func (b *mockBlockInfo) Root() common.Hash { return common.Hash{} } +func (b *mockBlockInfo) ReceiptHash() common.Hash { return common.Hash{} } +func (b *mockBlockInfo) GasUsed() uint64 { return 0 } +func (b *mockBlockInfo) GasLimit() uint64 { return 0 } +func (b *mockBlockInfo) BaseFee() *big.Int { return nil } +func (b *mockBlockInfo) BlobBaseFee(_ *params.ChainConfig) *big.Int { return nil } +func (b *mockBlockInfo) ExcessBlobGas() *uint64 { return nil } +func (b *mockBlockInfo) BlobGasUsed() *uint64 { return nil } +func (b *mockBlockInfo) ParentBeaconRoot() *common.Hash { return nil } +func (b *mockBlockInfo) WithdrawalsRoot() *common.Hash { return nil } +func (b *mockBlockInfo) Extra() []byte { return nil } +func (b *mockBlockInfo) ID() eth.BlockID { return eth.BlockID{Hash: b.hash, Number: b.number} } +func (b *mockBlockInfo) MixDigest() common.Hash { return common.Hash{} } + +// LoadCapturedData loads captured test data from a JSON file +func LoadCapturedData(path string) (*CapturedData, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("failed to open captured data file: %w", err) + } + defer file.Close() + + var data CapturedData + if err := json.NewDecoder(file).Decode(&data); err != nil { + return nil, fmt.Errorf("failed to decode captured data: %w", err) + } + + return &data, nil +} + +// LoadMockEthClientFromCapture creates a MockEthClient populated with captured data +func LoadMockEthClientFromCapture(path string) (*MockEthClient, error) { + data, err := LoadCapturedData(path) + if err != nil { + return nil, err + } + + mock := NewMockEthClient() + + var latestBlock eth.BlockInfo + for _, capturedBlock := range data.Blocks { + // Create block info + blockInfo := &mockBlockInfo{ + number: capturedBlock.Number, + hash: capturedBlock.Hash, + parentHash: capturedBlock.ParentHash, + timestamp: capturedBlock.Timestamp, + } + + // Convert captured receipts to geth receipts + var receipts gethTypes.Receipts + for _, capturedReceipt := range capturedBlock.Receipts { + receipt := &gethTypes.Receipt{ + TxHash: capturedReceipt.TxHash, + Logs: make([]*gethTypes.Log, len(capturedReceipt.Logs)), + } + for i, capturedLog := range capturedReceipt.Logs { + receipt.Logs[i] = &gethTypes.Log{ + Address: capturedLog.Address, + Topics: capturedLog.Topics, + Data: capturedLog.Data, + Index: capturedLog.Index, + } + } + receipts = append(receipts, receipt) + } + + mock.AddBlock(blockInfo, receipts) + + // Track the latest block + if latestBlock == nil || blockInfo.NumberU64() > latestBlock.NumberU64() { + latestBlock = blockInfo + } + } + + // Set the latest block as head + if latestBlock != nil { + mock.SetHeadBlock(latestBlock) + } + + return mock, nil +} diff --git a/op-interop-filter/filter/rpc_test.go b/op-interop-filter/filter/rpc_test.go new file mode 100644 index 00000000000..fc74e198cac --- /dev/null +++ b/op-interop-filter/filter/rpc_test.go @@ -0,0 +1,82 @@ +package filter + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-interop-filter/metrics" + "github.com/ethereum-optimism/optimism/op-service/eth" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +func TestQueryFrontendGetBlockHashByNumberRPC(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + mock := newMockChainIngester() + mock.AddBlock(eth.BlockID{Hash: common.HexToHash("0x01"), Number: 100}) + mock.AddBlock(eth.BlockID{Hash: common.HexToHash("0x02"), Number: 200}) + + backend := NewBackend(context.Background(), BackendParams{ + Logger: logger, + Metrics: metrics.NoopMetrics, + Chains: map[eth.ChainID]ChainIngester{eth.ChainIDFromUInt64(testChainA): mock}, + CrossValidator: &mockCrossValidator{}, + }) + + server := oprpc.NewServer( + "127.0.0.1", + 0, + "test", + oprpc.WithLogger(logger), + ) + server.AddAPI(rpc.API{ + Namespace: "interop", + Service: &QueryFrontend{backend: backend}, + }) + + require.NoError(t, server.Start()) + t.Cleanup(func() { + _ = server.Stop() + }) + + client, err := rpc.Dial("http://" + server.Endpoint()) + require.NoError(t, err) + t.Cleanup(client.Close) + + t.Run("latest selector", func(t *testing.T) { + var result common.Hash + err := client.Call(&result, "interop_getBlockHashByNumber", eth.ChainIDFromUInt64(testChainA), "latest") + require.NoError(t, err) + require.Equal(t, common.HexToHash("0x02"), result) + }) + + t.Run("numeric selector", func(t *testing.T) { + var result common.Hash + err := client.Call(&result, "interop_getBlockHashByNumber", eth.ChainIDFromUInt64(testChainA), rpc.BlockNumber(100)) + require.NoError(t, err) + require.Equal(t, common.HexToHash("0x01"), result) + }) + + t.Run("missing block", func(t *testing.T) { + var result common.Hash + err := client.Call(&result, "interop_getBlockHashByNumber", eth.ChainIDFromUInt64(testChainA), rpc.BlockNumber(999)) + require.ErrorContains(t, err, "not found") + }) + + t.Run("unknown chain", func(t *testing.T) { + var result common.Hash + err := client.Call(&result, "interop_getBlockHashByNumber", eth.ChainIDFromUInt64(999), rpc.BlockNumber(100)) + require.ErrorContains(t, err, "unknown chain") + }) + + t.Run("unsupported tag", func(t *testing.T) { + var result common.Hash + err := client.Call(&result, "interop_getBlockHashByNumber", eth.ChainIDFromUInt64(testChainA), "safe") + require.ErrorContains(t, err, "unsupported block tag") + }) +} diff --git a/op-interop-filter/filter/service.go b/op-interop-filter/filter/service.go index 77449649b52..7f499d28aae 100644 --- a/op-interop-filter/filter/service.go +++ b/op-interop-filter/filter/service.go @@ -59,6 +59,20 @@ func Main(version string) cliapp.LifecycleAction { opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l) l.Info("Initializing op-interop-filter", "version", version) + + if cfg.Passthrough { + l.Warn("PASSTHROUGH MODE ENABLED: all transactions will bypass interop filtering") + } + if cfg.LegacyCheckAccessListFormat { + l.Warn("LEGACY CHECK ACCESS LIST FORMAT ENABLED: interop_checkAccessList will not reject missing executing chain IDs") + } + + if !cfg.MessageExpiryWindowExplicit { + l.Debug("Using default message expiry window", "window", DefaultMessageExpiryWindow) + } else { + l.Debug("Message expiry window configured", "window", time.Duration(cfg.MessageExpiryWindow)*time.Second) + } + return NewService(cliCtx.Context, cfg, l) } } @@ -165,9 +179,8 @@ func (s *Service) initRPCServer(cfg *Config) error { opts..., ) - // Register supervisor query API server.AddAPI(rpc.API{ - Namespace: "supervisor", + Namespace: "interop", Service: &QueryFrontend{backend: s.backend}, Authenticated: false, }) @@ -240,3 +253,36 @@ func (s *Service) Stop(ctx context.Context) error { func (s *Service) Stopped() bool { return s.stopped.Load() } + +// HTTPEndpoint returns the HTTP endpoint of the RPC server, or empty string if not started. +func (s *Service) HTTPEndpoint() string { + if s.rpcServer == nil { + return "" + } + // Include http:// prefix as expected by ProxyAddr + return "http://" + s.rpcServer.Endpoint() +} + +// AdminHTTPEndpoint returns the HTTP endpoint of the admin RPC server, or empty string if not configured. +func (s *Service) AdminHTTPEndpoint() string { + if s.adminRPCServer == nil { + return "" + } + return "http://" + s.adminRPCServer.Endpoint() +} + +// Ready returns true if all chain ingesters have completed backfill. +func (s *Service) Ready() bool { + return s.backend.Ready() +} + +// SetFailsafeEnabled sets the manual failsafe override on the backend. +// Used by tests to toggle failsafe mode without admin RPC/JWT. +func (s *Service) SetFailsafeEnabled(enabled bool) { + s.backend.SetFailsafeEnabled(enabled) +} + +// FailsafeEnabled returns whether failsafe is currently active. +func (s *Service) FailsafeEnabled() bool { + return s.backend.FailsafeEnabled() +} diff --git a/op-interop-filter/flags/flags.go b/op-interop-filter/flags/flags.go index 465aa3d031d..99bb8534c3a 100644 --- a/op-interop-filter/flags/flags.go +++ b/op-interop-filter/flags/flags.go @@ -45,6 +45,69 @@ var ( Value: "", TakesFile: true, } + AdminRPCAddrFlag = &cli.StringFlag{ + Name: "admin.rpc.addr", + Usage: "Address to bind admin RPC server. If empty, admin RPC is disabled.", + EnvVars: prefixEnvVars("ADMIN_RPC_ADDR"), + Value: "", + } + AdminRPCPortFlag = &cli.IntFlag{ + Name: "admin.rpc.port", + Usage: "Port to bind admin RPC server.", + EnvVars: prefixEnvVars("ADMIN_RPC_PORT"), + Value: 8546, + } + RPCAddrFlag = &cli.StringFlag{ + Name: "rpc.addr", + Usage: "RPC listening address", + EnvVars: prefixEnvVars("RPC_ADDR"), + Value: "0.0.0.0", + } + RPCPortFlag = &cli.IntFlag{ + Name: "rpc.port", + Usage: "RPC listening port", + EnvVars: prefixEnvVars("RPC_PORT"), + Value: 8545, + } + PollIntervalFlag = &cli.DurationFlag{ + Name: "poll-interval", + Usage: "Interval for polling new blocks from L2 RPCs (e.g., 2s, 500ms)", + EnvVars: prefixEnvVars("POLL_INTERVAL"), + Value: 2 * time.Second, + } + ValidationIntervalFlag = &cli.DurationFlag{ + Name: "validation-interval", + Usage: "Interval for cross-chain validation loop (e.g., 500ms, 1s)", + EnvVars: prefixEnvVars("VALIDATION_INTERVAL"), + Value: 500 * time.Millisecond, + } + ReorgRecoveryEnabledFlag = &cli.BoolFlag{ + Name: "reorg-recovery-enabled", + Usage: "Automatically resolve reorg-triggered failsafe by rewinding logs DBs to finalized.", + EnvVars: prefixEnvVars("REORG_RECOVERY_ENABLED"), + } + RPCConcurrencyFlag = &cli.IntFlag{ + Name: "rpc-concurrency", + Usage: "Maximum number of concurrent RPC requests per chain", + EnvVars: prefixEnvVars("RPC_CONCURRENCY"), + Value: DefaultRPCConcurrency, + } + FetchConcurrencyFlag = &cli.IntFlag{ + Name: "fetch-concurrency", + Usage: "Number of blocks to fetch concurrently during ingestion. Must be <= rpc-concurrency.", + EnvVars: prefixEnvVars("FETCH_CONCURRENCY"), + Value: DefaultFetchConcurrency, + } + SupportLegacyCheckAccessListFormatFlag = &cli.BoolFlag{ + Name: "support-legacy-check-access-list-format", + Usage: "Support legacy interop_checkAccessList requests that omit executing chainID. DANGEROUS: intended only for compatibility with legacy clients; access-list source-chain validation still runs.", + EnvVars: prefixEnvVars("SUPPORT_LEGACY_CHECK_ACCESS_LIST_FORMAT"), + } + DangerouslyEnablePassthroughFlag = &cli.BoolFlag{ + Name: "dangerously-enable-passthrough", + Usage: "Allow all transactions through without interop filtering. DANGEROUS: disables all executing message validation.", + EnvVars: prefixEnvVars("DANGEROUSLY_ENABLE_PASSTHROUGH"), + } ) var requiredFlags = []cli.Flag{ diff --git a/op-node/README.md b/op-node/README.md index 4729ef3aeaa..0398eb86f58 100644 --- a/op-node/README.md +++ b/op-node/README.md @@ -8,7 +8,7 @@ Pull requests: User docs: -- [How to run a node](https://docs.optimism.io/builders/node-operators/rollup-node) +- [How to run a node](https://docs.optimism.io/node-operators/tutorials/run-node-from-source) Specs: diff --git a/op-node/chaincfg/chains_test.go b/op-node/chaincfg/chains_test.go index dd0ff560cab..6b97dec6ca8 100644 --- a/op-node/chaincfg/chains_test.go +++ b/op-node/chaincfg/chains_test.go @@ -60,26 +60,25 @@ var mainnetCfg = rollup.Config{ GasLimit: 30_000_000, }, }, - BlockTime: 2, - MaxSequencerDrift: 600, - SeqWindowSize: 3600, - ChannelTimeoutBedrock: 300, - L1ChainID: big.NewInt(1), - L2ChainID: big.NewInt(10), - BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"), - DepositContractAddress: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"), - L1SystemConfigAddress: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"), - RegolithTime: u64Ptr(0), - CanyonTime: u64Ptr(1704992401), - DeltaTime: u64Ptr(1708560000), - EcotoneTime: u64Ptr(1710374401), - FjordTime: u64Ptr(1720627201), - GraniteTime: u64Ptr(1726070401), - HoloceneTime: u64Ptr(1736445601), - IsthmusTime: u64Ptr(1746806401), - JovianTime: u64Ptr(1764691201), - ProtocolVersionsAddress: common.HexToAddress("0x8062AbC286f5e7D9428a0Ccb9AbD71e50d93b935"), - ChainOpConfig: defaultOpConfig, + BlockTime: 2, + MaxSequencerDrift: 600, + SeqWindowSize: 3600, + ChannelTimeoutBedrock: 300, + L1ChainID: big.NewInt(1), + L2ChainID: big.NewInt(10), + BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"), + DepositContractAddress: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"), + L1SystemConfigAddress: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"), + RegolithTime: u64Ptr(0), + CanyonTime: u64Ptr(1704992401), + DeltaTime: u64Ptr(1708560000), + EcotoneTime: u64Ptr(1710374401), + FjordTime: u64Ptr(1720627201), + GraniteTime: u64Ptr(1726070401), + HoloceneTime: u64Ptr(1736445601), + IsthmusTime: u64Ptr(1746806401), + JovianTime: u64Ptr(1764691201), + ChainOpConfig: defaultOpConfig, } var sepoliaCfg = rollup.Config{ @@ -100,27 +99,26 @@ var sepoliaCfg = rollup.Config{ GasLimit: 30000000, }, }, - BlockTime: 2, - MaxSequencerDrift: 600, - SeqWindowSize: 3600, - ChannelTimeoutBedrock: 300, - L1ChainID: big.NewInt(11155111), - L2ChainID: big.NewInt(11155420), - BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000011155420"), - DepositContractAddress: common.HexToAddress("0x16fc5058f25648194471939df75cf27a2fdc48bc"), - L1SystemConfigAddress: common.HexToAddress("0x034edd2a225f7f429a63e0f1d2084b9e0a93b538"), - RegolithTime: u64Ptr(0), - CanyonTime: u64Ptr(1699981200), - DeltaTime: u64Ptr(1703203200), - EcotoneTime: u64Ptr(1708534800), - FjordTime: u64Ptr(1716998400), - GraniteTime: u64Ptr(1723478400), - HoloceneTime: u64Ptr(1732633200), - PectraBlobScheduleTime: u64Ptr(1742486400), - IsthmusTime: u64Ptr(1744905600), - JovianTime: u64Ptr(1763568001), - ProtocolVersionsAddress: common.HexToAddress("0x79ADD5713B383DAa0a138d3C4780C7A1804a8090"), - ChainOpConfig: defaultOpConfig, + BlockTime: 2, + MaxSequencerDrift: 600, + SeqWindowSize: 3600, + ChannelTimeoutBedrock: 300, + L1ChainID: big.NewInt(11155111), + L2ChainID: big.NewInt(11155420), + BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000011155420"), + DepositContractAddress: common.HexToAddress("0x16fc5058f25648194471939df75cf27a2fdc48bc"), + L1SystemConfigAddress: common.HexToAddress("0x034edd2a225f7f429a63e0f1d2084b9e0a93b538"), + RegolithTime: u64Ptr(0), + CanyonTime: u64Ptr(1699981200), + DeltaTime: u64Ptr(1703203200), + EcotoneTime: u64Ptr(1708534800), + FjordTime: u64Ptr(1716998400), + GraniteTime: u64Ptr(1723478400), + HoloceneTime: u64Ptr(1732633200), + PectraBlobScheduleTime: u64Ptr(1742486400), + IsthmusTime: u64Ptr(1744905600), + JovianTime: u64Ptr(1763568001), + ChainOpConfig: defaultOpConfig, } var sepoliaDev0Cfg = rollup.Config{ @@ -141,27 +139,26 @@ var sepoliaDev0Cfg = rollup.Config{ GasLimit: 30000000, }, }, - BlockTime: 2, - MaxSequencerDrift: 600, - SeqWindowSize: 3600, - ChannelTimeoutBedrock: 300, - L1ChainID: big.NewInt(11155111), - L2ChainID: big.NewInt(11155421), - BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000011155421"), - DepositContractAddress: common.HexToAddress("0x76114bd29dFcC7a9892240D317E6c7C2A281Ffc6"), - L1SystemConfigAddress: common.HexToAddress("0xa6b72407e2dc9EBF84b839B69A24C88929cf20F7"), - RegolithTime: u64Ptr(0), - CanyonTime: u64Ptr(0), - DeltaTime: u64Ptr(0), - EcotoneTime: u64Ptr(1706634000), - FjordTime: u64Ptr(1715961600), - GraniteTime: u64Ptr(1723046400), - HoloceneTime: u64Ptr(1731682800), - PectraBlobScheduleTime: u64Ptr(1741687200), - IsthmusTime: u64Ptr(1744300800), - JovianTime: u64Ptr(1762185600), - ProtocolVersionsAddress: common.HexToAddress("0x252CbE9517F731C618961D890D534183822dcC8d"), - ChainOpConfig: defaultOpConfig, + BlockTime: 2, + MaxSequencerDrift: 600, + SeqWindowSize: 3600, + ChannelTimeoutBedrock: 300, + L1ChainID: big.NewInt(11155111), + L2ChainID: big.NewInt(11155421), + BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000011155421"), + DepositContractAddress: common.HexToAddress("0x76114bd29dFcC7a9892240D317E6c7C2A281Ffc6"), + L1SystemConfigAddress: common.HexToAddress("0xa6b72407e2dc9EBF84b839B69A24C88929cf20F7"), + RegolithTime: u64Ptr(0), + CanyonTime: u64Ptr(0), + DeltaTime: u64Ptr(0), + EcotoneTime: u64Ptr(1706634000), + FjordTime: u64Ptr(1715961600), + GraniteTime: u64Ptr(1723046400), + HoloceneTime: u64Ptr(1731682800), + PectraBlobScheduleTime: u64Ptr(1741687200), + IsthmusTime: u64Ptr(1744300800), + JovianTime: u64Ptr(1762185600), + ChainOpConfig: defaultOpConfig, } func u64Ptr(v uint64) *uint64 { diff --git a/op-node/config/config.go b/op-node/config/config.go index cff0cd51677..d7ba041408a 100644 --- a/op-node/config/config.go +++ b/op-node/config/config.go @@ -73,11 +73,7 @@ type Config struct { Sync sync.Config - // To halt when detecting the node does not support a signaled protocol version - // change of the given severity (major/minor/patch). Disabled if empty. - RollupHalt string - - // Cancel to request a premature shutdown of the node itself, e.g. when halting. This may be nil. + // Cancel to request a premature shutdown of the node itself. This may be nil. Cancel context.CancelCauseFunc // Conductor is used to determine this node is the leader sequencer. @@ -169,9 +165,6 @@ func (cfg *Config) Check() error { return fmt.Errorf("p2p config error: %w", err) } } - if !(cfg.RollupHalt == "" || cfg.RollupHalt == "major" || cfg.RollupHalt == "minor" || cfg.RollupHalt == "patch") { - return fmt.Errorf("invalid rollup halting option: %q", cfg.RollupHalt) - } if cfg.ConductorEnabled { if state, _ := cfg.ConfigPersistence.SequencerState(); state != StateUnset { return fmt.Errorf("config persistence must be disabled when conductor is enabled") diff --git a/op-node/flags/flags.go b/op-node/flags/flags.go index a5d29e20846..abf13614986 100644 --- a/op-node/flags/flags.go +++ b/op-node/flags/flags.go @@ -331,18 +331,6 @@ var ( Category: OperationsCategory, Hidden: true, } - RollupHalt = &cli.StringFlag{ - Name: "rollup.halt", - Usage: "Opt-in option to halt on incompatible protocol version requirements of the given level (major/minor/patch/none), as signaled onchain in L1", - EnvVars: prefixEnvVars("ROLLUP_HALT"), - Category: RollupCategory, - } - RollupLoadProtocolVersions = &cli.BoolFlag{ - Name: "rollup.load-protocol-versions", - Usage: "Load protocol versions from the superchain L1 ProtocolVersions contract (if available), and report in logs and metrics", - EnvVars: prefixEnvVars("ROLLUP_LOAD_PROTOCOL_VERSIONS"), - Category: RollupCategory, - } SafeDBPath = &cli.StringFlag{ Name: "safedb.path", Usage: "File path used to persist safe head update data. Disabled if not set.", @@ -497,8 +485,6 @@ var optionalFlags = []cli.Flag{ HeartbeatEnabledFlag, HeartbeatMonikerFlag, HeartbeatURLFlag, - RollupHalt, - RollupLoadProtocolVersions, ConductorEnabledFlag, ConductorRpcFlag, ConductorRpcTimeoutFlag, diff --git a/op-node/metrics/metrics.go b/op-node/metrics/metrics.go index bc147643936..90281edd882 100644 --- a/op-node/metrics/metrics.go +++ b/op-node/metrics/metrics.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum-optimism/optimism/op-node/p2p/store" ophttp "github.com/ethereum-optimism/optimism/op-service/httputil" "github.com/ethereum-optimism/optimism/op-service/metrics" - "github.com/ethereum/go-ethereum/params" pb "github.com/libp2p/go-libp2p-pubsub/pb" libp2pmetrics "github.com/libp2p/go-libp2p/core/metrics" @@ -71,7 +70,6 @@ type Metricer interface { RecordIPUnban() RecordDial(allow bool) RecordAccept(allow bool) - ReportProtocolVersions(local, engine, recommended, required params.ProtocolVersion) } // Metrics tracks all the metrics for the op-node. @@ -145,12 +143,6 @@ type Metrics struct { ChannelInputBytes prometheus.Counter - // Protocol version reporting - // Delta = params.ProtocolVersionComparison - ProtocolVersionDelta *prometheus.GaugeVec - // ProtocolVersions is pseudo-metric to report the exact protocol version info - ProtocolVersions *prometheus.GaugeVec - registry *prometheus.Registry factory metrics.Factory } @@ -378,24 +370,6 @@ func NewMetrics(procName string) *Metrics { Help: "Number of sequencer block sealing jobs", }), - ProtocolVersionDelta: factory.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: ns, - Name: "protocol_version_delta", - Help: "Difference between local and global protocol version, and execution-engine, per type of version", - }, []string{ - "type", - }), - ProtocolVersions: factory.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: ns, - Name: "protocol_versions", - Help: "Pseudo-metric tracking recommended and required protocol version info", - }, []string{ - "local", - "engine", - "recommended", - "required", - }), - AltDAMetrics: altda.MakeMetrics(ns, factory), registry: registry, @@ -634,13 +608,6 @@ func (m *Metrics) RecordAccept(allow bool) { m.Accepts.WithLabelValues("false").Inc() } } -func (m *Metrics) ReportProtocolVersions(local, engine, recommended, required params.ProtocolVersion) { - m.ProtocolVersionDelta.WithLabelValues("local_recommended").Set(float64(local.Compare(recommended))) - m.ProtocolVersionDelta.WithLabelValues("local_required").Set(float64(local.Compare(required))) - m.ProtocolVersionDelta.WithLabelValues("engine_recommended").Set(float64(engine.Compare(recommended))) - m.ProtocolVersionDelta.WithLabelValues("engine_required").Set(float64(engine.Compare(required))) - m.ProtocolVersions.WithLabelValues(local.String(), engine.String(), recommended.String(), required.String()).Set(1) -} type noopMetricer struct { metrics.NoopRPCMetrics @@ -766,5 +733,3 @@ func (n *noopMetricer) RecordDial(allow bool) { func (n *noopMetricer) RecordAccept(allow bool) { } -func (n *noopMetricer) ReportProtocolVersions(local, engine, recommended, required params.ProtocolVersion) { -} diff --git a/op-node/node/node.go b/op-node/node/node.go index 12f83da10cb..4aa2f7398cd 100644 --- a/op-node/node/node.go +++ b/op-node/node/node.go @@ -128,8 +128,6 @@ type OpNode struct { safeDB closableSafeDB - rollupHalt string // when to halt the rollup, disabled if empty - pprofService *oppprof.Service metricsSrv *httputil.HTTPServer @@ -147,9 +145,8 @@ type OpNode struct { closed atomic.Bool - // cancels execution prematurely, e.g. to halt. This may be nil. + // cancels execution prematurely. This may be nil. cancel context.CancelCauseFunc - halted atomic.Bool tracer tracer.Tracer // used for testing PublishBlock and SignAndPublishL2Payload } @@ -176,7 +173,6 @@ func NewWithOverride(ctx context.Context, cfg *config.Config, log log.Logger, ap clock: clk, appVersion: appVersion, metrics: m, - rollupHalt: cfg.RollupHalt, cancel: cfg.Cancel, tracer: cfg.Tracer, } @@ -256,7 +252,7 @@ func (n *OpNode) init(ctx context.Context, cfg *config.Config, overrides Initial return fmt.Errorf("failed to init L1 Source: %w", err) } - // initRuntimeConfig relies on side effects to set the runCfg, node.halted and call node.cancel if needed + // initRuntimeConfig relies on side effects to set the runCfg if err := initRuntimeConfig(ctx, cfg, n); err != nil { return fmt.Errorf("failed to init the runtime config: %w", err) } @@ -400,8 +396,7 @@ func initL1Handlers(cfg *config.Config, node *OpNode) (ethereum.Subscription, et // note: this function relies on side effects to set node.runCfg func initRuntimeConfig(ctx context.Context, cfg *config.Config, node *OpNode) error { // attempt to load runtime config, repeat N times - runCfg := runcfg.NewRuntimeConfig(node.log, node.l1Source, &cfg.Rollup, cfg.HemitrapEnabled) - // Set node.runCfg early so handleProtocolVersionsUpdate can access it during initialization + runCfg := runcfg.NewRuntimeConfig(node.log, node.l1Source, &cfg.Rollup) node.runCfg = runCfg confDepth := cfg.Driver.VerifierConfDepth @@ -435,16 +430,12 @@ func initRuntimeConfig(ctx context.Context, cfg *config.Config, node *OpNode) er return l1Head, err } - err = node.handleProtocolVersionsUpdate(ctx) - return l1Head, err + return l1Head, nil } // initialize the runtime config before unblocking if err := retry.Do0(ctx, 5, retry.Fixed(time.Second*10), func() error { _, err := reload(ctx) - if errors.Is(err, errNodeHalt) { // don't retry on halt error - err = nil - } return err }); err != nil { return fmt.Errorf("failed to load runtime configuration repeatedly, last error: %w", err) @@ -465,17 +456,7 @@ func initRuntimeConfig(ctx context.Context, cfg *config.Config, node *OpNode) er // Missing a runtime-config update is not critical, and we do not want to overwhelm the L1 RPC. l1Head, err := reload(ctx) if err != nil { - if errors.Is(err, errNodeHalt) { - node.halted.Store(true) - if node.cancel != nil { // node cancellation is always available when started as CLI app - node.cancel(errNodeHalt) - return - } else { - node.log.Debug("opted to halt, but cannot halt node", "l1_head", l1Head) - } - } else { - node.log.Warn("failed to reload runtime config", "err", err) - } + node.log.Warn("failed to reload runtime config", "err", err) } else { node.log.Debug("reloaded runtime config", "l1_head", l1Head) } @@ -955,18 +936,7 @@ func (n *OpNode) Stop(ctx context.Context) error { n.closed.Store(true) } - if n.halted.Load() { - // if we had a halt upon initialization, idle for a while, with open metrics, to prevent a rapid restart-loop - tim := time.NewTimer(time.Minute * 5) - n.log.Warn("halted, idling to avoid immediate shutdown repeats") - defer tim.Stop() - select { - case <-tim.C: - case <-ctx.Done(): - } - } - - // Close metrics and pprof only after we are done idling + // Close metrics and pprof if n.pprofService != nil { if err := n.pprofService.Stop(ctx); err != nil { result = multierror.Append(result, fmt.Errorf("failed to close pprof server: %w", err)) diff --git a/op-node/node/runcfg/runtime_config.go b/op-node/node/runcfg/runtime_config.go index ecd38acb138..2842f92fc69 100644 --- a/op-node/node/runcfg/runtime_config.go +++ b/op-node/node/runcfg/runtime_config.go @@ -7,26 +7,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" ) -var ( - // UnsafeBlockSignerAddressSystemConfigStorageSlot is the storage slot identifier of the unsafeBlockSigner - // `address` storage value in the SystemConfig L1 contract. Computed as `keccak256("systemconfig.unsafeblocksigner")` - UnsafeBlockSignerAddressSystemConfigStorageSlot = common.HexToHash("0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08") - - // RequiredProtocolVersionStorageSlot is the storage slot that the required protocol version is stored at. - // Computed as: `bytes32(uint256(keccak256("protocolversion.required")) - 1)` - RequiredProtocolVersionStorageSlot = common.HexToHash("0x4aaefe95bd84fd3f32700cf3b7566bc944b73138e41958b5785826df2aecace0") - - // RecommendedProtocolVersionStorageSlot is the storage slot that the recommended protocol version is stored at. - // Computed as: `bytes32(uint256(keccak256("protocolversion.recommended")) - 1)` - RecommendedProtocolVersionStorageSlot = common.HexToHash("0xe314dfc40f0025322aacc0ba8ef420b62fb3b702cf01e0cdf3d829117ac2ff1a") -) +// UnsafeBlockSignerAddressSystemConfigStorageSlot is the storage slot identifier of the unsafeBlockSigner +// `address` storage value in the SystemConfig L1 contract. Computed as `keccak256("systemconfig.unsafeblocksigner")` +var UnsafeBlockSignerAddressSystemConfigStorageSlot = common.HexToHash("0x65a7ed542fb37fe237fdfbdd70b31598523fe5b32879e307bae27a0bd9581c08") type RuntimeCfgL1Source interface { ReadStorageAt(ctx context.Context, address common.Address, storageSlot common.Hash, blockHash common.Hash) (common.Hash, error) @@ -34,8 +23,6 @@ type RuntimeCfgL1Source interface { type ReadonlyRuntimeConfig interface { P2PSequencerAddress() common.Address - RequiredProtocolVersion() params.ProtocolVersion - RecommendedProtocolVersion() params.ProtocolVersion } // RuntimeConfig maintains runtime-configurable options. @@ -62,10 +49,6 @@ type RuntimeConfig struct { // runtimeConfigData is a flat bundle of configurable data, easy and light to copy around. type runtimeConfigData struct { p2pBlockSignerAddr common.Address - - // superchain protocol version signals - recommended params.ProtocolVersion - required params.ProtocolVersion } var _ p2p.GossipRuntimeConfig = (*RuntimeConfig)(nil) @@ -85,18 +68,6 @@ func (r *RuntimeConfig) P2PSequencerAddress() common.Address { return r.p2pBlockSignerAddr } -func (r *RuntimeConfig) RequiredProtocolVersion() params.ProtocolVersion { - r.mu.RLock() - defer r.mu.RUnlock() - return r.required -} - -func (r *RuntimeConfig) RecommendedProtocolVersion() params.ProtocolVersion { - r.mu.RLock() - defer r.mu.RUnlock() - return r.recommended -} - // Load resets the runtime configuration by fetching the latest config data from L1 at the given L1 block. // Load is safe to call concurrently, but will lock the runtime configuration modifications only, // and will thus not block other Load calls with possibly alternative L1 block views. @@ -105,26 +76,10 @@ func (r *RuntimeConfig) Load(ctx context.Context, l1Ref eth.L1BlockRef) error { if err != nil { return fmt.Errorf("failed to fetch unsafe block signing address from system config: %w", err) } - // The superchain protocol version data is optional; only applicable to rollup configs that specify a ProtocolVersions address. - var requiredProtVersion, recommendedProtoVersion params.ProtocolVersion - if r.rollupCfg.ProtocolVersionsAddress != (common.Address{}) { - requiredVal, err := r.l1Client.ReadStorageAt(ctx, r.rollupCfg.ProtocolVersionsAddress, RequiredProtocolVersionStorageSlot, l1Ref.Hash) - if err != nil { - return fmt.Errorf("required-protocol-version value failed to load from L1 contract: %w", err) - } - requiredProtVersion = params.ProtocolVersion(requiredVal) - recommendedVal, err := r.l1Client.ReadStorageAt(ctx, r.rollupCfg.ProtocolVersionsAddress, RecommendedProtocolVersionStorageSlot, l1Ref.Hash) - if err != nil { - return fmt.Errorf("recommended-protocol-version value failed to load from L1 contract: %w", err) - } - recommendedProtoVersion = params.ProtocolVersion(recommendedVal) - } r.mu.Lock() defer r.mu.Unlock() r.l1Ref = l1Ref r.p2pBlockSignerAddr = common.BytesToAddress(p2pSignerVal[:]) - r.required = requiredProtVersion - r.recommended = recommendedProtoVersion r.log.Info("loaded new runtime config values!", "p2p_seq_address", r.p2pBlockSignerAddr) return nil } diff --git a/op-node/node/superchain.go b/op-node/node/superchain.go deleted file mode 100644 index 071198f6f76..00000000000 --- a/op-node/node/superchain.go +++ /dev/null @@ -1,75 +0,0 @@ -package node - -import ( - "context" - "errors" - - "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum/go-ethereum/eth/catalyst" - "github.com/ethereum/go-ethereum/params" -) - -var errNodeHalt = errors.New("opted to halt, unprepared for protocol change") - -func (n *OpNode) handleProtocolVersionsUpdate(ctx context.Context) error { - recommended := n.runCfg.RecommendedProtocolVersion() - required := n.runCfg.RequiredProtocolVersion() - // if the protocol version sources are disabled we do not process them - if recommended == (params.ProtocolVersion{}) && required == (params.ProtocolVersion{}) { - return nil - } - local := rollup.OPStackSupport - // forward to execution engine, and get back the protocol version that op-geth supports - engineSupport, err := n.l2Source.SignalSuperchainV1(ctx, recommended, required) - if err != nil { - n.log.Warn("failed to notify engine of protocol version", "err", err) - // engineSupport may still be available, or otherwise zero to signal as unknown - } else { - catalyst.LogProtocolVersionSupport(n.log.New("node", "op-node"), engineSupport, recommended, "recommended") - catalyst.LogProtocolVersionSupport(n.log.New("node", "op-node"), engineSupport, required, "required") - } - n.metrics.ReportProtocolVersions(local, engineSupport, recommended, required) - catalyst.LogProtocolVersionSupport(n.log.New("node", "engine"), local, recommended, "recommended") - catalyst.LogProtocolVersionSupport(n.log.New("node", "engine"), local, required, "required") - - // We may need to halt the node, if the user opted in to handling incompatible protocol-version signals - return n.haltMaybe() -} - -// haltMaybe returns errNodeHalt if the runtime config indicates an incompatible required protocol change -// and the node is configured to opt-in to halting at this protocol-change level. -func (n *OpNode) haltMaybe() error { - local := rollup.OPStackSupport - required := n.runCfg.RequiredProtocolVersion() - if haltMaybe(n.rollupHalt, local.Compare(required)) { // halt if we opted in to do so at this granularity - n.log.Error("Opted to halt, unprepared for protocol change", "required", required, "local", local) - // Avoid deadlocking the runtime config reloader by closing the OpNode elsewhere - return errNodeHalt - } - return nil -} - -// haltMaybe returns true when we should halt, given the halt-option and required-version comparison -func haltMaybe(haltOption string, reqCmp params.ProtocolVersionComparison) bool { - var needLevel int - switch haltOption { - case "major": - needLevel = 3 - case "minor": - needLevel = 2 - case "patch": - needLevel = 1 - default: - return false // do not consider halting if not configured to - } - haveLevel := 0 - switch reqCmp { - case params.OutdatedMajor: - haveLevel = 3 - case params.OutdatedMinor: - haveLevel = 2 - case params.OutdatedPatch: - haveLevel = 1 - } - return haveLevel >= needLevel -} diff --git a/op-node/node/superchain_test.go b/op-node/node/superchain_test.go deleted file mode 100644 index 021d14611a3..00000000000 --- a/op-node/node/superchain_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package node - -import ( - "slices" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/params" -) - -func TestHaltMaybe(t *testing.T) { - haltTest := func(opt string, halts ...params.ProtocolVersionComparison) { - t.Run(opt, func(t *testing.T) { - for _, h := range []params.ProtocolVersionComparison{ - params.AheadMajor, - params.OutdatedMajor, - params.AheadMinor, - params.OutdatedMinor, - params.AheadPatch, - params.OutdatedPatch, - params.AheadPrerelease, - params.OutdatedPrerelease, - params.Matching, - params.DiffVersionType, - params.DiffBuild, - params.EmptyVersion, - } { - expectedHalt := slices.Contains(halts, h) - gotHalt := haltMaybe(opt, h) - require.Equal(t, expectedHalt, gotHalt, "%s %d", opt, h) - } - }) - } - haltTest("") - haltTest("major", params.OutdatedMajor) - haltTest("minor", params.OutdatedMajor, params.OutdatedMinor) - haltTest("patch", params.OutdatedMajor, params.OutdatedMinor, params.OutdatedPatch) -} diff --git a/op-node/rollup/chain_spec_test.go b/op-node/rollup/chain_spec_test.go index 8e488665d15..9e7d5890831 100644 --- a/op-node/rollup/chain_spec_test.go +++ b/op-node/rollup/chain_spec_test.go @@ -34,27 +34,27 @@ var testConfig = Config{ GasLimit: 30_000_000, }, }, - BlockTime: 2, - MaxSequencerDrift: 600, - SeqWindowSize: 3600, - ChannelTimeoutBedrock: 300, - L1ChainID: big.NewInt(1), - L2ChainID: big.NewInt(10), - RegolithTime: u64ptr(10), - CanyonTime: u64ptr(20), - DeltaTime: u64ptr(30), - EcotoneTime: u64ptr(40), - FjordTime: u64ptr(50), - GraniteTime: u64ptr(60), - HoloceneTime: u64ptr(70), - IsthmusTime: u64ptr(80), - JovianTime: u64ptr(90), - InteropTime: u64ptr(100), - BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"), - DepositContractAddress: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"), - L1SystemConfigAddress: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"), - ProtocolVersionsAddress: common.HexToAddress("0x8062AbC286f5e7D9428a0Ccb9AbD71e50d93b935"), - AltDAConfig: nil, + BlockTime: 2, + MaxSequencerDrift: 600, + SeqWindowSize: 3600, + ChannelTimeoutBedrock: 300, + L1ChainID: big.NewInt(1), + L2ChainID: big.NewInt(10), + RegolithTime: u64ptr(10), + CanyonTime: u64ptr(20), + DeltaTime: u64ptr(30), + EcotoneTime: u64ptr(40), + FjordTime: u64ptr(50), + GraniteTime: u64ptr(60), + HoloceneTime: u64ptr(70), + IsthmusTime: u64ptr(80), + JovianTime: u64ptr(90), + KarstTime: u64ptr(100), + InteropTime: u64ptr(110), + BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000010"), + DepositContractAddress: common.HexToAddress("0xbEb5Fc579115071764c7423A4f12eDde41f106Ed"), + L1SystemConfigAddress: common.HexToAddress("0x229047fed2591dbec1eF1118d64F7aF3dB9EB290"), + AltDAConfig: nil, } func TestChainSpec_CanyonForkActivation(t *testing.T) { diff --git a/op-node/rollup/derive/interop_upgrade_transactions.go b/op-node/rollup/derive/interop_upgrade_transactions.go index bed2a600b88..0c91860499f 100644 --- a/op-node/rollup/derive/interop_upgrade_transactions.go +++ b/op-node/rollup/derive/interop_upgrade_transactions.go @@ -18,6 +18,22 @@ var ( CrossL2InboxAddress = crypto.CreateAddress(crossL2InboxDeployerAddress, 0) crossL2InboxDeploymentBytecode = common.FromHex("0x6080604052348015600e575f80fd5b506106828061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c8063331b637f1461004357806354fd4d5014610069578063ab4d6f75146100b2575b5f80fd5b610056610051366004610512565b6100c7565b6040519081526020015b60405180910390f35b6100a56040518060400160405280600581526020017f312e302e3100000000000000000000000000000000000000000000000000000081525081565b604051610060919061053b565b6100c56100c036600461058e565b61039e565b005b5f67ffffffffffffffff801683602001511115610110576040517fd1f79e8200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604083015163ffffffff1015610152576040517f94338eba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606083015167ffffffffffffffff1015610198576040517f596a19a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82516040515f916101dd91859060200160609290921b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000168252601482015260340190565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00181528282528051602091820120878201516060890151898501515f9487018590527fffffffffffffffff00000000000000000000000000000000000000000000000060c084811b8216602c8a015283901b1660348801527fffffffff0000000000000000000000000000000000000000000000000000000060e082901b16603c88015292965090949093919291016040516020818303038152906040526102ac906105bc565b90505f85826040516020016102cb929190918252602082015260400190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828252805160209182012060808d01519184018190529183015291505f90606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0300000000000000000000000000000000000000000000000000000000000000179a9950505050505050505050565b5f6103b76103b136859003850185610601565b836100c7565b90505f6103c38261043b565b509050806103fd576040517fe3c0081600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b827f5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba78560405161042d9190610622565b60405180910390a250505050565b5f805a835491505a6103e891031115939092509050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610475575f80fd5b919050565b5f60a0828403121561048a575f80fd5b60405160a0810181811067ffffffffffffffff821117156104d2577f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6040529050806104e183610452565b8152602083013560208201526040830135604082015260608301356060820152608083013560808201525092915050565b5f8060c08385031215610523575f80fd5b61052d848461047a565b9460a0939093013593505050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b5f8082840360c08112156105a0575f80fd5b60a08112156105ad575f80fd5b50919360a08501359350915050565b805160208083015191908110156105fb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b5f60a08284031215610611575f80fd5b61061b838361047a565b9392505050565b60a0810173ffffffffffffffffffffffffffffffffffffffff61064484610452565b168252602083013560208301526040830135604083015260608301356060830152608083013560808301529291505056fea164736f6c6343000819000a") + // SuperchainETHBridge Parameters + deploySuperchainETHBridgeSource = UpgradeDepositSource{Intent: "Interop: SuperchainETHBridge Deployment"} + updateSuperchainETHBridgeProxySource = UpgradeDepositSource{Intent: "Interop: SuperchainETHBridge Proxy Update"} + superchainETHBridgeDeployerAddress = common.HexToAddress("0x4220000000000000000000000000000000000002") + SuperchainETHBridgeAddress = crypto.CreateAddress(superchainETHBridgeDeployerAddress, 0) + superchainETHBridgeDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b5061078b806100206000396000f3fe6080604052600436106100345760003560e01c80634f0edcc91461003957806354fd4d501461005b57806364a197f3146100ba575b600080fd5b34801561004557600080fd5b506100596100543660046105ae565b6100db565b005b34801561006757600080fd5b506100a46040518060400160405280600581526020017f312e302e3100000000000000000000000000000000000000000000000000000081525081565b6040516100b1919061065a565b60405180910390f35b6100cd6100c8366004610674565b610340565b6040519081526020016100b1565b3373420000000000000000000000000000000000002314610128576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008073420000000000000000000000000000000000002373ffffffffffffffffffffffffffffffffffffffff16637936cbee6040518163ffffffff1660e01b81526004016040805180830381865afa158015610189573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ad91906106a0565b909250905073ffffffffffffffffffffffffffffffffffffffff82163014610201576040517fbc22e2aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa0712d68000000000000000000000000000000000000000000000000000000008152600481018490527342000000000000000000000000000000000000259063a0712d6890602401600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b50505050828460405161028d9061057d565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156102c7573d6000803e3d6000fd5b50508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d3598584604051610331929190918252602082015260400190565b60405180910390a35050505050565b600073ffffffffffffffffffffffffffffffffffffffff831661038f576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002573ffffffffffffffffffffffffffffffffffffffff166344df8e70346040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103eb57600080fd5b505af11580156103ff573d6000803e3d6000fd5b50506040805133602482015273ffffffffffffffffffffffffffffffffffffffff881660448201523460648083019190915282518083039091018152608490910182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f4f0edcc90000000000000000000000000000000000000000000000000000000017905290517f7056f41f0000000000000000000000000000000000000000000000000000000081527342000000000000000000000000000000000000239450637056f41f93506104de9250869130916004016106ce565b6020604051808303816000875af11580156104fd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610521919061070c565b604080513481526020810185905291925073ffffffffffffffffffffffffffffffffffffffff85169133917fed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402910160405180910390a392915050565b60598061072683390190565b73ffffffffffffffffffffffffffffffffffffffff811681146105ab57600080fd5b50565b6000806000606084860312156105c357600080fd5b83356105ce81610589565b925060208401356105de81610589565b929592945050506040919091013590565b6000815180845260005b81811015610615576020818501810151868301820152016105f9565b81811115610627576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061066d60208301846105ef565b9392505050565b6000806040838503121561068757600080fd5b823561069281610589565b946020939093013593505050565b600080604083850312156106b357600080fd5b82516106be81610589565b6020939093015192949293505050565b83815273ffffffffffffffffffffffffffffffffffffffff8316602082015260606040820152600061070360608301846105ef565b95945050505050565b60006020828403121561071e57600080fd5b505191905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a") + + // ETHLiquidity Parameters + deployETHLiquiditySource = UpgradeDepositSource{Intent: "Interop: ETHLiquidity Deployment"} + updateETHLiquidityProxySource = UpgradeDepositSource{Intent: "Interop: ETHLiquidity Proxy Update"} + ethLiquidityDeployerAddress = common.HexToAddress("0x4220000000000000000000000000000000000003") + ETHLiquidityAddress = crypto.CreateAddress(ethLiquidityDeployerAddress, 0) + ethLiquidityDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b50610393806100206000396000f3fe60806040526004361061003f5760003560e01c806344df8e701461004457806354fd4d501461004e578063a0712d68146100ad578063b60d4288146100cd575b600080fd5b61004c6100d5565b005b34801561005a57600080fd5b506100976040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b6040516100a491906102a1565b60405180910390f35b3480156100b957600080fd5b5061004c6100c8366004610314565b61015a565b61004c610229565b3373420000000000000000000000000000000000002414610122576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907f875e07afd7ce17c6531b1a6b7b34829dcd8b7e6639448afbd6a8e29fa1422b82906020015b60405180910390a2565b33734200000000000000000000000000000000000024146101a7576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80336040516101b590610295565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156101ef573d6000803e3d6000fd5b505060405181815233907f85719716ac5bd2744ae7ed3d16702129383049b97123b506320e7a5826ebbbba9060200160405180910390a250565b34600003610263576040517f2c5211c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907fbb9e497a5b82d1a37f9496dd70c6efb97ba0d98c66c3422d05010105d063359890602001610150565b60598061032e83390190565b600060208083528351808285015260005b818110156102ce578581018301518582016040015282016102b2565b818111156102e0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561032657600080fd5b503591905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a") + interopETHLiquidityFundingSource = UpgradeDepositSource{Intent: "Interop: ETHLiquidity Funding"} + interopETHLiquidityFundingCalldata = crypto.Keccak256([]byte("fund()"))[:4] + // L2ToL2CrossDomainMessenger Parameters deployL2ToL2MessengerSource = UpgradeDepositSource{Intent: "Interop: L2ToL2CrossDomainMessenger Deployment"} updateL2ToL2MessengerProxySource = UpgradeDepositSource{Intent: "Interop: L2ToL2CrossDomainMessenger Proxy Update"} @@ -26,8 +42,17 @@ var ( l2ToL2MessengerDeploymentBytecode = common.FromHex("0x6080604052348015600e575f80fd5b506111928061001c5f395ff3fe6080604052600436106100b8575f3560e01c80637056f41f116100715780638d1d298f1161004c5780638d1d298f14610253578063b1b1b20914610266578063ecc7042814610294575f80fd5b80637056f41f146101b65780637936cbee146101d557806382e3702d14610215575f80fd5b806352617f3c116100a157806352617f3c1461011c57806354fd4d50146101425780636b0c3c5e14610197575f80fd5b806324794462146100bc57806338ffde18146100e3575b5f80fd5b3480156100c7575f80fd5b506100d06102c8565b6040519081526020015b60405180910390f35b3480156100ee575f80fd5b506100f7610347565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100da565b348015610127575f80fd5b5061012f5f81565b60405161ffff90911681526020016100da565b34801561014d575f80fd5b5061018a6040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b6040516100da9190610ca9565b3480156101a2575f80fd5b506100d06101b1366004610d2b565b6103c6565b3480156101c1575f80fd5b506100d06101d0366004610da2565b6104b2565b3480156101e0575f80fd5b506101e96106e5565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152016100da565b348015610220575f80fd5b5061024361022f366004610dfa565b60026020525f908152604090205460ff1681565b60405190151581526020016100da565b61018a610261366004610e11565b610789565b348015610271575f80fd5b50610243610280366004610dfa565b5f6020819052908152604090205460ff1681565b34801561029f575f80fd5b506001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff166100d0565b5f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c610321576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b507f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75c90565b5f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c6103a0576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b507fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35c90565b5f61040a874688888888888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610b0c92505050565b5f8181526002602052604090205490915060ff16610454576040517f6eca2e4b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b858473ffffffffffffffffffffffffffffffffffffffff16887f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3208887876040516104a093929190610e67565b60405180910390a49695505050505050565b5f4685036104ec576040517f8ed9a95d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fffffffffffffffffffffffffbdffffffffffffffffffffffffffffffffffffdd73ffffffffffffffffffffffffffffffffffffffff85160161055b576040517f4faa250900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f6105856001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b90506105ca864683338989898080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610b0c92505050565b5f81815260026020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915580549294507dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff909216919061063583610ed0565b91906101000a8154817dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02191690837dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16021790555050808573ffffffffffffffffffffffffffffffffffffffff16877f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3203388886040516106d493929190610e67565b60405180910390a450949350505050565b5f807ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c61073f576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50507fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35c907f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75c90565b60607ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c156107e4576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60017ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5d73420000000000000000000000000000000000002361082a6020860186610f31565b73ffffffffffffffffffffffffffffffffffffffff1614610877576040517f7987c15700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002273ffffffffffffffffffffffffffffffffffffffff1663ab4d6f758585856040516108b8929190610f4c565b6040519081900381207fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1682526108f79291600401610f5b565b5f604051808303815f87803b15801561090e575f80fd5b505af1158015610920573d5f803e3d5ffd5b505050505f805f805f6109338888610b4a565b94509450945094509450468514610976576040517f31ac221100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60808901355f61098a878387878a88610b0c565b5f8181526020819052604090205490915060ff16156109d5576040517f9ca9480b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f81815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055610a158285610c13565b5f8673ffffffffffffffffffffffffffffffffffffffff163485604051610a3c9190610fb4565b5f6040518083038185875af1925050503d805f8114610a76576040519150601f19603f3d011682016040523d82523d5f602084013e610a7b565b606091505b509950905080610a8d57885189602001fd5b8186847fc270d73e26d2d39dee7ef92093555927e344e243415547ecc350b2b5385b68a28c80519060200120604051610ac891815260200190565b60405180910390a4610ada5f80610c13565b50505050505050505f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5d9392505050565b5f868686868686604051602001610b2896959493929190610fca565b6040516020818303038152906040528051906020012090509695505050505050565b5f808080606081610b5e602082898b611020565b810190610b6b9190610dfa565b90507f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3208114610bc6576040517fdf1eb58600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610bd460806020898b611020565b810190610be19190611047565b91975095509350610bf5876080818b611020565b810190610c0291906110a9565b969995985093965092949392505050565b817f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75d807fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35d5050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f610cbb6020830184610c5d565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610ce3575f80fd5b50565b5f8083601f840112610cf6575f80fd5b50813567ffffffffffffffff811115610d0d575f80fd5b602083019150836020828501011115610d24575f80fd5b9250929050565b5f805f805f8060a08789031215610d40575f80fd5b86359550602087013594506040870135610d5981610cc2565b93506060870135610d6981610cc2565b9250608087013567ffffffffffffffff811115610d84575f80fd5b610d9089828a01610ce6565b979a9699509497509295939492505050565b5f805f8060608587031215610db5575f80fd5b843593506020850135610dc781610cc2565b9250604085013567ffffffffffffffff811115610de2575f80fd5b610dee87828801610ce6565b95989497509550505050565b5f60208284031215610e0a575f80fd5b5035919050565b5f805f83850360c0811215610e24575f80fd5b60a0811215610e31575f80fd5b5083925060a084013567ffffffffffffffff811115610e4e575f80fd5b610e5a86828701610ce6565b9497909650939450505050565b73ffffffffffffffffffffffffffffffffffffffff8416815260406020820152816040820152818360608301375f818301606090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016010192915050565b5f7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808316818103610f27577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b6001019392505050565b5f60208284031215610f41575f80fd5b8135610cbb81610cc2565b818382375f9101908152919050565b60c081018335610f6a81610cc2565b73ffffffffffffffffffffffffffffffffffffffff1682526020848101359083015260408085013590830152606080850135908301526080938401359382019390935260a0015290565b5f82518060208501845e5f920191825250919050565b8681528560208201528460408201525f73ffffffffffffffffffffffffffffffffffffffff808616606084015280851660808401525060c060a083015261101460c0830184610c5d565b98975050505050505050565b5f808585111561102e575f80fd5b8386111561103a575f80fd5b5050820193919092039150565b5f805f60608486031215611059575f80fd5b83359250602084013561106b81610cc2565b929592945050506040919091013590565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f80604083850312156110ba575f80fd5b82356110c581610cc2565b9150602083013567ffffffffffffffff808211156110e1575f80fd5b818501915085601f8301126110f4575f80fd5b8135818111156111065761110661107c565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561114c5761114c61107c565b81604052828152886020848701011115611164575f80fd5b826020860160208301375f602084830101528095505050505050925092905056fea164736f6c6343000819000a") ) +// interopETHLiquidityFundingAmount returns the bootstrap liquidity minted into the +// ETHLiquidity contract at interop activation. This is set to the maximum uint128 +// value (2^128 - 1), which is the maximum value supported by the deposit tx mint +// field, providing ample liquidity for cross-chain ETH bridging. +func InteropETHLiquidityFundingAmount() *big.Int { + v, _ := new(big.Int).SetString("ffffffffffffffffffffffffffffffff", 16) + return v +} + func InteropNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { - upgradeTxns := make([]hexutil.Bytes, 0, 2) + upgradeTxns := make([]hexutil.Bytes, 0, 7) // Note that the CrossL2Inbox is not deployed here. // It is only deployed when the chain is first in a dependency set with at least two active chains. @@ -64,6 +89,87 @@ func InteropNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { } upgradeTxns = append(upgradeTxns, updateL2ToL2MessengerProxyTx) + // 3. Deploy SuperchainETHBridge + deploySuperchainETHBridgeTx, err := types.NewTx(&types.DepositTx{ + SourceHash: deploySuperchainETHBridgeSource.SourceHash(), + From: superchainETHBridgeDeployerAddress, + To: nil, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 500_000, + IsSystemTransaction: false, + Data: superchainETHBridgeDeploymentBytecode, + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, deploySuperchainETHBridgeTx) + + // 4. Update SuperchainETHBridge Proxy + updateSuperchainETHBridgeProxyTx, err := types.NewTx(&types.DepositTx{ + SourceHash: updateSuperchainETHBridgeProxySource.SourceHash(), + From: common.Address{}, + To: &predeploys.SuperchainETHBridgeAddr, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 50_000, + IsSystemTransaction: false, + Data: upgradeToCalldata(SuperchainETHBridgeAddress), + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, updateSuperchainETHBridgeProxyTx) + + // 5. Deploy ETHLiquidity + deployETHLiquidityTx, err := types.NewTx(&types.DepositTx{ + SourceHash: deployETHLiquiditySource.SourceHash(), + From: ethLiquidityDeployerAddress, + To: nil, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 375_000, + IsSystemTransaction: false, + Data: ethLiquidityDeploymentBytecode, + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, deployETHLiquidityTx) + + // 6. Update ETHLiquidity Proxy + updateETHLiquidityProxyTx, err := types.NewTx(&types.DepositTx{ + SourceHash: updateETHLiquidityProxySource.SourceHash(), + From: common.Address{}, + To: &predeploys.ETHLiquidityAddr, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 50_000, + IsSystemTransaction: false, + Data: upgradeToCalldata(ETHLiquidityAddress), + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, updateETHLiquidityProxyTx) + + // 7. Fund ETHLiquidity with the maximum deposit mint supported by the + // current deposit envelope type so interop ETH bridging has bootstrap liquidity. + fundETHLiquidityTx, err := types.NewTx(&types.DepositTx{ + SourceHash: interopETHLiquidityFundingSource.SourceHash(), + From: L1InfoDepositerAddress, + To: &predeploys.ETHLiquidityAddr, + Mint: InteropETHLiquidityFundingAmount(), + Value: InteropETHLiquidityFundingAmount(), + Gas: 50_000, + IsSystemTransaction: false, + Data: interopETHLiquidityFundingCalldata, + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, fundETHLiquidityTx) + return upgradeTxns, nil } diff --git a/op-node/rollup/derive/interop_upgrade_transactions_test.go b/op-node/rollup/derive/interop_upgrade_transactions_test.go index 84fcc65bc84..8bebb0220be 100644 --- a/op-node/rollup/derive/interop_upgrade_transactions_test.go +++ b/op-node/rollup/derive/interop_upgrade_transactions_test.go @@ -30,6 +30,26 @@ func TestInteropSourcesMatchSpec(t *testing.T) { source: updateL2ToL2MessengerProxySource, expectedHash: "0xe54b4d06bbcc857f41ae00e89d820339ac5ce0034aac722c817b2873e03a7e68", }, + { + source: deploySuperchainETHBridgeSource, + expectedHash: "0x53eccc738e298d613b3c3dcc8ad1d9e9626945a2f7b005252c2b57837176d960", + }, + { + source: updateSuperchainETHBridgeProxySource, + expectedHash: "0x50684989256294e3c64949ea1cf5bad586c7e6b91b8b7f21ee9ef7086efe60db", + }, + { + source: deployETHLiquiditySource, + expectedHash: "0xceec4ed75501efd5830d25045e10014464155345d91a8c78dba77aed02d5b08b", + }, + { + source: updateETHLiquidityProxySource, + expectedHash: "0x8c6c281c65cba9a9286233c61c3a1b4d606b899b1aee3b3a7221fd5212b22822", + }, + { + source: interopETHLiquidityFundingSource, + expectedHash: "0xa9b2a45c225d10db0a0a092d024192968cef10170a82f9d67d2bf0264d0c0555", + }, } { require.Equal(t, common.HexToHash(test.expectedHash), test.source.SourceHash(), "Source hash mismatch for intent: %s", test.source.Intent) } @@ -38,7 +58,7 @@ func TestInteropSourcesMatchSpec(t *testing.T) { func TestInteropNetworkTransactions(t *testing.T) { upgradeTxns, err := InteropNetworkUpgradeTransactions() require.NoError(t, err) - require.Len(t, upgradeTxns, 2) + require.Len(t, upgradeTxns, 7) // 1. Deploy L2ToL2CrossDomainMessenger sender3, tx3 := toDepositTxn(t, upgradeTxns[0]) @@ -57,6 +77,52 @@ func TestInteropNetworkTransactions(t *testing.T) { require.Equal(t, uint64(50_000), tx4.Gas(), "gas mismatch tx 4") expectedData, _ := hex.DecodeString("3659cfe60000000000000000000000000d0edd0ebd0e94d218670a8de867eb5c4d37cadd") require.Equal(t, expectedData, tx4.Data(), "data mismatch tx 4") + + // 3. Deploy SuperchainETHBridge + sender5, tx5 := toDepositTxn(t, upgradeTxns[2]) + require.Equal(t, superchainETHBridgeDeployerAddress, sender5, "sender mismatch tx 5") + require.Equal(t, deploySuperchainETHBridgeSource.SourceHash(), tx5.SourceHash(), "source hash mismatch tx 5") + require.Nil(t, tx5.To(), "to mismatch tx 5") + require.Equal(t, uint64(500_000), tx5.Gas(), "gas mismatch tx 5") + require.Equal(t, superchainETHBridgeDeploymentBytecode, tx5.Data(), "data mismatch tx 5") + + // 4. Update SuperchainETHBridge Proxy + sender6, tx6 := toDepositTxn(t, upgradeTxns[3]) + require.Equal(t, common.Address{}, sender6, "sender mismatch tx 6") + require.Equal(t, updateSuperchainETHBridgeProxySource.SourceHash(), tx6.SourceHash(), "source hash mismatch tx 6") + require.NotNil(t, tx6.To(), "to mismatch tx 6") + require.Equal(t, predeploys.SuperchainETHBridgeAddr, *tx6.To(), "to mismatch tx 6") + require.Equal(t, uint64(50_000), tx6.Gas(), "gas mismatch tx 6") + require.Equal(t, upgradeToCalldata(SuperchainETHBridgeAddress), tx6.Data(), "data mismatch tx 6") + + // 5. Deploy ETHLiquidity + sender7, tx7 := toDepositTxn(t, upgradeTxns[4]) + require.Equal(t, ethLiquidityDeployerAddress, sender7, "sender mismatch tx 7") + require.Equal(t, deployETHLiquiditySource.SourceHash(), tx7.SourceHash(), "source hash mismatch tx 7") + require.Nil(t, tx7.To(), "to mismatch tx 7") + require.Equal(t, uint64(375_000), tx7.Gas(), "gas mismatch tx 7") + require.Equal(t, ethLiquidityDeploymentBytecode, tx7.Data(), "data mismatch tx 7") + + // 6. Update ETHLiquidity Proxy + sender8, tx8 := toDepositTxn(t, upgradeTxns[5]) + require.Equal(t, common.Address{}, sender8, "sender mismatch tx 8") + require.Equal(t, updateETHLiquidityProxySource.SourceHash(), tx8.SourceHash(), "source hash mismatch tx 8") + require.NotNil(t, tx8.To(), "to mismatch tx 8") + require.Equal(t, predeploys.ETHLiquidityAddr, *tx8.To(), "to mismatch tx 8") + require.Equal(t, uint64(50_000), tx8.Gas(), "gas mismatch tx 8") + require.Equal(t, upgradeToCalldata(ETHLiquidityAddress), tx8.Data(), "data mismatch tx 8") + + // 7. Fund ETHLiquidity + sender9, tx9 := toDepositTxn(t, upgradeTxns[6]) + require.Equal(t, L1InfoDepositerAddress, sender9, "sender mismatch tx 9") + require.Equal(t, interopETHLiquidityFundingSource.SourceHash(), tx9.SourceHash(), "source hash mismatch tx 9") + require.NotNil(t, tx9.To(), "to mismatch tx 9") + require.Equal(t, predeploys.ETHLiquidityAddr, *tx9.To(), "to mismatch tx 9") + require.Equal(t, InteropETHLiquidityFundingAmount(), tx9.Mint(), "mint mismatch tx 9") + require.Equal(t, InteropETHLiquidityFundingAmount(), tx9.Value(), "value mismatch tx 9") + require.Equal(t, uint64(50_000), tx9.Gas(), "gas mismatch tx 9") + expectedFundData, _ := hex.DecodeString("b60d4288") + require.Equal(t, expectedFundData, tx9.Data(), "data mismatch tx 9") } func TestInteropActivateCrossL2InboxTransactions(t *testing.T) { diff --git a/op-node/rollup/derive/upgrade_transaction.go b/op-node/rollup/derive/upgrade_transaction.go new file mode 100644 index 00000000000..1ac729d8098 --- /dev/null +++ b/op-node/rollup/derive/upgrade_transaction.go @@ -0,0 +1,134 @@ +package derive + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "math/big" + "strings" + + "github.com/ethereum-optimism/optimism/op-core/forks" + "github.com/ethereum-optimism/optimism/op-core/nuts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +// Network Upgrade Transactions (NUTs) are read from a JSON file and +// converted into deposit transactions. + +// nutBundleVersion is the only bundle schema version this reader accepts. +const nutBundleVersion = "1.0.0" + +// nutMetadata contains version information for the NUT bundle format. +type nutMetadata struct { + Version string `json:"version"` +} + +// networkUpgradeTransaction defines a single deposit transaction within a NUT bundle. +type networkUpgradeTransaction struct { + Intent string `json:"intent"` + From common.Address `json:"from"` + To *common.Address `json:"to"` + Data hexutil.Bytes `json:"data"` + GasLimit uint64 `json:"gasLimit"` +} + +// nutBundle is the top-level structure of a NUT file. +type nutBundle struct { + ForkName forks.Name `json:"-"` + Metadata nutMetadata `json:"metadata"` + Transactions []networkUpgradeTransaction `json:"transactions"` +} + +// capitalizeForkName returns the fork name with its first character upper-cased. +// Mirrors rust/kona/crates/protocol/hardforks/build_helpers.rs::capitalize so the +// qualified intent strings (and therefore source hashes) agree across implementations. +func capitalizeForkName(f forks.Name) string { + s := string(f) + if s == "" { + return s + } + return strings.ToUpper(s[:1]) + s[1:] +} + +// readNUTBundle reads and parses a NUT bundle from an io.Reader. The fork name +// is used to namespace each transaction's intent when deriving source hashes. +func readNUTBundle(fork forks.Name, r io.Reader) (*nutBundle, error) { + var bundle nutBundle + if err := json.NewDecoder(r).Decode(&bundle); err != nil { + return nil, fmt.Errorf("failed to parse NUT bundle: %w", err) + } + if bundle.Metadata.Version != nutBundleVersion { + return nil, fmt.Errorf("unsupported NUT bundle version: got %q, want %q", bundle.Metadata.Version, nutBundleVersion) + } + bundle.ForkName = fork + return &bundle, nil +} + +// totalGas returns the sum of gas limits across all transactions in the bundle. +func (b *nutBundle) totalGas() uint64 { + var total uint64 + for _, tx := range b.Transactions { + total += tx.GasLimit + } + return total +} + +// toDepositTransactions converts the bundle's transactions into serialized deposit transactions. +func (b *nutBundle) toDepositTransactions() ([]hexutil.Bytes, error) { + txs := make([]hexutil.Bytes, 0, len(b.Transactions)) + for i, nutTx := range b.Transactions { + if nutTx.Intent == "" { + return nil, fmt.Errorf("tx %d: missing intent", i) + } + + // The fork name is capitalized to match kona's NUT bundle codegen + // (rust/kona/crates/protocol/hardforks/build_helpers.rs::capitalize), + // so both implementations derive the same UpgradeDepositSource hashes. + qualifiedIntent := fmt.Sprintf("%s %d: %s", capitalizeForkName(b.ForkName), i, nutTx.Intent) + source := UpgradeDepositSource{Intent: qualifiedIntent} + depTx := &types.DepositTx{ + SourceHash: source.SourceHash(), + From: nutTx.From, + To: nutTx.To, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: nutTx.GasLimit, + IsSystemTransaction: false, + Data: nutTx.Data, + } + + encoded, err := types.NewTx(depTx).MarshalBinary() + if err != nil { + return nil, fmt.Errorf("tx %d: failed to marshal deposit tx: %w", i, err) + } + txs = append(txs, encoded) + } + return txs, nil +} + +// UpgradeTransactions returns the deposit transactions and total gas required for a +// fork's NUT bundle. The fork name selects the embedded bundle JSON. +func UpgradeTransactions(fork forks.Name) ([]hexutil.Bytes, uint64, error) { + var bundleJSON []byte + switch fork { + case forks.Karst: + bundleJSON = nuts.KarstNUTBundleJSON + default: + return nil, 0, fmt.Errorf("no NUT bundle for fork %s", fork) + } + + bundle, err := readNUTBundle(fork, bytes.NewReader(bundleJSON)) + if err != nil { + return nil, 0, fmt.Errorf("reading %s NUT bundle: %w", fork, err) + } + + txs, err := bundle.toDepositTransactions() + if err != nil { + return nil, 0, fmt.Errorf("converting %s NUT bundle to deposit txs: %w", fork, err) + } + + return txs, bundle.totalGas(), nil +} diff --git a/op-node/rollup/superchain.go b/op-node/rollup/superchain.go index ec3da881430..d93a138cd0e 100644 --- a/op-node/rollup/superchain.go +++ b/op-node/rollup/superchain.go @@ -10,8 +10,6 @@ import ( "github.com/ethereum/go-ethereum/superchain" ) -var OPStackSupport = params.ProtocolVersionV0{Build: [8]byte{}, Major: 9, Minor: 0, Patch: 0, PreRelease: 0}.Encode() - // LoadOPStackRollupConfig loads the rollup configuration of the requested chain ID from the superchain-registry. // Some chains may require a SystemConfigProvider to retrieve any values not part of the registry. func LoadOPStackRollupConfig(chainID uint64) (*Config, error) { @@ -87,7 +85,6 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) { } applyHardforks(cfg, chConfig.Hardforks) - cfg.ProtocolVersionsAddress = superConfig.ProtocolVersionsAddr return cfg, nil } diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 43cc795b923..a7400bad401 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -143,9 +143,6 @@ type Config struct { // L1 System Config Address L1SystemConfigAddress common.Address `json:"l1_system_config_address"` - // L1 address that declares the protocol versions, optional (Beta feature) - ProtocolVersionsAddress common.Address `json:"protocol_versions_address,omitempty"` - // ChainOpConfig is the OptimismConfig of the execution layer ChainConfig. // It is used during safe chain consolidation to translate zero SystemConfig EIP1559 // parameters to the protocol values, like the execution layer does. @@ -783,8 +780,6 @@ func (c *Config) Description(l2Chains map[string]string) string { c.forEachFork(func(name string, _ string, time *uint64) { banner += fmt.Sprintf(" - %v: %s\n", name, fmtForkTimeOrUnset(time)) }) - // Report the protocol version - banner += fmt.Sprintf("Node supports up to OP-Stack Protocol Version: %s\n", OPStackSupport) if c.AltDAConfig != nil { banner += fmt.Sprintf("Node supports Alt-DA Mode with CommitmentType %v\n", c.AltDAConfig.CommitmentType) } @@ -847,7 +842,6 @@ func (c *Config) forEachFork(callback func(name string, logName string, time *ui func (c *Config) ParseRollupConfig(in io.Reader) error { dec := json.NewDecoder(in) - dec.DisallowUnknownFields() if err := dec.Decode(c); err != nil { return fmt.Errorf("failed to decode rollup config: %w", err) } diff --git a/op-node/service.go b/op-node/service.go index 1ac43ad80f4..c9f5d41df42 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -9,7 +9,6 @@ import ( "os" "strings" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -56,11 +55,6 @@ func NewConfig(ctx cliiface.Context, log log.Logger) (*config.Config, error) { return nil, err } - if !ctx.Bool(flags.RollupLoadProtocolVersions.Name) { - log.Info("Not opted in to ProtocolVersions signal loading, disabling ProtocolVersions contract now.") - rollupConfig.ProtocolVersionsAddress = common.Address{} - } - configPersistence := NewConfigPersistence(ctx) driverConfig := NewDriverConfig(ctx) @@ -87,11 +81,6 @@ func NewConfig(ctx cliiface.Context, log log.Logger) (*config.Config, error) { return nil, fmt.Errorf("failed to create the sync config: %w", err) } - haltOption := ctx.String(flags.RollupHalt.Name) - if haltOption == "none" { - haltOption = "" - } - if ctx.IsSet(flags.HeartbeatEnabledFlag.Name) || ctx.IsSet(flags.HeartbeatMonikerFlag.Name) || ctx.IsSet(flags.HeartbeatURLFlag.Name) { @@ -118,7 +107,6 @@ func NewConfig(ctx cliiface.Context, log log.Logger) (*config.Config, error) { SafeDBPath: ctx.String(flags.SafeDBPath.Name), Sync: *syncConfig, L2FollowSource: NewL2FollowSourceConfig(ctx), - RollupHalt: haltOption, ConductorEnabled: ctx.Bool(flags.ConductorEnabledFlag.Name), ConductorRpc: func(context.Context) (string, error) { @@ -273,12 +261,7 @@ Conflicting configuration is deprecated, and will stop the op-node from starting defer file.Close() var rollupConfig rollup.Config - dec := json.NewDecoder(file) - dec.DisallowUnknownFields() - if err := dec.Decode(&rollupConfig); err != nil { - return nil, fmt.Errorf("failed to decode rollup config: %w", err) - } - return &rollupConfig, nil + return &rollupConfig, rollupConfig.ParseRollupConfig(file) } func applyOverrides(ctx cliiface.Context, rollupConfig *rollup.Config) { diff --git a/op-program/README.md b/op-program/README.md index 2e6fd0ed55a..7668053e3bb 100644 --- a/op-program/README.md +++ b/op-program/README.md @@ -1,5 +1,9 @@ # op-program +> **Deprecated:** op-program is deprecated and being replaced by [kona-client](../rust/kona/). +> Existing deployments will be supported until the Karst hardfork, at which point migration to kona-client is required. +> See the [end-of-support notice](https://docs.optimism.io/notices/op-geth-deprecation) for full details and migration guidance. + Implements a fault proof program that runs through the rollup state-transition to verify an L2 output from L1 inputs. This verifiable output can then resolve a disputed output on L1. @@ -39,7 +43,7 @@ The absolute pre-state of the op-program can be generated by executing the makef `reproducible-prestate` target. Effectively, this builds a docker image specified by [Dockerfile.repro](./Dockerfile.repro) with pinned dependency versions to ensure the build is reproducible. -After running `make reproducible-prestate`, the generate prestates files can be found in [./bin/](./bin/). +After running `just reproducible-prestate`, the generated prestates files can be found in `./bin/`. The `prestate-proof-*.json` files contain the absolute pre-state hash under the `.pre` key that is also used by the [contracts][ctb] deploy script. The `prestate-*.bin.gz` files contain the actual prestate. diff --git a/op-program/chainconfig/test/configs/901-rollup.json b/op-program/chainconfig/test/configs/901-rollup.json index 13d0893eea3..fb398960c41 100644 --- a/op-program/chainconfig/test/configs/901-rollup.json +++ b/op-program/chainconfig/test/configs/901-rollup.json @@ -29,7 +29,6 @@ "fjord_time": 0, "batch_inbox_address": "0xff00000000000000000000000000000000000901", "deposit_contract_address": "0x55bdfb0bfef1070c457124920546359426153833", - "l1_system_config_address": "0x3649f526889a918af0a5498706db29e81bc91e0c", - "protocol_versions_address": "0x0000000000000000000000000000000000000000" + "l1_system_config_address": "0x3649f526889a918af0a5498706db29e81bc91e0c" } diff --git a/op-program/chainconfig/test/configs_typo/901-rollup.json b/op-program/chainconfig/test/configs_typo/901-rollup.json index 13d0893eea3..fb398960c41 100644 --- a/op-program/chainconfig/test/configs_typo/901-rollup.json +++ b/op-program/chainconfig/test/configs_typo/901-rollup.json @@ -29,7 +29,6 @@ "fjord_time": 0, "batch_inbox_address": "0xff00000000000000000000000000000000000901", "deposit_contract_address": "0x55bdfb0bfef1070c457124920546359426153833", - "l1_system_config_address": "0x3649f526889a918af0a5498706db29e81bc91e0c", - "protocol_versions_address": "0x0000000000000000000000000000000000000000" + "l1_system_config_address": "0x3649f526889a918af0a5498706db29e81bc91e0c" } diff --git a/op-proposer/README.md b/op-proposer/README.md index 175064bc256..f4d4dd6f94c 100644 --- a/op-proposer/README.md +++ b/op-proposer/README.md @@ -7,7 +7,7 @@ Pull requests: [monorepo](https://github.com/ethereum-optimism/optimism/pulls?q= User docs: - [Proposer Configuration docs] -[Proposer Configuration docs]: https://docs.optimism.io/builders/chain-operators/configuration/proposer +[Proposer Configuration docs]: https://docs.optimism.io/chain-operators/guides/configuration/proposer Specs: - [`proposals.md`](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/proposals.md) @@ -31,7 +31,7 @@ with an inclusion-proof of a withdrawn message (as registered in the L2 withdraw ```bash go run ./op-proposer/cmd \ --l1-eth-rpc http://l1:8545 \ - --rollup-rpc: http://op-node:8545 \ + --rollup-rpc http://op-node:8545 \ --game-factory-address=changeme \ --game-type=changeme ``` diff --git a/op-rbuilder b/op-rbuilder deleted file mode 160000 index 272d462d980..00000000000 --- a/op-rbuilder +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 272d462d980a43e7caf568df0fbbc0c2e0066207 diff --git a/op-service/README.md b/op-service/README.md index e5a9cad89b0..9b594ed62b6 100644 --- a/op-service/README.md +++ b/op-service/README.md @@ -29,12 +29,10 @@ Pull requests: [monorepo](https://github.com/ethereum-optimism/optimism/pulls?q= ├── locks - Lock utils, like read-write wrapped types ├── log - Logging CLI and middleware utils ├── logpipe - Logs streaming from io.Reader to logger -├── logfilter - Logging filters ├── logmods - Log handler wrapping/unwrapping utils ├── metrics - Metrics types, metering abstractions, server utils ├── oppprof - P-Prof CLI types and server setup ├── plan - Utils to create a lazy evaluated value following dependencies -├── predeploys - OP-Stack predeploy definitions ├── queue - Generic queue implementation ├── retry - Function retry utils ├── rpc - RPC server utils diff --git a/op-service/apis/interop_filter.go b/op-service/apis/interop_filter.go new file mode 100644 index 00000000000..698e80f94dc --- /dev/null +++ b/op-service/apis/interop_filter.go @@ -0,0 +1,18 @@ +package apis + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + "github.com/ethereum-optimism/optimism/op-service/eth" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +type InteropFilterQueryAPI interface { + CheckAccessList(ctx context.Context, inboxEntries []common.Hash, + minSafety safety.Level, executingDescriptor messages.ExecutingDescriptor) error + GetBlockHashByNumber(ctx context.Context, chainID eth.ChainID, blockNum rpc.BlockNumber) (common.Hash, error) +} diff --git a/op-service/apis/supervisor.go b/op-service/apis/supervisor.go index e9b04ba104c..31840eaceae 100644 --- a/op-service/apis/supervisor.go +++ b/op-service/apis/supervisor.go @@ -8,6 +8,9 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) type SupervisorAPI interface { @@ -26,7 +29,7 @@ type SupervisorAdminAPI interface { type SupervisorQueryAPI interface { CheckAccessList(ctx context.Context, inboxEntries []common.Hash, - minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error + minSafety safety.Level, executingDescriptor messages.ExecutingDescriptor) error CrossDerivedToSource(ctx context.Context, chainID eth.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) LocalSafe(ctx context.Context, chainID eth.ChainID) (result types.DerivedIDPair, err error) diff --git a/op-service/eth/safety/safety.go b/op-service/eth/safety/safety.go new file mode 100644 index 00000000000..d37d39bb7e4 --- /dev/null +++ b/op-service/eth/safety/safety.go @@ -0,0 +1,66 @@ +package safety + +import ( + "errors" + "fmt" +) + +var ( + errNilSafetyLevel = errors.New("nil safety level") + errUnrecognizedSafetyLevel = errors.New("unrecognized safety level") +) + +type Level string + +func (lvl Level) String() string { + return string(lvl) +} + +// Validate returns true if the Level is one of the recognized levels +func (lvl Level) Validate() bool { + switch lvl { + case Invalid, Finalized, CrossSafe, LocalSafe, CrossUnsafe, LocalUnsafe: + return true + default: + return false + } +} + +func (lvl Level) MarshalText() ([]byte, error) { + return []byte(lvl), nil +} + +func (lvl *Level) UnmarshalText(text []byte) error { + if lvl == nil { + return errNilSafetyLevel + } + x := Level(text) + if !x.Validate() { + return fmt.Errorf("%w: %q", errUnrecognizedSafetyLevel, text) + } + *lvl = x + return nil +} + +const ( + // Finalized is CrossSafe, with the additional constraint that every + // dependency is derived only from finalized L1 input data. + // This matches RPC label "finalized". + Finalized Level = "finalized" + // CrossSafe is as safe as LocalSafe, with all its dependencies + // also fully verified to be reproducible from L1. + // This matches RPC label "safe". + CrossSafe Level = "safe" + // LocalSafe is verified to be reproducible from L1, + // without any verified cross-L2 dependencies. + // This does not have an RPC label. + LocalSafe Level = "local-safe" + // CrossUnsafe is as safe as LocalUnsafe, + // but with verified cross-L2 dependencies that are at least CrossUnsafe. + // This does not have an RPC label. + CrossUnsafe Level = "cross-unsafe" + // LocalUnsafe is the safety of the tip of the chain. This matches RPC label "unsafe". + LocalUnsafe Level = "unsafe" + // Invalid is the safety of when the message or block is not matching the expected data. + Invalid Level = "invalid" +) diff --git a/op-service/eth/safety/safety_test.go b/op-service/eth/safety/safety_test.go new file mode 100644 index 00000000000..c56ade60bdb --- /dev/null +++ b/op-service/eth/safety/safety_test.go @@ -0,0 +1,32 @@ +package safety + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSafetyLevel(t *testing.T) { + for _, lvl := range []Level{ + Finalized, + CrossSafe, + LocalSafe, + CrossUnsafe, + LocalUnsafe, + Invalid, + } { + upper := strings.ToUpper(lvl.String()) + var x Level + require.ErrorContains(t, json.Unmarshal([]byte(fmt.Sprintf("%q", upper)), &x), "unrecognized", "case sensitive") + require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf("%q", lvl.String())), &x)) + dat, err := json.Marshal(x) + require.NoError(t, err) + require.Equal(t, fmt.Sprintf("%q", lvl.String()), string(dat)) + } + var x Level + require.ErrorContains(t, json.Unmarshal([]byte(`""`), &x), "unrecognized", "empty") + require.ErrorContains(t, json.Unmarshal([]byte(`"foobar"`), &x), "unrecognized", "other") +} diff --git a/op-service/sources/engine_client.go b/op-service/sources/engine_client.go index c8f95655486..ee3730cb754 100644 --- a/op-service/sources/engine_client.go +++ b/op-service/sources/engine_client.go @@ -8,9 +8,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/client" @@ -140,52 +138,3 @@ func (s *EngineAPIClient) GetPayload(ctx context.Context, payloadInfo eth.Payloa e.Trace("Received payload") return &result, nil } - -func (s *EngineAPIClient) SignalSuperchainV1(ctx context.Context, recommended, required params.ProtocolVersion) (params.ProtocolVersion, error) { - var result params.ProtocolVersion - err := s.RPC.CallContext(ctx, &result, "engine_signalSuperchainV1", &catalyst.SuperchainSignal{ - Recommended: recommended, - Required: required, - }) - return result, err -} - -func (s *EngineAPIClient) NewKeystone(ctx context.Context, keystone hemi.L2Keystone) (*eth.KeystoneStatus, error) { - e := s.log.New("ep_hash", keystone.EPHash) - e.Trace("sending keystone for insertion") - - method := eth.NewKeystone - - execCtx, cancel := context.WithTimeout(ctx, time.Second*5) - defer cancel() - var result eth.KeystoneStatus - - err := s.RPC.CallContext(execCtx, &result, string(method), keystone) - - e.Trace("Received keystone insertion result", "status", result.Status, "message", result.ValidationError) - if err != nil { - e.Error("Keystone insertion failed", "err", err) - return nil, fmt.Errorf("failed to insert keystone: %w", err) - } - return &result, nil -} - -func (s *EngineAPIClient) PopPayoutsByL2Keystone(ctx context.Context, abrevHash chainhash.Hash) ([]eth.PopPayout, error) { - e := s.log.New("hash", abrevHash) - e.Trace("asking for payouts for keystone") - - method := eth.GetPayouts - - execCtx, cancel := context.WithTimeout(ctx, time.Second*5) - defer cancel() - var result []eth.PopPayout - - err := s.RPC.CallContext(execCtx, &result, string(method), abrevHash) - - e.Trace("Received payouts for keystone") - if err != nil { - e.Error("Error retrieving payouts", "err", err) - return nil, fmt.Errorf("failed to retrieve payouts: %w", err) - } - return result, nil -} diff --git a/op-service/sources/interop_filter_client.go b/op-service/sources/interop_filter_client.go new file mode 100644 index 00000000000..af067aa5869 --- /dev/null +++ b/op-service/sources/interop_filter_client.go @@ -0,0 +1,48 @@ +package sources + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + "github.com/ethereum-optimism/optimism/op-service/apis" + "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/eth" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +type InteropFilterClient struct { + client client.RPC +} + +// This type-check keeps the Interop Filter server API and client API in sync. +var _ apis.InteropFilterQueryAPI = (*InteropFilterClient)(nil) + +func NewInteropFilterClient(client client.RPC) *InteropFilterClient { + return &InteropFilterClient{ + client: client, + } +} + +func (cl *InteropFilterClient) CheckAccessList(ctx context.Context, inboxEntries []common.Hash, + minSafety safety.Level, executingDescriptor messages.ExecutingDescriptor) error { + return cl.client.CallContext(ctx, nil, "interop_checkAccessList", inboxEntries, minSafety, executingDescriptor) +} + +func (cl *InteropFilterClient) GetBlockHashByNumber(ctx context.Context, chainID eth.ChainID, blockNum rpc.BlockNumber) (common.Hash, error) { + var result common.Hash + err := cl.client.CallContext(ctx, &result, "interop_getBlockHashByNumber", chainID, blockNum) + if isNotFound(err) { + err = fmt.Errorf("%w: %v", ethereum.NotFound, err.Error()) + return result, err + } + return result, err +} + +func (cl *InteropFilterClient) Close() { + cl.client.Close() +} diff --git a/op-service/sources/interop_filter_client_test.go b/op-service/sources/interop_filter_client_test.go new file mode 100644 index 00000000000..2b1e70a4846 --- /dev/null +++ b/op-service/sources/interop_filter_client_test.go @@ -0,0 +1,87 @@ +package sources + +import ( + "context" + "errors" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + "github.com/ethereum-optimism/optimism/op-service/eth" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" +) + +func TestInteropFilterClient_CheckAccessList(t *testing.T) { + ctx := context.Background() + rpcClient := new(mockRPC) + defer rpcClient.AssertExpectations(t) + client := NewInteropFilterClient(rpcClient) + + inboxEntries := []common.Hash{common.HexToHash("0x01")} + minSafety := safety.CrossUnsafe + execDescriptor := messages.ExecutingDescriptor{ + ChainID: eth.ChainIDFromUInt64(900), + Timestamp: 123, + } + + rpcClient.On( + "CallContext", + ctx, + nil, + "interop_checkAccessList", + []any{inboxEntries, minSafety, execDescriptor}, + ).Return([]error{nil}) + + require.NoError(t, client.CheckAccessList(ctx, inboxEntries, minSafety, execDescriptor)) +} + +func TestInteropFilterClient_GetBlockHashByNumber(t *testing.T) { + ctx := context.Background() + rpcClient := new(mockRPC) + defer rpcClient.AssertExpectations(t) + client := NewInteropFilterClient(rpcClient) + + chainID := eth.ChainIDFromUInt64(900) + blockNum := rpc.BlockNumber(123) + expected := common.HexToHash("0x1234") + + rpcClient.On( + "CallContext", + ctx, + new(common.Hash), + "interop_getBlockHashByNumber", + []any{chainID, blockNum}, + ).Run(func(args mock.Arguments) { + *args[1].(*common.Hash) = expected + }).Return([]error{nil}) + + actual, err := client.GetBlockHashByNumber(ctx, chainID, blockNum) + require.NoError(t, err) + require.Equal(t, expected, actual) +} + +func TestInteropFilterClient_GetBlockHashByNumber_NotFound(t *testing.T) { + ctx := context.Background() + rpcClient := new(mockRPC) + defer rpcClient.AssertExpectations(t) + client := NewInteropFilterClient(rpcClient) + + chainID := eth.ChainIDFromUInt64(900) + blockNum := rpc.BlockNumber(123) + + rpcClient.On( + "CallContext", + ctx, + new(common.Hash), + "interop_getBlockHashByNumber", + []any{chainID, blockNum}, + ).Return([]error{errors.New("block 123 for chain 900: not found")}) + + _, err := client.GetBlockHashByNumber(ctx, chainID, blockNum) + require.ErrorIs(t, err, ethereum.NotFound) +} diff --git a/op-service/sources/supervisor_client.go b/op-service/sources/supervisor_client.go index ca35f9bd606..9e11a640957 100644 --- a/op-service/sources/supervisor_client.go +++ b/op-service/sources/supervisor_client.go @@ -13,6 +13,9 @@ import ( "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) type SupervisorClient struct { @@ -74,7 +77,7 @@ func (cl *SupervisorClient) GetFailsafeEnabled(ctx context.Context) (bool, error } func (cl *SupervisorClient) CheckAccessList(ctx context.Context, inboxEntries []common.Hash, - minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { + minSafety safety.Level, executingDescriptor messages.ExecutingDescriptor) error { return cl.client.CallContext(ctx, nil, "supervisor_checkAccessList", inboxEntries, minSafety, executingDescriptor) } diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index cac9ca891e3..094c60126d1 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -18,6 +18,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/holiman/uint256" @@ -390,7 +391,8 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (* callMsg.BlobHashes = blobHashes } // If the gas limit is set, we can use that as the gas - if gasLimit == 0 { + if candidate.GasLimit == 0 { + callMsg.Gas = params.MaxTxGas gas, err := m.backend.EstimateGas(ctx, callMsg) if err != nil { return nil, fmt.Errorf("failed to estimate gas: %w", errutil.TryAddRevertReason(err)) diff --git a/op-service/txplan/txplan.go b/op-service/txplan/txplan.go index 96258ccb1ec..0230c9e8f86 100644 --- a/op-service/txplan/txplan.go +++ b/op-service/txplan/txplan.go @@ -225,7 +225,7 @@ func WithEstimator(cl Estimator, invalidateOnNewBlock bool) Option { msg := ethereum.CallMsg{ From: tx.Sender.Value(), To: tx.To.Value(), - Gas: 0, // infinite gas, will be estimated + Gas: params.MaxTxGas, // max gas, will be estimated GasPrice: nil, GasFeeCap: tx.GasFeeCap.Value(), GasTipCap: tx.GasTipCap.Value(), diff --git a/op-supernode/supernode/activity/interop/algo.go b/op-supernode/supernode/activity/interop/algo.go new file mode 100644 index 00000000000..f724c021ef5 --- /dev/null +++ b/op-supernode/supernode/activity/interop/algo.go @@ -0,0 +1,213 @@ +package interop + +import ( + "errors" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +// defaultMessageExpiryWindow is the default maximum age of an initiating message +// that can be executed. 7 days = 7 * 24 * 60 * 60 = 604800 seconds. +// The actual value used is read from the dependency set at construction time. +const defaultMessageExpiryWindow = 604800 + +var ( + // ErrUnknownChain is returned when an executing message references + // a chain that is not registered with the interop activity. + ErrUnknownChain = errors.New("unknown chain") + + // ErrTimestampViolation is returned when an executing message references + // an initiating message with a timestamp > the executing message's timestamp. + ErrTimestampViolation = errors.New("initiating message timestamp must not be greater than executing message timestamp") + + // ErrMessageExpired is returned when an executing message references + // an initiating message that has expired (older than the message expiry window). + ErrMessageExpired = errors.New("initiating message has expired") +) + +type blockPerChain = map[eth.ChainID]eth.BlockID + +// l1Inclusion returns the earliest L1 block such that all L2 blocks at the supplied timestamp were derived +// from a source at or before that L1 block. +func (i *Interop) l1Inclusion(ts uint64, blocksAtTimestamp blockPerChain) (eth.BlockID, error) { + l1Inclusion := eth.BlockID{} + for chainID := range blocksAtTimestamp { + chain, ok := i.chains[chainID] + if !ok { + continue + } + _, l1Block, err := chain.OptimisticAt(i.ctx, ts) + if err != nil { + i.log.Error("failed to get L1 inclusion for L2 block", "chainID", chainID, "timestamp", ts, "err", err) + return eth.BlockID{}, fmt.Errorf("chain %s: failed to get L1 inclusion: %w", chainID, err) + } + if l1Block.Number >= l1Inclusion.Number { + l1Inclusion = l1Block + } + } + return l1Inclusion, nil +} + +// verifyInteropMessages validates all executing messages at the given timestamp. +// Returns a Result indicating whether all messages are valid or which chains have invalid blocks. +// +// For each chain: +// 1. Open the block from the logsDB and verify it matches blocksAtTimestamp +// 2. For each executing message in the block: +// - Verify the initiating message exists in the source chain's logsDB +// - Verify the initiating message timestamp <= executing message timestamp +// - Verify the initiating message hasn't expired (within message expiry window) +func (i *Interop) verifyInteropMessages(ts uint64, blocksAtTimestamp blockPerChain) (Result, error) { + result := Result{ + Timestamp: ts, + L2Heads: make(blockPerChain), + InvalidHeads: make(map[eth.ChainID]InvalidHead), + } + + if l1Inclusion, err := i.l1Inclusion(ts, blocksAtTimestamp); err != nil { + return Result{}, err + } else { + result.L1Inclusion = l1Inclusion + } + + for chainID, expectedBlock := range blocksAtTimestamp { + var ( + blockRef eth.BlockRef + execMsgs map[uint32]*types.ExecutingMessage + err error + ) + if frontierBlock, ok := i.frontierView.block(chainID); ok { + blockRef = frontierBlock.ref + execMsgs = frontierBlock.execMsgs + } else { + db, ok := i.logsDBs[chainID] + if !ok { + // Skip chains that we don't have a logsDB for + // This can happen if blocksAtTimestamp includes chains not registered with the interop activity + continue + } + + // Get the block from the logsDB + blockRef, _, execMsgs, err = db.OpenBlock(expectedBlock.Number) + if err != nil { + // OpenBlock fails for the first block in the DB because it tries to find the parent. + // Handle this by checking if this is the first sealed block and using FirstSealedBlock instead. + if errors.Is(err, types.ErrSkipped) { + firstBlock, firstErr := db.FirstSealedBlock() + if firstErr != nil { + return Result{}, fmt.Errorf("chain %s: failed to open block %d and failed to get first block: %w", chainID, expectedBlock.Number, err) + } + if firstBlock.Number == expectedBlock.Number { + // This is the first block in the logsDB. Use FirstSealedBlock info. + // The first block has no executing messages (since we can't verify them without prior data). + if firstBlock.Hash != expectedBlock.Hash { + i.log.Warn("first block hash mismatch", + "chain", chainID, + "expected", expectedBlock.Hash, + "got", firstBlock.Hash, + ) + invalid, err := i.newInvalidHead(chainID, expectedBlock) + if err != nil { + return Result{}, fmt.Errorf("chain %s: %w", chainID, err) + } + result.InvalidHeads[chainID] = invalid + } + result.L2Heads[chainID] = expectedBlock + continue + } + } + return Result{}, fmt.Errorf("chain %s: failed to open block %d: %w", chainID, expectedBlock.Number, err) + } + } + + // Verify the block hash matches what we expect + if blockRef.Hash != expectedBlock.Hash { + i.log.Warn("block hash mismatch", + "chain", chainID, + "expected", expectedBlock.Hash, + "got", blockRef.Hash, + ) + invalid, err := i.newInvalidHead(chainID, expectedBlock) + if err != nil { + return Result{}, fmt.Errorf("chain %s: %w", chainID, err) + } + result.InvalidHeads[chainID] = invalid + result.L2Heads[chainID] = expectedBlock + continue + } + + // Verify each executing message + blockValid := true + for logIdx, execMsg := range execMsgs { + err := i.verifyExecutingMessage(chainID, blockRef.Time, logIdx, execMsg) + if err != nil { + i.log.Warn("invalid executing message", + "chain", chainID, + "block", expectedBlock.Number, + "logIdx", logIdx, + "execMsg", execMsg, + "err", err, + ) + blockValid = false + break + } + } + + result.L2Heads[chainID] = expectedBlock + if !blockValid { + invalid, err := i.newInvalidHead(chainID, expectedBlock) + if err != nil { + return Result{}, fmt.Errorf("chain %s: %w", chainID, err) + } + result.InvalidHeads[chainID] = invalid + } + } + + return result, nil +} + +// verifyExecutingMessage verifies a single executing message by checking: +// 1. The initiating message exists in the source chain's database +// 2. The initiating message's timestamp is not greater than the executing block's timestamp +// 3. The initiating message hasn't expired (timestamp + messageExpiryWindow >= executing timestamp) +func (i *Interop) verifyExecutingMessage(executingChain eth.ChainID, executingTimestamp uint64, logIdx uint32, execMsg *types.ExecutingMessage) error { + // Get the source chain's logsDB + sourceDB, ok := i.logsDBs[execMsg.ChainID] + if !ok { + return fmt.Errorf("source chain %s not found: %w", execMsg.ChainID, ErrUnknownChain) + } + + // Verify timestamp ordering: initiating message timestamp must be <= executing block timestamp. + if execMsg.Timestamp > executingTimestamp { + return fmt.Errorf("initiating timestamp %d > executing timestamp %d: %w", + execMsg.Timestamp, executingTimestamp, ErrTimestampViolation) + } + + // Verify the message hasn't expired: initiating timestamp + messageExpiryWindow must be >= executing timestamp + if execMsg.Timestamp+i.messageExpiryWindow < executingTimestamp { + return fmt.Errorf("initiating timestamp %d + expiry %d < executing timestamp %d: %w", + execMsg.Timestamp, i.messageExpiryWindow, executingTimestamp, ErrMessageExpired) + } + + // Build the query for the initiating message + query := types.ContainsQuery{ + BlockNum: execMsg.BlockNum, + LogIdx: execMsg.LogIdx, + Timestamp: execMsg.Timestamp, + Checksum: execMsg.Checksum, + } + + // Same-timestamp dependencies may live in the current frontier view rather + // than accepted-history logsDB. + if execMsg.Timestamp == executingTimestamp { + if _, ok := i.frontierView.contains(execMsg.ChainID, query); ok { + return nil + } + } + + // Check if the initiating message exists in the source chain's logsDB + _, err := sourceDB.Contains(query) + return err +} diff --git a/op-supernode/supernode/activity/interop/algo_test.go b/op-supernode/supernode/activity/interop/algo_test.go new file mode 100644 index 00000000000..485838559cf --- /dev/null +++ b/op-supernode/supernode/activity/interop/algo_test.go @@ -0,0 +1,981 @@ +package interop + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + gethlog "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supernode/supernode/activity" + cc "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/reads" + suptypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +// ============================================================================= +// TestVerifyInteropMessages - Table-Driven Tests +// ============================================================================= + +// newMockChainWithL1 creates a mock chain with the specified L1 block for OptimisticAt +func newMockChainWithL1(chainID eth.ChainID, l1Block eth.BlockID, blocks ...eth.BlockID) *algoMockChain { + hashes := make(map[uint64]common.Hash, len(blocks)) + for _, b := range blocks { + hashes[b.Number] = b.Hash + } + return &algoMockChain{ + id: chainID, + optimisticL1: l1Block, + blockHashes: hashes, + } +} + +// verifyInteropTestCase defines a single test case for verifyInteropMessages +type verifyInteropTestCase struct { + name string + setup func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) + expectError bool + errorMsg string + validate func(t *testing.T, result Result) +} + +func runVerifyInteropTest(t *testing.T, tc verifyInteropTestCase) { + t.Parallel() + interop, timestamp, blocks := tc.setup() + result, err := interop.verifyInteropMessages(timestamp, blocks) + + if tc.expectError { + require.Error(t, err) + if tc.errorMsg != "" { + require.Contains(t, err.Error(), tc.errorMsg) + } + } else { + require.NoError(t, err) + } + + if tc.validate != nil { + tc.validate(t, result) + } +} + +func TestL1Inclusion(t *testing.T) { + t.Parallel() + + type l1InclusionTestCase struct { + name string + setup func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) + expectError bool + errorMsg string + validate func(t *testing.T, l1 eth.BlockID) + } + + tests := []l1InclusionTestCase{ + { + name: "SingleChain", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + chainID := eth.ChainIDFromUInt64(10) + expectedBlock := eth.BlockID{Number: 100, Hash: common.HexToHash("0x123")} + l1Block := eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1")} + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{}, + chains: map[eth.ChainID]cc.ChainContainer{chainID: &algoMockChain{id: chainID, optimisticL1: l1Block}}, + } + return interop, 1000, map[eth.ChainID]eth.BlockID{chainID: expectedBlock} + }, + validate: func(t *testing.T, l1 eth.BlockID) { + require.Equal(t, eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1")}, l1) + }, + }, + { + name: "MultipleChains_HighestL1Selected", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + chain1ID := eth.ChainIDFromUInt64(10) + chain2ID := eth.ChainIDFromUInt64(8453) + chain3ID := eth.ChainIDFromUInt64(420) + + // Chain 1 has L1 at 60 (highest - should be selected) + // Chain 2 has L1 at 45 (earliest) + // Chain 3 has L1 at 50 (middle) + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{}, + chains: map[eth.ChainID]cc.ChainContainer{ + chain1ID: &algoMockChain{id: chain1ID, optimisticL1: eth.BlockID{Number: 60, Hash: common.HexToHash("0xL1_1")}}, + chain2ID: &algoMockChain{id: chain2ID, optimisticL1: eth.BlockID{Number: 45, Hash: common.HexToHash("0xL1_2")}}, + chain3ID: &algoMockChain{id: chain3ID, optimisticL1: eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1_3")}}, + }, + } + return interop, 1000, map[eth.ChainID]eth.BlockID{ + chain1ID: {Number: 100, Hash: common.HexToHash("0x1")}, + chain2ID: {Number: 200, Hash: common.HexToHash("0x2")}, + chain3ID: {Number: 150, Hash: common.HexToHash("0x3")}, + } + }, + validate: func(t *testing.T, l1 eth.BlockID) { + require.Equal(t, eth.BlockID{Number: 60, Hash: common.HexToHash("0xL1_1")}, l1) + }, + }, + { + name: "ChainNotInChainsMap_Skipped", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + chain1ID := eth.ChainIDFromUInt64(10) + chain2ID := eth.ChainIDFromUInt64(8453) // Not in chains map + + l1Block1 := eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1_1")} + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{}, + chains: map[eth.ChainID]cc.ChainContainer{ + chain1ID: &algoMockChain{id: chain1ID, optimisticL1: l1Block1}, + // chain2ID NOT in chains map + }, + } + return interop, 1000, map[eth.ChainID]eth.BlockID{ + chain1ID: {Number: 100, Hash: common.HexToHash("0x1")}, + chain2ID: {Number: 200, Hash: common.HexToHash("0x2")}, + } + }, + validate: func(t *testing.T, l1 eth.BlockID) { + require.Equal(t, eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1_1")}, l1) + }, + }, + { + name: "OptimisticAtError_ReturnsError", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + chainID := eth.ChainIDFromUInt64(10) + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{}, + chains: map[eth.ChainID]cc.ChainContainer{ + chainID: &algoMockChain{id: chainID, optimisticAtErr: errors.New("optimistic at error")}, + }, + } + return interop, 1000, map[eth.ChainID]eth.BlockID{ + chainID: {Number: 100, Hash: common.HexToHash("0x123")}, + } + }, + expectError: true, + errorMsg: "failed to get L1 inclusion", + }, + { + name: "NoChains_ReturnsEmpty", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{}, + chains: map[eth.ChainID]cc.ChainContainer{}, + } + return interop, 1000, map[eth.ChainID]eth.BlockID{} + }, + validate: func(t *testing.T, l1 eth.BlockID) { + require.Equal(t, eth.BlockID{}, l1) + }, + }, + { + name: "GenesisBlock_NoError", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + chainID := eth.ChainIDFromUInt64(10) + // L1 genesis block at number 0 + l1Block := eth.BlockID{Number: 0, Hash: common.HexToHash("0xGenesisL1")} + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{}, + chains: map[eth.ChainID]cc.ChainContainer{chainID: &algoMockChain{id: chainID, optimisticL1: l1Block}}, + } + return interop, 0, map[eth.ChainID]eth.BlockID{ + chainID: {Number: 0, Hash: common.HexToHash("0x123")}, + } + }, + // Genesis blocks included at L1 block number 0 must not cause an error. + validate: func(t *testing.T, l1 eth.BlockID) { + require.Equal(t, eth.BlockID{Number: 0, Hash: common.HexToHash("0xGenesisL1")}, l1) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + interop, ts, blocks := tc.setup() + l1, err := interop.l1Inclusion(ts, blocks) + + if tc.expectError { + require.Error(t, err) + if tc.errorMsg != "" { + require.Contains(t, err.Error(), tc.errorMsg) + } + } else { + require.NoError(t, err) + } + + if tc.validate != nil { + tc.validate(t, l1) + } + }) + } +} + +func TestVerifyInteropMessages(t *testing.T) { + t.Parallel() + + tests := []verifyInteropTestCase{ + // Valid block cases + { + name: "ValidBlocks/NoExecutingMessages", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + chainID := eth.ChainIDFromUInt64(10) + blockHash := common.HexToHash("0x123") + expectedBlock := eth.BlockID{Number: 100, Hash: blockHash} + l1Block := eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1")} + + mockDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: blockHash, Number: 100, Time: 1000}, + openBlockExecMsg: nil, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{chainID: mockDB}, + chains: map[eth.ChainID]cc.ChainContainer{chainID: newMockChainWithL1(chainID, l1Block)}, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{chainID: expectedBlock} + }, + validate: func(t *testing.T, result Result) { + chainID := eth.ChainIDFromUInt64(10) + expectedBlock := eth.BlockID{Number: 100, Hash: common.HexToHash("0x123")} + require.True(t, result.IsValid()) + require.Empty(t, result.InvalidHeads) + require.Equal(t, expectedBlock, result.L2Heads[chainID]) + }, + }, + { + name: "ValidBlocks/ValidExecutingMessage", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + sourceChainID := eth.ChainIDFromUInt64(10) + destChainID := eth.ChainIDFromUInt64(8453) + + sourceBlockHash := common.HexToHash("0xSource") + destBlockHash := common.HexToHash("0xDest") + + sourceBlock := eth.BlockID{Number: 50, Hash: sourceBlockHash} + destBlock := eth.BlockID{Number: 100, Hash: destBlockHash} + l1Block := eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")} + + execMsg := &suptypes.ExecutingMessage{ + ChainID: sourceChainID, + BlockNum: 50, + LogIdx: 0, + Timestamp: 500, // Source timestamp < dest timestamp (1000) + Checksum: suptypes.MessageChecksum{0x01}, + } + + sourceDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: sourceBlockHash, Number: 50, Time: 500}, + containsSeal: suptypes.BlockSeal{Number: 50, Timestamp: 500}, + } + + destDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: destBlockHash, Number: 100, Time: 1000}, + openBlockExecMsg: map[uint32]*suptypes.ExecutingMessage{ + 0: execMsg, + }, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{ + sourceChainID: sourceDB, + destChainID: destDB, + }, + chains: map[eth.ChainID]cc.ChainContainer{ + sourceChainID: newMockChainWithL1(sourceChainID, l1Block), + destChainID: newMockChainWithL1(destChainID, l1Block), + }, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{ + sourceChainID: sourceBlock, + destChainID: destBlock, + } + }, + validate: func(t *testing.T, result Result) { + require.True(t, result.IsValid()) + require.Empty(t, result.InvalidHeads) + }, + }, + { + name: "ValidBlocks/MessageAtExpiryBoundary", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + sourceChainID := eth.ChainIDFromUInt64(10) + destChainID := eth.ChainIDFromUInt64(8453) + + sourceBlockHash := common.HexToHash("0xSource") + destBlockHash := common.HexToHash("0xDest") + + // Message is exactly at the expiry boundary (should pass) + execTimestamp := uint64(1000000) + initTimestamp := execTimestamp - defaultMessageExpiryWindow // Exactly at boundary + + sourceBlock := eth.BlockID{Number: 50, Hash: sourceBlockHash} + destBlock := eth.BlockID{Number: 100, Hash: destBlockHash} + l1Block := eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")} + + execMsg := &suptypes.ExecutingMessage{ + ChainID: sourceChainID, + BlockNum: 50, + LogIdx: 0, + Timestamp: initTimestamp, // Exactly at expiry boundary + Checksum: suptypes.MessageChecksum{0x01}, + } + + sourceDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: sourceBlockHash, Number: 50, Time: initTimestamp}, + containsSeal: suptypes.BlockSeal{Number: 50, Timestamp: initTimestamp}, + } + + destDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: destBlockHash, Number: 100, Time: execTimestamp}, + openBlockExecMsg: map[uint32]*suptypes.ExecutingMessage{ + 0: execMsg, + }, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{ + sourceChainID: sourceDB, + destChainID: destDB, + }, + chains: map[eth.ChainID]cc.ChainContainer{ + sourceChainID: newMockChainWithL1(sourceChainID, l1Block), + destChainID: newMockChainWithL1(destChainID, l1Block), + }, + } + + return interop, execTimestamp, map[eth.ChainID]eth.BlockID{ + sourceChainID: sourceBlock, + destChainID: destBlock, + } + }, + validate: func(t *testing.T, result Result) { + require.True(t, result.IsValid()) + require.Empty(t, result.InvalidHeads) + }, + }, + { + name: "ValidBlocks/SameTimestampMessage", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + // Same-timestamp interop: executing message references an initiating message + // from the SAME timestamp. + sourceChainID := eth.ChainIDFromUInt64(10) + destChainID := eth.ChainIDFromUInt64(8453) + + sourceBlockHash := common.HexToHash("0xSource") + destBlockHash := common.HexToHash("0xDest") + + // Both blocks at the SAME timestamp + sharedTimestamp := uint64(1000) + + sourceBlock := eth.BlockID{Number: 50, Hash: sourceBlockHash} + destBlock := eth.BlockID{Number: 100, Hash: destBlockHash} + + execMsg := &suptypes.ExecutingMessage{ + ChainID: sourceChainID, + BlockNum: 50, + LogIdx: 0, + Timestamp: sharedTimestamp, // SAME as executing timestamp - should be VALID + Checksum: suptypes.MessageChecksum{0x01}, + } + + sourceDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: sourceBlockHash, Number: 50, Time: sharedTimestamp}, + containsSeal: suptypes.BlockSeal{Number: 50, Timestamp: sharedTimestamp}, + } + + destDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: destBlockHash, Number: 100, Time: sharedTimestamp}, + openBlockExecMsg: map[uint32]*suptypes.ExecutingMessage{ + 0: execMsg, + }, + } + + l1Block := eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")} + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{ + sourceChainID: sourceDB, + destChainID: destDB, + }, + chains: map[eth.ChainID]cc.ChainContainer{ + sourceChainID: newMockChainWithL1(sourceChainID, l1Block), + destChainID: newMockChainWithL1(destChainID, l1Block), + }, + } + + return interop, sharedTimestamp, map[eth.ChainID]eth.BlockID{ + sourceChainID: sourceBlock, + destChainID: destBlock, + } + }, + validate: func(t *testing.T, result Result) { + // Same-timestamp messages should now be VALID + require.True(t, result.IsValid(), "same-timestamp messages should be valid") + require.Empty(t, result.InvalidHeads, "no blocks should be invalid") + }, + }, + { + // Interop verification *never* expects to be given chain data for chains that are not part of the supernode, + // so this test is not helpful except to demonstrate the specified behavior: if chain data is available + // but is not part of the chains map for some reason, it should not be used at all, as it is unrelated to the + // superchain's interop verification. + name: "ValidBlocks/UnregisteredChainsSkipped", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + registeredChain := eth.ChainIDFromUInt64(10) + unregisteredChain := eth.ChainIDFromUInt64(9999) + + mockDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: common.HexToHash("0x1"), Number: 100, Time: 1000}, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{registeredChain: mockDB}, + chains: map[eth.ChainID]cc.ChainContainer{ + registeredChain: newMockChainWithL1(registeredChain, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}), + }, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{ + registeredChain: {Number: 100, Hash: common.HexToHash("0x1")}, + unregisteredChain: {Number: 200, Hash: common.HexToHash("0x2")}, + } + }, + validate: func(t *testing.T, result Result) { + registeredChain := eth.ChainIDFromUInt64(10) + unregisteredChain := eth.ChainIDFromUInt64(9999) + require.True(t, result.IsValid()) + require.Contains(t, result.L2Heads, registeredChain) + require.NotContains(t, result.L2Heads, unregisteredChain) + }, + }, + // Invalid block cases + { + name: "InvalidBlocks/BlockHashMismatch", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + chainID := eth.ChainIDFromUInt64(10) + expectedBlock := eth.BlockID{Number: 100, Hash: common.HexToHash("0xExpected")} + l1Block := eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")} + + mockDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{ + Hash: common.HexToHash("0xActual"), // Different from expected + Number: 100, + Time: 1000, + }, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{chainID: mockDB}, + chains: map[eth.ChainID]cc.ChainContainer{chainID: newMockChainWithL1(chainID, l1Block, expectedBlock)}, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{chainID: expectedBlock} + }, + validate: func(t *testing.T, result Result) { + chainID := eth.ChainIDFromUInt64(10) + expectedBlock := eth.BlockID{Number: 100, Hash: common.HexToHash("0xExpected")} + require.False(t, result.IsValid()) + require.Contains(t, result.InvalidHeads, chainID) + require.Equal(t, expectedBlock, result.InvalidHeads[chainID].BlockID) + }, + }, + { + name: "InvalidBlocks/InitiatingMessageNotFound", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + sourceChainID := eth.ChainIDFromUInt64(10) + destChainID := eth.ChainIDFromUInt64(8453) + + destBlockHash := common.HexToHash("0xDest") + destBlock := eth.BlockID{Number: 100, Hash: destBlockHash} + + execMsg := &suptypes.ExecutingMessage{ + ChainID: sourceChainID, + BlockNum: 50, + LogIdx: 0, + Timestamp: 500, + Checksum: suptypes.MessageChecksum{0x01}, + } + + sourceDB := &algoMockLogsDB{ + containsErr: suptypes.ErrConflict, // Message not found + } + + destDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: destBlockHash, Number: 100, Time: 1000}, + openBlockExecMsg: map[uint32]*suptypes.ExecutingMessage{ + 0: execMsg, + }, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{ + sourceChainID: sourceDB, + destChainID: destDB, + }, + chains: map[eth.ChainID]cc.ChainContainer{ + sourceChainID: newMockChainWithL1(sourceChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}), + destChainID: newMockChainWithL1(destChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}, destBlock), + }, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{destChainID: destBlock} + }, + validate: func(t *testing.T, result Result) { + destChainID := eth.ChainIDFromUInt64(8453) + require.False(t, result.IsValid()) + require.Contains(t, result.InvalidHeads, destChainID) + }, + }, + { + name: "InvalidBlocks/FutureTimestamp", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + // Future timestamp: initiating message timestamp > executing timestamp. + // This is INVALID (you can't execute a message that hasn't been initiated yet). + // Note: Same-timestamp (==) is ALLOWED, only strictly greater (>) is invalid. + sourceChainID := eth.ChainIDFromUInt64(10) + destChainID := eth.ChainIDFromUInt64(8453) + + destBlockHash := common.HexToHash("0xDest") + destBlock := eth.BlockID{Number: 100, Hash: destBlockHash} + + execMsg := &suptypes.ExecutingMessage{ + ChainID: sourceChainID, + BlockNum: 50, + LogIdx: 0, + Timestamp: 1001, // FUTURE timestamp (> 1000) - INVALID! + Checksum: suptypes.MessageChecksum{0x01}, + } + + sourceDB := &algoMockLogsDB{ + containsSeal: suptypes.BlockSeal{Number: 50, Timestamp: 1001}, + } + + destDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: destBlockHash, Number: 100, Time: 1000}, + openBlockExecMsg: map[uint32]*suptypes.ExecutingMessage{ + 0: execMsg, + }, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{ + sourceChainID: sourceDB, + destChainID: destDB, + }, + chains: map[eth.ChainID]cc.ChainContainer{ + sourceChainID: newMockChainWithL1(sourceChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}), + destChainID: newMockChainWithL1(destChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}, destBlock), + }, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{destChainID: destBlock} + }, + validate: func(t *testing.T, result Result) { + destChainID := eth.ChainIDFromUInt64(8453) + require.False(t, result.IsValid(), "future timestamp messages should be invalid") + require.Contains(t, result.InvalidHeads, destChainID) + }, + }, + { + name: "InvalidBlocks/UnknownSourceChain", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + unknownSourceChain := eth.ChainIDFromUInt64(9999) + destChainID := eth.ChainIDFromUInt64(8453) + + destBlockHash := common.HexToHash("0xDest") + destBlock := eth.BlockID{Number: 100, Hash: destBlockHash} + + execMsg := &suptypes.ExecutingMessage{ + ChainID: unknownSourceChain, // Not registered + BlockNum: 50, + LogIdx: 0, + Timestamp: 500, + Checksum: suptypes.MessageChecksum{0x01}, + } + + destDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: destBlockHash, Number: 100, Time: 1000}, + openBlockExecMsg: map[uint32]*suptypes.ExecutingMessage{ + 0: execMsg, + }, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{ + destChainID: destDB, + // Note: unknownSourceChain NOT in logsDBs + }, + chains: map[eth.ChainID]cc.ChainContainer{ + unknownSourceChain: newMockChainWithL1(unknownSourceChain, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}), + destChainID: newMockChainWithL1(destChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}, destBlock), + }, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{destChainID: destBlock} + }, + validate: func(t *testing.T, result Result) { + destChainID := eth.ChainIDFromUInt64(8453) + require.False(t, result.IsValid()) + require.Contains(t, result.InvalidHeads, destChainID) + }, + }, + { + name: "InvalidBlocks/ExpiredMessage", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + sourceChainID := eth.ChainIDFromUInt64(10) + destChainID := eth.ChainIDFromUInt64(8453) + + destBlockHash := common.HexToHash("0xDest") + // Executing block is at timestamp 1000000 (well after expiry) + execTimestamp := uint64(1000000) + // Initiating message timestamp is more than defaultMessageExpiryWindow (604800) before executing timestamp + initTimestamp := execTimestamp - defaultMessageExpiryWindow - 1 // 1 second past expiry + + destBlock := eth.BlockID{Number: 100, Hash: destBlockHash} + + execMsg := &suptypes.ExecutingMessage{ + ChainID: sourceChainID, + BlockNum: 50, + LogIdx: 0, + Timestamp: initTimestamp, // Expired! + Checksum: suptypes.MessageChecksum{0x01}, + } + + sourceDB := &algoMockLogsDB{ + containsSeal: suptypes.BlockSeal{Number: 50, Timestamp: initTimestamp}, + } + + destDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: destBlockHash, Number: 100, Time: execTimestamp}, + openBlockExecMsg: map[uint32]*suptypes.ExecutingMessage{ + 0: execMsg, + }, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{ + sourceChainID: sourceDB, + destChainID: destDB, + }, + chains: map[eth.ChainID]cc.ChainContainer{ + sourceChainID: newMockChainWithL1(sourceChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}), + destChainID: newMockChainWithL1(destChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}, destBlock), + }, + } + + return interop, execTimestamp, map[eth.ChainID]eth.BlockID{destChainID: destBlock} + }, + validate: func(t *testing.T, result Result) { + destChainID := eth.ChainIDFromUInt64(8453) + require.False(t, result.IsValid()) + require.Contains(t, result.InvalidHeads, destChainID) + }, + }, + { + name: "InvalidBlocks/MultipleChainsOneInvalid", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + sourceChainID := eth.ChainIDFromUInt64(10) + validChainID := eth.ChainIDFromUInt64(8453) + invalidChainID := eth.ChainIDFromUInt64(420) + + validBlockHash := common.HexToHash("0xValid") + invalidBlockHash := common.HexToHash("0xInvalid") + + validBlock := eth.BlockID{Number: 100, Hash: validBlockHash} + invalidBlock := eth.BlockID{Number: 200, Hash: invalidBlockHash} + + badExecMsg := &suptypes.ExecutingMessage{ + ChainID: sourceChainID, + BlockNum: 50, + LogIdx: 0, + Timestamp: 1001, // Future timestamp - INVALID + Checksum: suptypes.MessageChecksum{0x01}, + } + + sourceDB := &algoMockLogsDB{ + containsSeal: suptypes.BlockSeal{Number: 50, Timestamp: 1001}, + } + + validDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: validBlockHash, Number: 100, Time: 1000}, + openBlockExecMsg: nil, // No executing messages - valid + } + + invalidDB := &algoMockLogsDB{ + openBlockRef: eth.BlockRef{Hash: invalidBlockHash, Number: 200, Time: 1000}, + openBlockExecMsg: map[uint32]*suptypes.ExecutingMessage{ + 0: badExecMsg, + }, + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{ + sourceChainID: sourceDB, + validChainID: validDB, + invalidChainID: invalidDB, + }, + chains: map[eth.ChainID]cc.ChainContainer{ + invalidChainID: newMockChainWithL1(invalidChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}, invalidBlock), + sourceChainID: newMockChainWithL1(sourceChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}), + validChainID: newMockChainWithL1(validChainID, eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")}), + }, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{ + validChainID: validBlock, + invalidChainID: invalidBlock, + } + }, + validate: func(t *testing.T, result Result) { + validChainID := eth.ChainIDFromUInt64(8453) + invalidChainID := eth.ChainIDFromUInt64(420) + require.False(t, result.IsValid()) + // Both chains in L2Heads + require.Contains(t, result.L2Heads, validChainID) + require.Contains(t, result.L2Heads, invalidChainID) + // Only invalid in InvalidHeads + require.NotContains(t, result.InvalidHeads, validChainID) + require.Contains(t, result.InvalidHeads, invalidChainID) + }, + }, + // Error cases + { + name: "Errors/OpenBlockError", + setup: func() (*Interop, uint64, map[eth.ChainID]eth.BlockID) { + chainID := eth.ChainIDFromUInt64(10) + block := eth.BlockID{Number: 100, Hash: common.HexToHash("0x123")} + l1Block := eth.BlockID{Number: 40, Hash: common.HexToHash("0xL1")} + + mockDB := &algoMockLogsDB{ + openBlockErr: errors.New("database error"), + } + + interop := &Interop{ + messageExpiryWindow: defaultMessageExpiryWindow, + log: gethlog.New(), + logsDBs: map[eth.ChainID]LogsDB{chainID: mockDB}, + chains: map[eth.ChainID]cc.ChainContainer{chainID: newMockChainWithL1(chainID, l1Block)}, + } + + return interop, 1000, map[eth.ChainID]eth.BlockID{chainID: block} + }, + expectError: true, + errorMsg: "database error", + validate: func(t *testing.T, result Result) { + require.True(t, result.IsEmpty()) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + runVerifyInteropTest(t, tc) + }) + } +} + +// ============================================================================= +// Mock Types for Algorithm Tests +// ============================================================================= + +// algoMockLogsDB is a mock LogsDB for algorithm tests +type algoMockLogsDB struct { + openBlockRef eth.BlockRef + openBlockLogCnt uint32 + openBlockExecMsg map[uint32]*suptypes.ExecutingMessage + openBlockErr error + + firstSealedBlock suptypes.BlockSeal + firstSealedBlockErr error + + containsSeal suptypes.BlockSeal + containsErr error +} + +func (m *algoMockLogsDB) LatestSealedBlock() (eth.BlockID, bool) { return eth.BlockID{}, false } +func (m *algoMockLogsDB) FirstSealedBlock() (suptypes.BlockSeal, error) { + if m.firstSealedBlockErr != nil { + return suptypes.BlockSeal{}, m.firstSealedBlockErr + } + return m.firstSealedBlock, nil +} +func (m *algoMockLogsDB) FindSealedBlock(number uint64) (suptypes.BlockSeal, error) { + return suptypes.BlockSeal{}, nil +} +func (m *algoMockLogsDB) OpenBlock(blockNum uint64) (eth.BlockRef, uint32, map[uint32]*suptypes.ExecutingMessage, error) { + if m.openBlockErr != nil { + return eth.BlockRef{}, 0, nil, m.openBlockErr + } + return m.openBlockRef, m.openBlockLogCnt, m.openBlockExecMsg, nil +} +func (m *algoMockLogsDB) Contains(query suptypes.ContainsQuery) (suptypes.BlockSeal, error) { + if m.containsErr != nil { + return suptypes.BlockSeal{}, m.containsErr + } + return m.containsSeal, nil +} +func (m *algoMockLogsDB) AddLog(logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *suptypes.ExecutingMessage) error { + return nil +} +func (m *algoMockLogsDB) SealBlock(parentHash common.Hash, block eth.BlockID, timestamp uint64) error { + return nil +} +func (m *algoMockLogsDB) Rewind(inv reads.Invalidator, newHead eth.BlockID) error { return nil } +func (m *algoMockLogsDB) Clear(inv reads.Invalidator) error { return nil } +func (m *algoMockLogsDB) Close() error { return nil } + +var _ LogsDB = (*algoMockLogsDB)(nil) + +// testBlockInfo implements eth.BlockInfo for testing +type testBlockInfo struct { + hash common.Hash + parentHash common.Hash + number uint64 + timestamp uint64 +} + +func (m *testBlockInfo) Hash() common.Hash { return m.hash } +func (m *testBlockInfo) ParentHash() common.Hash { return m.parentHash } +func (m *testBlockInfo) Coinbase() common.Address { return common.Address{} } +func (m *testBlockInfo) Root() common.Hash { return common.Hash{} } +func (m *testBlockInfo) NumberU64() uint64 { return m.number } +func (m *testBlockInfo) Time() uint64 { return m.timestamp } +func (m *testBlockInfo) MixDigest() common.Hash { return common.Hash{} } +func (m *testBlockInfo) BaseFee() *big.Int { return big.NewInt(1) } +func (m *testBlockInfo) BlobBaseFee(chainConfig *params.ChainConfig) *big.Int { return big.NewInt(1) } +func (m *testBlockInfo) ExcessBlobGas() *uint64 { return nil } +func (m *testBlockInfo) ReceiptHash() common.Hash { return common.Hash{} } +func (m *testBlockInfo) GasUsed() uint64 { return 0 } +func (m *testBlockInfo) GasLimit() uint64 { return 30000000 } +func (m *testBlockInfo) BlobGasUsed() *uint64 { return nil } +func (m *testBlockInfo) ParentBeaconRoot() *common.Hash { return nil } +func (m *testBlockInfo) WithdrawalsRoot() *common.Hash { return nil } +func (m *testBlockInfo) HeaderRLP() ([]byte, error) { return nil, nil } +func (m *testBlockInfo) Header() *types.Header { return nil } +func (m *testBlockInfo) ID() eth.BlockID { return eth.BlockID{Hash: m.hash, Number: m.number} } + +var _ eth.BlockInfo = (*testBlockInfo)(nil) + +// ============================================================================= +// Mock Chain Container for Algo Tests +// ============================================================================= + +// algoMockChain is a simplified mock chain container for algo tests +type algoMockChain struct { + id eth.ChainID + optimisticL2 eth.BlockID + optimisticL1 eth.BlockID + optimisticAtErr error + blockHashes map[uint64]common.Hash +} + +func (m *algoMockChain) ID() eth.ChainID { return m.id } +func (m *algoMockChain) Start(ctx context.Context) error { return nil } +func (m *algoMockChain) Stop(ctx context.Context) error { return nil } +func (m *algoMockChain) Pause(ctx context.Context) error { return nil } +func (m *algoMockChain) Resume(ctx context.Context) error { return nil } +func (m *algoMockChain) PauseAndStopVN(ctx context.Context) error { return nil } +func (m *algoMockChain) RegisterVerifier(v activity.VerificationActivity) {} +func (m *algoMockChain) VerifierCurrentL1s() []eth.BlockID { return nil } +func (m *algoMockChain) LocalSafeBlockAtTimestamp(ctx context.Context, ts uint64) (eth.L2BlockRef, error) { + return eth.L2BlockRef{}, nil +} +func (m *algoMockChain) VerifiedAt(ctx context.Context, ts uint64) (eth.BlockID, eth.BlockID, error) { + return eth.BlockID{}, eth.BlockID{}, nil +} +func (m *algoMockChain) L1ForL2(ctx context.Context, l2Block eth.BlockID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} +func (m *algoMockChain) OptimisticAt(ctx context.Context, ts uint64) (eth.BlockID, eth.BlockID, error) { + if m.optimisticAtErr != nil { + return eth.BlockID{}, eth.BlockID{}, m.optimisticAtErr + } + return m.optimisticL2, m.optimisticL1, nil +} +func (m *algoMockChain) OutputRootAtL2BlockNumber(ctx context.Context, l2BlockNum uint64) (eth.Bytes32, error) { + return eth.Bytes32{}, nil +} +func (m *algoMockChain) OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*eth.OutputV0, error) { + return nil, nil +} +func (m *algoMockChain) FetchReceipts(ctx context.Context, blockID eth.BlockID) (eth.BlockInfo, types.Receipts, error) { + return nil, types.Receipts{}, nil +} +func (m *algoMockChain) SyncStatus(ctx context.Context) (*eth.SyncStatus, error) { + return ð.SyncStatus{}, nil +} +func (m *algoMockChain) TimestampToBlockNumber(ctx context.Context, ts uint64) (uint64, error) { + return ts, nil +} +func (m *algoMockChain) RewindEngine(ctx context.Context, timestamp uint64, invalidatedBlock eth.BlockRef) error { + return nil +} +func (m *algoMockChain) BlockTime() uint64 { return 1 } +func (m *algoMockChain) InvalidateBlock(ctx context.Context, height uint64, payloadHash common.Hash, decisionTimestamp uint64, stateRoot, messagePasserStorageRoot eth.Bytes32) (bool, error) { + return false, nil +} +func (m *algoMockChain) OutputV0AtBlockNumber(ctx context.Context, l2BlockNum uint64) (*eth.OutputV0, error) { + out := ð.OutputV0{} + if m.blockHashes != nil { + out.BlockHash = m.blockHashes[l2BlockNum] + } + return out, nil +} +func (m *algoMockChain) GetDeniedOutput(height uint64, payloadHash common.Hash) (*eth.OutputV0, error) { + return nil, nil +} +func (m *algoMockChain) PruneDeniedAtOrAfterTimestamp(timestamp uint64) (map[uint64][]common.Hash, error) { + return nil, nil +} +func (m *algoMockChain) IsDenied(height uint64, payloadHash common.Hash) (bool, error) { + return false, nil +} +func (m *algoMockChain) SetResetCallback(cb cc.ResetCallback) {} + +var _ cc.ChainContainer = (*algoMockChain)(nil) diff --git a/op-supernode/supernode/activity/interop/checker.go b/op-supernode/supernode/activity/interop/checker.go new file mode 100644 index 00000000000..1e429b70d28 --- /dev/null +++ b/op-supernode/supernode/activity/interop/checker.go @@ -0,0 +1,42 @@ +package interop + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// l1ByNumberSource provides L1 block lookups by number for consistency checking. +type l1ByNumberSource interface { + L1BlockRefByNumber(ctx context.Context, num uint64) (eth.L1BlockRef, error) +} + +// byNumberConsistencyChecker verifies that a set of L1 block IDs all belong to +// the same L1 fork by comparing each against the canonical chain. +type byNumberConsistencyChecker struct { + l1 l1ByNumberSource +} + +func newByNumberConsistencyChecker(l1 l1ByNumberSource) *byNumberConsistencyChecker { + if l1 == nil { + return nil + } + return &byNumberConsistencyChecker{l1: l1} +} + +// SameL1Chain returns true if all non-zero heads belong to the same canonical L1 chain. +func (c *byNumberConsistencyChecker) SameL1Chain(ctx context.Context, heads []eth.BlockID) (bool, error) { + for _, head := range heads { + if head == (eth.BlockID{}) { + continue + } + canonical, err := c.l1.L1BlockRefByNumber(ctx, head.Number) + if err != nil { + return false, err + } + if canonical.Hash != head.Hash { + return false, nil + } + } + return true, nil +} diff --git a/op-supernode/supernode/activity/interop/checker_test.go b/op-supernode/supernode/activity/interop/checker_test.go new file mode 100644 index 00000000000..79e99614d0a --- /dev/null +++ b/op-supernode/supernode/activity/interop/checker_test.go @@ -0,0 +1,77 @@ +package interop + +import ( + "context" + "fmt" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +type mockL1Source struct { + blocks map[uint64]eth.L1BlockRef +} + +func (m *mockL1Source) L1BlockRefByNumber(ctx context.Context, num uint64) (eth.L1BlockRef, error) { + ref, ok := m.blocks[num] + if !ok { + return eth.L1BlockRef{}, fmt.Errorf("block %d not found", num) + } + return ref, nil +} + +func TestByNumberConsistencyChecker_SameL1Chain(t *testing.T) { + t.Parallel() + + hashA := common.HexToHash("0xaaaa") + hashB := common.HexToHash("0xbbbb") + + source := &mockL1Source{ + blocks: map[uint64]eth.L1BlockRef{ + 100: {Hash: hashA, Number: 100}, + 200: {Hash: hashB, Number: 200}, + }, + } + checker := newByNumberConsistencyChecker(source) + + t.Run("all match canonical", func(t *testing.T) { + same, err := checker.SameL1Chain(context.Background(), []eth.BlockID{ + {Hash: hashA, Number: 100}, + {Hash: hashB, Number: 200}, + }) + require.NoError(t, err) + require.True(t, same) + }) + + t.Run("mismatch detected", func(t *testing.T) { + same, err := checker.SameL1Chain(context.Background(), []eth.BlockID{ + {Hash: hashA, Number: 100}, + {Hash: common.HexToHash("0xdead"), Number: 200}, // wrong hash + }) + require.NoError(t, err) + require.False(t, same) + }) + + t.Run("zero block IDs skipped", func(t *testing.T) { + same, err := checker.SameL1Chain(context.Background(), []eth.BlockID{ + {}, + {Hash: hashA, Number: 100}, + {}, + }) + require.NoError(t, err) + require.True(t, same) + }) + + t.Run("empty heads list", func(t *testing.T) { + same, err := checker.SameL1Chain(context.Background(), []eth.BlockID{}) + require.NoError(t, err) + require.True(t, same) + }) + + t.Run("nil checker returns nil", func(t *testing.T) { + nilChecker := newByNumberConsistencyChecker(nil) + require.Nil(t, nilChecker) + }) +} diff --git a/op-supernode/supernode/activity/interop/cycle.go b/op-supernode/supernode/activity/interop/cycle.go new file mode 100644 index 00000000000..6d6551c8d1b --- /dev/null +++ b/op-supernode/supernode/activity/interop/cycle.go @@ -0,0 +1,238 @@ +package interop + +import ( + "cmp" + "errors" + "fmt" + "slices" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +// ErrCycle is returned when a cycle is detected in same-timestamp messages. +var ErrCycle = errors.New("cycle detected in same-timestamp messages") + +// dependencyNode represents a log entry in the dependency graph. +// It tracks what this node depends on, and what depends on this node. +type dependencyNode struct { + chainID eth.ChainID + logIndex uint32 + execMsg *types.ExecutingMessage // nil if not an executing message + + resolved bool + dependsOn []*dependencyNode + dependedOnBy []*dependencyNode +} + +// dependencyGraph is a collection of dependency nodes for cycle checking. +type dependencyGraph []*dependencyNode + +// addNode adds a node to the graph. +func (g *dependencyGraph) addNode(n *dependencyNode) { + *g = append(*g, n) +} + +// addEdge adds a directed dependency: "from" depends on "to". +// This means "to" must be resolved before "from" can be resolved. +func (g *dependencyGraph) addEdge(from, to *dependencyNode) { + from.dependsOn = append(from.dependsOn, to) + to.dependedOnBy = append(to.dependedOnBy, from) +} + +// checkCycle runs Kahn's topological sort algorithm to detect cycles. +// Returns nil if the graph is acyclic (valid), ErrCycle if a cycle is detected. +// +// Algorithm: +// 1. Find nodes with no dependedOnBy (nothing depends on them) → add to removeSet, mark resolved +// 2. Remove items in removeSet from dependedOnBy of all nodes +// 3. Repeat until either: +// - All nodes resolved → acyclic (valid) +// - No progress (removeSet empty but unresolved nodes remain) → cycle detected +func checkCycle(g *dependencyGraph) error { + if len(*g) == 0 { + return nil + } + + for { + // Part 1: Find nodes with no dependedOnBy and mark them resolved + var removeSet []*dependencyNode + for _, node := range *g { + if !node.resolved && len(node.dependedOnBy) == 0 { + node.resolved = true + removeSet = append(removeSet, node) + } + } + + // If no nodes can be removed, check termination + if len(removeSet) == 0 { + // Check if all nodes are resolved + for _, node := range *g { + if !node.resolved { + // Unresolved nodes remain but no progress → cycle detected + return ErrCycle + } + } + // All nodes resolved → acyclic + return nil + } + + // Part 2: Remove items in removeSet from dependedOnBy of all nodes + for _, removed := range removeSet { + // Remove this node from dependedOnBy of nodes it depends on + for _, dependency := range removed.dependsOn { + dependency.dependedOnBy = removeFromSlice(dependency.dependedOnBy, removed) + } + } + } +} + +// removeFromSlice removes a node from a slice of nodes. +func removeFromSlice(slice []*dependencyNode, toRemove *dependencyNode) []*dependencyNode { + result := make([]*dependencyNode, 0, len(slice)) + for _, n := range slice { + if n != toRemove { + result = append(result, n) + } + } + return result +} + +// executingMessageBefore finds the latest EM in the slice with logIndex <= targetLogIdx. +// The slice must be sorted by logIndex ascending. +// Returns nil if no such EM exists. +func executingMessageBefore(chainEMs []*dependencyNode, targetLogIdx uint32) *dependencyNode { + var result *dependencyNode + for _, em := range chainEMs { + if em.logIndex <= targetLogIdx { + result = em // keep updating to get the latest one at or before target + } else { + break // since sorted, no need to continue + } + } + return result +} + +// buildCycleGraph constructs a dependency graph from executing messages at the given timestamp. +// it assumes all executing messages are included on blocks of the given timestamp +// For each EM, two types of edges are added: +// 1. Intra-chain: depends on the previous EM on the same chain (if exists) +// 2. Cross-chain: depends on executingMessageBefore(targetChain, targetLogIdx) (if exists) +func buildCycleGraph(ts uint64, chainEMs map[eth.ChainID]map[uint32]*types.ExecutingMessage) *dependencyGraph { + graph := &dependencyGraph{} + orderedExecutingMessages := make(map[eth.ChainID][]*dependencyNode) + + // First pass: create nodes for all same-timestamp EMs + for chainID, emsMap := range chainEMs { + for logIdx, em := range emsMap { + if em != nil && em.Timestamp == ts { + node := &dependencyNode{ + chainID: chainID, + logIndex: logIdx, + execMsg: em, + } + graph.addNode(node) + orderedExecutingMessages[chainID] = append(orderedExecutingMessages[chainID], node) + } + } + } + + // Sort each chain's nodes by logIndex (map iteration order is non-deterministic) + for _, nodes := range orderedExecutingMessages { + slices.SortFunc(nodes, func(a, b *dependencyNode) int { + return cmp.Compare(a.logIndex, b.logIndex) + }) + } + + // Second pass: add edges + for _, nodes := range orderedExecutingMessages { + for i, node := range nodes { + // all nodes point back to the previous node on the same chain + if i > 0 { + graph.addEdge(node, nodes[i-1]) + } + + // all nodes also point to their target + targetChainEMs := orderedExecutingMessages[node.execMsg.ChainID] + target := executingMessageBefore(targetChainEMs, node.execMsg.LogIdx) + if target != nil { + graph.addEdge(node, target) + } + } + } + + return graph +} + +// verifyCycleMessages is the cycle verification function for same-timestamp interop. +// It verifies that same-timestamp executing messages form valid dependency relationships +// using Kahn's topological sort algorithm. +// +// Returns a Result with InvalidHeads populated for chains participating in cycles. +func (i *Interop) verifyCycleMessages(ts uint64, blocksAtTimestamp map[eth.ChainID]eth.BlockID) (Result, error) { + result := Result{ + Timestamp: ts, + L2Heads: blocksAtTimestamp, + } + + // collect all EMs for the given blocks per chain + chainEMs := make(map[eth.ChainID]map[uint32]*types.ExecutingMessage) + for chainID, blockID := range blocksAtTimestamp { + if frontierBlock, ok := i.frontierView.block(chainID); ok { + if frontierBlock.ref.Time == ts { + chainEMs[chainID] = frontierBlock.execMsgs + } + continue + } + + db, ok := i.logsDBs[chainID] + if !ok { + // Chain not in logsDBs - skip it for cycle verification + continue + } + blockRef, _, execMsgs, err := db.OpenBlock(blockID.Number) + if err != nil { + // Can't open block - no EMs to add to the graph for this chain + // This can happen if the logsDB is empty or the block hasn't been indexed + continue + } + // Verify the block has the expected timestamp + if blockRef.Time != ts { + // Block timestamp mismatch - skip this chain for cycle verification + continue + } + chainEMs[chainID] = execMsgs + } + + // Build dependency graph and check for cycles + graph := buildCycleGraph(ts, chainEMs) + if err := checkCycle(graph); err != nil { + // Cycle detected - mark only chains with unresolved nodes as invalid + // (bystander chains that have same-ts EMs but aren't part of the cycle are spared) + cycleChains := collectCycleParticipants(graph) + if len(cycleChains) > 0 { + result.InvalidHeads = make(map[eth.ChainID]InvalidHead) + for chainID := range cycleChains { + invalid, err := i.newInvalidHead(chainID, blocksAtTimestamp[chainID]) + if err != nil { + return Result{}, fmt.Errorf("chain %s: %w", chainID, err) + } + result.InvalidHeads[chainID] = invalid + } + } + } + + return result, nil +} + +// collectCycleParticipants returns the set of chains that have unresolved nodes +// after running checkCycle. These are the chains actually participating in a cycle. +func collectCycleParticipants(graph *dependencyGraph) map[eth.ChainID]bool { + cycleChains := make(map[eth.ChainID]bool) + for _, node := range *graph { + if !node.resolved { + cycleChains[node.chainID] = true + } + } + return cycleChains +} diff --git a/op-supernode/supernode/activity/interop/interop.go b/op-supernode/supernode/activity/interop/interop.go new file mode 100644 index 00000000000..cf0639c34d6 --- /dev/null +++ b/op-supernode/supernode/activity/interop/interop.go @@ -0,0 +1,874 @@ +package interop + +import ( + "context" + "errors" + "fmt" + "sort" + "sync" + "sync/atomic" + "time" + + opservice "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supernode/flags" + "github.com/ethereum-optimism/optimism/op-supernode/supernode/activity" + cc "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +// Compile-time interface conformance assertions. +var ( + _ activity.RunnableActivity = (*Interop)(nil) + _ activity.VerificationActivity = (*Interop)(nil) + backoffPeriod = 1 * time.Second // backoff when chains aren't ready + errorBackoffPeriod = 2 * time.Second // backoff on errors +) + +// InteropActivationTimestampFlag is the CLI flag for the interop activation timestamp. +var InteropActivationTimestampFlag = &cli.Uint64Flag{ + Name: "interop.activation-timestamp", + Usage: "Override the interop activation timestamp derived from rollup configs", + EnvVars: opservice.PrefixEnvVar(flags.EnvVarPrefix, "INTEROP_ACTIVATION_TIMESTAMP"), + Value: 0, +} + +// InteropLogBackfillDepthFlag extends initiating-message log ingestion backward from the L2 tip by this duration (clamped to activation). Validation still starts only beyond the local safe head. +var InteropLogBackfillDepthFlag = &cli.DurationFlag{ + Name: "interop.log-backfill-depth", + Usage: "Duration to pre-ingest logs behind the tip before interop validation (e.g. 168h). Never loads logs before interop.activation-timestamp. Requires interop.activation-timestamp.", + Value: 0, +} + +func init() { + flags.RegisterActivityFlags(InteropActivationTimestampFlag, InteropLogBackfillDepthFlag) +} + +// chainsReadyResult holds the parallel query results from checkChainsReady. +type chainsReadyResult struct { + blocks map[eth.ChainID]eth.BlockID // L2 blocks at the timestamp + l1Heads map[eth.ChainID]eth.BlockID // per-chain L1 inclusion heads +} + +// RoundObservation is a consistent snapshot of the current round's state, +// captured upfront so the decision function operates on immutable data. +type RoundObservation struct { + LastVerifiedTS *uint64 + LastVerified *VerifiedResult + NextTimestamp uint64 + ChainsReady bool + BlocksAtTS map[eth.ChainID]eth.BlockID + L1Heads map[eth.ChainID]eth.BlockID + L1Consistent bool + Paused bool +} + +// Decision represents the outcome of the pure decision function. +type Decision int + +const ( + DecisionWait Decision = iota + DecisionAdvance + DecisionInvalidate + DecisionRewind +) + +// StepOutput combines a decision with the verification result (if any). +type StepOutput struct { + Decision Decision + Result Result +} + +// Interop is a VerificationActivity that can also run background work as a RunnableActivity. +type Interop struct { + log log.Logger + chains map[eth.ChainID]cc.ChainContainer + activationTimestamp uint64 // immutable protocol activation timestamp + + // runtimeActivationTimestamp is the effective activation timestamp used + // by the main loop and backfill. It starts equal to activationTimestamp + // and is advanced past the backfilled range after log backfill completes. + // Protocol-facing queries (VerifiedAtTimestamp, VerifiedBlockAtL1, etc.) + // always use the original activationTimestamp. + runtimeActivationTimestamp uint64 + + dataDir string + + messageExpiryWindow uint64 + + verifiedDB *VerifiedDB + logsDBs map[eth.ChainID]LogsDB + + mu sync.RWMutex + ctx context.Context + cancel context.CancelFunc + started bool + + currentL1 eth.BlockID + + verifyFn func(ts uint64, blocksAtTimestamp map[eth.ChainID]eth.BlockID) (Result, error) + + // cycleVerifyFn handles same-timestamp cycle verification. + // It is called after verifyFn in progressInterop, and its results are merged. + // Set to verifyCycleMessages by default in New(). + cycleVerifyFn func(ts uint64, blocksAtTimestamp map[eth.ChainID]eth.BlockID) (Result, error) + + // pauseAtTimestamp is used for integration test control only. + // When non-zero, progressInterop will return early without processing + // if the next timestamp to process is >= this value. + pauseAtTimestamp atomic.Uint64 + + // backfillAttempts counts the number of times runLogBackfill was invoked + // since Start. Read by integration tests to confirm the retry loop is engaged. + backfillAttempts atomic.Int32 + // backfillCompleted is set to true once runLogBackfill returns nil (or was skipped + // because logBackfillDepth <= 0). Read by integration tests to gate on backfill finishing. + backfillCompleted atomic.Bool + + l1Checker *byNumberConsistencyChecker + frontierView *frontierVerificationView + + logBackfillDepth time.Duration +} + +func (i *Interop) Name() string { + return "interop" +} + +// New constructs a new Interop activity. +func New( + log log.Logger, + activationTimestamp uint64, + messageExpiryWindow uint64, + chains map[eth.ChainID]cc.ChainContainer, + dataDir string, + l1Source l1ByNumberSource, + logBackfillDepth time.Duration, +) *Interop { + verifiedDB, err := OpenVerifiedDB(dataDir) + if err != nil { + log.Error("failed to open verified DB", "err", err) + return nil + } + + // Initialize logsDBs for each chain + logsDBs := make(map[eth.ChainID]LogsDB) + for chainID := range chains { + logsDB, err := openLogsDB(log, chainID, dataDir) + if err != nil { + log.Error("failed to open logs DB for chain", "chainID", chainID, "err", err) + // Clean up already created logsDBs + for _, db := range logsDBs { + _ = db.Close() + } + _ = verifiedDB.Close() + return nil + } + logsDBs[chainID] = logsDB + } + + if messageExpiryWindow == 0 { + messageExpiryWindow = defaultMessageExpiryWindow + } + i := &Interop{ + log: log, + chains: chains, + verifiedDB: verifiedDB, + logsDBs: logsDBs, + dataDir: dataDir, + activationTimestamp: activationTimestamp, + runtimeActivationTimestamp: activationTimestamp, + messageExpiryWindow: messageExpiryWindow, + logBackfillDepth: logBackfillDepth, + } + // default to using the verifyInteropMessages function + // (can be overridden by tests) + i.verifyFn = i.verifyInteropMessages + i.cycleVerifyFn = i.verifyCycleMessages + i.l1Checker = newByNumberConsistencyChecker(l1Source) + return i +} + +// Start begins the Interop activity background loop and blocks until ctx is canceled. +func (i *Interop) Start(ctx context.Context) error { + i.mu.Lock() + if i.started { + i.mu.Unlock() + <-ctx.Done() + return ctx.Err() + } + i.ctx, i.cancel = context.WithCancel(ctx) + i.started = true + i.mu.Unlock() + + if i.logBackfillDepth > 0 { + i.log.Info("interop log backfill depth configured", "duration", i.logBackfillDepth.String()) + for { + i.backfillAttempts.Add(1) + err := i.runLogBackfill() + if err == nil { + break + } + i.log.Warn("log backfill failed, retrying (virtual nodes may not be ready yet)", "err", err) + select { + case <-i.ctx.Done(): + return fmt.Errorf("log backfill interrupted: %w", i.ctx.Err()) + case <-time.After(errorBackoffPeriod): + } + } + } + i.backfillCompleted.Store(true) + + for { + select { + case <-i.ctx.Done(): + return i.ctx.Err() + default: + madeProgress, err := i.progressAndRecord() + if err != nil { + // Error: back off before next attempt + i.log.Error("failed to progress and record interop", "err", err) + time.Sleep(errorBackoffPeriod) + continue + } + if !madeProgress { + // Chains not ready, back off before next attempt + time.Sleep(backoffPeriod) + } + // Otherwise: immediately ready for next iteration (aggressive catch-up) + } + } +} + +// Stop stops the Interop activity. +func (i *Interop) Stop(ctx context.Context) error { + i.mu.Lock() + defer i.mu.Unlock() + if i.cancel != nil { + i.cancel() + } + // Close all logsDBs + for chainID, db := range i.logsDBs { + if err := db.Close(); err != nil { + i.log.Error("failed to close logs DB", "chainID", chainID, "err", err) + } + } + if i.verifiedDB != nil { + return i.verifiedDB.Close() + } + return nil +} + +// checkPreconditions determines whether observation alone already implies an +// action, before running verification. It returns nil when verification should +// proceed. +func checkPreconditions(obs RoundObservation) *StepOutput { + if obs.Paused { + output := StepOutput{Decision: DecisionWait} + return &output + } + if !obs.ChainsReady { + output := StepOutput{Decision: DecisionWait} + return &output + } + if !obs.L1Consistent { + output := StepOutput{Decision: DecisionRewind} + return &output + } + return nil +} + +// decideVerifiedResult determines the next action from a completed verification +// result. No side effects, no I/O. +func decideVerifiedResult(_ RoundObservation, verified Result) StepOutput { + if verified.IsEmpty() { + return StepOutput{Decision: DecisionWait} + } + if !verified.IsValid() { + return StepOutput{Decision: DecisionInvalidate, Result: verified} + } + return StepOutput{Decision: DecisionAdvance, Result: verified} +} + +// progressAndRecord attempts to progress interop and record the result. +// Returns (madeProgress, error) where madeProgress indicates if we advanced the verified timestamp. +func (i *Interop) progressAndRecord() (bool, error) { + pending, err := i.verifiedDB.GetPendingTransition() + if err != nil { + return false, fmt.Errorf("get pending transition: %w", err) + } + if pending != nil { + return i.applyPendingTransition(*pending) + } + + output, obs, err := i.progressInterop() + if err != nil { + return false, err + } + if output.Decision == DecisionWait { + return i.refreshCurrentL1OnWait() + } + if output.Decision == DecisionRewind && obs.LastVerifiedTS == nil { + return false, nil + } + + pendingTx, err := i.buildPendingTransition(output, obs) + if err != nil { + return false, err + } + if err := i.verifiedDB.SetPendingTransition(pendingTx); err != nil { + return false, fmt.Errorf("persist pending transition: %w", err) + } + return i.applyPendingTransition(pendingTx) +} + +func (i *Interop) refreshCurrentL1OnWait() (bool, error) { + localL1, err := i.collectCurrentL1() + if err != nil { + // Non-fatal: just keep existing currentL1. + i.log.Debug("failed to collect current L1 on wait", "err", err) + return false, nil + } + i.mu.Lock() + i.currentL1 = localL1 + i.mu.Unlock() + return false, nil +} + +// progressInterop prepares the next interop action by observing the world, +// optionally verifying the frontier, and returning the resulting decision. +// It does not apply any side effects itself. +func (i *Interop) progressInterop() (StepOutput, RoundObservation, error) { + obs, err := i.observeRound() + if err != nil { + return StepOutput{}, RoundObservation{}, err + } + + if early := checkPreconditions(obs); early != nil { + return *early, obs, nil + } + + result, err := i.verify(obs.NextTimestamp, obs.BlocksAtTS) + if err != nil { + return StepOutput{}, obs, err + } + + return decideVerifiedResult(obs, result), obs, nil +} + +// observeRound captures a consistent snapshot of the current round state. +// All reads happen here; the decision function operates on this snapshot. +func (i *Interop) observeRound() (RoundObservation, error) { + var obs RoundObservation + + lastTS, initialized := i.verifiedDB.LastTimestamp() + if initialized { + ts := lastTS + obs.LastVerifiedTS = &ts + result, err := i.verifiedDB.Get(lastTS) + if err != nil { + return obs, fmt.Errorf("failed to read last verified result: %w", err) + } + obs.LastVerified = &result + obs.NextTimestamp = lastTS + 1 + } else { + obs.NextTimestamp = i.runtimeActivationTimestamp + } + + if pauseTS := i.pauseAtTimestamp.Load(); pauseTS != 0 && obs.NextTimestamp >= pauseTS { + obs.Paused = true + return obs, nil + } + + ready, err := i.checkChainsReady(obs.NextTimestamp) + if err != nil { + if errors.Is(err, ethereum.NotFound) { + obs.ChainsReady = false + return obs, nil + } + return obs, err + } + obs.ChainsReady = true + obs.BlocksAtTS = ready.blocks + obs.L1Heads = ready.l1Heads + + // Check that all frontier L1 heads AND the accepted L1 head are on the same canonical fork. + obs.L1Consistent = true + if i.l1Checker != nil { + heads := make([]eth.BlockID, 0, len(obs.L1Heads)+1) + if obs.LastVerified != nil { + heads = append(heads, obs.LastVerified.L1Inclusion) + } + for _, l1 := range obs.L1Heads { + heads = append(heads, l1) + } + same, err := i.l1Checker.SameL1Chain(i.ctx, heads) + if err != nil { + return obs, fmt.Errorf("L1 consistency check: %w", err) + } + obs.L1Consistent = same + } + + return obs, nil +} + +// verify runs the heavy I/O: log loading, message verification, and cycle detection. +func (i *Interop) verify(ts uint64, blocksAtTS map[eth.ChainID]eth.BlockID) (Result, error) { + view, err := i.resolveFrontierVerificationView(blocksAtTS) + if err != nil { + return Result{}, fmt.Errorf("resolve frontier verification view: %w", err) + } + i.frontierView = view + defer func() { + i.frontierView = nil + }() + + result, err := i.verifyFn(ts, blocksAtTS) + if err != nil { + return Result{}, err + } + + cycleResult, err := i.cycleVerifyFn(ts, blocksAtTS) + if err != nil { + return Result{}, fmt.Errorf("cycle verification failed: %w", err) + } + + if len(cycleResult.InvalidHeads) > 0 { + if result.InvalidHeads == nil { + result.InvalidHeads = make(map[eth.ChainID]InvalidHead) + } + for chainID, invalidBlock := range cycleResult.InvalidHeads { + result.InvalidHeads[chainID] = invalidBlock + } + } + + return result, nil +} + +// newInvalidHead constructs a fully-formed InvalidHead with the output preimage +// fields already attached. Returns an error if the output cannot be computed — +// at verification time the engine should always have the block, so failure +// indicates a transient RPC issue or a serious invariant violation. +func (i *Interop) newInvalidHead(chainID eth.ChainID, blockID eth.BlockID) (InvalidHead, error) { + head := InvalidHead{BlockID: blockID} + chain, ok := i.chains[chainID] + if !ok { + return head, fmt.Errorf("chain %s not found", chainID) + } + outputV0, err := chain.OutputV0AtBlockNumber(i.ctx, blockID.Number) + if err != nil { + return head, fmt.Errorf("chain %s: failed to compute OutputV0 for block %d: %w", chainID, blockID.Number, err) + } + if outputV0.BlockHash != blockID.Hash { + return head, fmt.Errorf("chain %s: block %d hash changed (expected %s, got %s): possible reorg", + chainID, blockID.Number, blockID.Hash, outputV0.BlockHash) + } + head.StateRoot = outputV0.StateRoot + head.MessagePasserStorageRoot = outputV0.MessagePasserStorageRoot + return head, nil +} + +func (i *Interop) buildPendingTransition(output StepOutput, obs RoundObservation) (PendingTransition, error) { + switch output.Decision { + case DecisionAdvance, DecisionInvalidate: + result := output.Result + return PendingTransition{ + Decision: output.Decision, + Result: &result, + }, nil + case DecisionRewind: + rewindPlan, err := i.buildRewindPlan(*obs.LastVerifiedTS) + if err != nil { + return PendingTransition{}, fmt.Errorf("build rewind plan: %w", err) + } + return PendingTransition{ + Decision: DecisionRewind, + Rewind: &rewindPlan, + }, nil + default: + return PendingTransition{}, fmt.Errorf("unsupported transition decision: %v", output.Decision) + } +} + +func (i *Interop) applyPendingTransition(pending PendingTransition) (bool, error) { + switch pending.Decision { + case DecisionRewind: + if pending.Rewind == nil { + return false, fmt.Errorf("invalid pending rewind transition: missing rewind plan") + } + i.mu.Lock() + i.currentL1 = eth.BlockID{} + i.mu.Unlock() + if err := i.applyRewindPlan(*pending.Rewind); err != nil { + return false, fmt.Errorf("apply rewind plan: %w", err) + } + if err := i.verifiedDB.ClearPendingTransition(); err != nil { + return false, fmt.Errorf("clear pending transition: %w", err) + } + return false, nil + + case DecisionInvalidate: + if pending.Result == nil { + if err := i.verifiedDB.ClearPendingTransition(); err != nil { + return false, fmt.Errorf("clear empty invalidation transition: %w", err) + } + return false, nil + } + invalidations := make([]PendingInvalidation, 0, len(pending.Result.InvalidHeads)) + for chainID, invalidHead := range pending.Result.InvalidHeads { + invalidations = append(invalidations, PendingInvalidation{ + ChainID: chainID, + BlockID: invalidHead.BlockID, + Timestamp: pending.Result.Timestamp, + StateRoot: invalidHead.StateRoot, + MessagePasserStorageRoot: invalidHead.MessagePasserStorageRoot, + }) + } + sort.Slice(invalidations, func(i, j int) bool { + return invalidations[i].ChainID.Cmp(invalidations[j].ChainID) < 0 + }) + // Freeze ALL chains' VNs before rewinding any. Without this, a non-invalidated + // chain's still-running VN can observe the interop state change from onReset and + // issue a ForkchoiceUpdate that advances its safe head. If that chain is later + // invalidated (e.g. transitive invalidation across multiple rounds), its rewind + // will be rejected because the safe head was already advanced. + // This is broader than freezing only invalid chains because transitive invalidation + // requires multiple verification rounds — a chain valid in round N may become + // invalid in round N+1 after its dependency is replaced. + for chainID, chain := range i.chains { + if err := chain.PauseAndStopVN(i.ctx); err != nil { + i.log.Error("failed to freeze chain before rewind", "chainID", chainID, "err", err) + } + } + var failedAny bool + for _, p := range invalidations { + if err := i.invalidateBlock(p.ChainID, p.BlockID, p.Timestamp, p.StateRoot, p.MessagePasserStorageRoot); err != nil { + i.log.Error("invalidation failed, transition preserved for retry on restart", + "chain", p.ChainID, "block", p.BlockID, "err", err) + failedAny = true + } + } + // Resume non-invalidated chains. Invalidated chains are resumed by RewindEngine. + for chainID, chain := range i.chains { + if _, isInvalid := pending.Result.InvalidHeads[chainID]; !isInvalid { + if err := chain.Resume(i.ctx); err != nil { + i.log.Error("failed to resume chain after rewind", "chainID", chainID, "err", err) + } + } + } + if failedAny { + return false, fmt.Errorf("one or more invalidations failed, transition preserved") + } + if err := i.verifiedDB.ClearPendingTransition(); err != nil { + return false, fmt.Errorf("clear pending transition: %w", err) + } + return false, nil + + case DecisionAdvance: + if pending.Result == nil { + if err := i.verifiedDB.ClearPendingTransition(); err != nil { + return false, fmt.Errorf("clear empty advance transition: %w", err) + } + return false, nil + } + if err := i.persistFrontierLogs(pending.Result.Timestamp, pending.Result.L2Heads); err != nil { + return false, fmt.Errorf("persist frontier logs: %w", err) + } + + if err := i.commitVerifiedResult(pending.Result.Timestamp, pending.Result.ToVerifiedResult()); err != nil { + return false, fmt.Errorf("commit verified result: %w", err) + } + if err := i.verifiedDB.ClearPendingTransition(); err != nil { + return false, fmt.Errorf("clear pending transition: %w", err) + } + i.log.Info("committed verified result", "timestamp", pending.Result.Timestamp) + // L1Inclusion is the max L1 block used for derivation across all chains at this + // timestamp. It can exceed some chains' actual CurrentL1 — e.g. chain A derived + // from L1 1000 while chain B derived from L1 990. Chain B may then advance to + // the next timestamp (finding its batch at L1 995) without ever reaching L1 1000. + // Cap at the min of all nodes' CurrentL1 so this field is individually safe, not + // just safe when aggregated with chain CurrentL1s via syncstatus.Aggregate. + currentL1 := pending.Result.L1Inclusion + if localL1, err := i.collectCurrentL1(); err != nil { + i.log.Warn("failed to collect node CurrentL1 on advance, using L1Inclusion", "err", err) + } else if localL1.Number < currentL1.Number { + currentL1 = localL1 + } + i.mu.Lock() + i.currentL1 = currentL1 + i.mu.Unlock() + return true, nil + } + + return false, nil +} + +func (i *Interop) buildRewindPlan(lastTS uint64) (RewindPlan, error) { + plan := RewindPlan{ + RewindAtOrAfter: lastTS, + } + + if lastTS <= i.runtimeActivationTimestamp { + return plan, nil + } + + rewindTargetTS := lastTS - 1 + prevResult, err := i.verifiedDB.Get(rewindTargetTS) + if err != nil { + return RewindPlan{}, fmt.Errorf("read previous verified result at %d: %w", rewindTargetTS, err) + } + plan.ResetAllChainsTo = &rewindTargetTS + plan.TargetHeads = prevResult.L2Heads + return plan, nil +} + +func (i *Interop) applyRewindPlan(plan RewindPlan) error { + i.log.Warn("rewinding accepted state due to drift", "timestamp", plan.RewindAtOrAfter) + + if _, err := i.verifiedDB.Rewind(plan.RewindAtOrAfter); err != nil { + return fmt.Errorf("rewind verifiedDB: %w", err) + } + + var allErrs []error + recordErr := func(err error) { + if err != nil { + allErrs = append(allErrs, err) + } + } + + sortedChainIDs := make([]eth.ChainID, 0, len(i.chains)) + for chainID := range i.chains { + sortedChainIDs = append(sortedChainIDs, chainID) + } + sort.Slice(sortedChainIDs, func(a, b int) bool { + return sortedChainIDs[a].Cmp(sortedChainIDs[b]) < 0 + }) + + for _, chainID := range sortedChainIDs { + chain := i.chains[chainID] + if _, err := chain.PruneDeniedAtOrAfterTimestamp(plan.RewindAtOrAfter); err != nil { + i.log.Error("failed to prune deny list on rewind", "chain", chainID, "err", err) + recordErr(fmt.Errorf("chain %s: prune deny list on rewind: %w", chainID, err)) + } + if plan.ResetAllChainsTo != nil { + if err := chain.RewindEngine(i.ctx, *plan.ResetAllChainsTo, eth.BlockRef{}); err != nil { + i.log.Error("failed to reset chain engine on rewind", "chain", chainID, "err", err) + recordErr(fmt.Errorf("chain %s: reset chain engine on rewind: %w", chainID, err)) + } + } + } + + if plan.TargetHeads == nil { + for chainID, db := range i.logsDBs { + if err := db.Clear(&noopInvalidator{}); err != nil { + i.log.Error("failed to clear logsDB on full rewind", "chain", chainID, "err", err) + recordErr(fmt.Errorf("chain %s: clear logsDB on full rewind: %w", chainID, err)) + } + } + return errors.Join(allErrs...) + } + + for chainID, db := range i.logsDBs { + expectedHead, ok := plan.TargetHeads[chainID] + if !ok { + continue + } + latestBlock, hasBlocks := db.LatestSealedBlock() + if !hasBlocks || latestBlock == expectedHead || latestBlock.Number < expectedHead.Number { + continue + } + i.log.Info("rewinding logsDB to previous verified head", + "chain", chainID, "from", latestBlock, "to", expectedHead) + if err := db.Rewind(&noopInvalidator{}, expectedHead); err != nil { + i.log.Error("failed to rewind logsDB, transition preserved for retry", + "chain", chainID, "err", err) + recordErr(fmt.Errorf("chain %s: rewind logsDB to previous verified head: %w", chainID, err)) + } + } + + return errors.Join(allErrs...) +} + +// collectCurrentL1 collects the current L1 head of all chains, +// which is the minimum L1 head of all the derivation pipelines in Chain Containers +func (i *Interop) collectCurrentL1() (eth.BlockID, error) { + var currentL1 eth.BlockID + first := true + for _, chain := range i.chains { + status, err := chain.SyncStatus(i.ctx) + if err != nil { + return eth.BlockID{}, fmt.Errorf("chain %s not ready: %w", chain.ID(), err) + } + block := status.CurrentL1 + if first || block.Number < currentL1.Number { + currentL1 = block.ID() + first = false + } + } + return currentL1, nil +} + +// checkChainsReady checks if all chains are ready to process the next timestamp. +// Queries all chains in parallel for better performance. +// Returns both the L2 blocks at the timestamp and the L1 inclusion heads. +func (i *Interop) checkChainsReady(ts uint64) (chainsReadyResult, error) { + type result struct { + chainID eth.ChainID + blockID eth.BlockID + l1Head eth.BlockID + err error + } + + results := make(chan result, len(i.chains)) + + // Query all chains in parallel + for _, chain := range i.chains { + go func(c cc.ChainContainer) { + // Use OptimisticAt as the single atomic source for both L2 block and L1 head. + // This avoids a TOCTOU race between separate LocalSafeBlockAtTimestamp and OptimisticAt calls. + l2Block, l1Block, err := c.OptimisticAt(i.ctx, ts) + if err != nil { + results <- result{chainID: c.ID(), err: fmt.Errorf("chain %s not ready for timestamp %d: %w", c.ID(), ts, err)} + return + } + results <- result{chainID: c.ID(), blockID: l2Block, l1Head: l1Block} + }(chain) + } + + // Collect all results before returning so every goroutine completes before the + // next call spawns a new batch, preventing accumulation of in-flight RPC calls. + ready := chainsReadyResult{ + blocks: make(map[eth.ChainID]eth.BlockID), + l1Heads: make(map[eth.ChainID]eth.BlockID), + } + var firstErr error + for range i.chains { + r := <-results + if r.err != nil { + if firstErr == nil { + firstErr = r.err + } + } else { + ready.blocks[r.chainID] = r.blockID + ready.l1Heads[r.chainID] = r.l1Head + } + } + if firstErr != nil { + return chainsReadyResult{}, firstErr + } + + return ready, nil +} + +func (i *Interop) commitVerifiedResult(timestamp uint64, verifiedResult VerifiedResult) error { + return i.verifiedDB.Commit(verifiedResult) +} + +// CurrentL1 returns the L1 block which has been fully considered for interop, +// whether or not it advanced the verified timestamp. +func (i *Interop) CurrentL1() eth.BlockID { + i.mu.RLock() + defer i.mu.RUnlock() + return i.currentL1 +} + +// VerifiedAtTimestamp returns whether the data is verified at the given timestamp. +// For timestamps before the activation timestamp, this returns true since interop +// wasn't active yet and verification proceeds automatically. +// For timestamps at or after the activation timestamp, this checks the verifiedDB. +func (i *Interop) VerifiedAtTimestamp(ts uint64) (bool, error) { + // Timestamps before the activation timestamp are considered verified + // because interop wasn't active yet + if ts < i.activationTimestamp { + return true, nil + } + return i.verifiedDB.Has(ts) +} + +// IsActiveAt reports whether the interop verifier is responsible for verifying +// L2 content at the given timestamp. Returns false for timestamps strictly +// before the configured activation timestamp. +func (i *Interop) IsActiveAt(ts uint64) bool { + return ts >= i.activationTimestamp +} + +// LatestVerifiedL2Block returns the latest L2 block which has been verified, +// along with the timestamp at which it was verified. +func (i *Interop) LatestVerifiedL2Block(chainID eth.ChainID) (eth.BlockID, uint64) { + emptyBlock := eth.BlockID{} + ts, ok := i.verifiedDB.LastTimestamp() + if !ok { + return emptyBlock, 0 + } + res, err := i.verifiedDB.Get(ts) + if err != nil { + return emptyBlock, 0 + } + head, ok := res.L2Heads[chainID] + if !ok { + return emptyBlock, 0 + } + return head, ts +} + +// VerifiedBlockAtL1 returns the verified L2 block and timestamp +// which guarantees that the verified data at that timestamp +// originates from or before the supplied L1 block. +func (i *Interop) VerifiedBlockAtL1(chainID eth.ChainID, l1Block eth.L1BlockRef) (eth.BlockID, uint64) { + // If L1 block is empty/zero (e.g. during startup before FinalizedL1 is set), + // no verified result can match, so return early. + if l1Block == (eth.L1BlockRef{}) { + return eth.BlockID{}, 0 + } + + // Get the last verified timestamp + lastTs, ok := i.verifiedDB.LastTimestamp() + if !ok { + return eth.BlockID{}, 0 + } + + // Search backwards from the last timestamp to find the latest result + // where the L1 inclusion block is at or below the supplied L1 block number. + // Stop at activationTimestamp — no verified results exist before that. + lowerBound := i.activationTimestamp + for ts := lastTs; ts >= lowerBound && ts <= lastTs; ts-- { + result, err := i.verifiedDB.Get(ts) + if err != nil { + // Timestamp might not exist (due to gaps or rewinds), continue searching + continue + } + + // Check if this result's L1 inclusion is at or below the supplied L1 block number + if result.L1Inclusion.Number <= l1Block.Number { + // Found a finalized result, return the L2 head for this chain + head, ok := result.L2Heads[chainID] + if !ok { + return eth.BlockID{}, 0 + } + return head, ts + } + } + + // No verified block found + return eth.BlockID{}, 0 +} + +// Reset is intentionally a no-op for interop. +// Interop-owned invalidation and rewind handling is driven synchronously through +// PendingTransition application, so callback-driven resets are not part of the +// correctness path anymore. +func (i *Interop) Reset(chainID eth.ChainID, timestamp uint64, invalidatedBlock eth.BlockRef) { +} + +// invalidateBlock notifies the chain container to add the block to the denylist +// and potentially rewind if the chain is currently using that block. +func (i *Interop) invalidateBlock(chainID eth.ChainID, blockID eth.BlockID, decisionTimestamp uint64, stateRoot, messagePasserStorageRoot eth.Bytes32) error { + chain, ok := i.chains[chainID] + if !ok { + return fmt.Errorf("chain %s not found", chainID) + } + _, err := chain.InvalidateBlock(i.ctx, blockID.Number, blockID.Hash, decisionTimestamp, stateRoot, messagePasserStorageRoot) + return err +} diff --git a/op-supernode/supernode/activity/interop/interop_test.go b/op-supernode/supernode/activity/interop/interop_test.go new file mode 100644 index 00000000000..4bc49ae6c94 --- /dev/null +++ b/op-supernode/supernode/activity/interop/interop_test.go @@ -0,0 +1,2414 @@ +package interop + +import ( + "context" + "errors" + "math/big" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supernode/supernode/activity" + cc "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/reads" + suptypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + gethlog "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" +) + +// ============================================================================= +// Test Harness +// ============================================================================= + +// interopTestHarness provides a builder-pattern test setup for Interop tests. +// It reduces boilerplate by handling common setup: temp directories, mock chains, +// interop creation, context assignment, and cleanup. +type interopTestHarness struct { + t *testing.T + interop *Interop + mocks map[eth.ChainID]*mockChainContainer + activationTime uint64 + logBackfillDepth time.Duration + dataDir string + skipBuild bool // for tests that need custom construction +} + +// newInteropTestHarness creates a new test harness with sensible defaults. +func newInteropTestHarness(t *testing.T) *interopTestHarness { + t.Helper() + t.Parallel() + return &interopTestHarness{ + t: t, + mocks: make(map[eth.ChainID]*mockChainContainer), + activationTime: 1000, + dataDir: t.TempDir(), + } +} + +// WithActivation sets the interop activation timestamp. +func (h *interopTestHarness) WithActivation(ts uint64) *interopTestHarness { + h.activationTime = ts + return h +} + +// WithLogBackfillDepth sets op-supernode's --interop.log-backfill-depth for the built Interop. +func (h *interopTestHarness) WithLogBackfillDepth(d time.Duration) *interopTestHarness { + h.logBackfillDepth = d + return h +} + +// WithDataDir sets a custom data directory (useful for error testing). +func (h *interopTestHarness) WithDataDir(dir string) *interopTestHarness { + h.dataDir = dir + return h +} + +// WithChain adds a mock chain container with optional configuration. +func (h *interopTestHarness) WithChain(id uint64, configure func(*mockChainContainer)) *interopTestHarness { + mock := newMockChainContainer(id) + if configure != nil { + configure(mock) + } + h.mocks[mock.id] = mock + return h +} + +// SkipBuild marks that Build() should not create an Interop instance. +// Useful for tests that need to test New() directly. +func (h *interopTestHarness) SkipBuild() *interopTestHarness { + h.skipBuild = true + return h +} + +// Build creates the Interop instance from configured mocks. +// Sets up context and registers cleanup. +func (h *interopTestHarness) Build() *interopTestHarness { + if h.skipBuild { + return h + } + chains := make(map[eth.ChainID]cc.ChainContainer) + for id, mock := range h.mocks { + chains[id] = mock + } + h.interop = New(testLogger(), h.activationTime, 0, chains, h.dataDir, nil, h.logBackfillDepth) + if h.interop != nil { + h.interop.ctx = context.Background() + h.t.Cleanup(func() { _ = h.interop.Stop(context.Background()) }) + } + return h +} + +// Chains returns the map of chain containers for use with New(). +func (h *interopTestHarness) Chains() map[eth.ChainID]cc.ChainContainer { + chains := make(map[eth.ChainID]cc.ChainContainer) + for id, mock := range h.mocks { + chains[id] = mock + } + return chains +} + +// Mock returns the mock for a given chain ID. +func (h *interopTestHarness) Mock(id uint64) *mockChainContainer { + return h.mocks[eth.ChainIDFromUInt64(id)] +} + +// ============================================================================= +// TestNew +// ============================================================================= + +func TestNew(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + run func(t *testing.T, h *interopTestHarness) + }{ + { + name: "valid inputs initializes all components", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, nil).WithChain(8453, nil).SkipBuild() + }, + run: func(t *testing.T, h *interopTestHarness) { + interop := New(testLogger(), h.activationTime, 0, h.Chains(), h.dataDir, nil, 0) + require.NotNil(t, interop) + t.Cleanup(func() { _ = interop.Stop(context.Background()) }) + + require.Equal(t, uint64(1000), interop.activationTimestamp) + require.NotNil(t, interop.verifiedDB) + require.Len(t, interop.chains, 2) + require.Len(t, interop.logsDBs, 2) + require.NotNil(t, interop.verifyFn) + require.NotNil(t, interop.cycleVerifyFn) + + for chainID := range h.Chains() { + require.Contains(t, interop.logsDBs, chainID) + require.NotNil(t, interop.logsDBs[chainID]) + } + }, + }, + { + name: "invalid dataDir returns nil", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithDataDir("/nonexistent/path").SkipBuild() + }, + run: func(t *testing.T, h *interopTestHarness) { + interop := New(testLogger(), h.activationTime, 0, h.Chains(), h.dataDir, nil, 0) + require.Nil(t, interop) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + tc.run(t, h) + }) + } +} + +func TestInteropActivationTimestampFlagEnvVar(t *testing.T) { + t.Parallel() + + require.Contains(t, InteropActivationTimestampFlag.GetEnvVars(), "OP_SUPERNODE_INTEROP_ACTIVATION_TIMESTAMP") +} + +// ============================================================================= +// TestStartStop +// ============================================================================= + +func TestStartStop(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + run func(t *testing.T, h *interopTestHarness) + }{ + { + name: "Start blocks until context cancelled", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + m.blockAtTimestamp = eth.L2BlockRef{Number: 50} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan error, 1) + go func() { done <- h.interop.Start(ctx) }() + + require.Eventually(t, func() bool { + h.interop.mu.RLock() + defer h.interop.mu.RUnlock() + return h.interop.started + }, 5*time.Second, 100*time.Millisecond) + + cancel() + + var err error + require.Eventually(t, func() bool { + select { + case err = <-done: + return true + default: + return false + } + }, 5*time.Second, 100*time.Millisecond) + require.ErrorIs(t, err, context.Canceled) + }, + }, + { + name: "double Start blocked", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { _ = h.interop.Start(ctx) }() + + require.Eventually(t, func() bool { + h.interop.mu.RLock() + defer h.interop.mu.RUnlock() + return h.interop.started + }, 5*time.Second, 100*time.Millisecond) + + ctx2, cancel2 := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer cancel2() + + err := h.interop.Start(ctx2) + require.ErrorIs(t, err, context.DeadlineExceeded) + }, + }, + { + name: "Stop cancels running Start and closes DB", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + m.blockAtTimestampErr = ethereum.NotFound + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + done := make(chan error, 1) + go func() { done <- h.interop.Start(context.Background()) }() + + require.Eventually(t, func() bool { + h.interop.mu.RLock() + defer h.interop.mu.RUnlock() + return h.interop.started + }, 5*time.Second, 100*time.Millisecond) + + err := h.interop.Stop(context.Background()) + require.NoError(t, err) + + require.Eventually(t, func() bool { + select { + case <-done: + return true + default: + return false + } + }, 5*time.Second, 100*time.Millisecond) + + // Verify DB is closed + _, err = h.interop.verifiedDB.Has(100) + require.Error(t, err) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + tc.run(t, h) + }) + } +} + +// ============================================================================= +// TestCollectCurrentL1 +// ============================================================================= + +func TestCollectCurrentL1(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + assert func(t *testing.T, l1 eth.BlockID, err error) + }{ + { + name: "returns minimum L1 across multiple chains", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 200, Hash: common.HexToHash("0x2")} + }).WithChain(8453, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 100, Hash: common.HexToHash("0x1")} // minimum + }).Build() + }, + assert: func(t *testing.T, l1 eth.BlockID, err error) { + require.NoError(t, err) + require.Equal(t, uint64(100), l1.Number) + require.Equal(t, common.HexToHash("0x1"), l1.Hash) + }, + }, + { + name: "single chain returns its L1", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 500, Hash: common.HexToHash("0x5")} + }).Build() + }, + assert: func(t *testing.T, l1 eth.BlockID, err error) { + require.NoError(t, err) + require.Equal(t, uint64(500), l1.Number) + }, + }, + { + name: "chain error propagated", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1Err = errors.New("chain not synced") + }).Build() + }, + assert: func(t *testing.T, l1 eth.BlockID, err error) { + require.Error(t, err) + require.Contains(t, err.Error(), "not ready") + require.Equal(t, eth.BlockID{}, l1) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + l1, err := h.interop.collectCurrentL1() + tc.assert(t, l1, err) + }) + } +} + +// ============================================================================= +// TestCheckChainsReady +// ============================================================================= + +func TestCheckChainsReady(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + assert func(t *testing.T, h *interopTestHarness, ready chainsReadyResult, err error) + }{ + { + name: "all chains ready returns blocks", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }).WithChain(8453, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 200, Hash: common.HexToHash("0x2")} + }).Build() + }, + assert: func(t *testing.T, h *interopTestHarness, ready chainsReadyResult, err error) { + require.NoError(t, err) + require.Len(t, ready.blocks, 2) + require.NotEqual(t, common.Hash{}, ready.blocks[h.Mock(10).id].Hash) + require.NotEqual(t, common.Hash{}, ready.blocks[h.Mock(8453).id].Hash) + }, + }, + { + name: "one chain not ready returns error", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100} + }).WithChain(8453, func(m *mockChainContainer) { + m.blockAtTimestampErr = ethereum.NotFound + }).Build() + }, + assert: func(t *testing.T, h *interopTestHarness, ready chainsReadyResult, err error) { + require.Error(t, err) + require.Nil(t, ready.blocks) + }, + }, + { + name: "parallel execution works", + setup: func(h *interopTestHarness) *interopTestHarness { + for i := 0; i < 5; i++ { + idx := i // capture loop var + h.WithChain(uint64(10+idx), func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: uint64(100 + idx)} + }) + } + return h.Build() + }, + assert: func(t *testing.T, h *interopTestHarness, ready chainsReadyResult, err error) { + require.NoError(t, err) + require.Len(t, ready.blocks, 5) + }, + }, + { + // Verify that checkChainsReady drains ALL goroutine results before returning, + // even when one chain errors early. Without the drain, the slow chain's goroutine + // would still be running concurrently when the next call spawns a new batch — + // causing goroutine accumulation under repeated retries. + name: "drains all goroutine results before returning on error", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + // Errors immediately, causing an early-return path. + m.blockAtTimestampErr = ethereum.NotFound + }).WithChain(8453, func(m *mockChainContainer) { + // Slow chain: takes longer than the fast-error chain. + // After checkChainsReady returns, callsCompleted must be 1, + // proving the function waited for this goroutine to finish. + m.blockAtTimestamp = eth.L2BlockRef{Number: 200} + m.blockAtTimestampDelay = 30 * time.Millisecond + }).Build() + }, + assert: func(t *testing.T, h *interopTestHarness, ready chainsReadyResult, err error) { + require.Error(t, err) + require.Nil(t, ready.blocks) + // Both goroutines must have completed before checkChainsReady returned. + require.EqualValues(t, 1, h.Mock(10).callsCompleted.Load(), "chain 10 goroutine should have completed") + require.EqualValues(t, 1, h.Mock(8453).callsCompleted.Load(), "chain 8453 goroutine should have completed before return") + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + ready, err := h.interop.checkChainsReady(1000) + tc.assert(t, h, ready, err) + }) + } +} + +// ============================================================================= +// TestProgressInterop +// ============================================================================= + +func TestProgressInterop(t *testing.T) { + t.Parallel() + + // Default verifyFn that passes through + passThroughVerifyFn := func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{Timestamp: ts, L1Inclusion: eth.BlockID{Number: 100}, L2Heads: blocks}, nil + } + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + verifyFn func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) + assert func(t *testing.T, result Result, err error) + run func(t *testing.T, h *interopTestHarness) // override for complex cases + }{ + { + name: "not initialized uses activation timestamp", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithActivation(5000).WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }).Build() + }, + verifyFn: passThroughVerifyFn, + assert: func(t *testing.T, result Result, err error) { + require.NoError(t, err) + require.Equal(t, uint64(5000), result.Timestamp) + }, + }, + { + name: "initialized uses next timestamp", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + h.interop.verifyFn = passThroughVerifyFn + + // First progress + result1, err := progressInteropCompat(h.interop) + require.NoError(t, err) + require.Equal(t, uint64(1000), result1.Timestamp) + + // Commit + err = applyResultCompat(h.interop, result1) + require.NoError(t, err) + + // Second progress should use next timestamp + result2, err := progressInteropCompat(h.interop) + require.NoError(t, err) + require.Equal(t, uint64(1001), result2.Timestamp) + }, + }, + { + name: "chains not ready returns empty result", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestampErr = ethereum.NotFound + }).Build() + }, + assert: func(t *testing.T, result Result, err error) { + require.NoError(t, err) + require.True(t, result.IsEmpty()) + }, + }, + { + name: "chain error propagated", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestampErr = errors.New("internal error") + }).Build() + }, + assert: func(t *testing.T, result Result, err error) { + require.Error(t, err) + require.True(t, result.IsEmpty()) + }, + }, + { + name: "verifyFn error propagated", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithActivation(100).WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 1000, Hash: common.HexToHash("0xL1")} + m.blockAtTimestamp = eth.L2BlockRef{Number: 500, Hash: common.HexToHash("0xL2")} + }).Build() + }, + verifyFn: func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{}, errors.New("verification failed") + }, + assert: func(t *testing.T, result Result, err error) { + require.Error(t, err) + require.Contains(t, err.Error(), "verification failed") + require.True(t, result.IsEmpty()) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + if tc.run != nil { + tc.run(t, h) + return + } + if tc.verifyFn != nil { + h.interop.verifyFn = tc.verifyFn + } + result, err := progressInteropCompat(h.interop) + tc.assert(t, result, err) + }) + } +} + +// ============================================================================= +// TestProgressInteropWithCycleVerify +// ============================================================================= + +func TestProgressInteropWithCycleVerify(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + run func(t *testing.T, h *interopTestHarness) + }{ + { + name: "default cycleVerifyFn returns valid result", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + // Set verifyFn to return a valid result + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{Timestamp: ts, L2Heads: blocks}, nil + } + // cycleVerifyFn is overridden with this stub implementation. + + result, err := progressInteropCompat(h.interop) + require.NoError(t, err) + require.False(t, result.IsEmpty()) + require.True(t, result.IsValid()) + }, + }, + { + name: "cycleVerifyFn called after verifyFn and results merged", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }).WithChain(8453, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 200, Hash: common.HexToHash("0x2")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + verifyFnCalled := false + cycleVerifyFnCalled := false + chain10 := eth.ChainIDFromUInt64(10) + chain8453 := eth.ChainIDFromUInt64(8453) + + // verifyFn returns valid result + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + verifyFnCalled = true + return Result{Timestamp: ts, L2Heads: blocks}, nil + } + + // cycleVerifyFn marks chain 8453 as invalid + h.interop.cycleVerifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + require.True(t, verifyFnCalled, "verifyFn should be called before cycleVerifyFn") + cycleVerifyFnCalled = true + return Result{ + Timestamp: ts, + L2Heads: blocks, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chain8453: {BlockID: blocks[chain8453]}, + }, + }, nil + } + + result, err := progressInteropCompat(h.interop) + require.NoError(t, err) + require.True(t, verifyFnCalled, "verifyFn should be called") + require.True(t, cycleVerifyFnCalled, "cycleVerifyFn should be called") + require.False(t, result.IsValid(), "result should be invalid due to cycleVerifyFn") + require.Contains(t, result.InvalidHeads, chain8453) + require.NotContains(t, result.InvalidHeads, chain10) + }, + }, + { + name: "cycleVerifyFn error propagated", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{Timestamp: ts, L2Heads: blocks}, nil + } + h.interop.cycleVerifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{}, errors.New("cycle verification failed") + } + + result, err := progressInteropCompat(h.interop) + require.Error(t, err) + require.Contains(t, err.Error(), "cycle verification") + require.True(t, result.IsEmpty()) + }, + }, + { + name: "both verifyFn and cycleVerifyFn invalid heads are merged", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }).WithChain(8453, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 200, Hash: common.HexToHash("0x2")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + chain10 := eth.ChainIDFromUInt64(10) + chain8453 := eth.ChainIDFromUInt64(8453) + + // verifyFn marks chain 10 as invalid + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{ + Timestamp: ts, + L2Heads: blocks, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chain10: {BlockID: blocks[chain10]}, + }, + }, nil + } + + // cycleVerifyFn marks chain 8453 as invalid + h.interop.cycleVerifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{ + Timestamp: ts, + L2Heads: blocks, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chain8453: {BlockID: blocks[chain8453]}, + }, + }, nil + } + + result, err := progressInteropCompat(h.interop) + require.NoError(t, err) + require.False(t, result.IsValid()) + // Both chains should be in InvalidHeads + require.Contains(t, result.InvalidHeads, chain10, "chain10 from verifyFn should be invalid") + require.Contains(t, result.InvalidHeads, chain8453, "chain8453 from cycleVerifyFn should be invalid") + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + tc.run(t, h) + }) + } +} + +// ============================================================================= +// TestVerifiedAtTimestamp +// ============================================================================= + +func TestVerifiedAtTimestamp(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + run func(t *testing.T, h *interopTestHarness) + }{ + { + name: "before activation always verified", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + verified, err := h.interop.VerifiedAtTimestamp(999) + require.NoError(t, err) + require.True(t, verified) + + verified, err = h.interop.VerifiedAtTimestamp(0) + require.NoError(t, err) + require.True(t, verified) + }, + }, + { + name: "at/after activation not verified until committed", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + verified, err := h.interop.VerifiedAtTimestamp(1000) + require.NoError(t, err) + require.False(t, verified) + + verified, err = h.interop.VerifiedAtTimestamp(9999) + require.NoError(t, err) + require.False(t, verified) + }, + }, + { + name: "committed timestamp verified", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{Timestamp: ts, L1Inclusion: eth.BlockID{Number: 100}, L2Heads: blocks}, nil + } + + result, err := progressInteropCompat(h.interop) + require.NoError(t, err) + + err = applyResultCompat(h.interop, result) + require.NoError(t, err) + + verified, err := h.interop.VerifiedAtTimestamp(1000) + require.NoError(t, err) + require.True(t, verified) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + tc.run(t, h) + }) + } +} + +// ============================================================================= +// TestIsActiveAt +// ============================================================================= + +// TestIsActiveAt verifies that Interop.IsActiveAt reports the verifier as +// inactive for timestamps strictly before the configured activation timestamp +// and active at or after it. This is the per-verifier hook super_authority +// consults to decide whether pre-interop L2 content can bypass the verifier +// and fall back to local-safe / local-finalized. +func TestIsActiveAt(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + activation uint64 + ts uint64 + want bool + }{ + {"before activation returns false", 1000, 999, false}, + {"well before activation returns false", 1000, 0, false}, + {"at activation returns true", 1000, 1000, true}, + {"after activation returns true", 1000, 9999, true}, + {"activation zero always active", 0, 0, true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t).WithActivation(tc.activation).Build() + require.Equal(t, tc.want, h.interop.IsActiveAt(tc.ts)) + }) + } +} + +// ============================================================================= +// TestApplyResultCompat +// ============================================================================= + +func TestApplyResultCompat(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + run func(t *testing.T, h *interopTestHarness) + }{ + { + name: "empty result is no-op", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + err := applyResultCompat(h.interop, Result{}) + require.NoError(t, err) + + has, err := h.interop.verifiedDB.Has(0) + require.NoError(t, err) + require.False(t, has) + }, + }, + { + name: "valid result commits to DB with correct data", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, nil).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + mock := h.Mock(10) + validResult := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 100, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + mock.id: {Number: 500, Hash: common.HexToHash("0xL2")}, + }, + } + + err := applyResultCompat(h.interop, validResult) + require.NoError(t, err) + + has, err := h.interop.verifiedDB.Has(1000) + require.NoError(t, err) + require.True(t, has) + + retrieved, err := h.interop.verifiedDB.Get(1000) + require.NoError(t, err) + require.Equal(t, validResult.Timestamp, retrieved.Timestamp) + require.Equal(t, validResult.L1Inclusion, retrieved.L1Inclusion) + require.Equal(t, validResult.L2Heads[mock.id], retrieved.L2Heads[mock.id]) + }, + }, + { + name: "invalid result does not commit", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, nil).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + mock := h.Mock(10) + invalidResult := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 100, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + mock.id: {Number: 500, Hash: common.HexToHash("0xL2")}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + mock.id: {BlockID: eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD")}}, + }, + } + + err := applyResultCompat(h.interop, invalidResult) + require.NoError(t, err) + + has, err := h.interop.verifiedDB.Has(1000) + require.NoError(t, err) + require.False(t, has) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + tc.run(t, h) + }) + } +} + +// ============================================================================= +// TestInvalidateBlock +// ============================================================================= + +// TestInvalidateBlock verifies the invalidateBlock method correctly calls +// ChainContainer.InvalidateBlock with the right parameters and handles errors. +func TestInvalidateBlock(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + run func(t *testing.T, h *interopTestHarness) + }{ + { + name: "calls chain.InvalidateBlock with correct args", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, nil).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + mock := h.Mock(10) + blockID := eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD")} + err := h.interop.invalidateBlock(mock.id, blockID, 0, eth.Bytes32{}, eth.Bytes32{}) + require.NoError(t, err) + + require.Len(t, mock.invalidateBlockCalls, 1) + require.Equal(t, uint64(500), mock.invalidateBlockCalls[0].height) + require.Equal(t, common.HexToHash("0xBAD"), mock.invalidateBlockCalls[0].payloadHash) + }, + }, + { + name: "returns error when chain not found", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, nil).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + mock := h.Mock(10) + unknownChain := eth.ChainIDFromUInt64(999) + blockID := eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD")} + err := h.interop.invalidateBlock(unknownChain, blockID, 0, eth.Bytes32{}, eth.Bytes32{}) + + require.Error(t, err) + require.Contains(t, err.Error(), "not found") + require.Len(t, mock.invalidateBlockCalls, 0) + }, + }, + { + name: "returns error when chain.InvalidateBlock fails", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.invalidateBlockErr = errors.New("engine failure") + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + mock := h.Mock(10) + blockID := eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD")} + err := h.interop.invalidateBlock(mock.id, blockID, 0, eth.Bytes32{}, eth.Bytes32{}) + + require.Error(t, err) + require.Contains(t, err.Error(), "engine failure") + }, + }, + { + name: "handleResult calls invalidateBlock for each invalid head", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, nil).WithChain(8453, nil).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + mock1 := h.Mock(10) + mock2 := h.Mock(8453) + + invalidResult := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 100, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + mock1.id: {Number: 500, Hash: common.HexToHash("0xL2-1")}, + mock2.id: {Number: 600, Hash: common.HexToHash("0xL2-2")}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + mock1.id: {BlockID: eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD1")}}, + mock2.id: {BlockID: eth.BlockID{Number: 600, Hash: common.HexToHash("0xBAD2")}}, + }, + } + + err := applyResultCompat(h.interop, invalidResult) + require.NoError(t, err) + + require.Len(t, mock1.invalidateBlockCalls, 1) + require.Equal(t, uint64(500), mock1.invalidateBlockCalls[0].height) + require.Equal(t, common.HexToHash("0xBAD1"), mock1.invalidateBlockCalls[0].payloadHash) + + require.Len(t, mock2.invalidateBlockCalls, 1) + require.Equal(t, uint64(600), mock2.invalidateBlockCalls[0].height) + require.Equal(t, common.HexToHash("0xBAD2"), mock2.invalidateBlockCalls[0].payloadHash) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + tc.run(t, h) + }) + } +} + +// ============================================================================= +// TestProgressAndRecord +// ============================================================================= + +func TestProgressAndRecord(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setup func(h *interopTestHarness) *interopTestHarness + run func(t *testing.T, h *interopTestHarness) + }{ + { + name: "empty result sets L1 to collected minimum", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 200, Hash: common.HexToHash("0x2")} + m.blockAtTimestampErr = ethereum.NotFound + }).WithChain(8453, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + m.blockAtTimestampErr = ethereum.NotFound + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + require.Equal(t, eth.BlockID{}, h.interop.currentL1) + + madeProgress, err := h.interop.progressAndRecord() + require.NoError(t, err) + require.False(t, madeProgress, "empty result should not advance verified timestamp") + + require.Equal(t, uint64(100), h.interop.currentL1.Number) + require.Equal(t, common.HexToHash("0x1"), h.interop.currentL1.Hash) + }, + }, + { + name: "valid result sets L1 to result L1Head", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 200, Hash: common.HexToHash("0x200")} + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0xL2")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + expectedL1Inclusion := eth.BlockID{Number: 150, Hash: common.HexToHash("0xL1Result")} + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{Timestamp: ts, L1Inclusion: expectedL1Inclusion, L2Heads: blocks}, nil + } + + madeProgress, err := h.interop.progressAndRecord() + require.NoError(t, err) + require.True(t, madeProgress, "valid result should advance verified timestamp") + + require.Equal(t, expectedL1Inclusion.Number, h.interop.currentL1.Number) + require.Equal(t, expectedL1Inclusion.Hash, h.interop.currentL1.Hash) + }, + }, + { + name: "valid result caps L1 at min node CurrentL1 when L1Inclusion exceeds it", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 1000, Hash: common.HexToHash("0xleading")} + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0xL2a")} + }).WithChain(8453, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 990, Hash: common.HexToHash("0xlagging")} + m.blockAtTimestamp = eth.L2BlockRef{Number: 200, Hash: common.HexToHash("0xL2b")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + // L1Inclusion is 1000 (from the leading chain) but chain 8453 is only at 990. + // interop.currentL1 must be capped at 990 so it never exceeds any node's CurrentL1. + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Number: 1000, Hash: common.HexToHash("0xleading")}, + L2Heads: blocks, + }, nil + } + + madeProgress, err := h.interop.progressAndRecord() + require.NoError(t, err) + require.True(t, madeProgress) + + require.Equal(t, uint64(990), h.interop.currentL1.Number) + require.Equal(t, common.HexToHash("0xlagging"), h.interop.currentL1.Hash) + }, + }, + { + name: "invalid result does not update L1", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 200, Hash: common.HexToHash("0x200")} + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0xL2")} + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + mock := h.Mock(10) + initialL1 := eth.BlockID{Number: 50, Hash: common.HexToHash("0x50")} + h.interop.currentL1 = initialL1 + + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Number: 999, Hash: common.HexToHash("0xShouldNotBeUsed")}, + L2Heads: blocks, + InvalidHeads: map[eth.ChainID]InvalidHead{mock.id: {BlockID: eth.BlockID{Number: 100}}}, + }, nil + } + + madeProgress, err := h.interop.progressAndRecord() + require.NoError(t, err) + require.False(t, madeProgress, "invalid result should not advance verified timestamp") + + require.Equal(t, initialL1.Number, h.interop.currentL1.Number) + require.Equal(t, initialL1.Hash, h.interop.currentL1.Hash) + }, + }, + { + name: "errors propagated", + setup: func(h *interopTestHarness) *interopTestHarness { + return h.WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestampErr = errors.New("chain sync error") + }).Build() + }, + run: func(t *testing.T, h *interopTestHarness) { + madeProgress, err := h.interop.progressAndRecord() + require.Error(t, err) + require.False(t, madeProgress, "error should not advance verified timestamp") + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + h := newInteropTestHarness(t) + tc.setup(h) + tc.run(t, h) + }) + } +} + +// ============================================================================= +// TestInterop_FullCycle +// ============================================================================= + +func TestInterop_FullCycle(t *testing.T) { + t.Parallel() + dataDir := t.TempDir() + + mock := newMockChainContainer(10) + mock.currentL1 = eth.BlockRef{Number: 1000, Hash: common.HexToHash("0xL1")} + mock.blockAtTimestamp = eth.L2BlockRef{Number: 500, Hash: common.HexToHash("0xL2")} + + chains := map[eth.ChainID]cc.ChainContainer{mock.id: mock} + interop := New(testLogger(), 100, 0, chains, dataDir, nil, 0) + require.NotNil(t, interop) + interop.ctx = context.Background() + + // Verify logsDB is empty initially + _, hasBlocks := interop.logsDBs[mock.id].LatestSealedBlock() + require.False(t, hasBlocks) + + // Stub verifyFn + interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{Timestamp: ts, L1Inclusion: eth.BlockID{Number: 100}, L2Heads: blocks}, nil + } + + // Run 3 cycles + for i := 0; i < 3; i++ { + l1, err := interop.collectCurrentL1() + require.NoError(t, err) + require.Equal(t, uint64(1000), l1.Number) + + result, err := progressInteropCompat(interop) + require.NoError(t, err) + require.False(t, result.IsEmpty()) + + err = applyResultCompat(interop, result) + require.NoError(t, err) + } + + // Verify timestamps committed with correct L2Heads + for ts := uint64(100); ts <= 102; ts++ { + has, err := interop.verifiedDB.Has(ts) + require.NoError(t, err) + require.True(t, has) + + retrieved, err := interop.verifiedDB.Get(ts) + require.NoError(t, err) + require.Equal(t, ts, retrieved.Timestamp) + require.Contains(t, retrieved.L2Heads, mock.id) + require.Equal(t, ts, retrieved.L2Heads[mock.id].Number) + } + + // Verify logsDB populated + latestBlock, hasBlocks := interop.logsDBs[mock.id].LatestSealedBlock() + require.True(t, hasBlocks) + require.Equal(t, uint64(102), latestBlock.Number) +} + +// ============================================================================= +// TestResult_IsEmpty +// ============================================================================= + +func TestResult_IsEmpty(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + result Result + isEmpty bool + }{ + {"zero value", Result{}, true}, + {"only timestamp", Result{Timestamp: 1000}, true}, + {"with L1Head", Result{Timestamp: 1000, L1Inclusion: eth.BlockID{Number: 100}}, false}, + {"with L2Heads", Result{Timestamp: 1000, L2Heads: map[eth.ChainID]eth.BlockID{eth.ChainIDFromUInt64(10): {Number: 50}}}, false}, + {"with InvalidHeads", Result{Timestamp: 1000, InvalidHeads: map[eth.ChainID]InvalidHead{eth.ChainIDFromUInt64(10): {BlockID: eth.BlockID{Number: 50}}}}, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.isEmpty, tt.result.IsEmpty()) + }) + } +} + +// ============================================================================= +// Mock Types +// ============================================================================= + +type mockBlockInfo struct { + hash common.Hash + parentHash common.Hash + number uint64 + timestamp uint64 +} + +func (m *mockBlockInfo) Hash() common.Hash { return m.hash } +func (m *mockBlockInfo) ParentHash() common.Hash { return m.parentHash } +func (m *mockBlockInfo) Coinbase() common.Address { return common.Address{} } +func (m *mockBlockInfo) Root() common.Hash { return common.Hash{} } +func (m *mockBlockInfo) NumberU64() uint64 { return m.number } +func (m *mockBlockInfo) Time() uint64 { return m.timestamp } +func (m *mockBlockInfo) MixDigest() common.Hash { return common.Hash{} } +func (m *mockBlockInfo) BaseFee() *big.Int { return big.NewInt(1) } +func (m *mockBlockInfo) BlobBaseFee(chainConfig *params.ChainConfig) *big.Int { return big.NewInt(1) } +func (m *mockBlockInfo) ExcessBlobGas() *uint64 { return nil } +func (m *mockBlockInfo) ReceiptHash() common.Hash { return common.Hash{} } +func (m *mockBlockInfo) GasUsed() uint64 { return 0 } +func (m *mockBlockInfo) GasLimit() uint64 { return 30000000 } +func (m *mockBlockInfo) BlobGasUsed() *uint64 { return nil } +func (m *mockBlockInfo) ParentBeaconRoot() *common.Hash { return nil } +func (m *mockBlockInfo) WithdrawalsRoot() *common.Hash { return nil } +func (m *mockBlockInfo) HeaderRLP() ([]byte, error) { return nil, nil } +func (m *mockBlockInfo) Header() *types.Header { return nil } +func (m *mockBlockInfo) ID() eth.BlockID { return eth.BlockID{Hash: m.hash, Number: m.number} } + +var _ eth.BlockInfo = (*mockBlockInfo)(nil) + +// callLog records the order of method calls across multiple mock chain containers. +// Tests use it to verify that operations happen in the expected sequence +// (e.g., all freezes before any invalidation). +type callLog struct { + mu sync.Mutex + entries []callLogEntry +} + +type callLogEntry struct { + chainID eth.ChainID + method string +} + +func (cl *callLog) record(chainID eth.ChainID, method string) { + cl.mu.Lock() + defer cl.mu.Unlock() + cl.entries = append(cl.entries, callLogEntry{chainID: chainID, method: method}) +} + +func (cl *callLog) snapshot() []callLogEntry { + cl.mu.Lock() + defer cl.mu.Unlock() + out := make([]callLogEntry, len(cl.entries)) + copy(out, cl.entries) + return out +} + +type mockChainContainer struct { + id eth.ChainID + + currentL1 eth.BlockRef + currentL1Err error + + blockAtTimestamp eth.L2BlockRef + blockAtTimestampErr error + blockAtTimestampDelay time.Duration // if set, sleeps this long before responding + + // callsCompleted is incremented atomically when LocalSafeBlockAtTimestamp returns, + // allowing tests to verify all goroutines drained before checkChainsReady returned. + callsCompleted atomic.Int32 + + lastRequestedTimestamp uint64 + mu sync.Mutex + + // InvalidateBlock tracking + invalidateBlockCalls []invalidateBlockCall + invalidateBlockRet bool + invalidateBlockErr error + pruneDeniedResult map[uint64][]common.Hash + rewindEngineCalls []uint64 + rewindEngineErr error + + // OptimisticAt fields + optimisticL2 eth.BlockID + optimisticL1 eth.BlockID + optimisticAtErr error + + // If set, SyncStatus returns this instead of synthesizing from currentL1 only. + syncStatusFull *eth.SyncStatus + syncStatusOverride func() (*eth.SyncStatus, error) + + // PauseAndStopVN / Resume tracking + pauseAndStopVNCalls int + pauseAndStopVNErr error + resumeCalls int + resumeErr error + callLog *callLog // shared ordered call log across mocks + + outputV0Override func(ctx context.Context, l2BlockNum uint64) (*eth.OutputV0, error) + + // timestampToBlockNumberOverride lets tests decouple TimestampToBlockNumber + // from the default ts==blockNum identity. Used by + // TestLogBackfill_FailsWhenChainBehindLowerBound to simulate the + // inconsistent-SyncStatus case that runLogBackfill must reject. + timestampToBlockNumberOverride func(ctx context.Context, ts uint64) (uint64, error) + + // blockTimeOverride, when >0, is returned by BlockTime(). Used by tests + // that exercise chains whose blockTime is not 1 (e.g. misaligned + // activation timestamps). + blockTimeOverride uint64 + + // blockInfoTimeFn, when non-nil, computes the timestamp reported by + // FetchReceipts' returned blockInfo for a given block number. Used + // together with timestampToBlockNumberOverride to simulate a chain with + // a blockTime > 1 and genesis-offset scheduling. + blockInfoTimeFn func(blockNum uint64) uint64 +} + +type invalidateBlockCall struct { + height uint64 + payloadHash common.Hash + stateRoot eth.Bytes32 + messagePasserStorageRoot eth.Bytes32 +} + +func newMockChainContainer(id uint64) *mockChainContainer { + return &mockChainContainer{id: eth.ChainIDFromUInt64(id)} +} + +func (m *mockChainContainer) ID() eth.ChainID { return m.id } +func (m *mockChainContainer) Start(ctx context.Context) error { return nil } +func (m *mockChainContainer) Stop(ctx context.Context) error { return nil } +func (m *mockChainContainer) Pause(ctx context.Context) error { return nil } +func (m *mockChainContainer) Resume(ctx context.Context) error { + m.mu.Lock() + m.resumeCalls++ + m.mu.Unlock() + if m.callLog != nil { + m.callLog.record(m.id, "Resume") + } + return m.resumeErr +} +func (m *mockChainContainer) PauseAndStopVN(ctx context.Context) error { + m.mu.Lock() + m.pauseAndStopVNCalls++ + m.mu.Unlock() + if m.callLog != nil { + m.callLog.record(m.id, "PauseAndStopVN") + } + return m.pauseAndStopVNErr +} +func (m *mockChainContainer) RegisterVerifier(v activity.VerificationActivity) {} +func (m *mockChainContainer) VerifierCurrentL1s() []eth.BlockID { return nil } +func (m *mockChainContainer) LocalSafeBlockAtTimestamp(ctx context.Context, ts uint64) (eth.L2BlockRef, error) { + // Simulate slow chains. Sleep is outside the lock so it doesn't block other + // concurrent mock operations during tests. + if d := m.blockAtTimestampDelay; d > 0 { + time.Sleep(d) + } + // Increment after any simulated delay so callers can verify the goroutine + // has fully completed (not just started) by the time they observe the count. + defer m.callsCompleted.Add(1) + m.mu.Lock() + defer m.mu.Unlock() + if m.blockAtTimestampErr != nil { + return eth.L2BlockRef{}, m.blockAtTimestampErr + } + m.lastRequestedTimestamp = ts + ref := m.blockAtTimestamp + ref.Time = ts + ref.Number = ts + ref.Hash = common.BigToHash(big.NewInt(int64(ts))) + return ref, nil +} +func (m *mockChainContainer) VerifiedAt(ctx context.Context, ts uint64) (eth.BlockID, eth.BlockID, error) { + return eth.BlockID{}, eth.BlockID{}, nil +} +func (m *mockChainContainer) L1ForL2(ctx context.Context, l2Block eth.BlockID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} +func (m *mockChainContainer) OptimisticAt(ctx context.Context, ts uint64) (eth.BlockID, eth.BlockID, error) { + // Simulate slow chains. Sleep is outside the lock so it doesn't block other + // concurrent mock operations during tests. + if d := m.blockAtTimestampDelay; d > 0 { + time.Sleep(d) + } + // Increment after any simulated delay so callers can verify the goroutine + // has fully completed (not just started) by the time they observe the count. + defer m.callsCompleted.Add(1) + m.mu.Lock() + defer m.mu.Unlock() + if m.optimisticAtErr != nil { + return eth.BlockID{}, eth.BlockID{}, m.optimisticAtErr + } + // If explicit optimistic fields are set, use them + if m.optimisticL2 != (eth.BlockID{}) || m.optimisticL1 != (eth.BlockID{}) { + return m.optimisticL2, m.optimisticL1, nil + } + // Fall back to blockAtTimestamp-derived values (for checkChainsReady tests) + if m.blockAtTimestampErr != nil { + return eth.BlockID{}, eth.BlockID{}, m.blockAtTimestampErr + } + m.lastRequestedTimestamp = ts + ref := m.blockAtTimestamp + ref.Time = ts + ref.Number = ts + ref.Hash = common.BigToHash(big.NewInt(int64(ts))) + return ref.ID(), eth.BlockID{}, nil +} +func (m *mockChainContainer) OutputRootAtL2BlockNumber(ctx context.Context, l2BlockNum uint64) (eth.Bytes32, error) { + return eth.Bytes32{}, nil +} +func (m *mockChainContainer) OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*eth.OutputV0, error) { + return nil, nil +} +func (m *mockChainContainer) FetchReceipts(ctx context.Context, blockID eth.BlockID) (eth.BlockInfo, types.Receipts, error) { + m.mu.Lock() + defer m.mu.Unlock() + ts := blockID.Number + if ts == 0 { + ts = m.lastRequestedTimestamp + } + if m.blockInfoTimeFn != nil { + ts = m.blockInfoTimeFn(blockID.Number) + } + // parentHash is derived from the parent block's number (matching how + // OutputV0AtBlockNumber produces block hashes), not from ts-1. When + // blockNum != ts (e.g. blockTime > 1), these diverge; using + // blockID.Number-1 keeps the chain linkage consistent. + var parentHash common.Hash + if blockID.Number > 0 { + parentHash = common.BigToHash(new(big.Int).SetUint64(blockID.Number - 1)) + } + blockInfo := &mockBlockInfo{ + hash: blockID.Hash, + parentHash: parentHash, + number: blockID.Number, + timestamp: ts, + } + return blockInfo, types.Receipts{}, nil +} +func (m *mockChainContainer) SyncStatus(ctx context.Context) (*eth.SyncStatus, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.syncStatusOverride != nil { + return m.syncStatusOverride() + } + if m.currentL1Err != nil { + return nil, m.currentL1Err + } + if m.syncStatusFull != nil { + return m.syncStatusFull, nil + } + return ð.SyncStatus{CurrentL1: m.currentL1}, nil +} +func (m *mockChainContainer) TimestampToBlockNumber(ctx context.Context, ts uint64) (uint64, error) { + if m.timestampToBlockNumberOverride != nil { + return m.timestampToBlockNumberOverride(ctx, ts) + } + return ts, nil +} +func (m *mockChainContainer) RewindEngine(ctx context.Context, timestamp uint64, invalidatedBlock eth.BlockRef) error { + m.mu.Lock() + defer m.mu.Unlock() + m.rewindEngineCalls = append(m.rewindEngineCalls, timestamp) + return m.rewindEngineErr +} +func (m *mockChainContainer) BlockTime() uint64 { + if m.blockTimeOverride > 0 { + return m.blockTimeOverride + } + return 1 +} +func (m *mockChainContainer) InvalidateBlock(ctx context.Context, height uint64, payloadHash common.Hash, decisionTimestamp uint64, stateRoot, messagePasserStorageRoot eth.Bytes32) (bool, error) { + m.mu.Lock() + m.invalidateBlockCalls = append(m.invalidateBlockCalls, invalidateBlockCall{ + height: height, payloadHash: payloadHash, stateRoot: stateRoot, messagePasserStorageRoot: messagePasserStorageRoot, + }) + m.mu.Unlock() + if m.callLog != nil { + m.callLog.record(m.id, "InvalidateBlock") + } + return m.invalidateBlockRet, m.invalidateBlockErr +} +func (m *mockChainContainer) OutputV0AtBlockNumber(ctx context.Context, l2BlockNum uint64) (*eth.OutputV0, error) { + if m.outputV0Override != nil { + return m.outputV0Override(ctx, l2BlockNum) + } + return ð.OutputV0{ + StateRoot: eth.Bytes32(common.HexToHash("0xmockstate")), + MessagePasserStorageRoot: eth.Bytes32(common.HexToHash("0xmockmsg")), + BlockHash: common.BigToHash(new(big.Int).SetUint64(l2BlockNum)), + }, nil +} +func (m *mockChainContainer) GetDeniedOutput(height uint64, payloadHash common.Hash) (*eth.OutputV0, error) { + return nil, nil +} +func (m *mockChainContainer) PruneDeniedAtOrAfterTimestamp(timestamp uint64) (map[uint64][]common.Hash, error) { + if m.pruneDeniedResult != nil { + return m.pruneDeniedResult, nil + } + return nil, nil +} +func (m *mockChainContainer) IsDenied(height uint64, payloadHash common.Hash) (bool, error) { + return false, nil +} +func (m *mockChainContainer) SetResetCallback(cb cc.ResetCallback) {} + +var _ cc.ChainContainer = (*mockChainContainer)(nil) + +func testLogger() gethlog.Logger { + return gethlog.New() +} + +// ============================================================================= +// TestWAL_PreservedOnInvalidationFailure +// ============================================================================= + +// TestWAL_PreservedOnInvalidationFailure verifies that when invalidateBlock fails, +// the pending transition is NOT cleared, allowing retry on restart. +func TestWAL_PreservedOnInvalidationFailure(t *testing.T) { + h := newInteropTestHarness(t). // newInteropTestHarness calls t.Parallel() + WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + m.invalidateBlockErr = errors.New("engine failure") // InvalidateBlock will fail + }). + Build() + + mock := h.Mock(10) + + // Create an invalid result that will trigger DecisionInvalidate + invalidResult := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 100, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + mock.id: {Number: 500, Hash: common.HexToHash("0xL2")}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + mock.id: {BlockID: eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD")}}, + }, + } + + // Execute the invalidation decision + pending, err := h.interop.buildPendingTransition( + StepOutput{Decision: DecisionInvalidate, Result: invalidResult}, + RoundObservation{}, + ) + require.NoError(t, err) + require.NoError(t, h.interop.verifiedDB.SetPendingTransition(pending)) + _, err = h.interop.applyPendingTransition(pending) + + // Should return an error because invalidateBlock failed + require.Error(t, err) + require.Contains(t, err.Error(), "transition preserved") + + // The pending transition should still be in the WAL (not cleared) + storedPending, err := h.interop.verifiedDB.GetPendingTransition() + require.NoError(t, err) + require.NotNil(t, storedPending, "transition should be preserved when invalidation fails") + require.Equal(t, DecisionInvalidate, storedPending.Decision) + require.NotNil(t, storedPending.Result) + require.Equal(t, common.HexToHash("0xBAD"), storedPending.Result.InvalidHeads[mock.id].Hash) +} + +// ============================================================================= +// TestWAL_ReplayPreservedOnFailure +// ============================================================================= + +// TestPendingTransition_RecoverInvalidatePreservedOnFailure verifies that the +// generic pending transition recovery path preserves the transition when +// invalidation fails during replay. +func TestPendingTransition_RecoverInvalidatePreservedOnFailure(t *testing.T) { + h := newInteropTestHarness(t). // newInteropTestHarness calls t.Parallel() + WithChain(10, func(m *mockChainContainer) { + m.invalidateBlockErr = errors.New("engine unavailable") // Will fail on replay + }). + Build() + + mock := h.Mock(10) + + // Manually set a pending invalidation transition (simulating a crash mid-apply). + pending := PendingTransition{ + Decision: DecisionInvalidate, + Result: &Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0xL1"), Number: 100}, + InvalidHeads: map[eth.ChainID]InvalidHead{ + mock.id: {BlockID: eth.BlockID{Hash: common.HexToHash("0xBAD"), Number: 500}}, + }, + }, + } + err := h.interop.verifiedDB.SetPendingTransition(pending) + require.NoError(t, err) + + // Call progressAndRecord — it should recover through applyPendingTransition + // before attempting a normal round, and preserve the transition on failure. + _, err = h.interop.progressAndRecord() + require.Error(t, err) + require.Contains(t, err.Error(), "transition preserved") + + // The transition should NOT be cleared — it must survive for next restart. + got, err := h.interop.verifiedDB.GetPendingTransition() + require.NoError(t, err) + require.NotNil(t, got, "transition should be preserved when replay fails") + require.Equal(t, pending.Decision, got.Decision) + require.NotNil(t, got.Result) + require.Equal(t, pending.Result.InvalidHeads, got.Result.InvalidHeads) +} + +func TestPendingTransition_RecoverRewindPreservedOnFailure(t *testing.T) { + h := newInteropTestHarness(t). // newInteropTestHarness calls t.Parallel() + WithChain(10, func(m *mockChainContainer) { + m.rewindEngineErr = errors.New("rewind failed") + }). + Build() + + mock := h.Mock(10) + + require.NoError(t, h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1a")}, + L2Heads: map[eth.ChainID]eth.BlockID{mock.id: {Number: 100, Hash: common.HexToHash("0x1")}}, + })) + require.NoError(t, h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1001, + L1Inclusion: eth.BlockID{Number: 51, Hash: common.HexToHash("0xL1b")}, + L2Heads: map[eth.ChainID]eth.BlockID{mock.id: {Number: 101, Hash: common.HexToHash("0x2")}}, + })) + + lastTS := uint64(1001) + pending, err := h.interop.buildPendingTransition( + StepOutput{Decision: DecisionRewind}, + RoundObservation{LastVerifiedTS: &lastTS}, + ) + require.NoError(t, err) + require.NoError(t, h.interop.verifiedDB.SetPendingTransition(pending)) + _, err = h.interop.applyPendingTransition(pending) + require.Error(t, err) + require.Contains(t, err.Error(), "reset chain engine on rewind") + + storedPending, err := h.interop.verifiedDB.GetPendingTransition() + require.NoError(t, err) + require.NotNil(t, storedPending, "rewind transition should be preserved when apply fails") + require.Equal(t, DecisionRewind, storedPending.Decision) + require.NotNil(t, storedPending.Rewind) + require.Equal(t, uint64(1001), storedPending.Rewind.RewindAtOrAfter) + require.Len(t, mock.rewindEngineCalls, 1) + require.Equal(t, uint64(1000), mock.rewindEngineCalls[0]) +} + +func TestPendingTransition_RecoverRewindReportsAllFailures(t *testing.T) { + h := newInteropTestHarness(t). // newInteropTestHarness calls t.Parallel() + WithChain(10, func(m *mockChainContainer) { + m.rewindEngineErr = errors.New("rewind failed a") + }). + WithChain(8453, func(m *mockChainContainer) { + m.rewindEngineErr = errors.New("rewind failed b") + }). + Build() + + mockA := h.Mock(10) + mockB := h.Mock(8453) + + require.NoError(t, h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1a")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + mockA.id: {Number: 100, Hash: common.HexToHash("0x1")}, + mockB.id: {Number: 200, Hash: common.HexToHash("0x2")}, + }, + })) + require.NoError(t, h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1001, + L1Inclusion: eth.BlockID{Number: 51, Hash: common.HexToHash("0xL1b")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + mockA.id: {Number: 101, Hash: common.HexToHash("0x3")}, + mockB.id: {Number: 201, Hash: common.HexToHash("0x4")}, + }, + })) + + lastTS := uint64(1001) + pending, err := h.interop.buildPendingTransition( + StepOutput{Decision: DecisionRewind}, + RoundObservation{LastVerifiedTS: &lastTS}, + ) + require.NoError(t, err) + require.NoError(t, h.interop.verifiedDB.SetPendingTransition(pending)) + _, err = h.interop.applyPendingTransition(pending) + require.Error(t, err) + require.Contains(t, err.Error(), "chain 10: reset chain engine on rewind") + require.Contains(t, err.Error(), "chain 8453: reset chain engine on rewind") +} + +func TestPendingTransition_RecoverAdvanceAfterCommitClearsPendingTransition(t *testing.T) { + h := newInteropTestHarness(t). // newInteropTestHarness calls t.Parallel() + WithChain(10, nil). + Build() + + mock := h.Mock(10) + result := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0xL1"), Number: 100}, + L2Heads: map[eth.ChainID]eth.BlockID{ + mock.id: {Hash: common.HexToHash("0xaaa"), Number: 500}, + }, + } + + require.NoError(t, h.interop.verifiedDB.SetPendingTransition(PendingTransition{ + Decision: DecisionAdvance, + Result: &result, + })) + require.NoError(t, h.interop.verifiedDB.Commit(result.ToVerifiedResult())) + + madeProgress, err := h.interop.progressAndRecord() + require.NoError(t, err) + require.True(t, madeProgress, "replay should finish the already-applied advance") + + pending, err := h.interop.verifiedDB.GetPendingTransition() + require.NoError(t, err) + require.Nil(t, pending, "idempotent commit should let replay clear the pending transition") +} + +// ============================================================================= +// TestL1CanonicalityCheckErrorPropagates +// ============================================================================= + +// TestL1CanonicalityCheckErrorPropagates verifies that when the L1 canonicality +// checker returns an error, observeRound propagates it (does not silently proceed). +func TestL1CanonicalityCheckErrorPropagates(t *testing.T) { + h := newInteropTestHarness(t). // newInteropTestHarness calls t.Parallel() + WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }). + Build() + + mock := h.Mock(10) + + // Commit a verified result so observeRound has a LastVerified to check + err := h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{mock.id: {Number: 100, Hash: common.HexToHash("0x1")}}, + }) + require.NoError(t, err) + + // Set up a failing L1 checker using a mock that returns errors for all lookups + h.interop.l1Checker = newByNumberConsistencyChecker(&errorL1Source{ + err: errors.New("L1 RPC unavailable"), + }) + + // Call observeRound — should propagate the L1 checker error + _, err = h.interop.observeRound() + require.Error(t, err) + require.Contains(t, err.Error(), "L1 RPC unavailable") +} + +// ============================================================================= +// TestRewindAccepted +// ============================================================================= + +func TestRewindAccepted(t *testing.T) { + t.Run("rewinds verifiedDB and logsDB to previous frontier", func(t *testing.T) { + h := newInteropTestHarness(t). + WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + }). + Build() + + mock := h.Mock(10) + chainID := mock.id + + // Stub verifyFn + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + return Result{Timestamp: ts, L1Inclusion: eth.BlockID{Number: ts}, L2Heads: blocks}, nil + } + + // Commit two verified results: T=1000 and T=1001 + err := h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 50, Hash: common.HexToHash("0xL1a")}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Number: 100, Hash: common.HexToHash("0x1")}}, + }) + require.NoError(t, err) + err = h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1001, + L1Inclusion: eth.BlockID{Number: 51, Hash: common.HexToHash("0xL1b")}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Number: 101, Hash: common.HexToHash("0x2")}}, + }) + require.NoError(t, err) + + // Verify both exist + has, _ := h.interop.verifiedDB.Has(1001) + require.True(t, has) + + // Replace logsDB with a tracking mock + trackingDB := &mockLogsDBWithState{ + latestBlock: eth.BlockID{Hash: common.HexToHash("0x2"), Number: 101}, + hasBlocks: true, + } + h.interop.logsDBs[chainID] = trackingDB + + // Rewind timestamp 1001 + plan, err := h.interop.buildRewindPlan(1001) + require.NoError(t, err) + err = h.interop.applyRewindPlan(plan) + require.NoError(t, err) + + // verifiedDB should only have T=1000 + has, _ = h.interop.verifiedDB.Has(1001) + require.False(t, has, "T=1001 should be removed") + has, _ = h.interop.verifiedDB.Has(1000) + require.True(t, has, "T=1000 should remain") + + // logsDB should have been rewound (not cleared) + require.True(t, trackingDB.rewindCalled, "logsDB should be rewound to previous frontier") + require.Equal(t, 0, trackingDB.clearCalled, "logsDB should not be cleared") + }) + + t.Run("clears logsDB when rewinding to empty", func(t *testing.T) { + h := newInteropTestHarness(t). + WithChain(10, nil). + Build() + + chainID := h.Mock(10).id + + // Commit one result at activation timestamp + err := h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 50}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Number: 100}}, + }) + require.NoError(t, err) + + trackingDB := &mockLogsDBWithState{ + latestBlock: eth.BlockID{Number: 100}, + hasBlocks: true, + } + h.interop.logsDBs[chainID] = trackingDB + + // Rewind the only entry — verifiedDB becomes empty + plan, err := h.interop.buildRewindPlan(1000) + require.NoError(t, err) + err = h.interop.applyRewindPlan(plan) + require.NoError(t, err) + + // logsDB should be cleared (no previous frontier to rewind to) + require.True(t, trackingDB.clearCalled > 0, "logsDB should be cleared when rewinding to empty") + }) +} + +// ============================================================================= +// Mock types for new tests +// ============================================================================= + +// mockLogsDBWithState extends mockLogsDBForInterop with state tracking for trim tests. +type mockLogsDBWithState struct { + latestBlock eth.BlockID + hasBlocks bool + rewindCalled bool + clearCalled int +} + +func (m *mockLogsDBWithState) LatestSealedBlock() (eth.BlockID, bool) { + return m.latestBlock, m.hasBlocks +} +func (m *mockLogsDBWithState) FirstSealedBlock() (suptypes.BlockSeal, error) { + return suptypes.BlockSeal{}, nil +} +func (m *mockLogsDBWithState) FindSealedBlock(number uint64) (suptypes.BlockSeal, error) { + return suptypes.BlockSeal{}, nil +} +func (m *mockLogsDBWithState) OpenBlock(blockNum uint64) (eth.BlockRef, uint32, map[uint32]*suptypes.ExecutingMessage, error) { + return eth.BlockRef{}, 0, nil, nil +} +func (m *mockLogsDBWithState) Contains(query suptypes.ContainsQuery) (suptypes.BlockSeal, error) { + return suptypes.BlockSeal{}, nil +} +func (m *mockLogsDBWithState) AddLog(logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *suptypes.ExecutingMessage) error { + return nil +} +func (m *mockLogsDBWithState) SealBlock(parentHash common.Hash, block eth.BlockID, timestamp uint64) error { + return nil +} +func (m *mockLogsDBWithState) Rewind(inv reads.Invalidator, newHead eth.BlockID) error { + m.rewindCalled = true + return nil +} +func (m *mockLogsDBWithState) Clear(inv reads.Invalidator) error { + m.clearCalled++ + return nil +} +func (m *mockLogsDBWithState) Close() error { return nil } + +var _ LogsDB = (*mockLogsDBWithState)(nil) + +// errorL1Source implements l1ByNumberSource and always returns an error. +// This is separate from mockL1Source in checker_test.go which uses a map lookup. +type errorL1Source struct { + err error +} + +func (m *errorL1Source) L1BlockRefByNumber(ctx context.Context, num uint64) (eth.L1BlockRef, error) { + return eth.L1BlockRef{}, m.err +} + +// progressInteropCompat replicates the old progressInterop() behavior for test compatibility. +// It runs observeRound + verify, returning the same Result that the old method would have returned. +func progressInteropCompat(i *Interop) (Result, error) { + obs, err := i.observeRound() + if err != nil { + return Result{}, err + } + if obs.Paused || !obs.ChainsReady { + return Result{}, nil + } + return i.verify(obs.NextTimestamp, obs.BlocksAtTS) +} + +// applyResultCompat replicates the old result-application behavior for test compatibility. +func applyResultCompat(i *Interop, result Result) error { + if result.IsEmpty() { + return nil + } + obs := RoundObservation{} + var output StepOutput + if !result.IsValid() { + output = StepOutput{Decision: DecisionInvalidate, Result: result} + } else { + output = StepOutput{Decision: DecisionAdvance, Result: result} + } + pending, err := i.buildPendingTransition(output, obs) + if err != nil { + return err + } + if err := i.verifiedDB.SetPendingTransition(pending); err != nil { + return err + } + _, err = i.applyPendingTransition(pending) + return err +} + +// mockLogsDBForInterop implements LogsDB for interop tests +type mockLogsDBForInterop struct { + openBlockRef eth.BlockRef + openBlockLogCnt uint32 + openBlockExecMsg map[uint32]*suptypes.ExecutingMessage + openBlockErr error + containsSeal suptypes.BlockSeal + containsErr error + + // Track calls for verification + rewindCalls []eth.BlockID + clearCalls int + addLogCalls int + sealCalls int + + // Configurable return value for FirstSealedBlock + firstSealedBlock suptypes.BlockSeal +} + +func (m *mockLogsDBForInterop) LatestSealedBlock() (eth.BlockID, bool) { return eth.BlockID{}, false } +func (m *mockLogsDBForInterop) FirstSealedBlock() (suptypes.BlockSeal, error) { + return m.firstSealedBlock, nil +} +func (m *mockLogsDBForInterop) FindSealedBlock(number uint64) (suptypes.BlockSeal, error) { + return suptypes.BlockSeal{}, nil +} +func (m *mockLogsDBForInterop) OpenBlock(blockNum uint64) (eth.BlockRef, uint32, map[uint32]*suptypes.ExecutingMessage, error) { + if m.openBlockErr != nil { + return eth.BlockRef{}, 0, nil, m.openBlockErr + } + return m.openBlockRef, m.openBlockLogCnt, m.openBlockExecMsg, nil +} +func (m *mockLogsDBForInterop) Contains(query suptypes.ContainsQuery) (suptypes.BlockSeal, error) { + if m.containsErr != nil { + return suptypes.BlockSeal{}, m.containsErr + } + return m.containsSeal, nil +} +func (m *mockLogsDBForInterop) AddLog(logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *suptypes.ExecutingMessage) error { + m.addLogCalls++ + return nil +} +func (m *mockLogsDBForInterop) SealBlock(parentHash common.Hash, block eth.BlockID, timestamp uint64) error { + m.sealCalls++ + return nil +} +func (m *mockLogsDBForInterop) Rewind(inv reads.Invalidator, newHead eth.BlockID) error { + m.rewindCalls = append(m.rewindCalls, newHead) + return nil +} +func (m *mockLogsDBForInterop) Clear(inv reads.Invalidator) error { + m.clearCalls++ + return nil +} +func (m *mockLogsDBForInterop) Close() error { return nil } + +var _ LogsDB = (*mockLogsDBForInterop)(nil) + +func TestVerify_DoesNotPersistFrontierLogs(t *testing.T) { + h := newInteropTestHarness(t). // newInteropTestHarness calls t.Parallel() + WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 1000, Hash: common.HexToHash("0x1")} + }). + Build() + + mock := h.Mock(10) + trackingDB := &mockLogsDBForInterop{} + h.interop.logsDBs[mock.id] = trackingDB + + obs, err := h.interop.observeRound() + require.NoError(t, err) + require.True(t, obs.ChainsReady) + + result, err := h.interop.verify(obs.NextTimestamp, obs.BlocksAtTS) + require.NoError(t, err) + require.False(t, result.IsEmpty()) + require.Zero(t, trackingDB.addLogCalls, "verify must not write logs into logsDB") + require.Zero(t, trackingDB.sealCalls, "verify must not seal frontier blocks into logsDB") +} + +func TestResetIsNoOp(t *testing.T) { + h := newInteropTestHarness(t). + WithChain(10, nil). + Build() + + mock := h.Mock(10) + err := h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 50}, + L2Heads: map[eth.ChainID]eth.BlockID{mock.id: {Number: 100}}, + }) + require.NoError(t, err) + + trackingDB := &mockLogsDBWithState{ + latestBlock: eth.BlockID{Hash: common.HexToHash("0x1"), Number: 100}, + hasBlocks: true, + } + h.interop.logsDBs[mock.id] = trackingDB + h.interop.currentL1 = eth.BlockID{Number: 999, Hash: common.HexToHash("0xL1")} + + h.interop.Reset(mock.id, 1000, eth.BlockRef{Number: 100, ParentHash: common.HexToHash("0xparent")}) + + has, err := h.interop.verifiedDB.Has(1000) + require.NoError(t, err) + require.True(t, has, "reset callback should not mutate verifiedDB") + require.False(t, trackingDB.rewindCalled, "reset callback should not rewind logsDB") + require.Zero(t, trackingDB.clearCalled, "reset callback should not clear logsDB") + require.Equal(t, eth.BlockID{Number: 999, Hash: common.HexToHash("0xL1")}, h.interop.currentL1) +} + +// ============================================================================= +// TestVerifiedBlockAtL1 +// ============================================================================= + +func TestVerifiedBlockAtL1(t *testing.T) { + t.Run("zero l1Block returns empty immediately", func(t *testing.T) { + h := newInteropTestHarness(t). + WithChain(10, nil). + Build() + + // Commit some verified results so the DB is non-empty + for ts := uint64(100); ts <= 110; ts++ { + err := h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Number: ts + 1000}, + L2Heads: map[eth.ChainID]eth.BlockID{h.Mock(10).id: {Number: ts}}, + }) + require.NoError(t, err) + } + + // Call with zero L1BlockRef — should return empty without scanning the DB + blockID, ts := h.interop.VerifiedBlockAtL1(h.Mock(10).id, eth.L1BlockRef{}) + require.Equal(t, eth.BlockID{}, blockID) + require.Equal(t, uint64(0), ts) + }) + + t.Run("non-zero l1Block finds matching entry", func(t *testing.T) { + h := newInteropTestHarness(t). + WithChain(10, nil). + Build() + + chainID := h.Mock(10).id + // Use timestamps at/above activation (1000) so VerifiedBlockAtL1 scan finds them + expectedL2 := eth.BlockID{Hash: common.Hash{0xaa}, Number: 1005} + + for ts := uint64(1000); ts <= 1010; ts++ { + l2Head := eth.BlockID{Hash: common.Hash{byte(ts)}, Number: ts} + if ts == 1005 { + l2Head = expectedL2 + } + err := h.interop.verifiedDB.Commit(VerifiedResult{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Number: ts * 10}, // L1 inclusion grows with timestamp + L2Heads: map[eth.ChainID]eth.BlockID{chainID: l2Head}, + }) + require.NoError(t, err) + } + + // Query for L1 block 10059 — should match timestamp 1005 (L1Inclusion.Number=10050 <= 10059) + // but not timestamp 1006 (L1Inclusion.Number=10060 > 10059) + l1Block := eth.L1BlockRef{Hash: common.Hash{0x01}, Number: 10059, Time: 999} + blockID, ts := h.interop.VerifiedBlockAtL1(chainID, l1Block) + require.Equal(t, expectedL2, blockID) + require.Equal(t, uint64(1005), ts) + }) + + t.Run("empty DB returns empty", func(t *testing.T) { + h := newInteropTestHarness(t). + WithChain(10, nil). + Build() + + l1Block := eth.L1BlockRef{Hash: common.Hash{0x01}, Number: 1000, Time: 999} + blockID, ts := h.interop.VerifiedBlockAtL1(h.Mock(10).id, l1Block) + require.Equal(t, eth.BlockID{}, blockID) + require.Equal(t, uint64(0), ts) + }) +} + +// ============================================================================= +// TestFreezeAllBeforeRewind +// ============================================================================= + +// TestFreezeAllBeforeRewind verifies the freeze-all-then-resume behavior +// introduced in applyPendingTransition for DecisionInvalidate: +// - All chains (not just invalidated ones) are frozen via PauseAndStopVN +// before any invalidateBlock call +// - Only non-invalidated chains are resumed after the invalidation loop +// - Invalidated chains are NOT resumed (RewindEngine handles that internally) +func TestFreezeAllBeforeRewind(t *testing.T) { + t.Parallel() + + t.Run("all chains frozen before any invalidation", func(t *testing.T) { + cl := &callLog{} + h := newInteropTestHarness(t). + WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + m.callLog = cl + }). + WithChain(8453, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 200, Hash: common.HexToHash("0x2")} + m.callLog = cl + }). + WithChain(42, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 300, Hash: common.HexToHash("0x3")} + m.callLog = cl + }). + Build() + + chain10 := h.Mock(10).id + chain8453 := h.Mock(8453).id + + // Only chain 10 is invalidated; chains 8453 and 42 are valid. + invalidResult := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 100, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + chain10: {Number: 500, Hash: common.HexToHash("0xL2-10")}, + chain8453: {Number: 600, Hash: common.HexToHash("0xL2-8453")}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chain10: {BlockID: eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD")}}, + }, + } + + pending, err := h.interop.buildPendingTransition( + StepOutput{Decision: DecisionInvalidate, Result: invalidResult}, + RoundObservation{}, + ) + require.NoError(t, err) + require.NoError(t, h.interop.verifiedDB.SetPendingTransition(pending)) + _, err = h.interop.applyPendingTransition(pending) + require.NoError(t, err) + + entries := cl.snapshot() + + // All three chains must have PauseAndStopVN called. + require.Equal(t, 1, h.Mock(10).pauseAndStopVNCalls, "chain 10 should be frozen") + require.Equal(t, 1, h.Mock(8453).pauseAndStopVNCalls, "chain 8453 should be frozen") + require.Equal(t, 1, h.Mock(42).pauseAndStopVNCalls, "chain 42 should be frozen") + + // Find the index of the first InvalidateBlock call. + firstInvalidateIdx := -1 + for i, e := range entries { + if e.method == "InvalidateBlock" { + firstInvalidateIdx = i + break + } + } + require.NotEqual(t, -1, firstInvalidateIdx, "should have at least one InvalidateBlock call") + + // Every PauseAndStopVN must come before the first InvalidateBlock. + for i, e := range entries { + if e.method == "PauseAndStopVN" { + require.Less(t, i, firstInvalidateIdx, + "PauseAndStopVN on chain %s (index %d) must precede first InvalidateBlock (index %d)", + e.chainID, i, firstInvalidateIdx) + } + } + }) + + t.Run("only non-invalidated chains are resumed", func(t *testing.T) { + cl := &callLog{} + h := newInteropTestHarness(t). + WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + m.callLog = cl + }). + WithChain(8453, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 200, Hash: common.HexToHash("0x2")} + m.callLog = cl + }). + WithChain(42, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 300, Hash: common.HexToHash("0x3")} + m.callLog = cl + }). + Build() + + chain10 := h.Mock(10).id + chain8453 := h.Mock(8453).id + chain42 := h.Mock(42).id + + // Chains 10 and 8453 are invalidated; chain 42 is valid. + invalidResult := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 100, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + chain10: {Number: 500, Hash: common.HexToHash("0xL2-10")}, + chain8453: {Number: 600, Hash: common.HexToHash("0xL2-8453")}, + chain42: {Number: 700, Hash: common.HexToHash("0xL2-42")}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chain10: {BlockID: eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD10")}}, + chain8453: {BlockID: eth.BlockID{Number: 600, Hash: common.HexToHash("0xBAD8453")}}, + }, + } + + pending, err := h.interop.buildPendingTransition( + StepOutput{Decision: DecisionInvalidate, Result: invalidResult}, + RoundObservation{}, + ) + require.NoError(t, err) + require.NoError(t, h.interop.verifiedDB.SetPendingTransition(pending)) + _, err = h.interop.applyPendingTransition(pending) + require.NoError(t, err) + + // Only chain 42 (non-invalidated) should have Resume called. + require.Equal(t, 0, h.Mock(10).resumeCalls, "invalidated chain 10 should NOT be resumed") + require.Equal(t, 0, h.Mock(8453).resumeCalls, "invalidated chain 8453 should NOT be resumed") + require.Equal(t, 1, h.Mock(42).resumeCalls, "non-invalidated chain 42 should be resumed") + }) + + t.Run("resume happens after all invalidations", func(t *testing.T) { + cl := &callLog{} + h := newInteropTestHarness(t). + WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + m.callLog = cl + }). + WithChain(8453, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 200, Hash: common.HexToHash("0x2")} + m.callLog = cl + }). + Build() + + chain10 := h.Mock(10).id + chain8453 := h.Mock(8453).id + + // Only chain 10 is invalidated. + invalidResult := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 100, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + chain10: {Number: 500, Hash: common.HexToHash("0xL2-10")}, + chain8453: {Number: 600, Hash: common.HexToHash("0xL2-8453")}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chain10: {BlockID: eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD")}}, + }, + } + + pending, err := h.interop.buildPendingTransition( + StepOutput{Decision: DecisionInvalidate, Result: invalidResult}, + RoundObservation{}, + ) + require.NoError(t, err) + require.NoError(t, h.interop.verifiedDB.SetPendingTransition(pending)) + _, err = h.interop.applyPendingTransition(pending) + require.NoError(t, err) + + entries := cl.snapshot() + + // Find the last InvalidateBlock index. + lastInvalidateIdx := -1 + for i, e := range entries { + if e.method == "InvalidateBlock" { + lastInvalidateIdx = i + } + } + require.NotEqual(t, -1, lastInvalidateIdx) + + // Every Resume must come after the last InvalidateBlock. + for i, e := range entries { + if e.method == "Resume" { + require.Greater(t, i, lastInvalidateIdx, + "Resume on chain %s (index %d) must follow last InvalidateBlock (index %d)", + e.chainID, i, lastInvalidateIdx) + } + } + }) + + t.Run("single chain invalidated freezes and does not resume", func(t *testing.T) { + cl := &callLog{} + h := newInteropTestHarness(t). + WithChain(10, func(m *mockChainContainer) { + m.blockAtTimestamp = eth.L2BlockRef{Number: 100, Hash: common.HexToHash("0x1")} + m.callLog = cl + }). + Build() + + chain10 := h.Mock(10).id + + // The only chain is invalidated — no chain should be resumed. + invalidResult := Result{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{Number: 100, Hash: common.HexToHash("0xL1")}, + L2Heads: map[eth.ChainID]eth.BlockID{ + chain10: {Number: 500, Hash: common.HexToHash("0xL2-10")}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chain10: {BlockID: eth.BlockID{Number: 500, Hash: common.HexToHash("0xBAD")}}, + }, + } + + pending, err := h.interop.buildPendingTransition( + StepOutput{Decision: DecisionInvalidate, Result: invalidResult}, + RoundObservation{}, + ) + require.NoError(t, err) + require.NoError(t, h.interop.verifiedDB.SetPendingTransition(pending)) + _, err = h.interop.applyPendingTransition(pending) + require.NoError(t, err) + + require.Equal(t, 1, h.Mock(10).pauseAndStopVNCalls, "chain should be frozen") + require.Equal(t, 0, h.Mock(10).resumeCalls, "invalidated chain should NOT be resumed") + + entries := cl.snapshot() + for _, e := range entries { + require.NotEqual(t, "Resume", e.method, "no Resume calls expected when all chains are invalidated") + } + }) +} diff --git a/op-supernode/supernode/activity/interop/log_backfill_test.go b/op-supernode/supernode/activity/interop/log_backfill_test.go new file mode 100644 index 00000000000..70870621488 --- /dev/null +++ b/op-supernode/supernode/activity/interop/log_backfill_test.go @@ -0,0 +1,466 @@ +package interop + +import ( + "context" + "errors" + "math/big" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestLogBackfillLowerBound(t *testing.T) { + tests := []struct { + name string + crossSafe, act uint64 + depth time.Duration + wantTlo uint64 + }{ + {"zero depth returns crossSafe", 1000, 100, 0, 1000}, + {"clamp when raw before activation", 200, 100, 500 * time.Second, 100}, + {"raw above activation", 1000, 100, 100 * time.Second, 900}, + {"crossSafe below depth underflow then clamp", 50, 40, 100 * time.Second, 40}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := LogBackfillLowerBound(tt.crossSafe, tt.act, tt.depth) + require.Equal(t, tt.wantTlo, got) + }) + } +} + +// progressInteropUntil calls progressAndRecord up to maxIters times until cond() is true. +func progressInteropUntil(t *testing.T, i *Interop, maxIters int, cond func() bool) { + t.Helper() + for range maxIters { + if cond() { + return + } + _, err := i.progressAndRecord() + require.NoError(t, err) + } +} + +func TestLogBackfill_ResumesAfterInterruption(t *testing.T) { + const act = uint64(100) + depth := 10 * time.Second // crossSafe 110, depth 10s -> T_lo 100; should seal 100..110 + + h := newInteropTestHarness(t). + WithActivation(act). + WithLogBackfillDepth(depth). + WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 1, Hash: common.HexToHash("0xL1")} + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + SafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + LocalSafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + } + }). + Build() + h.interop.ctx = context.Background() + + // Simulate a previous partial run: seal blocks 100..105 into the logsDB. + chain10 := h.Mock(10) + for num := uint64(100); num <= 105; num++ { + out, err := chain10.OutputV0AtBlockNumber(context.Background(), num) + require.NoError(t, err) + bid := eth.BlockID{Hash: out.BlockHash, Number: num} + blockInfo, receipts, err := chain10.FetchReceipts(context.Background(), bid) + require.NoError(t, err) + err = h.interop.sealBlockDataIntoLogsDB(chain10.id, bid, blockInfo, receipts, blockInfo.Time(), true) + require.NoError(t, err) + } + + latest, has := h.interop.logsDBs[chain10.id].LatestSealedBlock() + require.True(t, has) + require.Equal(t, uint64(105), latest.Number) + + // Track how many OutputV0 calls happen during backfill to confirm we + // don't re-fetch blocks 100..105. + var fetchCount atomic.Int32 + chain10.outputV0Override = func(ctx context.Context, num uint64) (*eth.OutputV0, error) { + fetchCount.Add(1) + return ð.OutputV0{ + StateRoot: eth.Bytes32(common.HexToHash("0xmockstate")), + MessagePasserStorageRoot: eth.Bytes32(common.HexToHash("0xmockmsg")), + BlockHash: common.BigToHash(new(big.Int).SetUint64(num)), + }, nil + } + + require.NoError(t, h.interop.runLogBackfill()) + require.Equal(t, uint64(111), h.interop.runtimeActivationTimestamp) + require.Equal(t, act, h.interop.activationTimestamp, "protocol activation must not change") + + latest, has = h.interop.logsDBs[chain10.id].LatestSealedBlock() + require.True(t, has) + require.Equal(t, uint64(110), latest.Number) + + // Should have fetched only blocks 106..110 (5 blocks), not 100..110 (11 blocks). + require.Equal(t, int32(5), fetchCount.Load()) +} + +func TestLogBackfill_RetriesWhenVirtualNodesNotReady(t *testing.T) { + const act = uint64(100) + depth := 10 * time.Second + + // Track SyncStatus call count so we can make the first N calls fail. + var syncStatusCalls atomic.Int32 + failUntil := int32(3) // first 3 calls return error, then succeed + + h := newInteropTestHarness(t). + WithActivation(act). + WithLogBackfillDepth(depth). + WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 1, Hash: common.HexToHash("0xL1")} + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + SafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + LocalSafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + } + m.currentL1Err = errors.New("virtual node not ready") + m.syncStatusOverride = func() (*eth.SyncStatus, error) { + n := syncStatusCalls.Add(1) + if n <= failUntil { + return nil, errors.New("virtual node not ready") + } + return ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + SafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + LocalSafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + }, nil + } + }). + Build() + + // Use a shorter backoff for tests. + origBackoff := errorBackoffPeriod + errorBackoffPeriod = 10 * time.Millisecond + t.Cleanup(func() { errorBackoffPeriod = origBackoff }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + done := make(chan error, 1) + go func() { done <- h.interop.Start(ctx) }() + + // Wait for backfill to complete: runtime activation should advance past 110. + require.Eventually(t, func() bool { + return h.interop.runtimeActivationTimestamp > act + }, 5*time.Second, 20*time.Millisecond, "backfill should eventually succeed after retries") + + require.GreaterOrEqual(t, syncStatusCalls.Load(), failUntil, + "SyncStatus should have been called at least %d times (the failing ones)", failUntil) + require.Equal(t, uint64(111), h.interop.runtimeActivationTimestamp) + require.Equal(t, act, h.interop.activationTimestamp, "protocol activation must not change") + + cancel() + <-done +} + +func TestLogBackfill_RetriesStopOnContextCancel(t *testing.T) { + const act = uint64(100) + depth := 10 * time.Second + + h := newInteropTestHarness(t). + WithActivation(act). + WithLogBackfillDepth(depth). + WithChain(10, func(m *mockChainContainer) { + // SyncStatus always fails — backfill will retry forever. + m.currentL1Err = errors.New("virtual node not ready") + }). + Build() + + origBackoff := errorBackoffPeriod + errorBackoffPeriod = 10 * time.Millisecond + t.Cleanup(func() { errorBackoffPeriod = origBackoff }) + + ctx, cancel := context.WithCancel(context.Background()) + + done := make(chan error, 1) + go func() { done <- h.interop.Start(ctx) }() + + // Let it retry a few times, then cancel. + time.Sleep(100 * time.Millisecond) + cancel() + + select { + case err := <-done: + require.ErrorIs(t, err, context.Canceled) + case <-time.After(5 * time.Second): + t.Fatal("Start did not return after context cancellation") + } +} + +// TestLogBackfill_AsymmetricMultiChain exercises the two cross-chain minima +// that runLogBackfill computes with three chains at different sync tips: +// +// - T_lo is derived from min(SafeL2.Time) across chains (cross-safe). +// - runtimeActivationTimestamp is derived from min(LocalSafeL2.Time) across +// chains and must advance to that minimum + 1. +// +// All chains participate in backfill, and each chain's LocalSafe is at or +// beyond T_lo in both time and block number — the consistent case that +// runLogBackfill is required to support. +func TestLogBackfill_AsymmetricMultiChain(t *testing.T) { + const act = uint64(50) + depth := 10 * time.Second // min crossSafe 100 -> T_lo 90 + + // Chain 10: crossSafe/localSafe tip at 120. Backfill range 90..120. + // Chain 20: crossSafe 200, localSafe tip at 130. Backfill range 90..130. + // Chain 30: crossSafe 100 (the min, so it pins T_lo), localSafe tip at + // 110 (smallest localSafe, so it pins runtimeActivation). Backfill + // range 90..110. + h := newInteropTestHarness(t). + WithActivation(act). + WithLogBackfillDepth(depth). + WithChain(10, func(m *mockChainContainer) { + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 120, Time: 120}, + SafeL2: eth.L2BlockRef{Number: 120, Time: 120}, + LocalSafeL2: eth.L2BlockRef{Number: 120, Time: 120}, + } + }). + WithChain(20, func(m *mockChainContainer) { + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 130, Time: 130}, + SafeL2: eth.L2BlockRef{Number: 200, Time: 200}, + LocalSafeL2: eth.L2BlockRef{Number: 130, Time: 130}, + } + }). + WithChain(30, func(m *mockChainContainer) { + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + SafeL2: eth.L2BlockRef{Number: 100, Time: 100}, + LocalSafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + } + }). + Build() + h.interop.ctx = context.Background() + + fetchCount := make(map[eth.ChainID]*atomic.Int32, 3) + for _, id := range []uint64{10, 20, 30} { + c := h.Mock(id) + counter := new(atomic.Int32) + fetchCount[c.id] = counter + c.outputV0Override = func(ctx context.Context, num uint64) (*eth.OutputV0, error) { + counter.Add(1) + return ð.OutputV0{ + StateRoot: eth.Bytes32(common.HexToHash("0xmockstate")), + MessagePasserStorageRoot: eth.Bytes32(common.HexToHash("0xmockmsg")), + BlockHash: common.BigToHash(new(big.Int).SetUint64(num)), + }, nil + } + } + + require.NoError(t, h.interop.runLogBackfill()) + require.Equal(t, act, h.interop.activationTimestamp, "protocol activation must not change") + require.Equal(t, uint64(111), h.interop.runtimeActivationTimestamp, + "runtime activation must advance to min(localSafe)+1 across all chains") + + chain10 := h.Mock(10) + chain20 := h.Mock(20) + chain30 := h.Mock(30) + + require.Equal(t, int32(31), fetchCount[chain10.id].Load(), + "chain 10 should backfill blocks 90..120 (31 blocks)") + require.Equal(t, int32(41), fetchCount[chain20.id].Load(), + "chain 20 should backfill blocks 90..130 (41 blocks)") + require.Equal(t, int32(21), fetchCount[chain30.id].Load(), + "chain 30 should backfill blocks 90..110 (21 blocks)") + + latest10, has10 := h.interop.logsDBs[chain10.id].LatestSealedBlock() + require.True(t, has10) + require.Equal(t, uint64(120), latest10.Number) + + latest20, has20 := h.interop.logsDBs[chain20.id].LatestSealedBlock() + require.True(t, has20) + require.Equal(t, uint64(130), latest20.Number) + + latest30, has30 := h.interop.logsDBs[chain30.id].LatestSealedBlock() + require.True(t, has30) + require.Equal(t, uint64(110), latest30.Number) +} + +// TestLogBackfill_FailsWhenChainBehindLowerBound asserts that runLogBackfill +// refuses to proceed if TimestampToBlockNumber(T_lo) for any chain lands past +// that chain's LocalSafeL2 block number. This condition is unreachable under +// a consistent SyncStatus (localSafe >= crossSafe >= minCrossSafeTime >= T_lo), +// so if it fires, the chain container is reporting inconsistent state. +// Silently skipping such a chain would leave its logs DB empty while other +// chains seal from T_lo forward, wedging the main loop's first seal attempt +// on ErrStaleLogsDB. Failing the attempt surfaces the problem to the +// operator; remediation is to clear the data dir and restart. +func TestLogBackfill_FailsWhenChainBehindLowerBound(t *testing.T) { + const act = uint64(50) + depth := 10 * time.Second // min crossSafe 100 -> T_lo 90 + + h := newInteropTestHarness(t). + WithActivation(act). + WithLogBackfillDepth(depth). + WithChain(10, func(m *mockChainContainer) { + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 120, Time: 120}, + SafeL2: eth.L2BlockRef{Number: 100, Time: 100}, + LocalSafeL2: eth.L2BlockRef{Number: 120, Time: 120}, + } + }). + // Chain 20 reports a consistent-looking SyncStatus but a bogus + // TimestampToBlockNumber that maps T_lo to a block well past + // LocalSafeL2. This is the inconsistency the guard catches. + WithChain(20, func(m *mockChainContainer) { + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 95, Time: 95}, + SafeL2: eth.L2BlockRef{Number: 500, Time: 500}, + LocalSafeL2: eth.L2BlockRef{Number: 95, Time: 95}, + } + m.timestampToBlockNumberOverride = func(ctx context.Context, ts uint64) (uint64, error) { + return 200, nil + } + }). + Build() + h.interop.ctx = context.Background() + + err := h.interop.runLogBackfill() + require.Error(t, err) + require.ErrorIs(t, err, ErrChainBehindBackfillLowerBound) + require.Equal(t, act, h.interop.runtimeActivationTimestamp, + "runtime activation must not advance when backfill fails") +} + +// TestLogBackfill_MisalignedActivation asserts that backfill succeeds when +// the protocol activation timestamp does not land on a (genesis + k*blockTime) +// boundary. In this configuration TargetBlockNumber(activation) floors to the +// last block whose Time() is strictly before activation: that block +// represents the chain state as of the fork and is the correct pairing +// anchor for the first post-activation block. An overly strict +// "first seal must be >= activation" check would reject this block and the +// retry loop would spin forever with a misleading "virtual nodes may not be +// ready" log line. +// +// Concrete setup: blockTime=3, genesis=0, activation=1000. Block 333 has +// Time()=999 (the pairing anchor); block 334 is at 1002; LocalSafe is at +// block 340, Time=1020. T_lo clamps to activation so backfill must seal +// blocks 333..340 without error. runtimeActivation advances to 1021. +func TestLogBackfill_MisalignedActivation(t *testing.T) { + const ( + blockTime uint64 = 3 + act uint64 = 1000 + localSafeNum uint64 = 340 + localSafeTs uint64 = 1020 // 340 * blockTime + ) + depth := 60 * time.Second // crossSafe 1020 - 60 = 960 < activation → T_lo clamps to 1000 + + blockNumToTime := func(num uint64) uint64 { return num * blockTime } + tsToBlockNum := func(ctx context.Context, ts uint64) (uint64, error) { + return ts / blockTime, nil // floor, matches rollup.TargetBlockNumber + } + + h := newInteropTestHarness(t). + WithActivation(act). + WithLogBackfillDepth(depth). + WithChain(10, func(m *mockChainContainer) { + m.blockTimeOverride = blockTime + m.blockInfoTimeFn = blockNumToTime + m.timestampToBlockNumberOverride = tsToBlockNum + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: localSafeNum, Time: localSafeTs}, + SafeL2: eth.L2BlockRef{Number: localSafeNum, Time: localSafeTs}, + LocalSafeL2: eth.L2BlockRef{Number: localSafeNum, Time: localSafeTs}, + } + }). + Build() + h.interop.ctx = context.Background() + + require.NoError(t, h.interop.runLogBackfill()) + require.Equal(t, act, h.interop.activationTimestamp, "protocol activation must not change") + require.Equal(t, localSafeTs+1, h.interop.runtimeActivationTimestamp) + + chain10 := h.Mock(10) + db := h.interop.logsDBs[chain10.id] + + // processBlockLogs seals a "virtual parent" before the first real backfill + // block so subsequent blocks have a parent to link against. For the first + // backfill block at number 333, that virtual parent is at number 332 with + // the real block's Time() (999). FirstSealedBlock therefore returns the + // virtual parent — the anchor — and the invariant we care about is that + // its timestamp is strictly pre-activation but within one blockTime of it. + first, err := db.FirstSealedBlock() + require.NoError(t, err) + require.Equal(t, uint64(332), first.Number, "first sealed block is the virtual parent of TargetBlockNumber(activation)") + require.Equal(t, uint64(999), first.Timestamp, + "anchor's Time() is the real block's time, strictly pre-activation — this is the pairing anchor, not a violation") + require.Less(t, first.Timestamp, act, "sanity: anchor is strictly pre-activation") + require.Greater(t, first.Timestamp+blockTime, act, + "anchor must still be within one blockTime of activation (the anchor window)") + + latest, has := db.LatestSealedBlock() + require.True(t, has) + require.Equal(t, localSafeNum, latest.Number) +} + +func TestLogBackfill_AdvancesActivationAndStartsVerifyAfterCeiling(t *testing.T) { + const act = uint64(108) + depth := time.Second // crossSafe 110, depth 1s -> T_lo 109; seals 109..110; activation advances to 111 + + h := newInteropTestHarness(t). + WithActivation(act). + WithLogBackfillDepth(depth). + WithChain(10, func(m *mockChainContainer) { + m.currentL1 = eth.BlockRef{Number: 1, Hash: common.HexToHash("0xL1")} + m.syncStatusFull = ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 1, Hash: common.HexToHash("0xL1")}, + UnsafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + SafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + LocalSafeL2: eth.L2BlockRef{Number: 110, Time: 110}, + } + }). + Build() + + var verifyCalls atomic.Int32 + var firstVerifyTS atomic.Uint64 + h.interop.verifyFn = func(ts uint64, blocks map[eth.ChainID]eth.BlockID) (Result, error) { + if verifyCalls.Add(1) == 1 { + firstVerifyTS.Store(ts) + } + return Result{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Number: 1, Hash: common.HexToHash("0xL1")}, + L2Heads: blocks, + }, nil + } + h.interop.ctx = context.Background() + + require.NoError(t, h.interop.runLogBackfill()) + require.Equal(t, uint64(111), h.interop.runtimeActivationTimestamp) + require.Equal(t, act, h.interop.activationTimestamp, "protocol activation must not change") + + chain10 := h.Mock(10) + latest, has := h.interop.logsDBs[chain10.id].LatestSealedBlock() + require.True(t, has) + require.Equal(t, uint64(110), latest.Number) + require.Zero(t, verifyCalls.Load()) + + // Progress the main loop — first verify should be at 111 (activation after backfill). + progressInteropUntil(t, h.interop, 10, func() bool { + lastTS, ok := h.interop.verifiedDB.LastTimestamp() + return ok && lastTS >= 111 + }) + lastTS, ok := h.interop.verifiedDB.LastTimestamp() + require.True(t, ok) + require.GreaterOrEqual(t, lastTS, uint64(111)) + require.Equal(t, int32(1), verifyCalls.Load()) + require.Equal(t, uint64(111), firstVerifyTS.Load()) +} diff --git a/op-supernode/supernode/activity/interop/types_test.go b/op-supernode/supernode/activity/interop/types_test.go new file mode 100644 index 00000000000..52398ba5d9e --- /dev/null +++ b/op-supernode/supernode/activity/interop/types_test.go @@ -0,0 +1,201 @@ +package interop + +import ( + "encoding/json" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestResult_IsValid(t *testing.T) { + t.Parallel() + + t.Run("returns true when InvalidHeads is nil", func(t *testing.T) { + r := Result{ + Timestamp: 100, + L1Inclusion: eth.BlockID{Number: 1}, + L2Heads: map[eth.ChainID]eth.BlockID{eth.ChainIDFromUInt64(10): {Number: 100}}, + InvalidHeads: nil, + } + require.True(t, r.IsValid()) + }) + + t.Run("returns true when InvalidHeads is empty map", func(t *testing.T) { + r := Result{ + Timestamp: 100, + L1Inclusion: eth.BlockID{Number: 1}, + L2Heads: map[eth.ChainID]eth.BlockID{eth.ChainIDFromUInt64(10): {Number: 100}}, + InvalidHeads: map[eth.ChainID]InvalidHead{}, + } + require.True(t, r.IsValid()) + }) + + t.Run("returns false when InvalidHeads has entries", func(t *testing.T) { + r := Result{ + Timestamp: 100, + L1Inclusion: eth.BlockID{Number: 1}, + L2Heads: map[eth.ChainID]eth.BlockID{eth.ChainIDFromUInt64(10): {Number: 100}}, + InvalidHeads: map[eth.ChainID]InvalidHead{ + eth.ChainIDFromUInt64(10): {BlockID: eth.BlockID{Number: 100, Hash: common.HexToHash("0xbad")}}, + }, + } + require.False(t, r.IsValid()) + }) + + t.Run("returns false with multiple invalid heads", func(t *testing.T) { + r := Result{ + Timestamp: 100, + InvalidHeads: map[eth.ChainID]InvalidHead{ + eth.ChainIDFromUInt64(10): {BlockID: eth.BlockID{Number: 100}}, + eth.ChainIDFromUInt64(8453): {BlockID: eth.BlockID{Number: 200}}, + }, + } + require.False(t, r.IsValid()) + }) +} + +func TestResult_ToVerifiedResult(t *testing.T) { + t.Parallel() + + t.Run("copies all fields except InvalidHeads", func(t *testing.T) { + chainID1 := eth.ChainIDFromUInt64(10) + chainID2 := eth.ChainIDFromUInt64(8453) + + r := Result{ + Timestamp: 12345, + L1Inclusion: eth.BlockID{ + Hash: common.HexToHash("0x1111"), + Number: 100, + }, + L2Heads: map[eth.ChainID]eth.BlockID{ + chainID1: {Hash: common.HexToHash("0x2222"), Number: 200}, + chainID2: {Hash: common.HexToHash("0x3333"), Number: 300}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chainID1: {BlockID: eth.BlockID{Hash: common.HexToHash("0xbad"), Number: 199}}, + }, + } + + verified := r.ToVerifiedResult() + + require.Equal(t, r.Timestamp, verified.Timestamp) + require.Equal(t, r.L1Inclusion, verified.L1Inclusion) + require.Equal(t, r.L2Heads, verified.L2Heads) + }) + + t.Run("handles nil L2Heads", func(t *testing.T) { + r := Result{ + Timestamp: 100, + L1Inclusion: eth.BlockID{Number: 1}, + L2Heads: nil, + } + + verified := r.ToVerifiedResult() + + require.Equal(t, r.Timestamp, verified.Timestamp) + require.Nil(t, verified.L2Heads) + }) + + t.Run("handles empty L2Heads", func(t *testing.T) { + r := Result{ + Timestamp: 100, + L1Inclusion: eth.BlockID{Number: 1}, + L2Heads: map[eth.ChainID]eth.BlockID{}, + } + + verified := r.ToVerifiedResult() + + require.Empty(t, verified.L2Heads) + }) + + t.Run("original Result unchanged after conversion", func(t *testing.T) { + chainID := eth.ChainIDFromUInt64(10) + r := Result{ + Timestamp: 100, + L1Inclusion: eth.BlockID{Number: 1}, + L2Heads: map[eth.ChainID]eth.BlockID{ + chainID: {Number: 200}, + }, + InvalidHeads: map[eth.ChainID]InvalidHead{ + chainID: {BlockID: eth.BlockID{Number: 199}}, + }, + } + + _ = r.ToVerifiedResult() + + // Original should still have InvalidHeads + require.Len(t, r.InvalidHeads, 1) + }) +} + +func TestInvalidHead_JSONRoundTrip(t *testing.T) { + t.Parallel() + + original := InvalidHead{ + BlockID: eth.BlockID{ + Hash: common.HexToHash("0xdead"), + Number: 500, + }, + StateRoot: eth.Bytes32(common.HexToHash("0xstate")), + MessagePasserStorageRoot: eth.Bytes32(common.HexToHash("0xmsgpasser")), + } + + data, err := json.Marshal(original) + require.NoError(t, err) + + var decoded InvalidHead + require.NoError(t, json.Unmarshal(data, &decoded)) + + require.Equal(t, original.BlockID, decoded.BlockID) + require.Equal(t, original.StateRoot, decoded.StateRoot) + require.Equal(t, original.MessagePasserStorageRoot, decoded.MessagePasserStorageRoot) +} + +func TestInvalidHead_JSONRoundTrip_ZeroRoots(t *testing.T) { + t.Parallel() + + original := InvalidHead{ + BlockID: eth.BlockID{ + Hash: common.HexToHash("0xbeef"), + Number: 42, + }, + StateRoot: eth.Bytes32{}, + MessagePasserStorageRoot: eth.Bytes32{}, + } + + data, err := json.Marshal(original) + require.NoError(t, err) + + var decoded InvalidHead + require.NoError(t, json.Unmarshal(data, &decoded)) + + require.Equal(t, original.BlockID, decoded.BlockID) + require.Equal(t, original.StateRoot, decoded.StateRoot) + require.Equal(t, original.MessagePasserStorageRoot, decoded.MessagePasserStorageRoot) +} + +func TestPendingInvalidation_JSONRoundTrip(t *testing.T) { + t.Parallel() + + original := PendingInvalidation{ + ChainID: eth.ChainIDFromUInt64(10), + BlockID: eth.BlockID{Hash: common.HexToHash("0xbad"), Number: 100}, + Timestamp: 42, + StateRoot: eth.Bytes32(common.HexToHash("0xstate")), + MessagePasserStorageRoot: eth.Bytes32(common.HexToHash("0xmsgpasser")), + } + + data, err := json.Marshal(original) + require.NoError(t, err) + + var decoded PendingInvalidation + require.NoError(t, json.Unmarshal(data, &decoded)) + + require.Equal(t, original.ChainID, decoded.ChainID) + require.Equal(t, original.BlockID, decoded.BlockID) + require.Equal(t, original.Timestamp, decoded.Timestamp) + require.Equal(t, original.StateRoot, decoded.StateRoot) + require.Equal(t, original.MessagePasserStorageRoot, decoded.MessagePasserStorageRoot) +} diff --git a/op-supernode/supernode/activity/interop/verified_db.go b/op-supernode/supernode/activity/interop/verified_db.go new file mode 100644 index 00000000000..b675bb67644 --- /dev/null +++ b/op-supernode/supernode/activity/interop/verified_db.go @@ -0,0 +1,329 @@ +package interop + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "path/filepath" + "sync" + + "github.com/ethereum-optimism/optimism/op-service/eth" + + bolt "go.etcd.io/bbolt" +) + +const ( + verifiedDBName = "VerifiedAtTimestamp" +) + +var ( + ErrNotFound = errors.New("timestamp not found") + ErrNonSequential = errors.New("timestamps must be committed sequentially with no gaps") + ErrAlreadyCommitted = errors.New("timestamp already committed") + u64Len = 8 +) + +// bucketName is the name of the bbolt bucket used to store verified results. +var bucketName = []byte("verified") + +var pendingTransitionBucketName = []byte("pending_transition") +var pendingTransitionKey = []byte("pending") + +// PendingInvalidation records a chain invalidation that needs to be executed. +type PendingInvalidation struct { + ChainID eth.ChainID `json:"chainID"` + BlockID eth.BlockID `json:"blockID"` + Timestamp uint64 `json:"timestamp"` // the interop decision timestamp + StateRoot eth.Bytes32 `json:"stateRoot"` + MessagePasserStorageRoot eth.Bytes32 `json:"messagePasserStorageRoot"` +} + +// VerifiedDB provides persistence for verified timestamps using bbolt. +type VerifiedDB struct { + db *bolt.DB + mu sync.RWMutex + lastTimestamp uint64 + initialized bool +} + +// OpenVerifiedDB opens or creates a VerifiedDB at the given data directory. +func OpenVerifiedDB(dataDir string) (*VerifiedDB, error) { + dbPath := filepath.Join(dataDir, verifiedDBName+".db") + db, err := bolt.Open(dbPath, 0600, nil) + if err != nil { + return nil, fmt.Errorf("failed to open bbolt at %s: %w", dbPath, err) + } + + // Ensure the buckets exist + err = db.Update(func(tx *bolt.Tx) error { + if _, err := tx.CreateBucketIfNotExists(bucketName); err != nil { + return err + } + _, err := tx.CreateBucketIfNotExists(pendingTransitionBucketName) + return err + }) + if err != nil { + db.Close() + return nil, fmt.Errorf("failed to create bucket: %w", err) + } + + vdb := &VerifiedDB{ + db: db, + } + + // Initialize the last timestamp from the database + if err := vdb.initLastTimestamp(); err != nil { + db.Close() + return nil, fmt.Errorf("failed to initialize last timestamp: %w", err) + } + + return vdb, nil +} + +// initLastTimestamp scans the database to find the highest committed timestamp. +func (v *VerifiedDB) initLastTimestamp() error { + v.lastTimestamp = 0 + v.initialized = false + return v.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketName) + if b == nil { + return nil + } + + c := b.Cursor() + key, _ := c.Last() + if len(key) == u64Len { + v.lastTimestamp = binary.BigEndian.Uint64(key) + v.initialized = true + } + + return nil + }) +} + +// timestampToKey converts a timestamp to a big-endian byte key. +// Using big-endian ensures lexicographic ordering matches numeric ordering. +func timestampToKey(ts uint64) []byte { + key := make([]byte, u64Len) + binary.BigEndian.PutUint64(key, ts) + return key +} + +// Commit stores a verified result at the given timestamp. +// Timestamps must be committed sequentially with no gaps. +func (v *VerifiedDB) Commit(result VerifiedResult) error { + v.mu.Lock() + defer v.mu.Unlock() + + ts := result.Timestamp + + // Serialize the result up front so replay of an already-applied transition can + // be treated as success when the stored value is identical. + value, err := json.Marshal(result) + if err != nil { + return fmt.Errorf("failed to marshal verified result: %w", err) + } + + // Check for sequential commitment + if v.initialized { + if ts != v.lastTimestamp+1 { + if ts <= v.lastTimestamp { + key := timestampToKey(ts) + var existing []byte + err := v.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketName) + val := b.Get(key) + if val == nil { + return ErrNotFound + } + existing = append(existing[:0], val...) + return nil + }) + if err != nil { + return fmt.Errorf("failed to read existing verified result at %d: %w", ts, err) + } + if bytes.Equal(existing, value) { + return nil + } + return fmt.Errorf("%w: %d", ErrAlreadyCommitted, ts) + } + return fmt.Errorf("%w: expected %d, got %d", ErrNonSequential, v.lastTimestamp+1, ts) + } + } + + // Store in database + key := timestampToKey(ts) + err = v.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketName) + return b.Put(key, value) + }) + if err != nil { + return fmt.Errorf("failed to write to bbolt: %w", err) + } + + // Update state + v.lastTimestamp = ts + v.initialized = true + + return nil +} + +// Get retrieves the verified result at the given timestamp. +func (v *VerifiedDB) Get(ts uint64) (VerifiedResult, error) { + v.mu.RLock() + defer v.mu.RUnlock() + + key := timestampToKey(ts) + var value []byte + + err := v.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketName) + val := b.Get(key) + if val == nil { + return ErrNotFound + } + // Copy the value since it's only valid for the life of the transaction + value = make([]byte, len(val)) + copy(value, val) + return nil + }) + if err != nil { + if errors.Is(err, ErrNotFound) { + return VerifiedResult{}, ErrNotFound + } + return VerifiedResult{}, fmt.Errorf("failed to read from bbolt: %w", err) + } + + var result VerifiedResult + if err := json.Unmarshal(value, &result); err != nil { + return VerifiedResult{}, fmt.Errorf("failed to unmarshal verified result: %w", err) + } + + return result, nil +} + +// Has returns whether a timestamp has been verified. +func (v *VerifiedDB) Has(ts uint64) (bool, error) { + v.mu.RLock() + defer v.mu.RUnlock() + + key := timestampToKey(ts) + var found bool + + err := v.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketName) + found = b.Get(key) != nil + return nil + }) + if err != nil { + return false, fmt.Errorf("failed to check key in bbolt: %w", err) + } + + return found, nil +} + +// LastTimestamp returns the most recently committed timestamp. +// Returns 0 and false if no timestamps have been committed. +func (v *VerifiedDB) LastTimestamp() (uint64, bool) { + v.mu.RLock() + defer v.mu.RUnlock() + return v.lastTimestamp, v.initialized +} + +// Rewind removes all verified results at or after the given timestamp. +// Returns true if any results were deleted, false otherwise. +func (v *VerifiedDB) Rewind(timestamp uint64) (bool, error) { + v.mu.Lock() + defer v.mu.Unlock() + + var deleted bool + + err := v.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketName) + c := b.Cursor() + + // Start from the timestamp and delete all entries at or after it + startKey := timestampToKey(timestamp) + for k, _ := c.Seek(startKey); k != nil; k, _ = c.Next() { + if err := b.Delete(k); err != nil { + return err + } + deleted = true + } + return nil + }) + if err != nil { + return false, fmt.Errorf("failed to rewind verifiedDB: %w", err) + } + + // Update state + if deleted { + if err := v.initLastTimestamp(); err != nil { + return deleted, fmt.Errorf("failed to reinitialize lastTimestamp after rewind: %w", err) + } + } + + return deleted, nil +} + +// SetPendingTransition persists a generic interop transition as a write-ahead log. +// Must be called BEFORE executing any durable side effects for crash safety. +func (v *VerifiedDB) SetPendingTransition(pending PendingTransition) error { + v.mu.Lock() + defer v.mu.Unlock() + + value, err := json.Marshal(pending) + if err != nil { + return fmt.Errorf("failed to marshal pending transition: %w", err) + } + return v.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(pendingTransitionBucketName) + return b.Put(pendingTransitionKey, value) + }) +} + +// GetPendingTransition retrieves any pending transition from the WAL. +// Returns nil if no pending work exists. +func (v *VerifiedDB) GetPendingTransition() (*PendingTransition, error) { + v.mu.RLock() + defer v.mu.RUnlock() + + var pending PendingTransition + var found bool + err := v.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(pendingTransitionBucketName) + val := b.Get(pendingTransitionKey) + if val == nil { + return nil + } + found = true + data := make([]byte, len(val)) + copy(data, val) + return json.Unmarshal(data, &pending) + }) + if err != nil { + return nil, err + } + if !found { + return nil, nil + } + return &pending, nil +} + +// ClearPendingTransition removes the WAL entry after the transition is fully applied. +func (v *VerifiedDB) ClearPendingTransition() error { + v.mu.Lock() + defer v.mu.Unlock() + + return v.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(pendingTransitionBucketName) + return b.Delete(pendingTransitionKey) + }) +} + +// Close closes the database. +func (v *VerifiedDB) Close() error { + return v.db.Close() +} diff --git a/op-supernode/supernode/activity/interop/verified_db_test.go b/op-supernode/supernode/activity/interop/verified_db_test.go new file mode 100644 index 00000000000..46551b0aa33 --- /dev/null +++ b/op-supernode/supernode/activity/interop/verified_db_test.go @@ -0,0 +1,417 @@ +package interop + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestVerifiedDB_WriteAndRead(t *testing.T) { + // Create a temporary directory for the test database + dataDir := t.TempDir() + + // Open the database + db, err := OpenVerifiedDB(dataDir) + require.NoError(t, err) + defer db.Close() + + // Initially, there should be no last timestamp + lastTs, initialized := db.LastTimestamp() + require.False(t, initialized) + require.Equal(t, uint64(0), lastTs) + + // Create test data + chainID1 := eth.ChainIDFromUInt64(10) + chainID2 := eth.ChainIDFromUInt64(8453) + + result1 := VerifiedResult{ + Timestamp: 1000, + L1Inclusion: eth.BlockID{ + Hash: common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111"), + Number: 100, + }, + L2Heads: map[eth.ChainID]eth.BlockID{ + chainID1: { + Hash: common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222"), + Number: 200, + }, + chainID2: { + Hash: common.HexToHash("0x3333333333333333333333333333333333333333333333333333333333333333"), + Number: 300, + }, + }, + } + + // Write the first result + err = db.Commit(result1) + require.NoError(t, err) + + // Verify the last timestamp was updated + lastTs, initialized = db.LastTimestamp() + require.True(t, initialized) + require.Equal(t, uint64(1000), lastTs) + + // Read it back + retrieved, err := db.Get(1000) + require.NoError(t, err) + require.Equal(t, result1.Timestamp, retrieved.Timestamp) + require.Equal(t, result1.L1Inclusion, retrieved.L1Inclusion) + require.Equal(t, len(result1.L2Heads), len(retrieved.L2Heads)) + require.Equal(t, result1.L2Heads[chainID1], retrieved.L2Heads[chainID1]) + require.Equal(t, result1.L2Heads[chainID2], retrieved.L2Heads[chainID2]) + + // Check Has returns true + has, err := db.Has(1000) + require.NoError(t, err) + require.True(t, has) + + // Check Has returns false for non-existent timestamp + has, err = db.Has(999) + require.NoError(t, err) + require.False(t, has) + + // Try to read non-existent timestamp + _, err = db.Get(999) + require.ErrorIs(t, err, ErrNotFound) +} + +func TestVerifiedDB_SequentialCommits(t *testing.T) { + dataDir := t.TempDir() + + db, err := OpenVerifiedDB(dataDir) + require.NoError(t, err) + defer db.Close() + + chainID := eth.ChainIDFromUInt64(10) + + // Commit first timestamp + err = db.Commit(VerifiedResult{ + Timestamp: 100, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0x01"), Number: 1}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.HexToHash("0x02"), Number: 2}}, + }) + require.NoError(t, err) + + // Commit next sequential timestamp should succeed + err = db.Commit(VerifiedResult{ + Timestamp: 101, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0x03"), Number: 3}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.HexToHash("0x04"), Number: 4}}, + }) + require.NoError(t, err) + + // Try to commit non-sequential timestamp (gap) + err = db.Commit(VerifiedResult{ + Timestamp: 105, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0x05"), Number: 5}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.HexToHash("0x06"), Number: 6}}, + }) + require.ErrorIs(t, err, ErrNonSequential) + + // Try to commit already committed timestamp + err = db.Commit(VerifiedResult{ + Timestamp: 100, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0x07"), Number: 7}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.HexToHash("0x08"), Number: 8}}, + }) + require.ErrorIs(t, err, ErrAlreadyCommitted) + + // Verify the database state is correct + lastTs, _ := db.LastTimestamp() + require.Equal(t, uint64(101), lastTs) +} + +func TestVerifiedDB_Persistence(t *testing.T) { + dataDir := t.TempDir() + chainID := eth.ChainIDFromUInt64(42161) + + // Open database and write some data + db, err := OpenVerifiedDB(dataDir) + require.NoError(t, err) + + err = db.Commit(VerifiedResult{ + Timestamp: 500, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0xaaaa"), Number: 50}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.HexToHash("0xbbbb"), Number: 100}}, + }) + require.NoError(t, err) + + err = db.Commit(VerifiedResult{ + Timestamp: 501, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0xcccc"), Number: 51}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.HexToHash("0xdddd"), Number: 101}}, + }) + require.NoError(t, err) + + db.Close() + + // Reopen database and verify data persisted + db2, err := OpenVerifiedDB(dataDir) + require.NoError(t, err) + defer db2.Close() + + // Last timestamp should be restored + lastTs, initialized := db2.LastTimestamp() + require.True(t, initialized) + require.Equal(t, uint64(501), lastTs) + + // Data should be readable + result, err := db2.Get(500) + require.NoError(t, err) + require.Equal(t, uint64(500), result.Timestamp) + require.Equal(t, common.HexToHash("0xaaaa"), result.L1Inclusion.Hash) + + result, err = db2.Get(501) + require.NoError(t, err) + require.Equal(t, uint64(501), result.Timestamp) + + // Next commit should continue from last timestamp + err = db2.Commit(VerifiedResult{ + Timestamp: 502, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0xeeee"), Number: 52}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.HexToHash("0xffff"), Number: 102}}, + }) + require.NoError(t, err) +} + +func TestVerifiedDB_RewindTo(t *testing.T) { + t.Parallel() + + t.Run("removes entries at and after timestamp", func(t *testing.T) { + t.Parallel() + dataDir := t.TempDir() + + db, err := OpenVerifiedDB(dataDir) + require.NoError(t, err) + defer db.Close() + + chainID := eth.ChainIDFromUInt64(10) + + // Commit several timestamps + for ts := uint64(100); ts <= 105; ts++ { + err = db.Commit(VerifiedResult{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Hash: common.BytesToHash([]byte{byte(ts)}), Number: ts}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.BytesToHash([]byte{byte(ts + 100)}), Number: ts}}, + }) + require.NoError(t, err) + } + + // Verify all exist + lastTs, _ := db.LastTimestamp() + require.Equal(t, uint64(105), lastTs) + + // Rewind to 103 (should remove 103, 104, 105) + deleted, err := db.Rewind(103) + require.NoError(t, err) + require.True(t, deleted) + + // Verify 100, 101, 102 still exist + for ts := uint64(100); ts <= 102; ts++ { + has, err := db.Has(ts) + require.NoError(t, err) + require.True(t, has, "timestamp %d should still exist", ts) + } + + // Verify 103, 104, 105 are gone + for ts := uint64(103); ts <= 105; ts++ { + has, err := db.Has(ts) + require.NoError(t, err) + require.False(t, has, "timestamp %d should be deleted", ts) + } + + // Last timestamp should be updated to 102 + lastTs, _ = db.LastTimestamp() + require.Equal(t, uint64(102), lastTs) + }) + + t.Run("returns false when no entries deleted", func(t *testing.T) { + t.Parallel() + dataDir := t.TempDir() + + db, err := OpenVerifiedDB(dataDir) + require.NoError(t, err) + defer db.Close() + + chainID := eth.ChainIDFromUInt64(10) + + // Commit up to timestamp 100 + for ts := uint64(98); ts <= 100; ts++ { + err = db.Commit(VerifiedResult{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Hash: common.BytesToHash([]byte{byte(ts)}), Number: ts}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.BytesToHash([]byte{byte(ts + 100)}), Number: ts}}, + }) + require.NoError(t, err) + } + + // Rewind to 200 (nothing to delete) + deleted, err := db.Rewind(200) + require.NoError(t, err) + require.False(t, deleted) + + // All entries should still exist + lastTs, _ := db.LastTimestamp() + require.Equal(t, uint64(100), lastTs) + }) + + t.Run("rewind all entries", func(t *testing.T) { + t.Parallel() + dataDir := t.TempDir() + + db, err := OpenVerifiedDB(dataDir) + require.NoError(t, err) + defer db.Close() + + chainID := eth.ChainIDFromUInt64(10) + + // Commit a few entries + for ts := uint64(100); ts <= 102; ts++ { + err = db.Commit(VerifiedResult{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Hash: common.BytesToHash([]byte{byte(ts)}), Number: ts}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.BytesToHash([]byte{byte(ts + 100)}), Number: ts}}, + }) + require.NoError(t, err) + } + + // Rewind to 0 (delete all) + deleted, err := db.Rewind(0) + require.NoError(t, err) + require.True(t, deleted) + + // No entries should exist + for ts := uint64(100); ts <= 102; ts++ { + has, err := db.Has(ts) + require.NoError(t, err) + require.False(t, has) + } + + // Last timestamp should be reset to uninitialized + _, initialized := db.LastTimestamp() + require.False(t, initialized) + }) + + t.Run("allows sequential commits after rewind", func(t *testing.T) { + t.Parallel() + dataDir := t.TempDir() + + db, err := OpenVerifiedDB(dataDir) + require.NoError(t, err) + defer db.Close() + + chainID := eth.ChainIDFromUInt64(10) + + // Commit 100-105 + for ts := uint64(100); ts <= 105; ts++ { + err = db.Commit(VerifiedResult{ + Timestamp: ts, + L1Inclusion: eth.BlockID{Hash: common.BytesToHash([]byte{byte(ts)}), Number: ts}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.BytesToHash([]byte{byte(ts + 100)}), Number: ts}}, + }) + require.NoError(t, err) + } + + // Rewind to 103 + _, err = db.Rewind(103) + require.NoError(t, err) + + // Should be able to commit 103 again (sequential from 102) + err = db.Commit(VerifiedResult{ + Timestamp: 103, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0xNEW"), Number: 103}, + L2Heads: map[eth.ChainID]eth.BlockID{chainID: {Hash: common.HexToHash("0xNEW2"), Number: 103}}, + }) + require.NoError(t, err) + + // Verify new data + result, err := db.Get(103) + require.NoError(t, err) + require.Equal(t, common.HexToHash("0xNEW"), result.L1Inclusion.Hash) + }) +} + +func TestVerifiedDB_PendingTransition(t *testing.T) { + t.Parallel() + + t.Run("set get clear round trip", func(t *testing.T) { + t.Parallel() + dir := t.TempDir() + vdb, err := OpenVerifiedDB(dir) + require.NoError(t, err) + defer vdb.Close() + + // Initially empty + pending, err := vdb.GetPendingTransition() + require.NoError(t, err) + require.Nil(t, pending) + + // Set pending + transition := PendingTransition{ + Decision: DecisionInvalidate, + Result: &Result{ + Timestamp: 42, + L1Inclusion: eth.BlockID{Hash: common.HexToHash("0x1111"), Number: 42}, + InvalidHeads: map[eth.ChainID]InvalidHead{ + eth.ChainIDFromUInt64(1): { + BlockID: eth.BlockID{Hash: common.HexToHash("0xaaaa"), Number: 100}, + StateRoot: eth.Bytes32(common.HexToHash("0xstate1")), + MessagePasserStorageRoot: eth.Bytes32(common.HexToHash("0xmsg1")), + }, + eth.ChainIDFromUInt64(2): { + BlockID: eth.BlockID{Hash: common.HexToHash("0xbbbb"), Number: 200}, + StateRoot: eth.Bytes32(common.HexToHash("0xstate2")), + MessagePasserStorageRoot: eth.Bytes32(common.HexToHash("0xmsg2")), + }, + }, + }, + } + require.NoError(t, vdb.SetPendingTransition(transition)) + + // Get pending + got, err := vdb.GetPendingTransition() + require.NoError(t, err) + require.NotNil(t, got) + require.Equal(t, transition.Decision, got.Decision) + require.NotNil(t, got.Result) + require.Equal(t, transition.Result.Timestamp, got.Result.Timestamp) + require.Equal(t, transition.Result.InvalidHeads, got.Result.InvalidHeads) + + // Clear + require.NoError(t, vdb.ClearPendingTransition()) + got, err = vdb.GetPendingTransition() + require.NoError(t, err) + require.Nil(t, got) + }) + + t.Run("survives close and reopen", func(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + // Write pending and close + vdb, err := OpenVerifiedDB(dir) + require.NoError(t, err) + transition := PendingTransition{ + Decision: DecisionRewind, + Rewind: &RewindPlan{ + RewindAtOrAfter: 99, + }, + } + require.NoError(t, vdb.SetPendingTransition(transition)) + require.NoError(t, vdb.Close()) + + // Reopen and verify + vdb2, err := OpenVerifiedDB(dir) + require.NoError(t, err) + defer vdb2.Close() + + got, err := vdb2.GetPendingTransition() + require.NoError(t, err) + require.NotNil(t, got) + require.Equal(t, transition.Decision, got.Decision) + require.NotNil(t, got.Rewind) + require.Equal(t, transition.Rewind.RewindAtOrAfter, got.Rewind.RewindAtOrAfter) + }) +} diff --git a/op-supernode/supernode/activity/supernode/supernode_test.go b/op-supernode/supernode/activity/supernode/supernode_test.go new file mode 100644 index 00000000000..4d9df7cfbfd --- /dev/null +++ b/op-supernode/supernode/activity/supernode/supernode_test.go @@ -0,0 +1,278 @@ +package supernode + +import ( + "context" + "fmt" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supernode/supernode/activity" + cc "github.com/ethereum-optimism/optimism/op-supernode/supernode/chain_container" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + gethlog "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +type mockCC struct { + status *eth.SyncStatus + verifierL1s []eth.BlockID + + verifiedErr error + outputErr error + syncStatusErr error +} + +func (m *mockCC) Start(ctx context.Context) error { return nil } +func (m *mockCC) Stop(ctx context.Context) error { return nil } +func (m *mockCC) Pause(ctx context.Context) error { return nil } +func (m *mockCC) Resume(ctx context.Context) error { return nil } +func (m *mockCC) PauseAndStopVN(ctx context.Context) error { return nil } + +func (m *mockCC) RegisterVerifier(v activity.VerificationActivity) {} +func (m *mockCC) VerifierCurrentL1s() []eth.BlockID { + return m.verifierL1s +} + +func (m *mockCC) LocalSafeBlockAtTimestamp(ctx context.Context, ts uint64) (eth.L2BlockRef, error) { + return eth.L2BlockRef{}, nil +} + +func (m *mockCC) SyncStatus(ctx context.Context) (*eth.SyncStatus, error) { + if m.syncStatusErr != nil { + return nil, m.syncStatusErr + } + if m.status == nil { + return ð.SyncStatus{}, nil + } + return m.status, nil +} + +func (m *mockCC) SafeHeadAtL1(ctx context.Context, l1BlockNum uint64) (eth.BlockID, eth.BlockID, error) { + return eth.BlockID{}, eth.BlockID{}, nil +} + +func (m *mockCC) L1AtSafeHead(ctx context.Context, l2 eth.BlockID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} + +func (m *mockCC) VerifiedAt(ctx context.Context, ts uint64) (eth.BlockID, eth.BlockID, error) { + if m.verifiedErr != nil { + return eth.BlockID{}, eth.BlockID{}, m.verifiedErr + } + return eth.BlockID{}, eth.BlockID{}, nil +} + +func (m *mockCC) OptimisticAt(ctx context.Context, ts uint64) (eth.BlockID, eth.BlockID, error) { + return eth.BlockID{}, eth.BlockID{}, nil +} + +func (m *mockCC) OutputRootAtL2BlockNumber(ctx context.Context, l2BlockNum uint64) (eth.Bytes32, error) { + if m.outputErr != nil { + return eth.Bytes32{}, m.outputErr + } + return eth.Bytes32{}, nil +} + +func (m *mockCC) OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*eth.OutputV0, error) { + return ð.OutputV0{}, nil +} + +func (m *mockCC) RewindEngine(ctx context.Context, timestamp uint64, invalidatedBlock eth.BlockRef) error { + return nil +} + +func (m *mockCC) L1ForL2(ctx context.Context, l2Block eth.BlockID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} + +func (m *mockCC) FetchReceipts(ctx context.Context, blockID eth.BlockID) (eth.BlockInfo, types.Receipts, error) { + return nil, nil, nil +} + +func (m *mockCC) ID() eth.ChainID { + return eth.ChainIDFromUInt64(10) +} + +func (m *mockCC) BlockTime() uint64 { return 1 } + +func (m *mockCC) InvalidateBlock(ctx context.Context, height uint64, payloadHash common.Hash, decisionTimestamp uint64, stateRoot, messagePasserStorageRoot eth.Bytes32) (bool, error) { + return false, nil +} +func (m *mockCC) OutputV0AtBlockNumber(ctx context.Context, l2BlockNum uint64) (*eth.OutputV0, error) { + return ð.OutputV0{}, nil +} +func (m *mockCC) GetDeniedOutput(height uint64, payloadHash common.Hash) (*eth.OutputV0, error) { + return nil, nil +} + +func (m *mockCC) IsDenied(height uint64, payloadHash common.Hash) (bool, error) { + return false, nil +} + +func (m *mockCC) PruneDeniedAtOrAfterTimestamp(timestamp uint64) (map[uint64][]common.Hash, error) { + return nil, nil +} + +func (m *mockCC) SetResetCallback(cb cc.ResetCallback) {} + +func (m *mockCC) TimestampToBlockNumber(ctx context.Context, ts uint64) (uint64, error) { + return ts, nil +} + +var _ cc.ChainContainer = (*mockCC)(nil) + +func TestSupernode_SyncStatus_Succeeds(t *testing.T) { + t.Parallel() + chainA := eth.ChainIDFromUInt64(10) + chainB := eth.ChainIDFromUInt64(420) + + chains := map[eth.ChainID]cc.ChainContainer{ + chainA: &mockCC{ + status: ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 2000}, + UnsafeL2: eth.L2BlockRef{Number: 120, Time: 220}, + CrossUnsafeL2: eth.L2BlockRef{Number: 118, Time: 205}, + SafeL2: eth.L2BlockRef{Number: 110, Time: 170}, + LocalSafeL2: eth.L2BlockRef{Number: 111, Time: 180}, + FinalizedL2: eth.L2BlockRef{Number: 100, Time: 140}, + }, + }, + chainB: &mockCC{ + status: ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 2000}, + UnsafeL2: eth.L2BlockRef{Number: 130, Time: 230}, + CrossUnsafeL2: eth.L2BlockRef{Number: 128, Time: 215}, + SafeL2: eth.L2BlockRef{Number: 112, Time: 175}, + LocalSafeL2: eth.L2BlockRef{Number: 113, Time: 190}, + FinalizedL2: eth.L2BlockRef{Number: 101, Time: 150}, + }, + }, + } + + s := New(gethlog.New(), chains) + api := &api{a: s} + out, err := api.SyncStatus(context.Background()) + require.NoError(t, err) + + require.Len(t, out.Chains, 2) + require.Contains(t, out.Chains, chainA) + require.Contains(t, out.Chains, chainB) + require.Equal(t, []eth.ChainID{chainA, chainB}, out.ChainIDs) + require.Equal(t, uint64(2000), out.CurrentL1.Number) + require.Equal(t, out.Chains[chainA].CurrentL1.ID(), out.CurrentL1) + require.Equal(t, uint64(170), out.SafeTimestamp) + require.Equal(t, uint64(180), out.LocalSafeTimestamp) + require.Equal(t, uint64(140), out.FinalizedTimestamp) + + statusA := out.Chains[chainA] + require.Greater(t, statusA.UnsafeL2.Number, statusA.SafeL2.Number) + require.Greater(t, statusA.SafeL2.Number, statusA.FinalizedL2.Number) + require.Greater(t, statusA.LocalSafeL2.Number, statusA.SafeL2.Number) +} + +func TestSupernode_SyncStatus_UsesMinimumCurrentL1(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + status: ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 100, Hash: common.Hash{0x11}}, + }, + }, + eth.ChainIDFromUInt64(11): &mockCC{ + status: ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 101, Hash: common.Hash{0x22}}, + }, + }, + } + s := New(gethlog.New(), chains) + api := &api{a: s} + out, err := api.SyncStatus(context.Background()) + require.NoError(t, err) + require.Equal(t, eth.BlockID{Number: 100, Hash: common.Hash{0x11}}, out.CurrentL1) +} + +func TestSupernode_SyncStatus_UsesMinimumVerifierCurrentL1(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + status: ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 200, Hash: common.Hash{0x11}}, + }, + verifierL1s: []eth.BlockID{ + {Number: 150, Hash: common.Hash{0x33}}, + {Number: 175, Hash: common.Hash{0x44}}, + }, + }, + eth.ChainIDFromUInt64(11): &mockCC{ + status: ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 180, Hash: common.Hash{0x22}}, + }, + verifierL1s: []eth.BlockID{ + {Number: 190, Hash: common.Hash{0x55}}, + }, + }, + } + s := New(gethlog.New(), chains) + api := &api{a: s} + out, err := api.SyncStatus(context.Background()) + require.NoError(t, err) + require.Equal(t, eth.BlockID{Number: 150, Hash: common.Hash{0x33}}, out.CurrentL1) +} + +func TestSupernode_SyncStatus_ErrorOnCurrentL1(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + syncStatusErr: assertErr(), + }, + } + s := New(gethlog.New(), chains) + api := &api{a: s} + _, err := api.SyncStatus(context.Background()) + require.Error(t, err) +} + +func TestSupernode_SyncStatus_IgnoresUnsafeOutputRootErrors(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{ + eth.ChainIDFromUInt64(10): &mockCC{ + verifiedErr: fmt.Errorf("not available: %w", ethereum.NotFound), + outputErr: assertErr(), + status: ð.SyncStatus{ + CurrentL1: eth.L1BlockRef{Number: 100}, + UnsafeL2: eth.L2BlockRef{Number: 10, Time: 20}, + LocalSafeL2: eth.L2BlockRef{Number: 9, Time: 18}, + FinalizedL2: eth.L2BlockRef{Number: 8, Time: 16}, + }, + }, + } + s := New(gethlog.New(), chains) + api := &api{a: s} + out, err := api.SyncStatus(context.Background()) + require.NoError(t, err) + require.Equal(t, []eth.ChainID{eth.ChainIDFromUInt64(10)}, out.ChainIDs) + require.Equal(t, eth.BlockID{Number: 100}, out.CurrentL1) + require.Equal(t, out.Chains[eth.ChainIDFromUInt64(10)].CurrentL1.ID(), out.CurrentL1) + require.Equal(t, uint64(0), out.SafeTimestamp) + require.Equal(t, uint64(18), out.LocalSafeTimestamp) +} + +func TestSupernode_SyncStatus_EmptyChains(t *testing.T) { + t.Parallel() + chains := map[eth.ChainID]cc.ChainContainer{} + s := New(gethlog.New(), chains) + api := &api{a: s} + + out, err := api.SyncStatus(context.Background()) + require.NoError(t, err) + require.Len(t, out.Chains, 0) + require.Len(t, out.ChainIDs, 0) + require.Equal(t, eth.BlockID{}, out.CurrentL1) + require.Equal(t, uint64(0), out.SafeTimestamp) + require.Equal(t, uint64(0), out.LocalSafeTimestamp) + require.Equal(t, uint64(0), out.FinalizedTimestamp) +} + +func assertErr() error { return fmt.Errorf("mock error") } diff --git a/op-supernode/supernode/activity/superroot/superroot_test.go b/op-supernode/supernode/activity/superroot/superroot_test.go index f85acd8572e..e87da695ab0 100644 --- a/op-supernode/supernode/activity/superroot/superroot_test.go +++ b/op-supernode/supernode/activity/superroot/superroot_test.go @@ -67,8 +67,44 @@ func (m *mockCC) OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*e if m.optimisticErr != nil { return nil, m.optimisticErr } - // Return minimal output response; tests only assert presence/count - return ð.OutputResponse{}, nil + return ð.OutputV0{}, nil +} +func (m *mockCC) RewindEngine(ctx context.Context, timestamp uint64, invalidatedBlock eth.BlockRef) error { + return nil +} + +func (m *mockCC) L1ForL2(ctx context.Context, l2Block eth.BlockID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} + +func (m *mockCC) FetchReceipts(ctx context.Context, blockID eth.BlockID) (eth.BlockInfo, types.Receipts, error) { + return nil, nil, nil +} + +func (m *mockCC) ID() eth.ChainID { + return eth.ChainIDFromUInt64(10) +} + +func (m *mockCC) BlockTime() uint64 { return 1 } +func (m *mockCC) InvalidateBlock(ctx context.Context, height uint64, payloadHash common.Hash, decisionTimestamp uint64, stateRoot, messagePasserStorageRoot eth.Bytes32) (bool, error) { + return false, nil +} +func (m *mockCC) OutputV0AtBlockNumber(ctx context.Context, l2BlockNum uint64) (*eth.OutputV0, error) { + return ð.OutputV0{}, nil +} +func (m *mockCC) GetDeniedOutput(height uint64, payloadHash common.Hash) (*eth.OutputV0, error) { + return nil, nil +} +func (m *mockCC) PruneDeniedAtOrAfterTimestamp(timestamp uint64) (map[uint64][]common.Hash, error) { + return nil, nil +} +func (m *mockCC) IsDenied(height uint64, payloadHash common.Hash) (bool, error) { + return false, nil +} +func (m *mockCC) SetResetCallback(cb cc.ResetCallback) {} + +func (m *mockCC) TimestampToBlockNumber(ctx context.Context, ts uint64) (uint64, error) { + return ts, nil } var _ cc.ChainContainer = (*mockCC)(nil) diff --git a/op-supernode/supernode/chain_container/chain_container.go b/op-supernode/supernode/chain_container/chain_container.go index 0e262ee94db..151004e71bb 100644 --- a/op-supernode/supernode/chain_container/chain_container.go +++ b/op-supernode/supernode/chain_container/chain_container.go @@ -38,11 +38,59 @@ type ChainContainer interface { VerifiedAt(ctx context.Context, ts uint64) (l2, l1 eth.BlockID, err error) OptimisticAt(ctx context.Context, ts uint64) (l2, l1 eth.BlockID, err error) OutputRootAtL2BlockNumber(ctx context.Context, l2BlockNum uint64) (eth.Bytes32, error) - // OptimisticOutputAtTimestamp returns the full Output at the optimistic L2 block for the given timestamp. - OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*eth.OutputResponse, error) + OptimisticOutputAtTimestamp(ctx context.Context, ts uint64) (*eth.OutputV0, error) + // RewindEngine rewinds the engine to the highest block with timestamp less than or equal to the given timestamp. + // invalidatedBlock is the block that triggered the rewind and is passed to reset callbacks. + // WARNING: this is a dangerous stateful operation and is intended to be called only + // by interop transition application. Other callers should not use it until the + // interface is refactored to make that ownership explicit. + // TODO(#19561): remove this footgun by moving reorg-triggering operations behind a + // smaller interop-owned interface. + RewindEngine(ctx context.Context, timestamp uint64, invalidatedBlock eth.BlockRef) error + RegisterVerifier(v activity.VerificationActivity) + // VerifierCurrentL1s returns the CurrentL1 from each registered verifier. + // This allows callers to determine the minimum L1 block that all verifiers have processed. + VerifierCurrentL1s() []eth.BlockID + // FetchReceipts fetches the receipts for a given block by hash. + // Returns block info and receipts, or an error if the block or receipts cannot be fetched. + FetchReceipts(ctx context.Context, blockHash eth.BlockID) (eth.BlockInfo, types.Receipts, error) + // BlockTime returns the block time in seconds for this chain. + BlockTime() uint64 + // InvalidateBlock adds a block to the deny list and triggers a rewind if the chain + // currently uses that block at the specified height. + // output is the marshaled eth.Output preimage for optimistic root computation. + // WARNING: this is a dangerous stateful operation and is intended to be called only + // by interop transition application. Other callers should not use it until the + // interface is refactored to make that ownership explicit. + // TODO(#19561): remove this footgun by moving reorg-triggering operations behind a + // smaller interop-owned interface. + // Returns true if a rewind was triggered, false otherwise. + InvalidateBlock(ctx context.Context, height uint64, payloadHash common.Hash, decisionTimestamp uint64, stateRoot, messagePasserStorageRoot eth.Bytes32) (bool, error) + // PruneDeniedAtOrAfterTimestamp removes deny-list entries with DecisionTimestamp >= timestamp. + // Returns map of removed hashes by height. + PruneDeniedAtOrAfterTimestamp(timestamp uint64) (map[uint64][]common.Hash, error) + // PauseAndStopVN pauses the chain container restart loop and stops the virtual node. + // This is used to freeze a chain's VN before a multi-chain rewind begins, preventing + // the VN from issuing forkchoice updates that race with the rewind of a peer chain. + PauseAndStopVN(ctx context.Context) error + // IsDenied checks if a block hash is on the deny list at the given height. + IsDenied(height uint64, payloadHash common.Hash) (bool, error) + // GetDeniedOutput returns the reconstructed OutputV0 for a denied block. + // Returns nil if the block is not denied at that height. + GetDeniedOutput(height uint64, payloadHash common.Hash) (*eth.OutputV0, error) + // OutputV0AtBlockNumber returns the full OutputV0 for the block at the given number. + OutputV0AtBlockNumber(ctx context.Context, l2BlockNum uint64) (*eth.OutputV0, error) + // SetResetCallback sets a callback that is invoked when the chain resets. + // The supernode uses this to notify activities about chain resets. + SetResetCallback(cb ResetCallback) } -type virtualNodeFactory func(cfg *opnodecfg.Config, log gethlog.Logger, initOverrides *rollupNode.InitializationOverrides, appVersion string) virtual_node.VirtualNode +type virtualNodeFactory func(cfg *opnodecfg.Config, log gethlog.Logger, initOverrides *rollupNode.InitializationOverrides, appVersion string, superAuthority rollup.SuperAuthority) virtual_node.VirtualNode + +// ResetCallback is called when the chain container resets due to an invalidated block. +// The supernode uses this to notify activities about the reset. +// invalidatedBlock is the block that was invalidated and triggered the reset. +type ResetCallback func(chainID eth.ChainID, timestamp uint64, invalidatedBlock eth.BlockRef) type simpleChainContainer struct { vn virtual_node.VirtualNode @@ -65,6 +113,7 @@ type simpleChainContainer struct { // Interface conformance assertions var _ ChainContainer = (*simpleChainContainer)(nil) +var _ rollup.SuperAuthority = (*simpleChainContainer)(nil) func NewChainContainer( chainID eth.ChainID, @@ -74,7 +123,7 @@ func NewChainContainer( initOverload *rollupNode.InitializationOverrides, rpcHandler *oprpc.Handler, setHandler func(chainID string, h http.Handler), - setMetricsHandler func(chainID string, h http.Handler), + addMetricsRegistry func(key string, g prometheus.Gatherer), ) ChainContainer { c := &simpleChainContainer{ vncfg: vncfg, @@ -347,3 +396,118 @@ func (c *simpleChainContainer) attachInProcRollupClient() error { c.rollupClient = sources.NewRollupClient(client.NewBaseRPCClient(inproc)) return nil } + +// isCriticalRewindError returns true if the error is a critical configuration error +// that should not be retried. +func isCriticalRewindError(err error) bool { + return errors.Is(err, engine_controller.ErrNoEngineClient) || + errors.Is(err, engine_controller.ErrNoRollupConfig) || + errors.Is(err, engine_controller.ErrRewindComputeTargetsFailed) || + errors.Is(err, engine_controller.ErrRewindTimestampToBlockConversion) || + errors.Is(err, engine_controller.ErrRewindOverFinalizedHead) +} + +// WARNING: this should only be called by the interop activity. +// Other callers risk triggering chain rewinds outside the interop WAL model. +// TODO(#19561): remove this footgun by moving reorg-triggering operations behind a +// smaller interop-owned interface. +func (c *simpleChainContainer) RewindEngine(ctx context.Context, timestamp uint64, invalidatedBlock eth.BlockRef) error { + if !c.resetting.CompareAndSwap(false, true) { + return fmt.Errorf("reset already in progress") + } + defer c.resetting.Store(false) + + if c.vn == nil { + return fmt.Errorf("virtual node not initialized") + } + if c.engine == nil { + return fmt.Errorf("engine not initialized") + } + + // Pause the container to stop it restarting the vn when we kill it + err := c.Pause(ctx) + if err != nil { + return err + } + // Always resume the container on return, even if we exit early due to context cancellation + // or an error mid-rewind. Without this, a cancelled ctx leaves pause=true permanently, + // causing the Start() loop to spin forever and block Supernode.Stop()'s wg.Wait(). + defer c.Resume(context.Background()) //nolint:errcheck + c.log.Info("chain_container/RewindEngine: paused container") + + // stop the vn + err = c.vn.Stop(ctx) + if err != nil { + return err + } + c.log.Info("chain_container/RewindEngine: stopped vn") + +retryLoop: + for { + err = c.engine.RewindToTimestamp(ctx, timestamp) + switch { + case errors.Is(err, context.DeadlineExceeded): + c.log.Error("chain_container/RewindEngine: timeout exceeded") + return err + case isCriticalRewindError(err): + c.log.Error("chain_container/RewindEngine: critical error", "err", err) + return err + case err == nil: + c.log.Info("chain_container/RewindEngine: executed engine rewind") + break retryLoop + default: + c.log.Error("chain_container/RewindEngine: temporary error", "err", err) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Second): + } + } + } + + // Notify activities about the reset + if c.onReset != nil { + c.onReset(c.chainID, timestamp, invalidatedBlock) + } + + // resume the chain container to trigger a new vn to be started + err = c.Resume(ctx) + if err != nil { + return err + } + c.log.Info("chain_container/RewindEngine: resumed container") + + return nil +} + +// PauseAndStopVN pauses the container restart loop and stops the running virtual node. +// This must be called before a multi-chain rewind to prevent a peer chain's VN from +// issuing forkchoice updates that race with the rewind operation. +// RewindEngine's own Pause+Stop calls are idempotent when called after this. +func (c *simpleChainContainer) PauseAndStopVN(ctx context.Context) error { + if err := c.Pause(ctx); err != nil { + return err + } + if c.vn == nil { + return nil + } + return c.vn.Stop(ctx) +} + +// SetResetCallback sets a callback that is invoked when the chain resets. +// This must only be called during initialization, before the chain container starts processing. +// Calling this while InvalidateBlock may be running is unsafe. +func (c *simpleChainContainer) SetResetCallback(cb ResetCallback) { + c.onReset = cb +} + +// blockNumberToTimestamp converts a block number to its timestamp using rollup config. +func (c *simpleChainContainer) blockNumberToTimestamp(blockNum uint64) (uint64, error) { + if c.vncfg == nil { + return 0, fmt.Errorf("rollup config not available") + } + if blockNum < c.vncfg.Rollup.Genesis.L2.Number { + return 0, fmt.Errorf("block number %d before genesis %d", blockNum, c.vncfg.Rollup.Genesis.L2.Number) + } + return c.vncfg.Rollup.TimestampForBlock(blockNum), nil +} diff --git a/op-supernode/supernode/chain_container/invalidation.go b/op-supernode/supernode/chain_container/invalidation.go new file mode 100644 index 00000000000..175cfd0ed6f --- /dev/null +++ b/op-supernode/supernode/chain_container/invalidation.go @@ -0,0 +1,413 @@ +package chain_container + +import ( + "context" + "encoding/binary" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sync" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + bolt "go.etcd.io/bbolt" +) + +const ( + denyListDBName = "denylist" +) + +// denyListBucketName is the name of the bbolt bucket used to store denied block hashes. +var denyListBucketName = []byte("denied_blocks") + +// DenyList provides persistence for invalid block payload hashes using bbolt. +// Blocks are keyed by block height, with each height potentially having multiple denied hashes. +type DenyList struct { + db *bolt.DB + mu sync.RWMutex +} + +// DenyRecord stores a denied payload hash along with decision provenance +// and the output preimage fields for optimistic root computation. +type DenyRecord struct { + PayloadHash common.Hash `json:"payloadHash"` + DecisionTimestamp uint64 `json:"decisionTimestamp"` + StateRoot eth.Bytes32 `json:"stateRoot"` + MessagePasserStorageRoot eth.Bytes32 `json:"messagePasserStorageRoot"` +} + +func encodeDenyRecords(records []DenyRecord) ([]byte, error) { + return json.Marshal(records) +} + +func decodeDenyRecords(raw []byte) ([]DenyRecord, error) { + if len(raw) == 0 { + return nil, nil + } + var records []DenyRecord + if err := json.Unmarshal(raw, &records); err != nil { + return nil, fmt.Errorf("failed to decode denylist records: %w", err) + } + return records, nil +} + +// OpenDenyList opens or creates a DenyList at the given data directory. +func OpenDenyList(dataDir string) (*DenyList, error) { + if err := os.MkdirAll(dataDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create denylist directory %s: %w", dataDir, err) + } + dbPath := filepath.Join(dataDir, denyListDBName+".db") + db, err := bolt.Open(dbPath, 0600, nil) + if err != nil { + return nil, fmt.Errorf("failed to open denylist bbolt at %s: %w", dbPath, err) + } + + // Ensure the bucket exists + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists(denyListBucketName) + return err + }) + if err != nil { + db.Close() + return nil, fmt.Errorf("failed to create denylist bucket: %w", err) + } + + return &DenyList{db: db}, nil +} + +// heightToKey converts a block height to a big-endian byte key. +// Using big-endian ensures lexicographic ordering matches numeric ordering. +func heightToKey(height uint64) []byte { + key := make([]byte, 8) + binary.BigEndian.PutUint64(key, height) + return key +} + +// Add adds a payload hash to the deny list at the given block height. +// stateRoot and messagePasserStorageRoot are the output preimage fields for optimistic root computation. +// Multiple hashes can be denied at the same height. +func (d *DenyList) Add(height uint64, payloadHash common.Hash, decisionTimestamp uint64, stateRoot, messagePasserStorageRoot eth.Bytes32) error { + d.mu.Lock() + defer d.mu.Unlock() + + key := heightToKey(height) + + return d.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(denyListBucketName) + + existing := b.Get(key) + records, err := decodeDenyRecords(existing) + if err != nil { + return err + } + + for _, r := range records { + if r.PayloadHash == payloadHash { + return nil + } + } + + records = append(records, DenyRecord{ + PayloadHash: payloadHash, + DecisionTimestamp: decisionTimestamp, + StateRoot: stateRoot, + MessagePasserStorageRoot: messagePasserStorageRoot, + }) + + encoded, err := encodeDenyRecords(records) + if err != nil { + return err + } + return b.Put(key, encoded) + }) +} + +// LastDeniedOutputV0 returns the OutputV0 for the most recently denied block at the given height. +// Returns nil if no blocks are denied at that height. +// Note: supernode does not currently behave in well defined ways when there are multiple denied blocks at the same height. +func (d *DenyList) LastDeniedOutputV0(height uint64) (*eth.OutputV0, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + key := heightToKey(height) + var result *eth.OutputV0 + + err := d.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(denyListBucketName) + existing := b.Get(key) + if existing == nil { + return nil + } + + records, err := decodeDenyRecords(existing) + if err != nil { + return err + } + if len(records) > 0 { + r := records[len(records)-1] + result = ð.OutputV0{ + StateRoot: r.StateRoot, + MessagePasserStorageRoot: r.MessagePasserStorageRoot, + BlockHash: r.PayloadHash, + } + } + return nil + }) + + return result, err +} + +// GetOutputV0 reconstructs and returns the full OutputV0 for a denied block. +// Returns nil if the hash is not denied at that height. +func (d *DenyList) GetOutputV0(height uint64, payloadHash common.Hash) (*eth.OutputV0, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + key := heightToKey(height) + var result *eth.OutputV0 + + err := d.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(denyListBucketName) + existing := b.Get(key) + if existing == nil { + return nil + } + + records, err := decodeDenyRecords(existing) + if err != nil { + return err + } + for _, r := range records { + if r.PayloadHash == payloadHash { + result = ð.OutputV0{ + StateRoot: r.StateRoot, + MessagePasserStorageRoot: r.MessagePasserStorageRoot, + BlockHash: payloadHash, + } + return nil + } + } + return nil + }) + + return result, err +} + +// Contains checks if a payload hash is denied at the given block height. +func (d *DenyList) Contains(height uint64, payloadHash common.Hash) (bool, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + key := heightToKey(height) + var found bool + + err := d.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(denyListBucketName) + existing := b.Get(key) + if existing == nil { + return nil + } + + records, err := decodeDenyRecords(existing) + if err != nil { + return err + } + for _, r := range records { + if r.PayloadHash == payloadHash { + found = true + return nil + } + } + return nil + }) + + return found, err +} + +// GetDeniedHashes returns all denied payload hashes at the given block height. +func (d *DenyList) GetDeniedHashes(height uint64) ([]common.Hash, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + key := heightToKey(height) + var hashes []common.Hash + + err := d.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(denyListBucketName) + existing := b.Get(key) + if existing == nil { + return nil + } + + records, err := decodeDenyRecords(existing) + if err != nil { + return err + } + for _, r := range records { + hashes = append(hashes, r.PayloadHash) + } + return nil + }) + + return hashes, err +} + +// GetDeniedRecords returns all denied records at the given block height. +func (d *DenyList) GetDeniedRecords(height uint64) ([]DenyRecord, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + key := heightToKey(height) + var records []DenyRecord + + err := d.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(denyListBucketName) + existing := b.Get(key) + if existing == nil { + return nil + } + + var decErr error + records, decErr = decodeDenyRecords(existing) + return decErr + }) + + return records, err +} + +// PruneAtOrAfterTimestamp iterates all keys in the bucket, decodes records, +// removes any where DecisionTimestamp >= timestamp, re-encodes remaining. +// Returns map of removed hashes by height. +func (d *DenyList) PruneAtOrAfterTimestamp(timestamp uint64) (map[uint64][]common.Hash, error) { + d.mu.Lock() + defer d.mu.Unlock() + + removed := make(map[uint64][]common.Hash) + + err := d.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(denyListBucketName) + c := b.Cursor() + + for k, v := c.First(); k != nil; k, v = c.Next() { + height := binary.BigEndian.Uint64(k) + + records, err := decodeDenyRecords(v) + if err != nil { + return err + } + + var kept []DenyRecord + for _, r := range records { + if r.DecisionTimestamp >= timestamp { + removed[height] = append(removed[height], r.PayloadHash) + } else { + kept = append(kept, r) + } + } + + if len(kept) == 0 { + if err := b.Delete(k); err != nil { + return err + } + } else if len(kept) < len(records) { + encoded, err := encodeDenyRecords(kept) + if err != nil { + return err + } + if err := b.Put(k, encoded); err != nil { + return err + } + } + } + return nil + }) + + return removed, err +} + +// Close closes the database. +func (d *DenyList) Close() error { + return d.db.Close() +} + +// InvalidateBlock adds a block to the deny list and triggers a rewind if the chain +// currently uses that block at the specified height. +// WARNING: this should only be called by interop transition application. +// Other callers risk triggering chain rewinds outside the interop WAL model. +// TODO(#19561): remove this footgun by moving reorg-triggering operations behind a +// smaller interop-owned interface. +// Returns true if a rewind was triggered, false otherwise. +// Note: Genesis block (height=0) cannot be invalidated as there is no prior block to rewind to. +func (c *simpleChainContainer) InvalidateBlock(ctx context.Context, height uint64, payloadHash common.Hash, decisionTimestamp uint64, stateRoot, messagePasserStorageRoot eth.Bytes32) (bool, error) { + if c.denyList == nil { + return false, fmt.Errorf("deny list not initialized") + } + + // Cannot invalidate genesis block - there is no prior block to rewind to + if height == 0 { + return false, fmt.Errorf("cannot invalidate genesis block (height=0)") + } + + // Add to deny list with the output preimage fields + if err := c.denyList.Add(height, payloadHash, decisionTimestamp, stateRoot, messagePasserStorageRoot); err != nil { + return false, fmt.Errorf("failed to add block to deny list: %w", err) + } + + c.log.Info("added block to deny list", + "height", height, + "payloadHash", payloadHash, + ) + + // Check if the current chain uses this block at this height + if c.engine == nil { + c.log.Warn("engine not initialized, cannot check current block") + return false, nil + } + + currentBlock, err := c.engine.L2BlockRefByNumber(ctx, height) + if err != nil { + c.log.Warn("failed to get current block at height", "height", height, "err", err) + return false, nil + } + + // Compare the current block hash with the invalidated hash + if currentBlock.Hash != payloadHash { + c.log.Info("current block differs from invalidated block, no rewind needed", + "height", height, + "currentHash", currentBlock.Hash, + "invalidatedHash", payloadHash, + ) + return false, nil + } + + c.log.Warn("current block matches invalidated block, initiating rewind", + "height", height, + "hash", payloadHash, + ) + + invalidatedBlock := currentBlock.BlockRef() + + // Rewind to the prior block's timestamp + priorTimestamp, err := c.blockNumberToTimestamp(height - 1) + if err != nil { + return false, fmt.Errorf("failed to compute rewind timestamp: %w", err) + } + if err := c.RewindEngine(ctx, priorTimestamp, invalidatedBlock); err != nil { + return false, fmt.Errorf("failed to rewind engine: %w", err) + } + + c.log.Info("rewind completed after block invalidation", + "invalidatedHeight", height, + "rewindToTimestamp", priorTimestamp, + ) + + return true, nil +} + +func (c *simpleChainContainer) PruneDeniedAtOrAfterTimestamp(timestamp uint64) (map[uint64][]common.Hash, error) { + if c.denyList == nil { + return nil, fmt.Errorf("deny list not initialized") + } + return c.denyList.PruneAtOrAfterTimestamp(timestamp) +} diff --git a/op-supernode/supernode/supernode.go b/op-supernode/supernode/supernode.go index 6fb50868282..24ff45b6ee9 100644 --- a/op-supernode/supernode/supernode.go +++ b/op-supernode/supernode/supernode.go @@ -27,19 +27,25 @@ import ( ) type Supernode struct { - log gethlog.Logger - version string - requestStop context.CancelCauseFunc - stopped bool - cfg *config.CLIConfig - chains map[eth.ChainID]cc.ChainContainer - activities []activity.Activity - rootRPC *oprpc.Handler - wg sync.WaitGroup - l1Client *sources.L1Client - beaconClient *sources.L1BeaconClient - httpServer *httputil.HTTPServer - rpcRouter *resources.Router + log gethlog.Logger + version string + requestStop context.CancelCauseFunc + stopped bool + cfg *config.CLIConfig + chains map[eth.ChainID]cc.ChainContainer + // activitiesMu guards reads and writes of the activities slice. Concurrent + // readers (onChainReset, InteropActivity, Stop) can race with the + // test-only RestartInteropActivity path that swaps the interop activity + // while other activities and chain containers are still running. + activitiesMu sync.RWMutex + activities []activity.Activity + rootRPC *oprpc.Handler + wg sync.WaitGroup + lifecycleCancel context.CancelFunc // canceled in Stop() to unblock goroutines from Start() + l1Client *sources.L1Client + beaconClient *sources.L1BeaconClient + httpServer *httputil.HTTPServer + rpcRouter *resources.Router // Metrics router/server for per-chain metrics metrics *resources.MetricsService metricsRouter *resources.MetricsRouter @@ -82,9 +88,11 @@ func New(ctx context.Context, log gethlog.Logger, version string, requestStop co } s.chains[chainID] = cc.NewChainContainer(chainID, vnCfgs[chainID], log, *cfg, initOverrides, nil, s.rpcRouter.SetHandler, s.metricsRouter.SetHandler) } - // Initialize activities + + // Initialize fixed activities s.activities = []activity.Activity{ heartbeat.New(log.New("activity", "heartbeat"), 10*time.Second), + supernodeactivity.New(log.New("activity", "supernode"), s.chains), superroot.New(log.New("activity", "superroot"), s.chains), } addr := net.JoinHostPort(cfg.RPCConfig.ListenAddr, strconv.Itoa(cfg.RPCConfig.ListenPort)) diff --git a/op-supervisor/supervisor/backend/backend.go b/op-supervisor/supervisor/backend/backend.go index 50deaf133a0..e67974e268e 100644 --- a/op-supervisor/supervisor/backend/backend.go +++ b/op-supervisor/supervisor/backend/backend.go @@ -32,6 +32,9 @@ import ( "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/syncnode" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) type SupervisorBackend struct { @@ -549,17 +552,17 @@ func (su *SupervisorBackend) checkAccessWithRPC(ctx context.Context, acc types.A // checkSafety is a helper method to check if a block has the given safety level. // It is already assumed to exist in the canonical unsafe chain. -func (su *SupervisorBackend) checkSafety(chainID eth.ChainID, blockID eth.BlockID, safetyLevel types.SafetyLevel) error { +func (su *SupervisorBackend) checkSafety(chainID eth.ChainID, blockID eth.BlockID, safetyLevel safety.Level) error { switch safetyLevel { - case types.LocalUnsafe: + case safety.LocalUnsafe: return nil // msg exists, nothing more to check - case types.CrossUnsafe: + case safety.CrossUnsafe: return su.chainDBs.IsCrossUnsafe(chainID, blockID) - case types.LocalSafe: + case safety.LocalSafe: return su.chainDBs.IsLocalSafe(chainID, blockID) - case types.CrossSafe: + case safety.CrossSafe: return su.chainDBs.IsCrossSafe(chainID, blockID) - case types.Finalized: + case safety.Finalized: return su.chainDBs.IsFinalized(chainID, blockID) default: return types.ErrConflict @@ -567,7 +570,7 @@ func (su *SupervisorBackend) checkSafety(chainID eth.ChainID, blockID eth.BlockI } func (su *SupervisorBackend) CheckAccessList(ctx context.Context, inboxEntries []common.Hash, - minSafety types.SafetyLevel, execDescr types.ExecutingDescriptor) error { + minSafety safety.Level, execDescr messages.ExecutingDescriptor) error { // Check if failsafe is enabled if su.isFailsafeEnabled() { su.logger.Debug("Failsafe is enabled, rejecting access-list check") @@ -575,7 +578,7 @@ func (su *SupervisorBackend) CheckAccessList(ctx context.Context, inboxEntries [ } switch minSafety { - case types.LocalUnsafe, types.CrossUnsafe, types.LocalSafe, types.CrossSafe, types.Finalized: + case safety.LocalUnsafe, safety.CrossUnsafe, safety.LocalSafe, safety.CrossSafe, safety.Finalized: // valid safety level default: return ErrUnexpectedMinSafetyLevel diff --git a/op-supervisor/supervisor/backend/backend_test.go b/op-supervisor/supervisor/backend/backend_test.go index ca5035bc7c0..0db35b00b13 100644 --- a/op-supervisor/supervisor/backend/backend_test.go +++ b/op-supervisor/supervisor/backend/backend_test.go @@ -28,6 +28,9 @@ import ( "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/superevents" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/syncnode" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) const testChainIDOffset = 900 @@ -631,7 +634,7 @@ func TestFailsafeEnabled(t *testing.T) { require.False(t, enabled, "failsafe should be disabled by default") // Test that CheckAccessList works normally in initial state - err = b.CheckAccessList(context.Background(), []common.Hash{}, types.LocalUnsafe, types.ExecutingDescriptor{}) + err = b.CheckAccessList(context.Background(), []common.Hash{}, safety.LocalUnsafe, messages.ExecutingDescriptor{}) require.NoError(t, err, "CheckAccessList should work normally when failsafe is disabled") // Test setting failsafe to true @@ -642,7 +645,7 @@ func TestFailsafeEnabled(t *testing.T) { require.True(t, enabled, "failsafe should be enabled after setting to true") // Test that CheckAccessList returns ErrFailsafeEnabled when failsafe is enabled - err = b.CheckAccessList(context.Background(), []common.Hash{}, types.LocalUnsafe, types.ExecutingDescriptor{}) + err = b.CheckAccessList(context.Background(), []common.Hash{}, safety.LocalUnsafe, messages.ExecutingDescriptor{}) require.ErrorIs(t, err, types.ErrFailsafeEnabled, "CheckAccessList should return ErrFailsafeEnabled when failsafe is enabled") // Test setting failsafe to false @@ -653,7 +656,7 @@ func TestFailsafeEnabled(t *testing.T) { require.False(t, enabled, "failsafe should be disabled after setting to false") // Test that CheckAccessList works normally when failsafe is disabled - err = b.CheckAccessList(context.Background(), []common.Hash{}, types.LocalUnsafe, types.ExecutingDescriptor{}) + err = b.CheckAccessList(context.Background(), []common.Hash{}, safety.LocalUnsafe, messages.ExecutingDescriptor{}) require.NoError(t, err, "CheckAccessList should work normally when failsafe is disabled") } diff --git a/op-supervisor/supervisor/backend/mock.go b/op-supervisor/supervisor/backend/mock.go index 122323dbf14..7b1c6156aa0 100644 --- a/op-supervisor/supervisor/backend/mock.go +++ b/op-supervisor/supervisor/backend/mock.go @@ -6,6 +6,7 @@ import ( "sync/atomic" "github.com/ethereum-optimism/optimism/op-service/eth" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum/go-ethereum/common" @@ -47,7 +48,7 @@ func (m *MockBackend) AddL2RPC(ctx context.Context, rpc string, jwtSecret eth.By } func (m *MockBackend) CheckAccessList(ctx context.Context, inboxEntries []common.Hash, - minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { + minSafety safety.Level, executingDescriptor messages.ExecutingDescriptor) error { return nil } diff --git a/op-supervisor/supervisor/frontend/frontend.go b/op-supervisor/supervisor/frontend/frontend.go index a4c42292279..696de29badb 100644 --- a/op-supervisor/supervisor/frontend/frontend.go +++ b/op-supervisor/supervisor/frontend/frontend.go @@ -10,6 +10,9 @@ import ( "github.com/ethereum-optimism/optimism/op-service/apis" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) type Backend interface { @@ -24,7 +27,7 @@ type QueryFrontend struct { var _ apis.SupervisorQueryAPI = (*QueryFrontend)(nil) func (q *QueryFrontend) CheckAccessList(ctx context.Context, inboxEntries []common.Hash, - minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { + minSafety safety.Level, executingDescriptor messages.ExecutingDescriptor) error { err := q.Supervisor.CheckAccessList(ctx, inboxEntries, minSafety, executingDescriptor) if err != nil { return &rpc.JsonError{ diff --git a/op-supervisor/supervisor/service_test.go b/op-supervisor/supervisor/service_test.go index c82694ee794..e25f88a774d 100644 --- a/op-supervisor/supervisor/service_test.go +++ b/op-supervisor/supervisor/service_test.go @@ -21,7 +21,9 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-supervisor/config" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + messages "github.com/ethereum-optimism/optimism/op-core/interop/messages" + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestSupervisorService(t *testing.T) { @@ -71,7 +73,7 @@ func TestSupervisorService(t *testing.T) { require.NoError(t, err) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) err = cl.CallContext(ctx, nil, "supervisor_checkAccessList", - []common.Hash{}, types.CrossUnsafe, types.ExecutingDescriptor{ + []common.Hash{}, safety.CrossUnsafe, messages.ExecutingDescriptor{ Timestamp: 1234568, ChainID: eth.ChainIDFromUInt64(123)}) cancel() require.NoError(t, err) diff --git a/op-supervisor/supervisor/types/types.go b/op-supervisor/supervisor/types/types.go index 339f0a33418..b0c0a496096 100644 --- a/op-supervisor/supervisor/types/types.go +++ b/op-supervisor/supervisor/types/types.go @@ -1,9 +1,6 @@ package types import ( - "encoding/binary" - "encoding/json" - "errors" "fmt" "math" @@ -17,15 +14,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" ) -var ( - errLogIndexTooLarge = errors.New("log index too large") - errNilSafetyLevel = errors.New("nil safety level") - errUnrecognizedSafetyLevel = errors.New("unrecognized safety level") - errInvalidParentBlock = errors.New("invalid parent block") -) - -var ExecutingMessageEventTopic = crypto.Keccak256Hash([]byte("ExecutingMessage(bytes32,(address,uint256,uint256,uint256,uint256))")) - type Revision uint64 // RevisionAny is used as indicator to ignore the revision during lookups. @@ -69,366 +57,6 @@ func (r Revision) Cmp(blockNum uint64) int { return -1 } -// ContainsQuery contains all the information needed to check a message -// against a chain's database, to determine if it is valid (ie all invariants hold). -type ContainsQuery struct { - Timestamp uint64 - BlockNum uint64 - LogIdx uint32 - Checksum MessageChecksum -} - -type ExecutingMessage struct { - ChainID eth.ChainID - BlockNum uint64 - LogIdx uint32 - Timestamp uint64 - Checksum MessageChecksum -} - -func (s *ExecutingMessage) String() string { - return fmt.Sprintf("ExecMsg(chain: %s, block: %d, log: %d, time: %d, checksum: %s)", - s.ChainID, s.BlockNum, s.LogIdx, s.Timestamp, s.Checksum) -} - -type Message struct { - Identifier Identifier `json:"identifier"` - PayloadHash common.Hash `json:"payloadHash"` -} - -func (m *Message) ToCheckSumArgs() ChecksumArgs { - return ChecksumArgs{ - BlockNumber: m.Identifier.BlockNumber, - LogIndex: m.Identifier.LogIndex, - Timestamp: m.Identifier.Timestamp, - ChainID: m.Identifier.ChainID, - LogHash: PayloadHashToLogHash(m.PayloadHash, m.Identifier.Origin), - } -} - -func (m *Message) Checksum() MessageChecksum { - return m.ToCheckSumArgs().Checksum() -} - -func (m *Message) Access() Access { - return m.ToCheckSumArgs().Access() -} - -func (m *Message) DecodeEvent(topics []common.Hash, data []byte) error { - if len(topics) != 2 { // event hash, indexed payloadHash - return fmt.Errorf("unexpected number of event topics: %d", len(topics)) - } - if topics[0] != ExecutingMessageEventTopic { - return fmt.Errorf("unexpected event topic %q", topics[0]) - } - if len(data) != 32*5 { - return fmt.Errorf("unexpected identifier data length: %d", len(data)) - } - take := func(length uint) []byte { - taken := data[:length] - data = data[length:] - return taken - } - takeZeroes := func(length uint) error { - for _, v := range take(length) { - if v != 0 { - return errors.New("expected zero") - } - } - return nil - } - if err := takeZeroes(12); err != nil { - return fmt.Errorf("invalid address padding: %w", err) - } - m.Identifier.Origin = common.Address(take(20)) - if err := takeZeroes(32 - 8); err != nil { - return fmt.Errorf("invalid block number padding: %w", err) - } - m.Identifier.BlockNumber = binary.BigEndian.Uint64(take(8)) - if err := takeZeroes(32 - 4); err != nil { - return fmt.Errorf("invalid log index padding: %w", err) - } - m.Identifier.LogIndex = binary.BigEndian.Uint32(take(4)) - if err := takeZeroes(32 - 8); err != nil { - return fmt.Errorf("invalid timestamp padding: %w", err) - } - m.Identifier.Timestamp = binary.BigEndian.Uint64(take(8)) - m.Identifier.ChainID = eth.ChainIDFromBytes32([32]byte(take(32))) - m.PayloadHash = topics[1] - return nil -} - -type ChecksumArgs struct { - BlockNumber uint64 - LogIndex uint32 - Timestamp uint64 - ChainID eth.ChainID - LogHash common.Hash -} - -func (args ChecksumArgs) Checksum() MessageChecksum { - idPacked := make([]byte, 12, 32) // 12 zero bytes, as padding to 32 bytes - idPacked = binary.BigEndian.AppendUint64(idPacked, args.BlockNumber) - idPacked = binary.BigEndian.AppendUint64(idPacked, args.Timestamp) - idPacked = binary.BigEndian.AppendUint32(idPacked, args.LogIndex) - idLogHash := crypto.Keccak256Hash(args.LogHash[:], idPacked) - chainID := args.ChainID.Bytes32() - out := crypto.Keccak256Hash(idLogHash[:], chainID[:]) - out[0] = 0x03 // type/version byte - return MessageChecksum(out) -} - -func (args ChecksumArgs) Access() Access { - return Access{ - BlockNumber: args.BlockNumber, - Timestamp: args.Timestamp, - LogIndex: args.LogIndex, - ChainID: args.ChainID, - Checksum: args.Checksum(), - } -} - -func (args ChecksumArgs) Query() ContainsQuery { - return ContainsQuery{ - BlockNum: args.BlockNumber, - Timestamp: args.Timestamp, - LogIdx: args.LogIndex, - Checksum: args.Checksum(), - } -} - -type Identifier struct { - Origin common.Address - BlockNumber uint64 - LogIndex uint32 - Timestamp uint64 - ChainID eth.ChainID // flat, not a pointer, to make Identifier safe as map key -} - -type identifierMarshaling struct { - Origin common.Address `json:"origin"` - BlockNumber hexutil.Uint64 `json:"blockNumber"` - LogIndex hexutil.Uint64 `json:"logIndex"` - Timestamp hexutil.Uint64 `json:"timestamp"` - ChainID hexutil.U256 `json:"chainID"` -} - -func (id Identifier) MarshalJSON() ([]byte, error) { - var enc identifierMarshaling - enc.Origin = id.Origin - enc.BlockNumber = hexutil.Uint64(id.BlockNumber) - enc.LogIndex = hexutil.Uint64(id.LogIndex) - enc.Timestamp = hexutil.Uint64(id.Timestamp) - enc.ChainID = (hexutil.U256)(id.ChainID) - return json.Marshal(&enc) -} - -func (id *Identifier) UnmarshalJSON(input []byte) error { - var dec identifierMarshaling - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - id.Origin = dec.Origin - id.BlockNumber = uint64(dec.BlockNumber) - if dec.LogIndex > math.MaxUint32 { - return fmt.Errorf("%w: %d", errLogIndexTooLarge, dec.LogIndex) - } - id.LogIndex = uint32(dec.LogIndex) - id.Timestamp = uint64(dec.Timestamp) - id.ChainID = (eth.ChainID)(dec.ChainID) - return nil -} - -func (id Identifier) ChecksumArgs(msgHash common.Hash) ChecksumArgs { - return ChecksumArgs{ - BlockNumber: id.BlockNumber, - Timestamp: id.Timestamp, - LogIndex: id.LogIndex, - ChainID: id.ChainID, - LogHash: PayloadHashToLogHash(msgHash, id.Origin), - } -} - -type SafetyLevel string - -func (lvl SafetyLevel) String() string { - return string(lvl) -} - -// Validate returns true if the SafetyLevel is one of the recognized levels -func (lvl SafetyLevel) Validate() bool { - switch lvl { - case Invalid, Finalized, CrossSafe, LocalSafe, CrossUnsafe, LocalUnsafe: - return true - default: - return false - } -} - -func (lvl SafetyLevel) MarshalText() ([]byte, error) { - return []byte(lvl), nil -} - -func (lvl *SafetyLevel) UnmarshalText(text []byte) error { - if lvl == nil { - return errNilSafetyLevel - } - x := SafetyLevel(text) - if !x.Validate() { - return fmt.Errorf("%w: %q", errUnrecognizedSafetyLevel, text) - } - *lvl = x - return nil -} - -const ( - // Finalized is CrossSafe, with the additional constraint that every - // dependency is derived only from finalized L1 input data. - // This matches RPC label "finalized". - Finalized SafetyLevel = "finalized" - // CrossSafe is as safe as LocalSafe, with all its dependencies - // also fully verified to be reproducible from L1. - // This matches RPC label "safe". - CrossSafe SafetyLevel = "safe" - // LocalSafe is verified to be reproducible from L1, - // without any verified cross-L2 dependencies. - // This does not have an RPC label. - LocalSafe SafetyLevel = "local-safe" - // CrossUnsafe is as safe as LocalUnsafe, - // but with verified cross-L2 dependencies that are at least CrossUnsafe. - // This does not have an RPC label. - CrossUnsafe SafetyLevel = "cross-unsafe" - // LocalUnsafe is the safety of the tip of the chain. This matches RPC label "unsafe". - LocalUnsafe SafetyLevel = "unsafe" - // Invalid is the safety of when the message or block is not matching the expected data. - Invalid SafetyLevel = "invalid" -) - -type ExecutingDescriptor struct { - // ChainID of the executing message - ChainID eth.ChainID - - // Timestamp is the timestamp of the executing message - Timestamp uint64 - - // Timeout, requests verification to still hold at Timestamp+Timeout (incl.). Defaults to 0. - // I.e. Timestamp is used as lower-bound validity, and Timeout defines the span to the upper-bound. - Timeout uint64 -} - -type executingDescriptorMarshaling struct { - ChainID eth.ChainID `json:"chainID"` - Timestamp hexutil.Uint64 `json:"timestamp"` - Timeout hexutil.Uint64 `json:"timeout,omitempty"` -} - -func (ed ExecutingDescriptor) MarshalJSON() ([]byte, error) { - var enc executingDescriptorMarshaling - enc.ChainID = ed.ChainID - enc.Timestamp = hexutil.Uint64(ed.Timestamp) - enc.Timeout = hexutil.Uint64(ed.Timeout) - return json.Marshal(&enc) -} - -func (ed *ExecutingDescriptor) UnmarshalJSON(input []byte) error { - var dec executingDescriptorMarshaling - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - ed.ChainID = dec.ChainID - ed.Timestamp = uint64(dec.Timestamp) - ed.Timeout = uint64(dec.Timeout) - return nil -} - -type BlockSeal struct { - Hash common.Hash `json:"hash"` - Number uint64 `json:"number"` - Timestamp uint64 `json:"timestamp"` -} - -func (s BlockSeal) String() string { - return fmt.Sprintf("BlockSeal(hash:%s, number:%d, time:%d)", s.Hash, s.Number, s.Timestamp) -} - -func (s BlockSeal) ID() eth.BlockID { - return eth.BlockID{Hash: s.Hash, Number: s.Number} -} - -func (s BlockSeal) MustWithParent(parent eth.BlockID) eth.BlockRef { - ref, err := s.WithParent(parent) - if err != nil { - panic(err) - } - return ref -} - -func (s BlockSeal) WithParent(parent eth.BlockID) (eth.BlockRef, error) { - // prevent parent attachment if the parent is not the previous block, - // and the block is not the genesis block - if s.Number != parent.Number+1 && s.Number != 0 { - return eth.BlockRef{}, fmt.Errorf("%w: parent %s to combine with %s", errInvalidParentBlock, parent, s) - } - return eth.BlockRef{ - Hash: s.Hash, - Number: s.Number, - ParentHash: parent.Hash, - Time: s.Timestamp, - }, nil -} - -// WithZeroParent returns [s] with a zero parent hash. This should only be used -// where a BlockRef is required, but from the calling context it is guaranteed that -// the parent hash is not needed. -func (s BlockSeal) WithZeroParent() eth.BlockRef { - return eth.BlockRef{ - Hash: s.Hash, - Number: s.Number, - Time: s.Timestamp, - } -} - -func (s BlockSeal) ForceWithParent(parent eth.BlockID) eth.BlockRef { - return eth.BlockRef{ - Hash: s.Hash, - Number: s.Number, - ParentHash: parent.Hash, - Time: s.Timestamp, - } -} - -func BlockSealFromRef(ref eth.BlockRef) BlockSeal { - return BlockSeal{ - Hash: ref.Hash, - Number: ref.Number, - Timestamp: ref.Time, - } -} - -// PayloadHashToLogHash converts the payload hash to the log hash -// it is the concatenation of the log's address and the hash of the log's payload, -// which is then hashed again. This is the hash that is stored in the log storage. -// The logHash can then be used to traverse from the executing message -// to the log the referenced initiating message. -func PayloadHashToLogHash(payloadHash common.Hash, addr common.Address) common.Hash { - msg := make([]byte, 0, 2*common.HashLength) - msg = append(msg, addr.Bytes()...) - msg = append(msg, payloadHash.Bytes()...) - return crypto.Keccak256Hash(msg) -} - -// LogToMessagePayload is the data that is hashed to get the payloadHash -// it is the concatenation of the log's topics and data -// the implementation is based on the interop messaging spec -func LogToMessagePayload(l *ethTypes.Log) []byte { - msg := make([]byte, 0) - for _, topic := range l.Topics { - msg = append(msg, topic.Bytes()...) - } - msg = append(msg, l.Data...) - return msg -} - // DerivedBlockRefPair is a pair of block refs, where Derived (L2) is derived from Source (L1). type DerivedBlockRefPair struct { Source eth.BlockRef `json:"source"` diff --git a/op-supervisor/supervisor/types/types_test.go b/op-supervisor/supervisor/types/types_test.go index e30d20af404..add657362bc 100644 --- a/op-supervisor/supervisor/types/types_test.go +++ b/op-supervisor/supervisor/types/types_test.go @@ -1,12 +1,6 @@ package types import ( - "encoding/binary" - "encoding/json" - "fmt" - "math/big" - "math/rand" - "strings" "testing" "github.com/stretchr/testify/require" @@ -20,583 +14,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" ) -func FuzzRoundtripIdentifierJSONMarshal(f *testing.F) { - f.Fuzz(func(t *testing.T, origin []byte, blockNumber uint64, logIndex uint32, timestamp uint64, chainID []byte) { - if len(chainID) > 32 { - chainID = chainID[:32] - } - - id := Identifier{ - Origin: common.BytesToAddress(origin), - BlockNumber: blockNumber, - LogIndex: logIndex, - Timestamp: timestamp, - ChainID: eth.ChainIDFromBig(new(big.Int).SetBytes(chainID)), - } - - raw, err := json.Marshal(&id) - require.NoError(t, err) - - var dec Identifier - require.NoError(t, json.Unmarshal(raw, &dec)) - - require.Equal(t, id.Origin, dec.Origin) - require.Equal(t, id.BlockNumber, dec.BlockNumber) - require.Equal(t, id.LogIndex, dec.LogIndex) - require.Equal(t, id.Timestamp, dec.Timestamp) - require.Equal(t, id.ChainID, dec.ChainID) - }) -} - -func FuzzMessage_DecodeEvent(f *testing.F) { - f.Fuzz(func(t *testing.T, validEvTopic bool, numTopics uint8, data []byte) { - if len(data) < 32 { - return - } - if len(data) > 100_000 { - return - } - if validEvTopic { // valid even signature topic implies a topic to be there - numTopics += 1 - } - if numTopics > 4 { // There can be no more than 4 topics per log event - return - } - if int(numTopics)*32 > len(data) { - return - } - var topics []common.Hash - if validEvTopic { - topics = append(topics, ExecutingMessageEventTopic) - } - for i := 0; i < int(numTopics); i++ { - var topic common.Hash - copy(topic[:], data[:]) - data = data[32:] - } - require.NotPanics(t, func() { - var m Message - _ = m.DecodeEvent(topics, data) - }) - }) -} - -func TestInteropMessageFormatEdgeCases(t *testing.T) { - tests := []struct { - name string - log *ethTypes.Log - expectedError string - }{ - { - name: "Empty Topics", - log: ðTypes.Log{ - Address: params.InteropCrossL2InboxAddress, - Topics: []common.Hash{}, - Data: make([]byte, 32*5), - }, - expectedError: "unexpected number of event topics: 0", - }, - { - name: "Wrong Event Topic", - log: ðTypes.Log{ - Address: params.InteropCrossL2InboxAddress, - Topics: []common.Hash{ - common.BytesToHash([]byte("wrong topic")), - common.BytesToHash([]byte("payloadHash")), - }, - Data: make([]byte, 32*5), - }, - expectedError: "unexpected event topic", - }, - { - name: "Missing PayloadHash Topic", - log: ðTypes.Log{ - Address: params.InteropCrossL2InboxAddress, - Topics: []common.Hash{ - common.BytesToHash(ExecutingMessageEventTopic[:]), - }, - Data: make([]byte, 32*5), - }, - expectedError: "unexpected number of event topics: 1", - }, - { - name: "Too Many Topics", - log: ðTypes.Log{ - Address: params.InteropCrossL2InboxAddress, - Topics: []common.Hash{ - common.BytesToHash(ExecutingMessageEventTopic[:]), - common.BytesToHash([]byte("payloadHash")), - common.BytesToHash([]byte("extra")), - }, - Data: make([]byte, 32*5), - }, - expectedError: "unexpected number of event topics: 3", - }, - { - name: "Data Too Short", - log: ðTypes.Log{ - Address: params.InteropCrossL2InboxAddress, - Topics: []common.Hash{ - common.BytesToHash(ExecutingMessageEventTopic[:]), - common.BytesToHash([]byte("payloadHash")), - }, - Data: make([]byte, 32*4), // One word too short - }, - expectedError: "unexpected identifier data length: 128", - }, - { - name: "Data Too Long", - log: ðTypes.Log{ - Address: params.InteropCrossL2InboxAddress, - Topics: []common.Hash{ - common.BytesToHash(ExecutingMessageEventTopic[:]), - common.BytesToHash([]byte("payloadHash")), - }, - Data: make([]byte, 32*6), // One word too long - }, - expectedError: "unexpected identifier data length: 192", - }, - { - name: "Invalid Address Padding", - log: ðTypes.Log{ - Address: params.InteropCrossL2InboxAddress, - Topics: []common.Hash{ - common.BytesToHash(ExecutingMessageEventTopic[:]), - common.BytesToHash([]byte("payloadHash")), - }, - Data: func() []byte { - data := make([]byte, 32*5) - data[0] = 1 // Add non-zero byte in address padding - return data - }(), - }, - expectedError: "invalid address padding", - }, - { - name: "Invalid Block Number Padding", - log: ðTypes.Log{ - Address: params.InteropCrossL2InboxAddress, - Topics: []common.Hash{ - common.BytesToHash(ExecutingMessageEventTopic[:]), - common.BytesToHash([]byte("payloadHash")), - }, - Data: func() []byte { - data := make([]byte, 32*5) - data[32+23] = 1 // Add non-zero byte in block number padding - return data - }(), - }, - expectedError: "invalid block number padding", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var msg Message - err := msg.DecodeEvent(tt.log.Topics, tt.log.Data) - if tt.expectedError != "" { - require.Error(t, err) - require.ErrorContains(t, err, tt.expectedError) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestHashing(t *testing.T) { - keccak256 := func(name string, parts ...[]byte) (h common.Hash) { - t.Logf("%s = H(", name) - for _, p := range parts { - t.Logf(" %x,", p) - } - t.Logf(")") - h = crypto.Keccak256Hash(parts...) - t.Logf("%s = %s", name, h) - return h - } - id := Identifier{ - Origin: common.HexToAddress("0xe0e1e2e3e4e5e6e7e8e9f0f1f2f3f4f5f6f7f8f9"), - BlockNumber: 0xa1a2_a3a4_a5a6_a7a8, - LogIndex: 0xb1b2_b3b4, - Timestamp: 0xc1c2_c3c4_c5c6_c7c8, - ChainID: eth.ChainIDFromUInt64(0xd1d2_d3d4_d5d6_d7d8), - } - payloadHash := keccak256("payloadHash", []byte("example payload")) // aka msgHash - logHash := keccak256("logHash", id.Origin[:], payloadHash[:]) - x := PayloadHashToLogHash(payloadHash, id.Origin) - require.Equal(t, logHash, x, "check op-supervisor version of log-hashing matches intermediate value") - - var idPacked []byte - idPacked = append(idPacked, make([]byte, 12)...) - idPacked = binary.BigEndian.AppendUint64(idPacked, id.BlockNumber) - idPacked = binary.BigEndian.AppendUint64(idPacked, id.Timestamp) - idPacked = binary.BigEndian.AppendUint32(idPacked, id.LogIndex) - t.Logf("idPacked: %x", idPacked) - - idLogHash := keccak256("idLogHash", logHash[:], idPacked) - chainID := id.ChainID.Bytes32() - bareChecksum := keccak256("bareChecksum", idLogHash[:], chainID[:]) - - checksum := bareChecksum - checksum[0] = 0x03 - t.Logf("Checksum: %s", checksum) -} - -var ( - testOrigin = common.HexToAddress("0xe0e1e2e3e4e5e6e7e8e9f0f1f2f3f4f5f6f7f8f9") - testBlockNumber = uint64(0xa1a2_a3a4_a5a6_a7a8) - testLogIndex = uint32(0xb1b2_b3b4) - testTimestamp = uint64(0xc1c2_c3c4_c5c6_c7c8) - testChainID = eth.ChainIDFromUInt64(0xd1d2_d3d4_d5d6_d7d8) - testPayload = []byte("example payload") - testMsgHash = common.HexToHash("0x8017559a85b12c04b14a1a425d53486d1015f833714a09bd62f04152a7e2ae9b") - testLogHash = common.HexToHash("0xf9ed05990c887d3f86718aabd7e940faaa75d6a5cd44602e89642586ce85f2aa") - testChecksum = MessageChecksum(common.HexToHash("0x03749e87fd7789575de9906569deb05aaf220dc4cfab3d8abbfd34a2e1d7d357")) - testLookupEntry = common.HexToHash("0x01000000d1d2d3d4d5d6d7d8a1a2a3a4a5a6a7a8c1c2c3c4c5c6c7c8b1b2b3b4") -) - -func TestMessage(t *testing.T) { - msg := Message{ - Identifier: Identifier{ - Origin: testOrigin, - BlockNumber: testBlockNumber, - LogIndex: testLogIndex, - Timestamp: testTimestamp, - ChainID: testChainID, - }, - PayloadHash: testMsgHash, - } - t.Run("checksum", func(t *testing.T) { - require.Equal(t, testChecksum, msg.Checksum()) - }) - t.Run("json roundtrip", func(t *testing.T) { - data, err := json.Marshal(msg) - require.NoError(t, err) - var out Message - require.NoError(t, json.Unmarshal(data, &out)) - require.Equal(t, msg, out) - }) -} - -func TestChecksumArgs(t *testing.T) { - args := ChecksumArgs{ - BlockNumber: testBlockNumber, - LogIndex: testLogIndex, - Timestamp: testTimestamp, - ChainID: testChainID, - LogHash: testLogHash, - } - t.Run("checksum", func(t *testing.T) { - require.Equal(t, testChecksum, args.Checksum()) - }) - t.Run("as query", func(t *testing.T) { - q := args.Query() - require.Equal(t, testBlockNumber, q.BlockNum) - require.Equal(t, testTimestamp, q.Timestamp) - require.Equal(t, testLogIndex, q.LogIdx) - require.Equal(t, testChecksum, q.Checksum) - }) - t.Run("as access", func(t *testing.T) { - acc := args.Access() - require.Equal(t, testBlockNumber, acc.BlockNumber) - require.Equal(t, testTimestamp, acc.Timestamp) - require.Equal(t, testLogIndex, acc.LogIndex) - require.Equal(t, testChainID, acc.ChainID) - require.Equal(t, testChecksum, acc.Checksum) - }) -} - -func TestIdentifier(t *testing.T) { - id := Identifier{ - Origin: testOrigin, - BlockNumber: testBlockNumber, - LogIndex: testLogIndex, - Timestamp: testTimestamp, - ChainID: testChainID, - } - t.Run("json roundtrip", func(t *testing.T) { - data, err := json.Marshal(id) - require.NoError(t, err) - var out Identifier - require.NoError(t, json.Unmarshal(data, &out)) - require.Equal(t, id, out) - }) -} - -func TestSafetyLevel(t *testing.T) { - for _, lvl := range []SafetyLevel{ - Finalized, - CrossSafe, - LocalSafe, - CrossUnsafe, - LocalUnsafe, - Invalid, - } { - upper := strings.ToUpper(lvl.String()) - var x SafetyLevel - require.ErrorContains(t, json.Unmarshal([]byte(fmt.Sprintf("%q", upper)), &x), "unrecognized", "case sensitive") - require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf("%q", lvl.String())), &x)) - dat, err := json.Marshal(x) - require.NoError(t, err) - require.Equal(t, fmt.Sprintf("%q", lvl.String()), string(dat)) - } - var x SafetyLevel - require.ErrorContains(t, json.Unmarshal([]byte(`""`), &x), "unrecognized", "empty") - require.ErrorContains(t, json.Unmarshal([]byte(`"foobar"`), &x), "unrecognized", "other") -} - -func TestPayloadHashToLogHash(t *testing.T) { - logHash := PayloadHashToLogHash(testMsgHash, testOrigin) - require.Equal(t, testLogHash, logHash) -} - -func TestLogToMessagePayload(t *testing.T) { - payload := LogToMessagePayload(ðTypes.Log{ - Data: testPayload, - }) - require.Equal(t, hexutil.Encode(testPayload), hexutil.Encode(payload)) - - t.Run("1 topic", func(t *testing.T) { - v := LogToMessagePayload(ðTypes.Log{ - Data: []byte(`foobar`), - Topics: []common.Hash{ - crypto.Keccak256Hash([]byte(`topic0`)), - }, - }) - expected := make([]byte, 0) - expected = append(expected, crypto.Keccak256([]byte(`topic0`))...) - expected = append(expected, []byte(`foobar`)...) - require.Equal(t, expected, v) - }) - - t.Run("4 topics", func(t *testing.T) { - v := LogToMessagePayload(ðTypes.Log{ - Data: []byte(`foobar`), - Topics: []common.Hash{ - crypto.Keccak256Hash([]byte(`topic0`)), - crypto.Keccak256Hash([]byte(`topic1`)), - crypto.Keccak256Hash([]byte(`topic2`)), - crypto.Keccak256Hash([]byte(`topic3`)), - }, - }) - expected := make([]byte, 0) - expected = append(expected, crypto.Keccak256([]byte(`topic0`))...) - expected = append(expected, crypto.Keccak256([]byte(`topic1`))...) - expected = append(expected, crypto.Keccak256([]byte(`topic2`))...) - expected = append(expected, crypto.Keccak256([]byte(`topic3`))...) - expected = append(expected, []byte(`foobar`)...) - require.Equal(t, expected, v) - }) -} - -func TestAccess(t *testing.T) { - acc := Access{ - BlockNumber: testBlockNumber, - Timestamp: testTimestamp, - LogIndex: testLogIndex, - ChainID: testChainID, - Checksum: MessageChecksum(testChecksum), - } - t.Run("json roundtrip", func(t *testing.T) { - data, err := json.Marshal(acc) - require.NoError(t, err) - var out Access - require.NoError(t, json.Unmarshal(data, &out)) - require.Equal(t, acc, out) - }) -} - -func TestParseAccess(t *testing.T) { - t.Run("empty", func(t *testing.T) { - _, _, err := ParseAccess(nil) - require.ErrorIs(t, err, errExpectedEntry) - }) - t.Run("unexpected 0 type", func(t *testing.T) { - _, _, err := ParseAccess([]common.Hash{ - {0: 0x00}, - }) - require.ErrorIs(t, err, errUnexpectedEntryType) - require.ErrorContains(t, err, "expected lookup") - }) - t.Run("unexpected arbitrary type", func(t *testing.T) { - _, _, err := ParseAccess([]common.Hash{ - {0: 10}, - }) - require.ErrorIs(t, err, errUnexpectedEntryType) - require.ErrorContains(t, err, "expected lookup") - }) - t.Run("unexpected non-zero padding", func(t *testing.T) { - _, _, err := ParseAccess([]common.Hash{ - {0: PrefixLookup, 1: 0x01}, // valid lookup prefix byte, but non-zero value in padding area - }) - require.ErrorIs(t, err, errMalformedEntry) - require.ErrorContains(t, err, "expected zero bytes") - }) - t.Run("incomplete", func(t *testing.T) { - _, _, err := ParseAccess([]common.Hash{ - {0: PrefixLookup}, // valid lookup, but no checksum after - }) - require.ErrorIs(t, err, errExpectedEntry) - }) - t.Run("unexpected 0 type after checksum", func(t *testing.T) { - _, _, err := ParseAccess([]common.Hash{ - {0: PrefixLookup}, - {0: 0}, - }) - require.ErrorIs(t, err, errUnexpectedEntryType) - }) - t.Run("unexpected lookup repeat", func(t *testing.T) { - _, _, err := ParseAccess([]common.Hash{ - {0: PrefixLookup}, - {0: PrefixLookup}, - }) - require.ErrorIs(t, err, errUnexpectedEntryType) - }) - t.Run("unexpected arbitrary type after checksum", func(t *testing.T) { - _, _, err := ParseAccess([]common.Hash{ - {0: PrefixLookup}, - {0: 10}, // unexpected type byte - }) - require.ErrorIs(t, err, errUnexpectedEntryType) - }) - t.Run("valid but zero", func(t *testing.T) { - remaining, acc, err := ParseAccess([]common.Hash{ - {0: PrefixLookup}, // valid lookup entry - {0: PrefixChecksum}, // valid checksum entry - }) - require.NoError(t, err) - require.Equal(t, Access{ - BlockNumber: 0, - Timestamp: 0, - LogIndex: 0, - ChainID: eth.ChainID{}, - Checksum: MessageChecksum{0: PrefixChecksum}, - }, acc) - require.Empty(t, remaining) - }) - t.Run("valid", func(t *testing.T) { - acc := Access{ - BlockNumber: testBlockNumber, - Timestamp: testTimestamp, - LogIndex: testLogIndex, - ChainID: testChainID, - Checksum: MessageChecksum(testChecksum), - } - remaining, parsed, err := ParseAccess([]common.Hash{ - testLookupEntry, - common.Hash(acc.Checksum), - }) - require.NoError(t, err) - require.Equal(t, acc, parsed) - require.Empty(t, remaining) - }) - t.Run("repeat", func(t *testing.T) { - acc := Access{ - BlockNumber: testBlockNumber, - Timestamp: testTimestamp, - LogIndex: testLogIndex, - ChainID: testChainID, - Checksum: MessageChecksum(testChecksum), - } - remaining, parsed, err := ParseAccess([]common.Hash{ - testLookupEntry, - common.Hash(acc.Checksum), - testLookupEntry, - common.Hash(acc.Checksum), - }) - require.NoError(t, err) - require.Equal(t, acc, parsed) - require.Len(t, remaining, 2) - remaining2, parsed2, err := ParseAccess(remaining) - require.NoError(t, err) - require.Equal(t, acc, parsed2) - require.Empty(t, remaining2) - }) - t.Run("with chainID extension", func(t *testing.T) { - acc := Access{ - BlockNumber: testBlockNumber, - Timestamp: testTimestamp, - LogIndex: testLogIndex, - ChainID: eth.ChainIDFromBytes32([32]byte{0: 7, 31: 10}), - Checksum: MessageChecksum(testChecksum), - } - remaining, parsed, err := ParseAccess([]common.Hash{ - acc.lookupEntry(), - acc.chainIDExtensionEntry(), - common.Hash(acc.Checksum), - }) - require.NoError(t, err) - require.Equal(t, acc, parsed) - require.Empty(t, remaining) - }) -} - -func TestEncodeAccessList(t *testing.T) { - acc := Access{ - BlockNumber: testBlockNumber, - Timestamp: testTimestamp, - LogIndex: testLogIndex, - ChainID: testChainID, - Checksum: MessageChecksum(testChecksum), - } - t.Run("valid single", func(t *testing.T) { - accList := EncodeAccessList([]Access{acc}) - require.Len(t, accList, 2) - require.Equal(t, testLookupEntry, accList[0]) - require.Equal(t, common.Hash(testChecksum), accList[1]) - _, result, err := ParseAccess(accList) - require.NoError(t, err) - require.Equal(t, acc, result, "roundtrip") - }) - t.Run("valid repeat", func(t *testing.T) { - accList := EncodeAccessList([]Access{ - acc, - acc, - }) - require.Len(t, accList, 4) - require.Equal(t, testLookupEntry, accList[0]) - require.Equal(t, common.Hash(testChecksum), accList[1]) - require.Equal(t, testLookupEntry, accList[2]) - require.Equal(t, common.Hash(testChecksum), accList[3]) - }) - t.Run("roundtrip", func(t *testing.T) { - accObjects := make([]Access, 0) - rng := rand.New(rand.NewSource(1234)) - randB32 := func() (out [32]byte) { - rng.Read(out[:]) - return - } - // test a big random access-list - count := 200 - for i := 0; i < count; i++ { - chainID := eth.ChainIDFromBytes32(randB32()) - if rng.Intn(5) < 2 { // don't make them all full random bytes32 - chainID = eth.ChainIDFromUInt64(rng.Uint64()) - } - checksum := randB32() - checksum[0] = PrefixChecksum - accObjects = append(accObjects, Access{ - BlockNumber: rng.Uint64(), - Timestamp: rng.Uint64(), - LogIndex: rng.Uint32(), - ChainID: chainID, - Checksum: checksum, - }) - } - list := EncodeAccessList(accObjects) - var result []Access - for i := 0; i < count && len(list) > 0; i++ { - remaining, v, err := ParseAccess(list) - require.NoError(t, err) - result = append(result, v) - list = remaining - } - require.Empty(t, list, "need to exhaust entries, expecting to be done") - require.Equal(t, accObjects, result, "roundtrip of random entries should work") - }) -} - func TestRevision(t *testing.T) { require.True(t, RevisionAny.Any()) // RevisionAny does not have a sort-order diff --git a/ops/docker/op-stack-go/Dockerfile b/ops/docker/op-stack-go/Dockerfile index ef4e0956dfe..66e9f338936 100644 --- a/ops/docker/op-stack-go/Dockerfile +++ b/ops/docker/op-stack-go/Dockerfile @@ -205,7 +205,7 @@ RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache op-interop-mon/op-interop-mon # The Rust version must match rust/rust-toolchain.toml. We don't use "latest" to ensure reproducibility -FROM --platform=$BUILDPLATFORM rust:1.92 AS kona-host-builder +FROM --platform=$BUILDPLATFORM rust:1.94 AS kona-host-builder ARG TARGETARCH # Install build dependencies and cross-compilation toolchains RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -218,6 +218,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* # Copy the entire rust workspace since kona depends on sibling crates (op-alloy, alloy-op-evm, etc.) COPY ./rust /rust +# kona-hardforks build.rs probes ancestor paths for the embedded NUT bundle JSON. +# Keep op-core as a sibling of /rust so Docker builds see the monorepo layout. +COPY ./op-core/nuts/bundles /op-core/nuts/bundles WORKDIR /rust # Map TARGETARCH to Rust target triple, install it, and build RUN --mount=type=cache,target=/usr/local/cargo/registry --mount=type=cache,target=/rust/target \ diff --git a/ops/scripts/source-ci-archive-rpcs.sh b/ops/scripts/source-ci-archive-rpcs.sh new file mode 100644 index 00000000000..27efcdd8fe3 --- /dev/null +++ b/ops/scripts/source-ci-archive-rpcs.sh @@ -0,0 +1,4 @@ +# shellcheck shell=bash +# Source from CI recipes that need historical L1 state. +export SEPOLIA_RPC_URL="${SEPOLIA_RPC_URL:-${OP_CI_SEPOLIA_L1_ARCHIVE_RPC_URL:?OP_CI_SEPOLIA_L1_ARCHIVE_RPC_URL must be set}}" +export MAINNET_RPC_URL="${MAINNET_RPC_URL:-${OP_CI_MAINNET_L1_ARCHIVE_RPC_URL:?OP_CI_MAINNET_L1_ARCHIVE_RPC_URL must be set}}" diff --git a/packages/contracts-bedrock/book/src/contributing/style-guide.md b/packages/contracts-bedrock/book/src/contributing/style-guide.md index 65057018ae0..cd8ad95558e 100644 --- a/packages/contracts-bedrock/book/src/contributing/style-guide.md +++ b/packages/contracts-bedrock/book/src/contributing/style-guide.md @@ -252,8 +252,8 @@ contract BadStorageLayout { All contracts should be assumed to live behind proxies (except in certain special circumstances). This means that new contracts MUST be built under the assumption of upgradeability. -We use a minimal [`Proxy`](../src/universal/Proxy.sol) contract designed to be owned by a -corresponding [`ProxyAdmin`](../src/universal/ProxyAdmin.sol) which follow the interfaces +We use a minimal [`Proxy`](../../../src/universal/Proxy.sol) contract designed to be owned by a +corresponding [`ProxyAdmin`](../../../src/universal/ProxyAdmin.sol) which follow the interfaces of OpenZeppelin's `Proxy` and `ProxyAdmin` contracts, respectively. Unless explicitly discussed otherwise, you MUST include the following basic upgradeability diff --git a/packages/contracts-bedrock/lib/superchain-registry b/packages/contracts-bedrock/lib/superchain-registry index 84bce73573f..cc07e96d91a 160000 --- a/packages/contracts-bedrock/lib/superchain-registry +++ b/packages/contracts-bedrock/lib/superchain-registry @@ -1 +1 @@ -Subproject commit 84bce73573f130008d84bae6e924163bab589a11 +Subproject commit cc07e96d91a1647cbce7eef6572098bece1f7fca diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index c189309fddd..1767cef9709 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -263,6 +263,8 @@ contract L2Genesis is Script { setCrossL2Inbox(); // 22 } setL2ToL2CrossDomainMessenger(); // 23 + setSuperchainETHBridge(); // 24 + setETHLiquidity(); // 25 } if (_input.useCustomGasToken) { setLiquidityController(_input); // 29 @@ -517,7 +519,7 @@ contract L2Genesis is Script { /// This contract has no initializer. function setETHLiquidity() internal { _setImplementationCode(Predeploys.ETH_LIQUIDITY); - vm.deal(Predeploys.ETH_LIQUIDITY, type(uint248).max); + vm.deal(Predeploys.ETH_LIQUIDITY, type(uint128).max); } /// @notice This predeploy is following the safety invariant #1. diff --git a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh index 6a0389b4f28..707f2aebde2 100755 --- a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh +++ b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh @@ -16,6 +16,65 @@ EXCLUDED_CONTRACTS=( "src/vendor/asterisc/RISCV.sol" ) +github_token() { + if [ -n "${GH_TOKEN:-}" ]; then + printf '%s' "$GH_TOKEN" + elif [ -n "${GITHUB_TOKEN:-}" ]; then + printf '%s' "$GITHUB_TOKEN" + elif [ -n "${GHTOKEN:-}" ]; then + printf '%s' "$GHTOKEN" + fi +} + +github_repo() { + local owner="${CIRCLE_PROJECT_USERNAME:-}" + local repo="${CIRCLE_PROJECT_REPONAME:-}" + local origin_url + + if [ -z "$owner" ] || [ -z "$repo" ]; then + origin_url="$(git config --get remote.origin.url || true)" + origin_url="${origin_url%.git}" + origin_url="${origin_url#git@github.com:}" + origin_url="${origin_url#https://github.com/}" + owner="${origin_url%%/*}" + repo="${origin_url#*/}" + fi + + if [ -n "$owner" ] && [ -n "$repo" ] && [ "$owner" != "$repo" ]; then + printf '%s/%s' "$owner" "$repo" + fi +} + +fetch_upstream_file_from_github() { + local path="$1" + local output="$2" + local token + local repo + local url + local curl_args=(-fsSL -H "Accept: application/vnd.github.raw") + + token="$(github_token)" + repo="$(github_repo)" + if [ -z "$token" ] || [ -z "$repo" ]; then + return 1 + fi + + curl_args+=(-H "Authorization: Bearer ${token}") + url="https://api.github.com/repos/${repo}/contents/${path}?ref=${TARGET_BRANCH}" + curl "${curl_args[@]}" "$url" > "$output" +} + +get_upstream_file() { + local path="$1" + local output="$2" + + if fetch_upstream_file_from_github "$path" "$output" 2> /dev/null; then + return 0 + fi + + git show "$UPSTREAM_REF":"$path" > "$output" 2> /dev/null +} + # Helper function to check if a contract is excluded. is_excluded() { local contract="$1" @@ -44,7 +103,7 @@ if ! { fi # Get the upstream semver-lock.json. -if ! git show "$UPSTREAM_REF":packages/contracts-bedrock/snapshots/semver-lock.json > "$temp_dir/upstream_semver_lock.json" 2> /dev/null; then +if ! get_upstream_file packages/contracts-bedrock/snapshots/semver-lock.json "$temp_dir/upstream_semver_lock.json"; then echo "❌ Error: Could not find semver-lock.json in the snapshots/ directory of $TARGET_BRANCH branch" exit 1 fi @@ -86,7 +145,7 @@ for contract in $changed_contracts; do # Extract the old and new source files. old_source_file="$temp_dir/old_${contract##*/}" new_source_file="$temp_dir/new_${contract##*/}" - git show "$UPSTREAM_REF":packages/contracts-bedrock/"$contract" > "$old_source_file" 2> /dev/null || true + get_upstream_file packages/contracts-bedrock/"$contract" "$old_source_file" || true cp "$contract" "$new_source_file" # Extract the old and new versions. diff --git a/packages/contracts-bedrock/scripts/checks/test-validation/exclusions.toml b/packages/contracts-bedrock/scripts/checks/test-validation/exclusions.toml index eb7d2bd537f..ae3ea1b3fdf 100644 --- a/packages/contracts-bedrock/scripts/checks/test-validation/exclusions.toml +++ b/packages/contracts-bedrock/scripts/checks/test-validation/exclusions.toml @@ -29,8 +29,8 @@ src_validation = [ "test/universal/ExtendedPause.t.sol", # Tests extended functionality "test/vendor/Initializable.t.sol", # Tests external vendor code "test/vendor/InitializableOZv5.t.sol", # Tests external vendor code - "test/L2/LegacyFeeSplitter.t.sol", # Tests legacy fee splitter with updated vaults interface - "test/L2/FeeSplitterVaults.t.sol", # Tests FeeSplitter with vault-specific scenarios using test helper + "test/L2/fork/L2ForkUpgrade.t.sol", # Tests L2 fork upgrade workflow + "test/L2/L2GenesisForkUpgrade.t.sol", # Tests L2 genesis fork upgrade workflow ] # PATHS EXCLUDED FROM CONTRACT NAME FILE PATH VALIDATION: diff --git a/packages/contracts-bedrock/scripts/ops/get-target-branch.sh b/packages/contracts-bedrock/scripts/ops/get-target-branch.sh new file mode 100644 index 00000000000..f960fd7e1a7 --- /dev/null +++ b/packages/contracts-bedrock/scripts/ops/get-target-branch.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Determines the PR target branch and exports TARGET_BRANCH + +resolve_pr_target_branch() { + local pr_number="${CIRCLE_PULL_REQUEST##*/}" + local response + local url + local curl_args=(-fsSL) + + if [ -n "${GH_TOKEN:-}" ]; then + curl_args+=(-H "Authorization: Bearer ${GH_TOKEN}") + elif [ -n "${GITHUB_TOKEN:-}" ]; then + curl_args+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") + elif [ -n "${GHTOKEN:-}" ]; then + curl_args+=(-H "Authorization: Bearer ${GHTOKEN}") + fi + + if [ -z "${CIRCLE_PROJECT_USERNAME:-}" ] || [ -z "${CIRCLE_PROJECT_REPONAME:-}" ]; then + echo "Error: CIRCLE_PROJECT_USERNAME and CIRCLE_PROJECT_REPONAME are required to resolve PR target branch" >&2 + return 1 + fi + + url="https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/pulls/${pr_number}" + response="$(curl "${curl_args[@]}" "$url")" || { + echo "Error: failed to resolve target branch for PR #${pr_number}" >&2 + return 1 + } + + TARGET_BRANCH="$(jq -er '.base.ref | select(type == "string" and length > 0)' <<< "$response")" || { + echo "Error: GitHub API response did not include base.ref for PR #${pr_number}" >&2 + return 1 + } +} + +TARGET_BRANCH="" + +# In merge queues, CIRCLE_BRANCH is gh-readonly-queue//pr--. +# Extract the base branch via regex; BASH_REMATCH[1] captures the first +# parenthesised group, i.e. the segment between the slashes. +if [[ "${CIRCLE_BRANCH:-}" =~ ^gh-readonly-queue/([^/]+)/ ]]; then + TARGET_BRANCH="${BASH_REMATCH[1]}" +elif [ -n "${CIRCLE_PULL_REQUEST:-}" ]; then + # PR jobs must fail closed if GitHub cannot return the true base branch. + # Falling back to CIRCLE_BRANCH can compare a branch against itself. + resolve_pr_target_branch || exit 1 +else + # Non-PR/manual branch jobs do not have a reliable source for the true base. + # Use the repository default branch instead of CIRCLE_BRANCH so diff-based + # checks cannot accidentally compare a branch against itself. + TARGET_BRANCH="develop" +fi + +echo "Resolved TARGET_BRANCH=$TARGET_BRANCH" >&2 +export TARGET_BRANCH diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index eb0bc8f4b61..c9343e28713 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -215,13 +215,9 @@ "initCodeHash": "0x6fc59e2da083c9e2093e42b0fda705e8215cc216e4dcedbf728c08f69ec2d3bd", "sourceCodeHash": "0x7fc97734c12e207f011c4f079fffe84f5bd11f4fb4a95dd56ad6a69df184584f" }, - "src/dispute/v2/PermissionedDisputeGameV2.sol:PermissionedDisputeGameV2": { - "initCodeHash": "0x9896fd04e9a3f9fe4f1d6e93eb298b37a6bfa33424aa705e68cc58d0ba7f3f90", - "sourceCodeHash": "0xc0ff6e93b6e2b9111c11e81b5df8948ab71d02b9d2c4dfda982fcb615519f1f7" - }, - "src/dispute/zk/OPSuccinctFaultDisputeGame.sol:OPSuccinctFaultDisputeGame": { - "initCodeHash": "0xb9d0d9ca4df242f188b2d5be7d692459a12409a67a6504ef44ef589c6ca2c1a9", - "sourceCodeHash": "0x85f80adb845f59e9137d462e219c0cdba27058be77d855075e286aa316735aa0" + "src/dispute/zk/ZKDisputeGame.sol:ZKDisputeGame": { + "initCodeHash": "0xc78c8c200f5655b83c366c3b6c0e71a8630deede19f7a30575fd31959a64f87f", + "sourceCodeHash": "0xc3bb31bf4dbcedf090f0d269ade04a2c2414ed1236c11c5129061d91cbfb635c" }, "src/legacy/DeployerWhitelist.sol:DeployerWhitelist": { "initCodeHash": "0x2e0ef4c341367eb59cc6c25190c64eff441d3fe130189da91d4d126f6bdbc9b5", diff --git a/packages/contracts-bedrock/specs/l2-upgrades-2-contracts.md b/packages/contracts-bedrock/specs/l2-upgrades-2-contracts.md new file mode 100644 index 00000000000..a21b78c9d5e --- /dev/null +++ b/packages/contracts-bedrock/specs/l2-upgrades-2-contracts.md @@ -0,0 +1,523 @@ +# L2 Upgrade Contracts + + + +**Table of Contents** + +- [Overview](#overview) +- [ConditionalDeployer](#conditionaldeployer) + - [Overview](#overview-1) + - [Definitions](#definitions) + - [CREATE2 Collision](#create2-collision) + - [Deterministic Deployment Proxy](#deterministic-deployment-proxy) + - [Assumptions](#assumptions) + - [aCD-001: Deterministic Deployment Proxy is Available and Correct](#acd-001-deterministic-deployment-proxy-is-available-and-correct) + - [Mitigations](#mitigations) + - [aCD-002: Initcode is Well-Formed](#acd-002-initcode-is-well-formed) + - [Mitigations](#mitigations-1) + - [Invariants](#invariants) + - [iCD-001: Deterministic Address Derivation](#icd-001-deterministic-address-derivation) + - [Impact](#impact) + - [iCD-002: Idempotent Deployment Operations](#icd-002-idempotent-deployment-operations) + - [Impact](#impact-1) + - [iCD-003: Non-Reverting Collision Handling](#icd-003-non-reverting-collision-handling) + - [Impact](#impact-2) + - [iCD-004: Collision Detection Accuracy](#icd-004-collision-detection-accuracy) + - [Impact](#impact-3) + - [iCD-005: Contract Availability After Deployment](#icd-005-contract-availability-after-deployment) + - [Impact](#impact-4) +- [L2ProxyAdmin](#l2proxyadmin) + - [Overview](#overview-2) + - [Definitions](#definitions-1) + - [Depositor Account](#depositor-account) + - [Predeploy](#predeploy) + - [Assumptions](#assumptions-1) + - [aL2PA-001: Depositor Account is Controlled by Protocol](#al2pa-001-depositor-account-is-controlled-by-protocol) + - [Mitigations](#mitigations-2) + - [aL2PA-002: L2ContractsManager Code is Not Malicious](#al2pa-002-l2contractsmanager-code-is-not-malicious) + - [Mitigations](#mitigations-3) + - [aL2PA-003: Predeploy Proxies Follow Expected Patterns](#al2pa-003-predeploy-proxies-follow-expected-patterns) + - [Mitigations](#mitigations-4) + - [Invariants](#invariants-1) + - [iL2PA-001: Exclusive Depositor Authorization for Batch Upgrades](#il2pa-001-exclusive-depositor-authorization-for-batch-upgrades) + - [Impact](#impact-5) + - [iL2PA-002: Safe Delegation to L2ContractsManager](#il2pa-002-safe-delegation-to-l2contractsmanager) + - [Impact](#impact-6) + - [iL2PA-003: Backwards Compatibility Maintained](#il2pa-003-backwards-compatibility-maintained) + - [Impact](#impact-7) +- [L2ContractsManager](#l2contractsmanager) + - [Overview](#overview-3) + - [Definitions](#definitions-2) + - [Network-Specific Configuration](#network-specific-configuration) + - [Feature Flag](#feature-flag) + - [Initialization Parameters](#initialization-parameters) + - [Assumptions](#assumptions-2) + - [aL2CM-001: Existing Predeploys Provide Valid Configuration](#al2cm-001-existing-predeploys-provide-valid-configuration) + - [Mitigations](#mitigations-5) + - [aL2CM-002: Implementation Addresses Are Pre-Computed Correctly](#al2cm-002-implementation-addresses-are-pre-computed-correctly) + - [Mitigations](#mitigations-6) + - [aL2CM-003: Predeploy Proxies Are Upgradeable](#al2cm-003-predeploy-proxies-are-upgradeable) + - [Mitigations](#mitigations-7) + - [aL2CM-004: Feature Flags Are Correctly Configured](#al2cm-004-feature-flags-are-correctly-configured) + - [Mitigations](#mitigations-8) + - [Invariants](#invariants-2) + - [iL2CM-001: Deterministic Upgrade Execution](#il2cm-001-deterministic-upgrade-execution) + - [Impact](#impact-8) + - [iL2CM-002: Configuration Preservation](#il2cm-002-configuration-preservation) + - [Impact](#impact-9) + - [iL2CM-003: Upgrade Atomicity](#il2cm-003-upgrade-atomicity) + - [Impact](#impact-10) + - [iL2CM-004: Clear-and-Reinitialize Pattern](#il2cm-004-clear-and-reinitialize-pattern) + - [Impact](#impact-11) + - [iL2CM-005: No Storage Corruption During DELEGATECALL](#il2cm-005-no-storage-corruption-during-delegatecall) + - [Impact](#impact-12) + - [iL2CM-006: Complete Upgrade Coverage](#il2cm-006-complete-upgrade-coverage) + - [Impact](#impact-13) +- [Upgrade Execution](#upgrade-execution) + + + +## Overview + +This specification defines the mechanism for upgrading L2 predeploy contracts through deterministic, hard fork-driven +Network Upgrade Transactions (NUTs). The system enables safe, well-tested upgrades of L2 contracts with both +implementation and upgrade paths written in Solidity, ensuring determinism, verifiability, and testability across all +client implementations. + +The upgrade system maintains the existing pattern of injecting Network Upgrade Transactions at specific fork block +heights while improving the development and testing process. Upgrade transactions are defined in JSON bundles (see +Bundle Format) that are tracked in git, generated from Solidity scripts, +and executed deterministically at fork activation. + +## ConditionalDeployer + +### Overview + +The ConditionalDeployer contract enables deterministic deployment of contract implementations while maintaining +idempotency across upgrade transactions. It ensures that unchanged contract bytecode always deploys to the same +address, and that attempting to deploy already-deployed bytecode succeeds silently _rather than reverting_. + +This component enables upgrade transactions to unconditionally deploy for all implementation contracts without +requiring developers to manually track which contracts have changed between upgrades. + +The ConditionalDeployer is included in the L2Genesis state to ensure availability for all future network upgrades. It is +deployed as a proxied predeploy at a deterministic address. + +The deployment function returns an address for off-chain convenience, but this return value is not used in Network +Upgrade Transactions, as deployment addresses must be pre-computed before transaction generation. + +### Definitions + +#### CREATE2 Collision + +A CREATE2 collision occurs when attempting to deploy contract bytecode to an address where a contract with identical +bytecode already exists. This happens when the same initcode and salt are used in multiple deployment attempts. + +Note: when CREATE2 targets an address that already has code, the +[zero address is placed on the stack][create2-spec] (execution specs). + +[create2-spec]: +https://github.com/ethereum/execution-specs/blob/4ef381a0f75c96b52da635653ab580e731d3882a/src/ethereum/forks/prague/vm/instructions/system.py#L112 + +#### Deterministic Deployment Proxy + +The canonical deterministic deployment proxy contract at address `0x4e59b44847b379578588920cA78FbF26c0B4956C`, +originally deployed by Nick Johnson (Arachnid). This contract provides CREATE2-based deployment with a fixed deployer +address across all chains. + +Note: when the deterministic deployment proxy deploys to an address that already has code, [it will revert with no data](https://github.com/Arachnid/deterministic-deployment-proxy/blob/be3c5974db5028d502537209329ff2e730ed336c/source/deterministic-deployment-proxy.yul#L13). +Otherwise the ConditionalDeployer would not be required. + +### Assumptions + +#### aCD-001: Deterministic Deployment Proxy is Available and Correct + +The [Deterministic Deployment Proxy](#deterministic-deployment-proxy) exists at the expected address and correctly +implements CREATE2 deployment semantics. The proxy must deterministically compute deployment addresses and execute +deployments as specified. + +##### Mitigations + +- The [Deterministic Deployment Proxy](#deterministic-deployment-proxy) is a well-established contract deployed across + all EVM chains using the same keyless deployment transaction +- The proxy's behavior is verifiable by inspecting its bytecode and testing deployment operations +- The proxy contract is immutable and cannot be upgraded or modified + +#### aCD-002: Initcode is Well-Formed + +Callers provide valid EVM initcode that, when executed, will either successfully deploy a contract or revert with a +clear error. Malformed initcode that produces undefined behavior is not considered. + +##### Mitigations + +- Initcode is generated by the Solidity compiler from verified source code +- The upgrade transaction generation process includes validation of all deployment operations +- Fork-based testing exercises all deployments before inclusion in upgrade bundles + +### Invariants + +#### iCD-001: Deterministic Address Derivation + +For any given initcode and salt combination, the ConditionalDeployer MUST always compute the same deployment address, +regardless of whether the contract has been previously deployed. The address calculation MUST match the CREATE2 +address that would be computed by the [Deterministic Deployment Proxy](#deterministic-deployment-proxy). + +##### Impact + +**Severity: Critical** + +If address derivation is non-deterministic or inconsistent with CREATE2 semantics, upgrade transactions could deploy +implementations to unexpected addresses, breaking proxy upgrade operations. + +#### iCD-002: Idempotent Deployment Operations + +Calling the ConditionalDeployer multiple times with identical initcode and salt MUST produce the same outcome: the +first call deploys the contract, and subsequent calls succeed without modification. No operation should revert due to +a [CREATE2 Collision](#create2-collision). + +##### Impact + +**Severity: Critical** + +If deployments are not idempotent, upgrade transactions that attempt to deploy unchanged implementations would revert +or deploy the implementation to an unexpected address, breaking the upgrade. + +#### iCD-003: Non-Reverting Collision Handling + +When a [CREATE2 Collision](#create2-collision) is detected (contract already deployed at the target address), the +ConditionalDeployer MUST return successfully without reverting and without modifying blockchain state. + +##### Impact + +**Severity: Medium** + +If collisions cause reverts, the presence of reverting transactions in an upgrade block would cause confusion. + +#### iCD-004: Collision Detection Accuracy + +The ConditionalDeployer MUST correctly distinguish between addresses where no contract exists (deploy needed) and +addresses where a contract already exists (collision detected). False negatives (failing to detect existing contracts) +and false positives (detecting non-existent contracts) are both prohibited. + +##### Impact + +**Severity: High** + +False negatives would cause failed deployments while false positives would prevent legitimate deployments, both +breaking the upgrade process. + +#### iCD-005: Contract Availability After Deployment + +After execution of the ConditionalDeployer, the address returned by the deployment operation MUST contain the runtime +bytecode derived from the provided initcode. This ensures that contracts deployed through the ConditionalDeployer are +immediately available and functional at their expected addresses. + +##### Impact + +**Severity: Critical** + +If the contract is not properly available at the expected address after deployment, subsequent transactions that +attempt to call or upgrade to that implementation address will fail, causing the upgrade to fail and potentially +halting the chain. + +## L2ProxyAdmin + +### Overview + +The L2ProxyAdmin is the administrative contract responsible for managing proxy upgrades for L2 predeploy contracts. It +is deployed as a predeploy at address `0x4200000000000000000000000000000000000018` and serves as the `admin` for all +upgradeable L2 predeploy proxies. + +The upgraded L2ProxyAdmin implementation extends the existing proxy administration interface with a new +`upgradePredeploys()` function that orchestrates batch upgrades of multiple predeploys by delegating to an +[L2ContractsManager](#l2contractsmanager) contract. This design enables deterministic, testable upgrade paths written +entirely in Solidity. + +### Definitions + +#### Depositor Account + +The special system address `0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001` controlled by the L2 protocol's derivation +pipeline. This account is used to submit system transactions including L1 attributes updates and upgrade +transactions. + +#### Predeploy + +A contract deployed at a predetermined address in the L2 genesis state. Predeploys provide core L2 protocol +functionality and are typically deployed behind proxies to enable upgradability. + +### Assumptions + +#### aL2PA-001: Depositor Account is Controlled by Protocol + +The [Depositor Account](#depositor-account) is exclusively controlled by the L2 protocol's derivation and execution +pipeline. No external parties can submit transactions from this address. + +##### Mitigations + +- The [Depositor Account](#depositor-account) address has no known private key +- Transactions from this address can only originate from the protocol's derivation pipeline processing L1 deposit events +- The address is hardcoded in the protocol specification and client implementations + +#### aL2PA-002: L2ContractsManager Code is Not Malicious + +The [L2ContractsManager](#l2contractsmanager) contract that receives the DELEGATECALL from `upgradePredeploys()` +correctly implements the upgrade logic and does not contain malicious code. This includes not corrupting the +L2ProxyAdmin's storage, not performing unauthorized operations, and not introducing vulnerabilities when executing +in the context of the L2ProxyAdmin. + +##### Mitigations + +- The L2ContractsManager address is deterministically computed and verified during upgrade bundle generation +- The L2ContractsManager implementation is developed, reviewed, and tested alongside the upgrade bundle +- Fork-based testing validates the complete upgrade execution before production deployment +- The L2ContractsManager bytecode is verifiable against source code on a specific commit +- Code review and security audits examine the L2ContractsManager implementation + +#### aL2PA-003: Predeploy Proxies Follow Expected Patterns + +[Predeploys](#predeploy) being upgraded follow the expected proxy patterns (ERC-1967 or similar) and correctly handle +`upgradeTo()` and `upgradeToAndCall()` operations when called by the ProxyAdmin. + +##### Mitigations + +- All L2 predeploys use standardized proxy implementations from the contracts-bedrock package +- Proxy implementations are thoroughly tested and audited +- Fork-based testing validates upgrade operations against actual deployed proxies + +### Invariants + +#### iL2PA-001: Exclusive Depositor Authorization for Batch Upgrades + +The `upgradePredeploys()` function MUST only be callable by the [Depositor Account](#depositor-account). No other +address, including the current ProxyAdmin owner, can invoke this function. + +##### Impact + +**Severity: Critical** + +If unauthorized addresses could call `upgradePredeploys()`, attackers could execute arbitrary upgrade, enabling +complete takeover of all L2 predeploy contracts. + +#### iL2PA-002: Safe Delegation to L2ContractsManager + +When `upgradePredeploys()` executes a DELEGATECALL to the [L2ContractsManager](#l2contractsmanager), the call MUST +preserve the ProxyAdmin's storage context. + +##### Impact + +**Severity: Critical** + +If the DELEGATECALL is not properly executed, upgrades could fail silently or the ProxyAdmin's storage could be +corrupted, resulting in loss of admin control over predeploys. + +#### iL2PA-003: Backwards Compatibility Maintained + +The upgraded L2ProxyAdmin implementation MUST maintain the existing interface for standard proxy administration +functions. Existing functionality for upgrading individual proxies, changing proxy admins, and querying proxy state +MUST continue to work as before. + +Note: Backwards compatibility requires maintaining the full ProxyAdmin interface, but does not require supporting +upgrades of legacy proxy types (ResolvedDelegate and Chugsplash proxies). Currently, no predeploy uses these legacy +proxy types. + +##### Impact + +**Severity: High** + +If backwards compatibility is broken, existing tooling and scripts that interact with the ProxyAdmin could fail, +preventing emergency responses and breaking operational procedures. + +## L2ContractsManager + +### Overview + +The L2ContractsManager is a contract deployed fresh for each upgrade that contains the upgrade logic and coordination +for a specific set of predeploy upgrades. When invoked via DELEGATECALL from the [L2ProxyAdmin](#l2proxyadmin), it +gathers network-specific configuration from existing predeploys, and executes +upgrade operations for all affected predeploys. + +Each L2ContractsManager instance is purpose-built for a specific upgrade, deployed via the +[ConditionalDeployer](#conditionaldeployer), and referenced directly in the upgrade transaction. The contract is +stateless and contains all upgrade logic in code, ensuring determinism and verifiability. + +The L2ContractsManager assumes that all prerequisite contracts (implementations, ConditionalDeployer, etc.) have +already been deployed and are available in the state before the L2ContractsManager is called. The transaction +execution sequence ensures this ordering. + +### Definitions + +#### Network-Specific Configuration + +Configuration values that vary between L2 chains, such as custom gas token parameters, operator fee configurations, or +chain-specific feature flags. These values are typically stored in system predeploys like `L1Block` and must be +preserved across upgrades. + +#### Feature Flag + +A boolean or enumerated value that enables or disables optional protocol features. Feature flags allow different +upgrade paths for development environments (alphanets), testing environments, and production chains. Flags are read +from a dedicated FeatureFlags contract during upgrade execution. + +#### Initialization Parameters + +Constructor arguments or initializer function parameters required by a predeploy implementation. Similar to the OPCMv2 +implementation, we will assume that all config values are first read, and then contracts are reinitialized with +those same parameters. + +### Assumptions + +#### aL2CM-001: Existing Predeploys Provide Valid Configuration + +The existing [predeploy](#predeploy) contracts contain valid [network-specific configuration](#network-specific-configuration) +that can be read and used during the upgrade. Configuration values are accurate, properly formatted, and represent the +intended chain configuration. + +##### Mitigations + +- Configuration is read from well-established predeploys that have been operating correctly +- Fork-based testing validates configuration gathering against real chain state + +#### aL2CM-002: Implementation Addresses Are Pre-Computed Correctly + +The implementation addresses used by the L2ContractsManager are pre-computed by the off-chain bundle generation script +using the same CREATE2 parameters that will be used by the [ConditionalDeployer](#conditionaldeployer). The +L2ContractsManager receives these addresses via its constructor and does not compute them. Address mismatches would +cause proxies to point to incorrect or non-existent implementations. + +##### Mitigations + +- Implementation addresses are computed off-chain using deterministic CREATE2 formula during bundle generation +- The computed addresses are provided to the L2ContractsManager constructor at deployment time +- Fork-based testing validates that all implementation addresses exist and contain expected bytecode +- Address computation is isolated in shared libraries to prevent divergence + +#### aL2CM-003: Predeploy Proxies Are Upgradeable + +All [predeploy](#predeploy) proxies targeted for upgrade support the `upgradeTo()` and `upgradeToAndCall()` functions +and will accept upgrade calls from the [L2ProxyAdmin](#l2proxyadmin) executing the DELEGATECALL. + +##### Mitigations + +- All L2 predeploys use standardized proxy implementations with well-tested upgrade functions +- Fork-based testing exercises upgrade operations against actual deployed proxies +- Non-upgradeable predeploys (if they exist) will be excluded from the upgrade process + +#### aL2CM-004: Feature Flags Are Correctly Configured + +When [feature flags](#feature-flag) are used to customize upgrade behavior, the FeatureFlags contract is properly +configured in the environment and returns consistent values throughout the upgrade execution. +Production features which are enabled must be exposed by the L1Block contract's interface. + +##### Mitigations + +- Fork and local testing validates feature flag behavior across different configurations + +### Invariants + +#### iL2CM-001: Deterministic Upgrade Execution + +The L2ContractsManager's `upgrade()` function MUST execute deterministically, producing identical state changes when +given identical pre-upgrade blockchain state. The function MUST NOT read external state that could vary between +executions (timestamps, block hashes, etc.) and MUST NOT accept runtime parameters. + +##### Impact + +**Severity: Critical** + +If upgrade execution is non-deterministic, different L2 nodes could produce different post-upgrade states, causing +consensus failures and halting the chain. + +#### iL2CM-002: Configuration Preservation + +All [network-specific configuration](#network-specific-configuration) that exists before the upgrade MUST be preserved +in the upgraded predeploy implementations. Configuration values MUST be read from existing predeploys and properly +passed to new implementations during upgrade. + +##### Impact + +**Severity: Critical** + +If configuration is not preserved, chains could lose critical settings like custom gas token addresses or operator fee +parameters, breaking fee calculations and chain-specific functionality. + +#### iL2CM-003: Upgrade Atomicity + +All predeploy upgrades within a single L2ContractsManager execution MUST succeed or fail atomically. If any upgrade +operation fails, the entire DELEGATECALL MUST revert, leaving all predeploys in their pre-upgrade state. + +##### Impact + +**Severity: Critical** + +If upgrades are not atomic, a partial failure could leave some predeploys upgraded and others not, creating an +inconsistent system state that breaks inter-contract dependencies. + +#### iL2CM-004: Clear-and-Reinitialize Pattern + +For each predeploy being upgraded, the L2ContractsManager MUST: +1. use `upgradeTo()` to set the implementation to the StorageSetter +2. Reset the `initialized` value to 0 for both the OZ v4 storage slot and the OZ v5 ERC-7201 namespaced storage slot +3. use `upgradeToAndCall()` to call the `initialize()` method. + +This ensures storage is properly cleared and reconstructed, avoiding storage layout conflicts. + +If the `_initializing` flag is set during the upgrade—whether in the OZ v4 storage slot or the OZ v5 ERC-7201 +namespaced storage slot—the operation MUST revert. A contract should never be mid-initialization when an upgrade is +applied. + +##### Impact + +**Severity: Critical** + +If contracts are not properly reinitialized with preserved configuration, chain-specific settings could be lost or +storage corruption could occur, breaking critical system contracts. + +#### iL2CM-005: No Storage Corruption During DELEGATECALL + +When executing in the [L2ProxyAdmin](#l2proxyadmin) context via DELEGATECALL, the L2ContractsManager MUST NOT corrupt +or modify the ProxyAdmin's own storage. All storage modifications must be directed to the predeploy proxies being +upgraded. + +##### Impact + +**Severity: Critical** + +If the L2ContractsManager corrupts ProxyAdmin storage, it could change the ProxyAdmin's owner or disable future upgrade +capability, compromising the entire upgrade system. + +#### iL2CM-006: Complete Upgrade Coverage + +The L2ContractsManager MUST upgrade all predeploys intended for the upgrade. It MUST NOT skip predeploys that should +be upgraded, even if their implementations are unchanged, to maintain consistency across all chains executing the +upgrade. + +##### Impact + +**Severity: High** + +If predeploys are skipped incorrectly, chains would have inconsistent contract versions, violating the goal of bringing +all chains to a consistent version. + +#### iL2CM-007: Downgrade Prevention + +The L2ContractsManager MUST NOT downgrade a predeploy to a lower semantic version than the currently deployed +implementation. Before upgrading, if the proxy already has code, the current version MUST be compared against the new +implementation version. If the current version is greater, the upgrade MUST revert. + +##### Impact + +**Severity: High** + +If downgrades are allowed, predeploys could revert to older versions with known bugs or missing features, breaking +chain functionality and violating the deterministic upgrade guarantee. + +## Upgrade Execution + +The L2ContractsManager contract should be deployed with all implementation addresses provided to its constructor and +stored in an `Implementations` struct. Where a dev feature or feature flag requires a different implementation, both +implementations will be deployed and stored in the L2ContractsManager. The L2ContractsManager will contain branching +logic which will enable a different implementation depending on the configured features. + +The upgrade flow of the L2ContractsManager will be similar to the OPCMv2, where the `FullConfig` is first collected from +all contracts, and the config values are provided to the contract's `initializer()` method using `upgradeToAndCall()`. diff --git a/packages/contracts-bedrock/src/dispute/lib/LibGameArgs.sol b/packages/contracts-bedrock/src/dispute/lib/LibGameArgs.sol index 9191e34d34e..f3def4806ea 100644 --- a/packages/contracts-bedrock/src/dispute/lib/LibGameArgs.sol +++ b/packages/contracts-bedrock/src/dispute/lib/LibGameArgs.sol @@ -8,6 +8,7 @@ import { InvalidGameArgsLength } from "src/dispute/lib/Errors.sol"; library LibGameArgs { uint256 public constant PERMISSIONLESS_ARGS_LENGTH = 124; uint256 public constant PERMISSIONED_ARGS_LENGTH = 164; + uint256 public constant ZK_ARGS_LENGTH = 172; /// @notice Struct representing the game arguments. struct GameArgs { @@ -93,4 +94,30 @@ library LibGameArgs { function isValidPermissionedArgs(bytes memory _args) internal pure returns (bool) { return _args.length == PERMISSIONED_ARGS_LENGTH; } + + /// @notice Checks if the provided game arguments are valid for a ZK dispute game. + function isValidZKArgs(bytes memory _args) internal pure returns (bool) { + return _args.length == ZK_ARGS_LENGTH; + } + + /// @notice Decodes the anchorStateRegistry, weth, and l2ChainId from packed ZK game template + /// args as produced by OPContractsManagerUtils._encodeGameArgs for ZK_DISPUTE_GAME. + /// Layout (abi.encodePacked, ZK_ARGS_LENGTH bytes): + /// [0-31] absolutePrestate (bytes32) + /// [32-51] verifier (address) + /// [52-59] maxChallengeDuration (uint64) + /// [60-67] maxProveDuration (uint64) + /// [68-99] challengerBond (uint256) + /// [100-119] anchorStateRegistry (address) + /// [120-139] weth (address) + /// [140-171] l2ChainId (uint256) + function decodeZK(bytes memory _args) internal pure returns (address asr_, address weth_, uint256 l2ChainId_) { + if (_args.length != ZK_ARGS_LENGTH) revert InvalidGameArgsLength(); + assembly { + let base := add(_args, 0x20) + asr_ := shr(96, mload(add(base, 100))) + weth_ := shr(96, mload(add(base, 120))) + l2ChainId_ := mload(add(base, 140)) + } + } } diff --git a/packages/contracts-bedrock/src/dispute/zk/OPSuccinctFaultDisputeGame.sol b/packages/contracts-bedrock/src/dispute/zk/OPSuccinctFaultDisputeGame.sol index f7c1e6e7639..09e23ace37c 100644 --- a/packages/contracts-bedrock/src/dispute/zk/OPSuccinctFaultDisputeGame.sol +++ b/packages/contracts-bedrock/src/dispute/zk/OPSuccinctFaultDisputeGame.sol @@ -146,8 +146,8 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { AccessManager internal immutable ACCESS_MANAGER; /// @notice Semantic version. - /// @custom:semver 0.0.0 - string public constant version = "0.0.0"; + /// @custom:semver 1.1.0 + string public constant version = "1.1.0"; /// @notice The starting timestamp of the game. Timestamp public createdAt; @@ -263,28 +263,25 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { // The first game is initialized with a parent index of uint32.max if (parentIndex() != type(uint32).max) { // For subsequent games, get the parent game's information - (,, IDisputeGame proxy) = DISPUTE_GAME_FACTORY.gameAtIndex(parentIndex()); - - // We perform a subset of AnchorStateRegistry.isGameProper() checks plus isGameRespected(): - // 1. isGameRespected(): Verifies the parent game was respected when it was created. - // There's only one respected game type in an AnchorStateRegistry at a time. - // 2. isGameRetired(): Ensures the game hasn't been retroactively marked as retired. - // 3. isGameBlacklisted(): Confirms the parent game isn't blacklisted. - // Note: isGameRegistered() check is skipped since the parent game is coming directly from factory. - if ( - !ANCHOR_STATE_REGISTRY.isGameRespected(proxy) || ANCHOR_STATE_REGISTRY.isGameBlacklisted(proxy) - || ANCHOR_STATE_REGISTRY.isGameRetired(proxy) - ) { + (,, IDisputeGame parent) = disputeGameFactory.gameAtIndex(parentIndex()); + + // Verify parent game is not blacklisted or retired. + if (anchorStateRegistry().isGameBlacklisted(parent) || anchorStateRegistry().isGameRetired(parent)) { revert InvalidParentGame(); } + // INVARIANT: The parent game must be of the same game type. + if (IDisputeGame(payable(address(parent))).gameType().raw() != gameType().raw()) { + revert UnexpectedGameType(); + } + startingProposal = Proposal({ - l2SequenceNumber: OPSuccinctFaultDisputeGame(address(proxy)).l2SequenceNumber(), - root: Hash.wrap(OPSuccinctFaultDisputeGame(address(proxy)).rootClaim().raw()) + l2SequenceNumber: IDisputeGame(payable(address(parent))).l2SequenceNumber(), + root: Hash.wrap(IDisputeGame(payable(address(parent))).rootClaim().raw()) }); // INVARIANT: The parent game must be a valid game. - if (proxy.status() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); + if (parent.status() == GameStatus.CHALLENGER_WINS) revert InvalidParentGame(); } else { // When there is no parent game, the starting output root is the anchor state for the game type. (startingProposal.root, startingProposal.l2SequenceNumber) = @@ -412,7 +409,10 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { } /// @notice Returns the status of the parent game. - /// @dev If the parent game index is `uint32.max`, then the parent game's status is considered as `DEFENDER_WINS`. + /// @dev A parentIndex of uint32.max is the sentinel value representing the absence of a parent game + /// Treating the anchor as DEFENDER_WINS is safe because the anchor + /// state is only ever updated from a previously resolved DEFENDER_WINS game, so its root + /// is already trusted. Any other parentIndex fetches the actual parent from the factory. function getParentGameStatus() private view returns (GameStatus) { if (parentIndex() != type(uint32).max) { (,, IDisputeGame parentGame) = DISPUTE_GAME_FACTORY.gameAtIndex(parentIndex()); @@ -430,17 +430,22 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { /// `CHALLENGER_WINS` when the proposer's claim has been challenged, but the proposer has not proven /// its claim within the `MAX_PROVE_DURATION`. function resolve() external returns (GameStatus) { - // INVARIANT: Resolution cannot occur unless the game has already been resolved. + // INVARIANT: Resolution cannot occur if the game has already been resolved. if (status != GameStatus.IN_PROGRESS) revert ClaimAlreadyResolved(); // INVARIANT: Cannot resolve a game if the parent game has not been resolved. + // Note: Parent blacklisting or retirement is NOT propagated automatically to descendants. + // resolve() only checks the parent's GameStatus. If a parent is blacklisted after a child is created, + // the child must be manually blacklisted by the guardian to enter REFUND mode. GameStatus parentGameStatus = getParentGameStatus(); if (parentGameStatus == GameStatus.IN_PROGRESS) revert ParentGameNotResolved(); // INVARIANT: If the parent game's claim is invalid, then the current game's claim is invalid. if (parentGameStatus == GameStatus.CHALLENGER_WINS) { // Parent game is invalid so this game is invalid too. Therefore the challenger wins and gets all bonds. - // If the game has not been challenged then there will not be any challenger address and the bond is burned. + // Note: If unchallenged, the bond is credited to normalModeCredit[address(0)] and effectively + // burned inside DelayedWETH where the owner can recover it via hold()/recover(). Proposers + // should wait for sufficient parent finality before extending to avoid this loss. status = GameStatus.CHALLENGER_WINS; normalModeCredit[claimData.counteredBy] = address(this).balance; } else { @@ -538,8 +543,26 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { revert InvalidBondDistributionMode(); } + // We won't close the game if the system is currently paused. Paused games are temporarily + // invalid which would cause the game to go into refund mode and potentially cause some + // confusion for honest challengers. By blocking the game from being closed while the + // system is paused, the game will only go into refund mode if it ends up being explicitly + // invalidated in the AnchorStateRegistry. If the game has already been closed and a refund + // mode has been selected, we'll already have returned and we won't hit this revert. + if (anchorStateRegistry().paused()) { + revert GamePaused(); + } + + // Make sure that the game is resolved. + // AnchorStateRegistry should be checking this but we're being defensive here. + if (resolvedAt.raw() == 0) { + revert GameNotResolved(); + } + + IDisputeGame self = IDisputeGame(address(this)); + // Game must be finalized according to the AnchorStateRegistry. - bool finalized = ANCHOR_STATE_REGISTRY.isGameFinalized(IDisputeGame(address(this))); + bool finalized = anchorStateRegistry().isGameFinalized(self); if (!finalized) { revert GameNotFinalized(); } @@ -547,10 +570,10 @@ contract OPSuccinctFaultDisputeGame is Clone, ISemver, IDisputeGame { // Try to update the anchor game first. Won't always succeed because delays can lead // to situations in which this game might not be eligible to be a new anchor game. // nosemgrep: sol-safety-trycatch-eip150 - try ANCHOR_STATE_REGISTRY.setAnchorState(IDisputeGame(address(this))) { } catch { } + try anchorStateRegistry().setAnchorState(self) { } catch { } // Check if the game is a proper game, which will determine the bond distribution mode. - bool properGame = ANCHOR_STATE_REGISTRY.isGameProper(IDisputeGame(address(this))); + bool properGame = anchorStateRegistry().isGameProper(self); // If the game is a proper game, the bonds should be distributed normally. Otherwise, go // into refund mode and distribute bonds back to their original depositors. diff --git a/packages/contracts-bedrock/test/dispute/zk/ZKDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/zk/ZKDisputeGame.t.sol new file mode 100644 index 00000000000..d238d2fbeaf --- /dev/null +++ b/packages/contracts-bedrock/test/dispute/zk/ZKDisputeGame.t.sol @@ -0,0 +1,1342 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { DisputeGameFactory_TestInit } from "test/dispute/DisputeGameFactory.t.sol"; + +// Libraries +import { DevFeatures } from "src/libraries/DevFeatures.sol"; +import { BondDistributionMode, Claim, Duration, GameStatus, GameType, Hash, Timestamp } from "src/dispute/lib/Types.sol"; +import { + IncorrectBondAmount, + UnexpectedRootClaim, + UnexpectedGameType, + NoCreditToClaim, + GameNotFinalized, + GameNotResolved, + GamePaused, + ParentGameNotResolved, + InvalidParentGame, + ClaimAlreadyChallenged, + ClaimAlreadyResolved, + GameOver, + GameNotOver, + UnknownChainId, + BondTransferFailed, + AlreadyInitialized +} from "src/dispute/lib/Errors.sol"; +import { GameTypes } from "src/dispute/lib/Types.sol"; + +// Contracts +import { ZKDisputeGame } from "src/dispute/zk/ZKDisputeGame.sol"; + +// Interfaces +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IZKVerifier } from "interfaces/dispute/zk/IZKVerifier.sol"; + +/// @title ZKDisputeGame_TestInit +/// @notice Base test contract with shared setup for ZKDisputeGame tests. +abstract contract ZKDisputeGame_TestInit is DisputeGameFactory_TestInit { + // Events + event Challenged(address indexed challenger); + event Proved(address indexed prover); + event Resolved(GameStatus indexed status); + + ZKDisputeGame gameImpl; + ZKDisputeGame parentGame; + ZKDisputeGame game; + + address proposer = address(0x123); + address challenger = address(0x456); + address prover = address(0x789); + + // Fixed parameters. + GameType gameType = GameTypes.ZK_DISPUTE_GAME; + Duration maxChallengeDuration = Duration.wrap(12 hours); + Duration maxProveDuration = Duration.wrap(3 days); + Claim rootClaim = Claim.wrap(keccak256("rootClaim")); + + // Sequence number offsets from anchor state (for parent and child games). + uint256 parentSequenceOffset = 1000; + uint256 childSequenceOffset = 2000; + + // Game indices are set dynamically in setUp (on fork, existing games already exist) + uint32 parentGameIndex; + uint32 childGameIndex; + + // Offsets from child sequence number for grandchild games. + uint256 grandchildOffset1 = 1000; + uint256 grandchildOffset2 = 2000; + uint256 grandchildOffset3 = 3000; + uint256 grandchildOffset4 = 8000; + + // Actual sequence numbers (set in setUp based on anchor state) + uint256 anchorL2SequenceNumber; + uint256 parentL2SequenceNumber; + uint256 childL2SequenceNumber; + + function setUp() public virtual override { + super.setUp(); + skipIfDevFeatureDisabled(DevFeatures.ZK_DISPUTE_GAME); + + // Get anchor state to calculate valid sequence numbers + (, anchorL2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); + parentL2SequenceNumber = anchorL2SequenceNumber + parentSequenceOffset; + childL2SequenceNumber = anchorL2SequenceNumber + childSequenceOffset; + + // Setup game implementation using shared helper + address impl; + (impl,) = setupZKDisputeGame( + ZKDisputeGameParams({ + maxChallengeDuration: maxChallengeDuration, + maxProveDuration: maxProveDuration, + absolutePrestate: bytes32(0), + challengerBond: 1 ether + }) + ); + gameImpl = ZKDisputeGame(payable(impl)); + + // Create the first (parent) game - it uses uint32.max as parent index. + vm.startPrank(proposer); + vm.deal(proposer, 10 ether); + + // Warp time forward to ensure the parent game is created after the respectedGameTypeUpdatedAt timestamp. + vm.warp(block.timestamp + 1000); + + // Create parent game (uses uint32.max to indicate first game in chain). + parentGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("genesis")), + abi.encodePacked(parentL2SequenceNumber, type(uint32).max) + ) + ) + ) + ); + + // Record actual index of parent game (on fork, existing games already occupy indices 0, 1, ...) + parentGameIndex = uint32(disputeGameFactory.gameCount() - 1); + + // We want the parent game to finalize. We'll skip its challenge period. + (,,,, Timestamp parentGameDeadline,) = parentGame.claimData(); + vm.warp(parentGameDeadline.raw() + 1 seconds); + parentGame.resolve(); + + // Create the child game before claiming parent credit, because claimCredit triggers + // closeGame() which advances the anchor to parentL2SequenceNumber. After that, the + // parent's seq num would equal the anchor, violating the "strictly above" invariant. + game = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, rootClaim, abi.encodePacked(childL2SequenceNumber, parentGameIndex) + ) + ) + ) + ); + + // Record actual index of child game. + childGameIndex = uint32(disputeGameFactory.gameCount() - 1); + + vm.stopPrank(); + } + + /// @notice Helper to perform two-phase credit claim (unlock + withdraw). + function _claimCreditTwoPhase(ZKDisputeGame _game, address _recipient) internal { + _game.claimCredit(_recipient); // Phase 1: unlock + vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); + _game.claimCredit(_recipient); // Phase 2: withdraw + } +} + +/// @title ZKDisputeGame_Initialize_Test +/// @notice Tests for initialization of ZKDisputeGame. +contract ZKDisputeGame_Initialize_Test is ZKDisputeGame_TestInit { + function test_initialize_succeeds() public view { + // Test that the factory is correctly initialized. + assertEq(address(disputeGameFactory.owner()), address(this)); + assertEq(address(disputeGameFactory.gameImpls(gameType)), address(gameImpl)); + // We expect games including parent and child (indices may vary on fork). + assertEq(disputeGameFactory.gameCount(), childGameIndex + 1); + + // Check that our child game matches the game at childGameIndex. + (,, IDisputeGame proxy_) = disputeGameFactory.gameAtIndex(childGameIndex); + assertEq(address(game), address(proxy_)); + + // Check the child game fields via CWIA getters. + assertEq(game.gameType().raw(), gameType.raw()); + assertEq(game.rootClaim().raw(), rootClaim.raw()); + assertEq(game.maxChallengeDuration().raw(), maxChallengeDuration.raw()); + assertEq(game.maxProveDuration().raw(), maxProveDuration.raw()); + assertEq(address(game.disputeGameFactory()), address(disputeGameFactory)); + assertEq(game.l2SequenceNumber(), childL2SequenceNumber); + assertEq(game.l2ChainId(), l2ChainId); + assertEq(address(game.weth()), address(delayedWeth)); + assertEq(address(game.anchorStateRegistry()), address(anchorStateRegistry)); + assertEq(game.parentIndex(), parentGameIndex); + assertEq(game.absolutePrestate(), bytes32(0)); + assertTrue(address(game.verifier()) != address(0)); + assertEq(game.challengerBond(), 1 ether); + assertTrue(game.l1Head().raw() != bytes32(0)); + assertEq(game.rootClaimByChainId(l2ChainId).raw(), rootClaim.raw()); + + // The game was created while its game type was respected. + assertTrue(game.wasRespectedGameTypeWhenCreated()); + + // extraData is 36 bytes: l2SequenceNumber (32) + parentIndex (4). + bytes memory extra = game.extraData(); + assertEq(extra.length, 36); + + // The parent's sequence number (startingBlockNumber() returns l2SequenceNumber). + assertEq(game.startingBlockNumber(), parentL2SequenceNumber); + + // The parent's root was keccak256("genesis"). + assertEq(game.startingRootHash().raw(), keccak256("genesis")); + + // ETH is deposited into DelayedWETH, so game balance is 0. + assertEq(address(game).balance, 0); + + // Check the claimData. + ( + uint32 parentIndex_, + ZKDisputeGame.ProposalStatus status_, + address challenger_, + address prover_, + Timestamp deadline_, + Claim claim_ + ) = game.claimData(); + + assertEq(parentIndex_, parentGameIndex); + assertEq(challenger_, address(0)); + assertEq(game.gameCreator(), proposer); + assertEq(prover_, address(0)); + assertEq(claim_.raw(), rootClaim.raw()); + + // Initially, the status is Unchallenged. + assertEq(uint8(status_), uint8(ZKDisputeGame.ProposalStatus.Unchallenged)); + + // The child's initial deadline is block.timestamp + maxChallengeDuration. + uint256 currentTime = block.timestamp; + uint256 expectedDeadline = currentTime + maxChallengeDuration.raw(); + assertEq(deadline_.raw(), expectedDeadline); + } + + function test_initialize_permissionless_succeeds() public { + // Any address can propose (permissionless). + address anyUser = address(0x9999); + vm.startPrank(anyUser); + vm.deal(anyUser, 1 ether); + + ZKDisputeGame newGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("permissionless-claim")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ) + ) + ) + ); + vm.stopPrank(); + + assertEq(newGame.gameCreator(), anyUser); + } + + function test_initialize_blockNumberTooSmall_reverts() public { + // Try to create a child game that references a block number smaller than parent's. + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + + // We expect revert because l2BlockNumber (1) < parent's block number + vm.expectRevert( + abi.encodeWithSelector( + UnexpectedRootClaim.selector, + Claim.wrap(keccak256("rootClaim")) // The rootClaim we pass. + ) + ); + + disputeGameFactory.create{ value: 1 ether }( + gameType, + rootClaim, + abi.encodePacked(uint256(1), parentGameIndex) // L2 block is smaller than parent's block. + ); + vm.stopPrank(); + } + + function testFuzz_initialize_blockNumberTooLarge_reverts(uint256 _l2SequenceNumber) public { + _l2SequenceNumber = bound(_l2SequenceNumber, uint256(type(uint64).max) + 1, type(uint256).max); + + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(abi.encodeWithSelector(UnexpectedRootClaim.selector, rootClaim)); + disputeGameFactory.create{ value: 1 ether }( + gameType, rootClaim, abi.encodePacked(_l2SequenceNumber, parentGameIndex) + ); + vm.stopPrank(); + } + + function test_initialize_parentBlacklisted_reverts() public { + // Blacklist the game on the anchor state registry (which is what's actually used for validation). + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); + + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(InvalidParentGame.selector); + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("blacklisted-parent-game")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ); + vm.stopPrank(); + } + + function test_initialize_parentBlacklistedAfterCreation_reverts() public { + // Create a new game which will be the parent. + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + ZKDisputeGame parentNotRespected = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("not-respected-parent-game")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ) + ) + ) + ); + uint32 parentNotRespectedIndex = uint32(disputeGameFactory.gameCount() - 1); + vm.stopPrank(); + + // Blacklist the parent game to make it invalid. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentNotRespected))); + + // Try to create a game with a parent game that is not valid. + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(InvalidParentGame.selector); + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("child-with-not-respected-parent")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset2, parentNotRespectedIndex) + ); + vm.stopPrank(); + } + + function test_initialize_parentRetired_reverts() public { + // Retire all existing games by updating the retirement timestamp. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + + // Try to create a new game referencing the (now retired) child game as parent. + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(InvalidParentGame.selector); + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("child-of-retired-parent")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ); + vm.stopPrank(); + } + + function test_initialize_parentChallengerWins_reverts() public { + // Challenge the child game (our `game`) and let it expire without proof. + vm.startPrank(challenger); + vm.deal(challenger, 1 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + + // Trying to create a new game referencing the invalidated game should revert. + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(InvalidParentGame.selector); + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("child-of-challenger-wins")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ); + vm.stopPrank(); + } + + function test_initialize_parentDifferentGameType_reverts() public { + // Deploy a second ZK game impl with a different GameType id. + IZKVerifier zkVerifier = IZKVerifier(address(new ZKMockVerifier())); + address newImpl = address(new ZKDisputeGame()); + GameType differentGameType = GameType.wrap(201); + + bytes memory gameArgs = abi.encodePacked( + bytes32(0), + zkVerifier, + maxChallengeDuration, + maxProveDuration, + uint256(1 ether), + anchorStateRegistry, + delayedWeth, + l2ChainId + ); + + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.setRespectedGameType(differentGameType); + + vm.startPrank(disputeGameFactory.owner()); + disputeGameFactory.setImplementation(differentGameType, IDisputeGame(newImpl), gameArgs); + disputeGameFactory.setInitBond(differentGameType, 1 ether); + vm.stopPrank(); + + // Try to create a game of differentGameType referencing childGameIndex (which is gameType). + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(UnexpectedGameType.selector); + disputeGameFactory.create{ value: 1 ether }( + differentGameType, + Claim.wrap(keccak256("different-type-claim")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ); + vm.stopPrank(); + } + + function test_initialize_parentBelowAnchor_reverts() public { + // Resolve and finalize the child game so it becomes the new anchor. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + uint256 finalityDelay = anchorStateRegistry.disputeGameFinalityDelaySeconds(); + vm.warp(game.resolvedAt().raw() + finalityDelay + 1 seconds); + game.closeGame(); + + // Now the anchor is at childL2SequenceNumber, which is above the parent's sequence number. + // Trying to create a new game referencing the parent (whose seq num < anchor) should revert. + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(InvalidParentGame.selector); + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("below-anchor-claim")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, parentGameIndex) + ); + vm.stopPrank(); + } + + function test_initialize_parentEqualAnchor_reverts() public { + // Resolve and finalize the child game so it becomes the new anchor. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + uint256 finalityDelay = anchorStateRegistry.disputeGameFinalityDelaySeconds(); + vm.warp(game.resolvedAt().raw() + finalityDelay + 1 seconds); + game.closeGame(); + + // Anchor is now at childL2SequenceNumber. + // The child game's l2SequenceNumber == anchor, so using it as parent should revert + // because parent seq num must be strictly above anchor. + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(InvalidParentGame.selector); + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("equal-anchor-claim")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ); + vm.stopPrank(); + } + + function test_initialize_l2ChainIdZero_reverts() public { + // Deploy a new game impl with l2ChainId = 0 in gameArgs + IZKVerifier zkVerifier = IZKVerifier(address(new ZKMockVerifier())); + address newImpl = address(new ZKDisputeGame()); + + bytes memory gameArgs = abi.encodePacked( + bytes32(0), // absolutePrestate + zkVerifier, // verifier + maxChallengeDuration, // maxChallengeDuration + maxProveDuration, // maxProveDuration + uint256(1 ether), // challengerBond + anchorStateRegistry, // anchorStateRegistry + delayedWeth, // weth + uint256(0) // l2ChainId = 0 + ); + + GameType zeroChainGameType = GameType.wrap(200); + + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.setRespectedGameType(zeroChainGameType); + + vm.startPrank(disputeGameFactory.owner()); + disputeGameFactory.setImplementation(zeroChainGameType, IDisputeGame(newImpl), gameArgs); + disputeGameFactory.setInitBond(zeroChainGameType, 1 ether); + vm.stopPrank(); + + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + vm.expectRevert(UnknownChainId.selector); + disputeGameFactory.create{ value: 1 ether }( + zeroChainGameType, + Claim.wrap(keccak256("zero-chain-claim")), + abi.encodePacked(parentL2SequenceNumber, type(uint32).max) + ); + vm.stopPrank(); + } + + function test_initialize_fromAnchorState_succeeds() public { + // Create a first game (parentIndex = uint32.max) and verify it uses anchor state values. + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + + uint256 seqNum = anchorL2SequenceNumber + 5000; + ZKDisputeGame anchorGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("anchor-start-claim")), + abi.encodePacked(seqNum, type(uint32).max) + ) + ) + ) + ); + vm.stopPrank(); + + // The starting proposal should match the anchor state values. + (Hash anchorRoot, uint256 anchorSeqNum) = anchorStateRegistry.getAnchorRoot(); + assertEq(anchorGame.startingRootHash().raw(), anchorRoot.raw()); + assertEq(anchorGame.startingBlockNumber(), anchorSeqNum); + assertEq(anchorGame.parentIndex(), type(uint32).max); + } + + function test_initialize_alreadyInitialized_reverts() public { + // The game is already initialized in setUp. Calling initialize again should revert. + vm.expectRevert(AlreadyInitialized.selector); + game.initialize{ value: 1 ether }(); + } +} + +/// @title ZKDisputeGame_Challenge_Test +/// @notice Tests for challenge functionality of ZKDisputeGame. +contract ZKDisputeGame_Challenge_Test is ZKDisputeGame_TestInit { + function test_challenge_permissionless_succeeds() public { + // Record deadline before challenge. + (,,,, Timestamp deadlineBefore,) = game.claimData(); + + // Any address can challenge (permissionless). + address anyChallenger = address(0x9999); + vm.startPrank(anyChallenger); + vm.deal(anyChallenger, 1 ether); + + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + (,, address challenger_,, Timestamp deadlineAfter,) = game.claimData(); + assertEq(challenger_, anyChallenger); + + // The deadline should be reset to block.timestamp + maxProveDuration after challenge. + assertEq(deadlineAfter.raw(), block.timestamp + maxProveDuration.raw()); + assertTrue(deadlineAfter.raw() != deadlineBefore.raw()); + } + + function test_challenge_alreadyChallenged_reverts() public { + // Initially unchallenged. + (, ZKDisputeGame.ProposalStatus status_, address challenger_,,,) = game.claimData(); + assertEq(challenger_, address(0)); + assertEq(uint8(status_), uint8(ZKDisputeGame.ProposalStatus.Unchallenged)); + + // The first challenge is valid. + vm.startPrank(challenger); + vm.deal(challenger, 2 ether); + game.challenge{ value: 1 ether }(); + + // A second challenge from any party should revert because the proposal is no longer "Unchallenged". + vm.expectRevert(ClaimAlreadyChallenged.selector); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + } + + function test_challenge_afterDeadline_reverts() public { + // Warp past the challenge deadline so the game is over. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + + vm.startPrank(challenger); + vm.deal(challenger, 1 ether); + vm.expectRevert(GameOver.selector); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + } + + function test_challenge_afterProve_reverts() public { + // Prove the game so it is over (prover != address(0)). + vm.prank(prover); + game.prove(bytes("")); + + vm.startPrank(challenger); + vm.deal(challenger, 1 ether); + vm.expectRevert(GameOver.selector); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + } +} + +/// @title ZKDisputeGame_Prove_Test +/// @notice Tests for prove functionality of ZKDisputeGame. +contract ZKDisputeGame_Prove_Test is ZKDisputeGame_TestInit { + function test_prove_afterDeadline_reverts() public { + // Challenge first. + vm.startPrank(challenger); + vm.deal(challenger, 1 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + // Move time forward beyond the prove period. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + + vm.startPrank(prover); + // Attempting to prove after the deadline is exceeded. + vm.expectRevert(GameOver.selector); + game.prove(bytes("")); + vm.stopPrank(); + } + + function test_prove_alreadyProved_reverts() public { + vm.startPrank(prover); + game.prove(bytes("")); + vm.expectRevert(GameOver.selector); + game.prove(bytes("")); + vm.stopPrank(); + } + + function test_prove_alreadyResolved_reverts() public { + // Warp past the challenge deadline so the game is over. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + + // Resolve the game. + game.resolve(); + + // Attempting to prove after resolution should revert. + vm.expectRevert(ClaimAlreadyResolved.selector); + game.prove(bytes("")); + } + + function test_prove_parentChallengerWins_reverts() public { + // Create a child game referencing our game as parent. + vm.startPrank(proposer); + ZKDisputeGame childGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("child-claim")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ) + ) + ) + ); + vm.stopPrank(); + + // Challenge the parent game so it resolves as CHALLENGER_WINS. + vm.startPrank(challenger); + vm.deal(challenger, 1 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + (,,,, Timestamp gameDeadline,) = game.claimData(); + vm.warp(gameDeadline.raw() + 1); + game.resolve(); + + // Attempting to prove the child game should revert because parent is invalid. + vm.expectRevert(InvalidParentGame.selector); + childGame.prove(bytes("")); + } + + function test_prove_emitsProvedEvent_succeeds() public { + vm.expectEmit(true, false, false, false, address(game)); + emit Proved(prover); + + vm.prank(prover); + game.prove(bytes("")); + } + + function test_prove_invalidProof_reverts() public { + // Deploy a rejecting verifier and create a game that uses it. + ZKRejectingVerifier rejectingVerifier = new ZKRejectingVerifier(); + address newImpl = address(new ZKDisputeGame()); + GameType rejectGameType = GameType.wrap(202); + + bytes memory gameArgs = abi.encodePacked( + bytes32(0), + rejectingVerifier, + maxChallengeDuration, + maxProveDuration, + uint256(1 ether), + anchorStateRegistry, + delayedWeth, + l2ChainId + ); + + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.setRespectedGameType(rejectGameType); + + vm.startPrank(disputeGameFactory.owner()); + disputeGameFactory.setImplementation(rejectGameType, IDisputeGame(newImpl), gameArgs); + disputeGameFactory.setInitBond(rejectGameType, 1 ether); + vm.stopPrank(); + + vm.startPrank(proposer); + vm.deal(proposer, 1 ether); + ZKDisputeGame rejectGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + rejectGameType, + Claim.wrap(keccak256("reject-claim")), + abi.encodePacked(parentL2SequenceNumber, type(uint32).max) + ) + ) + ) + ); + vm.stopPrank(); + + // Proving should revert because the verifier always rejects. + vm.expectRevert("ZKRejectingVerifier: invalid proof"); + vm.prank(prover); + rejectGame.prove(bytes("")); + } +} + +/// @title ZKDisputeGame_Resolve_Test +/// @notice Tests for resolve functionality of ZKDisputeGame. +contract ZKDisputeGame_Resolve_Test is ZKDisputeGame_TestInit { + function test_resolve_unchallenged_succeeds() public { + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + + // Should revert if we try to resolve before deadline. + vm.expectRevert(GameNotOver.selector); + game.resolve(); + + // Warp forward past the challenge deadline. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + + // Expect the Resolved event. + vm.expectEmit(true, false, false, false, address(game)); + emit Resolved(GameStatus.DEFENDER_WINS); + + // Now we can resolve successfully. + game.resolve(); + + // Proposer gets the bond back (two-phase). + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + vm.prank(proposer); + _claimCreditTwoPhase(game, proposer); + + // Check final state + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + // After withdrawal, game balance is 0. + assertEq(address(game).balance, 0); + } + + function test_resolve_unchallengedWithProof_succeeds() public { + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + + // Should revert if we try to resolve before the first challenge deadline. + vm.expectRevert(GameNotOver.selector); + game.resolve(); + + // Prover proves the claim while unchallenged. + vm.startPrank(prover); + game.prove(bytes("")); + vm.stopPrank(); + + // Now the proposal is UnchallengedAndValidProofProvided; we can resolve immediately. + game.resolve(); + + // Prover does not get any credit. First call closes the game and returns without + // reverting; second call reverts with NoCreditToClaim. + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + game.claimCredit(prover); + vm.expectRevert(NoCreditToClaim.selector); + game.claimCredit(prover); + + // Proposer gets the bond back (two-phase). + _claimCreditTwoPhase(game, proposer); + + // Final status: DEFENDER_WINS. + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + assertEq(address(game).balance, 0); + } + + function test_resolve_challengedWithProof_succeeds() public { + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + + // Try to resolve too early. + vm.expectRevert(GameNotOver.selector); + game.resolve(); + + // Challenger posts the bond incorrectly. + vm.startPrank(challenger); + vm.deal(challenger, 2 ether); + + // Must pay exactly the required bond. + vm.expectRevert(IncorrectBondAmount.selector); + game.challenge{ value: 0.5 ether }(); + + // Correctly challenge the game. + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + // Confirm the proposal is in Challenged state. + (, ZKDisputeGame.ProposalStatus challStatus, address challenger_,,,) = game.claimData(); + assertEq(challenger_, challenger); + assertEq(uint8(challStatus), uint8(ZKDisputeGame.ProposalStatus.Challenged)); + + // Prover proves the claim in time. + vm.startPrank(prover); + game.prove(bytes("")); + vm.stopPrank(); + + // Confirm the proposal is now ChallengedAndValidProofProvided. + (, challStatus,,,,) = game.claimData(); + assertEq(uint8(challStatus), uint8(ZKDisputeGame.ProposalStatus.ChallengedAndValidProofProvided)); + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + + // Resolve the game. + game.resolve(); + + // Prover gets the proof reward (two-phase). + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + _claimCreditTwoPhase(game, prover); + + // Proposer gets the bond back (two-phase). + _claimCreditTwoPhase(game, proposer); + + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + assertEq(address(game).balance, 0); + + // Final balances: + // - The prover gets 1 ether reward (challenger's bond). + // - The challenger gets nothing. + assertEq(prover.balance, 1 ether); + assertEq(challenger.balance, 1 ether); // started with 2, spent 1 + } + + function test_resolve_challengedProverIsCreator_succeeds() public { + // Challenge the game. + vm.startPrank(challenger); + vm.deal(challenger, 1 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + // Proposer (game creator) proves their own claim. + vm.prank(proposer); + game.prove(bytes("")); + + // Resolve the game. + game.resolve(); + + // Proposer should get the entire totalBonds (2 ether: 1 proposer bond + 1 challenger bond). + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + uint256 balanceBefore = proposer.balance; + _claimCreditTwoPhase(game, proposer); + + assertEq(proposer.balance, balanceBefore + 2 ether); + assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + + // Challenger gets nothing. Game already closed, so second call reverts. + vm.expectRevert(NoCreditToClaim.selector); + game.claimCredit(challenger); + } + + function test_resolve_challengedNoProof_succeeds() public { + // Challenge the game. + vm.startPrank(challenger); + vm.deal(challenger, 2 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + // We must wait for the prove deadline to pass. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + + // Now we can resolve, resulting in CHALLENGER_WINS. + game.resolve(); + + // Challenger gets the bond back and wins proposer's bond (two-phase). + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + _claimCreditTwoPhase(game, challenger); + + assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + + // The challenger receives the entire 2 ether (proposer bond + challenger bond). + assertEq(challenger.balance, 3 ether); // started with 2, spent 1, got 2 from the game. + + // The contract balance is zero. + assertEq(address(game).balance, 0); + } + + function test_resolve_alreadyResolved_reverts() public { + // Warp past deadline and resolve. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + // Second resolve should revert. + vm.expectRevert(ClaimAlreadyResolved.selector); + game.resolve(); + } + + function test_resolve_parentGameInProgress_reverts() public { + vm.startPrank(proposer); + + // Create a new game referencing the child game as parent. + ZKDisputeGame childGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("new-claim")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ) + ) + ) + ); + + vm.stopPrank(); + + // The parent game is still in progress, not resolved. + // So, if we try to resolve the childGame, it should revert with ParentGameNotResolved. + vm.expectRevert(ParentGameNotResolved.selector); + childGame.resolve(); + } + + function test_resolve_parentGameInvalid_succeeds() public { + // 1) Now create a child game referencing that losing parent at index 1. + vm.startPrank(proposer); + ZKDisputeGame childGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("child-of-loser")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset4, childGameIndex) + ) + ) + ) + ); + vm.stopPrank(); + + // 2) Challenge the parent game so that it ends up CHALLENGER_WINS when proof is not provided within the prove + // deadline. + vm.startPrank(challenger); + vm.deal(challenger, 2 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + // 3) Warp past the prove deadline. + (,,,, Timestamp gameDeadline,) = game.claimData(); + vm.warp(gameDeadline.raw() + 1); + + // 4) The game resolves as CHALLENGER_WINS. + game.resolve(); + + // Challenger gets the bond back and wins proposer's bond (two-phase). + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + _claimCreditTwoPhase(game, challenger); + + assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + + // 5) If we try to resolve the child game, it should be resolved as CHALLENGER_WINS + // because parent's claim is invalid. + // The child's bond is lost since there is no challenger for the child game. + childGame.resolve(); + + // Challenger hasn't challenged the child game, so it gets nothing. First call closes + // the game and returns without reverting; second call reverts with NoCreditToClaim. + vm.warp(childGame.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + childGame.claimCredit(challenger); + vm.expectRevert(NoCreditToClaim.selector); + childGame.claimCredit(challenger); + + assertEq(uint8(childGame.status()), uint8(GameStatus.CHALLENGER_WINS)); + + // Bond is in DelayedWETH, game ETH balance is 0. + assertEq(address(childGame).balance, 0); + } + + function test_resolve_parentInvalidChildChallenged_succeeds() public { + // Create a child game referencing our game as parent. + vm.startPrank(proposer); + ZKDisputeGame childGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("child-of-invalid-parent")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset4, childGameIndex) + ) + ) + ) + ); + vm.stopPrank(); + + // Challenge the child game. + vm.startPrank(challenger); + vm.deal(challenger, 2 ether); + childGame.challenge{ value: 1 ether }(); + vm.stopPrank(); + + // Make the parent game invalid: challenge it and let it expire without proof. + vm.startPrank(address(0xABC)); + vm.deal(address(0xABC), 1 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + (,,,, Timestamp gameDeadline,) = game.claimData(); + vm.warp(gameDeadline.raw() + 1); + game.resolve(); + assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + + // Resolve the child game - parent is invalid so challenger wins everything. + childGame.resolve(); + assertEq(uint8(childGame.status()), uint8(GameStatus.CHALLENGER_WINS)); + + // Challenger should get totalBonds (2 ether: proposer bond + challenger bond). + vm.warp(childGame.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + uint256 balanceBefore = challenger.balance; + _claimCreditTwoPhase(childGame, challenger); + assertEq(challenger.balance, balanceBefore + 2 ether); + } +} + +/// @title ZKDisputeGame_ClaimCredit_Test +/// @notice Tests for claimCredit functionality of ZKDisputeGame. +contract ZKDisputeGame_ClaimCredit_Test is ZKDisputeGame_TestInit { + /// @notice Phase 1: claimCredit zeros the credit mapping and unlocks in DelayedWETH, + /// then returns early. The recipient's on-chain credit is zeroed immediately. + function test_claimCredit_phaseOne_succeeds() public { + // Resolve and finalize the game so it can be closed. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + + // Before phase 1: proposer has non-zero refund credit. + assertGt(game.refundModeCredit(proposer), 0); + + // Phase 1: credit is zeroed and unlock is queued in DelayedWETH. + game.claimCredit(proposer); + + // After phase 1: on-chain credit mappings are zeroed. + assertEq(game.refundModeCredit(proposer), 0); + assertEq(game.normalModeCredit(proposer), 0); + + // A pending withdrawal should exist in DelayedWETH. + (uint256 amount,) = delayedWeth.withdrawals(address(game), proposer); + assertGt(amount, 0); + } + + /// @notice Phase 2: after the DelayedWETH delay, claimCredit withdraws and transfers ETH. + function test_claimCredit_phaseTwo_succeeds() public { + // Resolve and finalize the game. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + + uint256 expectedCredit = game.refundModeCredit(proposer); + + // Phase 1: unlock. + game.claimCredit(proposer); + + // Warp past DelayedWETH delay. + vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); + + uint256 balanceBefore = proposer.balance; + + // Phase 2: withdraw and transfer. + game.claimCredit(proposer); + + // Proposer received the ETH. + assertEq(proposer.balance, balanceBefore + expectedCredit); + + // No pending withdrawal remains. + (uint256 remaining,) = delayedWeth.withdrawals(address(game), proposer); + assertEq(remaining, 0); + } + + /// @notice claimCredit can be used to close the game even when the recipient has no credit. + /// First call closes the game and returns without reverting; second call reverts. + function test_claimCredit_noCredit_succeeds() public { + // Resolve and finalize the game. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + + address noCredit = address(0xdead); + + assertTrue(game.bondDistributionMode() == BondDistributionMode.UNDECIDED); + + // Game is open (UNDECIDED). First call closes it and returns without reverting. + game.claimCredit(noCredit); + + // Game is now closed. + assertTrue(game.bondDistributionMode() != BondDistributionMode.UNDECIDED); + + // Second call: game already closed, no credit → revert. + vm.expectRevert(NoCreditToClaim.selector); + game.claimCredit(noCredit); + } + + function test_claimCredit_refundMode_succeeds() public { + // Challenge the game so both proposer and challenger have refund credits. + vm.startPrank(challenger); + vm.deal(challenger, 1 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + // Let the prove deadline expire and resolve. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + // Wait for finality delay. + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + + // Retire all games to make isGameProper return false. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + + // Close the game - enters REFUND mode. + game.closeGame(); + assertTrue(game.bondDistributionMode() == BondDistributionMode.REFUND); + + // Both proposer and challenger should get their original bonds back. + uint256 proposerBalanceBefore = proposer.balance; + _claimCreditTwoPhase(game, proposer); + assertEq(proposer.balance, proposerBalanceBefore + 1 ether); + + uint256 challengerBalanceBefore = challenger.balance; + _claimCreditTwoPhase(game, challenger); + assertEq(challenger.balance, challengerBalanceBefore + 1 ether); + } + + function test_claimCredit_notFinalized_reverts() public { + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + // Game is resolved but not finalized - closeGame should revert with GameNotFinalized + vm.expectRevert(GameNotFinalized.selector); + game.claimCredit(proposer); + } + + function test_claimCredit_bondTransferFailed_reverts() public { + // Deploy a contract that rejects ETH transfers. + ZKDisputeGame_RevertOnReceive_Harness revertingRecipient = new ZKDisputeGame_RevertOnReceive_Harness(); + + // Create a game where the proposer is the reverting contract. + vm.startPrank(address(revertingRecipient)); + vm.deal(address(revertingRecipient), 1 ether); + ZKDisputeGame revertGame = ZKDisputeGame( + payable( + address( + disputeGameFactory.create{ value: 1 ether }( + gameType, + Claim.wrap(keccak256("revert-recipient-claim")), + abi.encodePacked(childL2SequenceNumber + grandchildOffset1, childGameIndex) + ) + ) + ) + ); + vm.stopPrank(); + + // Resolve the parent game first (required for child resolution). + (,,,, Timestamp parentDeadline,) = game.claimData(); + vm.warp(parentDeadline.raw() + 1); + game.resolve(); + + // Let the revertGame expire unchallenged and resolve. + (,,,, Timestamp deadline,) = revertGame.claimData(); + vm.warp(deadline.raw() + 1); + revertGame.resolve(); + + // Wait for finality delay. + vm.warp(revertGame.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + + // Phase 1: unlock. + revertGame.claimCredit(address(revertingRecipient)); + + // Wait for DelayedWETH delay. + vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); + + // Phase 2: should revert because recipient rejects ETH. + vm.expectRevert(BondTransferFailed.selector); + revertGame.claimCredit(address(revertingRecipient)); + } +} + +/// @title ZKDisputeGame_CloseGame_Test +/// @notice Tests for closeGame functionality of ZKDisputeGame. +contract ZKDisputeGame_CloseGame_Test is ZKDisputeGame_TestInit { + function test_closeGame_updatesAnchorGame_succeeds() public { + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + game.closeGame(); + + assertEq(address(anchorStateRegistry.anchorGame()), address(game)); + } + + function test_closeGame_refundModeRetired_succeeds() public { + // Resolve the game. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + // Wait for finality delay. + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + + // Retire all existing games by updating the retirement timestamp. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + + // Close the game - it should enter REFUND mode because isGameProper returns false. + game.closeGame(); + assertTrue(game.bondDistributionMode() == BondDistributionMode.REFUND); + } + + function test_closeGame_alreadyClosed_succeeds() public { + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + game.closeGame(); + assertTrue(game.bondDistributionMode() == BondDistributionMode.NORMAL); + + // Calling closeGame again should return early without reverting. + game.closeGame(); + assertTrue(game.bondDistributionMode() == BondDistributionMode.NORMAL); + } + + function test_closeGame_notResolved_reverts() public { + // Game is not resolved, so closeGame should revert with GameNotResolved + vm.expectRevert(GameNotResolved.selector); + game.closeGame(); + } + + function test_closeGame_paused_reverts() public { + // Resolve the game first + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + + // Pause the system + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); + + // closeGame should revert with GamePaused + vm.expectRevert(GamePaused.selector); + game.closeGame(); + + // Unpause + vm.prank(superchainConfig.guardian()); + superchainConfig.unpause(address(0)); + } +} + +/// @title ZKDisputeGame_GameOver_Test +/// @notice Tests for gameOver view function of ZKDisputeGame. +contract ZKDisputeGame_GameOver_Test is ZKDisputeGame_TestInit { + function test_gameOver_beforeDeadline_succeeds() public view { + assertFalse(game.gameOver()); + } + + function test_gameOver_afterDeadline_succeeds() public { + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + assertTrue(game.gameOver()); + } + + function test_gameOver_afterProve_succeeds() public { + vm.prank(prover); + game.prove(bytes("")); + assertTrue(game.gameOver()); + } +} + +/// @title ZKDisputeGame_Credit_Test +/// @notice Tests for credit view function of ZKDisputeGame. +contract ZKDisputeGame_Credit_Test is ZKDisputeGame_TestInit { + function test_credit_normalMode_succeeds() public { + // Let the game expire unchallenged. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + + // Close in NORMAL mode (game is proper). + game.closeGame(); + assertTrue(game.bondDistributionMode() == BondDistributionMode.NORMAL); + + // credit() should return normal mode credits. + assertEq(game.credit(proposer), 1 ether); + assertEq(game.credit(challenger), 0); + } + + function test_credit_refundMode_succeeds() public { + // Challenge the game. + vm.startPrank(challenger); + vm.deal(challenger, 1 ether); + game.challenge{ value: 1 ether }(); + vm.stopPrank(); + + // Resolve. + (,,,, Timestamp deadline,) = game.claimData(); + vm.warp(deadline.raw() + 1); + game.resolve(); + vm.warp(game.resolvedAt().raw() + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1 seconds); + + // Retire and close in REFUND mode. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + game.closeGame(); + + // credit() should return refund mode credits. + assertEq(game.credit(proposer), 1 ether); + assertEq(game.credit(challenger), 1 ether); + } + + function test_credit_defaultMode_succeeds() public view { + // Before closeGame is called, bondDistributionMode is UNDECIDED. + // credit() should default to returning normalModeCredit. + assertEq(game.credit(proposer), 0); + } +} + +/// @title ZKDisputeGame_RevertOnReceive_Harness +/// @notice Helper contract that rejects ETH transfers. +contract ZKDisputeGame_RevertOnReceive_Harness { + receive() external payable { + revert BondTransferFailed(); + } +} + +// Import needed for the l2ChainId=0 test +import { ZKMockVerifier } from "test/dispute/zk/ZKMockVerifier.sol"; +import { ZKRejectingVerifier } from "test/dispute/zk/ZKRejectingVerifier.sol"; diff --git a/packages/contracts-bedrock/test/kontrol/README.md b/packages/contracts-bedrock/test/kontrol/README.md index a22e918bba3..619d0205ede 100644 --- a/packages/contracts-bedrock/test/kontrol/README.md +++ b/packages/contracts-bedrock/test/kontrol/README.md @@ -35,14 +35,12 @@ The directory is structured as follows
 ├── pausability-lemmas.md: File containing the necessary lemmas for this project
-├── deployment: Custom deploy sequence for Kontrol proofs and tests for its fast summarization
-│   ├── KontrolDeployment.sol: Deployment sequence for Kontrol proofs
 ├── proofs: Where the proofs (tests) themselves live
 │   ├── *.k.sol: Symbolic property tests for contracts
 │   └── utils: Proof dependencies, including the autogenerated deployment summary contracts
 └── scripts: Where the scripts of the projects live
-    ├── json: Data cleaning scripts for the output of KontrolDeployment.sol
-    ├── make-summary-deployment.sh: Executes KontrolDeployment.sol, curates the result and writes the summary deployment contract
+    ├── json: Data cleaning scripts for the output of KontrolDeployment.sol
+    ├── make-summary-deployment.sh: Executes KontrolDeployment.sol, curates the result and writes the summary deployment contract
     └── run-kontrol.sh: Wrapper around the kontrol CLI to run the proofs
 
@@ -58,7 +56,7 @@ Verifying proofs has two steps: build, and execute. ### Build Deployment Summary -First, generate a deployment summary contract from the deploy script in [`KontrolDeployment.sol`](./deployment/KontrolDeployment.sol) by running the following command: +First, generate a deployment summary contract from the deploy script in `KontrolDeployment.sol` by running the following command: ``` ./test/kontrol/scripts/make-summary-deployment.sh [container|local|dev] @@ -69,7 +67,7 @@ First, generate a deployment summary contract from the deploy script in [`Kontro The [`make-summary-deployment.sh`](./scripts/make-summary-deployment.sh) supports the same execution modes as `run-kontrol.sh` below. -[`KontrolDeployment.sol`](./deployment/KontrolDeployment.sol) contains the deployment sequence required by the proofs. +`KontrolDeployment.sol` contains the deployment sequence required by the proofs. The [`make-summary-deployment.sh`](./scripts/make-summary-deployment.sh) script will generate a JSON state diff. This state diff is used in two ways by Kontrol. First, Kontrol generates a summary contract recreating the state diffs recorded in the JSON. This contract is used to test that the information contained in the generated JSON is correct and aids in the specification of the symbolic property tests. The generation of this contract is also handled by the `make-summary-deployment.sh` script. Second, the state diff JSON is used to load the post-deployment state directly into Kontrol when running the proofs. @@ -77,7 +75,7 @@ Second, the state diff JSON is used to load the post-deployment state directly i This step is optional if an up-to-date summary contract already exists, which will be the case until the `KontrolDeployment` contract changes, or any of the source contracts under test change. See the [Implementation Details](#implementation-details) section for more information, and to learn about the CI check that ensures the committed autogenerated files from this step are up-to-date. -The summary contract can be found in [`DeploymentSummary.sol`](./proofs/utils/DeploymentSummary.sol), which is summarization (state changes) of the [`KontrolDeployment.sol`](./deployment/KontrolDeployment.sol) contract. +The summary contract can be found in [`DeploymentSummary.sol`](./scripts/DeploymentSummary.sol), which is summarization (state changes) of the `KontrolDeployment.sol` contract. ### Execute Proofs @@ -104,11 +102,11 @@ For a similar description of the options run `run-kontrol.sh --help`. ### Add New Proofs -These are the instructions to add a new proof to this project. If all functions involved in the new proof are from a contract already deployed by [`KontrolDeployment`](./deployment/KontrolDeployment.sol) the first two steps can be skipped. +These are the instructions to add a new proof to this project. If all functions involved in the new proof are from a contract already deployed by `KontrolDeployment` the first two steps can be skipped. #### Make Kontrol aware of the new contract being tested -The `runKontrolDeployment` function of [`KontrolDeployment`](./deployment/KontrolDeployment.sol) reproduces the deployment process laid out in the `_run` function of [`Deploy.s.sol`](../../scripts/deploy/Deploy.s.sol). `runKontrolDeployment` has the `stateDiff` modifier to make use of [Foundry's state diff cheatcodes](https://book.getfoundry.sh/cheatcodes/start-state-diff-recording). Kontrol utilizes the JSON resulting from this modifier for two purposes: +The `runKontrolDeployment` function of `KontrolDeployment` reproduces the deployment process laid out in the `_run` function of [`Deploy.s.sol`](../../scripts/deploy/Deploy.s.sol). `runKontrolDeployment` has the `stateDiff` modifier to make use of [Foundry's state diff cheatcodes](https://book.getfoundry.sh/cheatcodes/start-state-diff-recording). Kontrol utilizes the JSON resulting from this modifier for two purposes: 1. Load all the state updates generated by `runKontrolDeployment` as the initial configuration for all proofs, effectively offloading the computation of the deployment process to `forge` and thus improving performance. 2. Produce the [`DeploymentSummary`](./proofs/utils/DeploymentSummary.sol) script contract to test that the produced JSON contains correct updates. @@ -149,7 +147,7 @@ As described in [Execute Proofs](#execute-proofs), there's a `script` mode for s 1. A critical invariant of the `KontrolDeployment.sol` contract is that it stays in sync with the original `Deploy.s.sol` contract. A more rigorous approach may be to leverage the `ChainAssertions` library, but more investigation is required to determine if this is feasible without large changes to the deploy script. -2. Size of `bytes[]` arguments. In [`OptimismPortal.k.sol`](./proofs/OptimismPortal.k.sol), the `prove_proveWithdrawalTransaction_paused` proof is broken down into 11 different proofs, each corresponding to a different size of the `_withdrawalProof` argument, which is of type `bytes[]`. We execute the same logic for lengths of `_withdrawalProof` ranging from 0 to 10, setting the length of each symbolic `bytes` element to 600. +2. Size of `bytes[]` arguments. In [`OptimismPortal2.k.sol`](./proofs/OptimismPortal2.k.sol), the `prove_proveWithdrawalTransaction_paused` proof is broken down into 11 different proofs, each corresponding to a different size of the `_withdrawalProof` argument, which is of type `bytes[]`. We execute the same logic for lengths of `_withdrawalProof` ranging from 0 to 10, setting the length of each symbolic `bytes` element to 600. - The reason for a max length of 10 is that it provides a conservative upper bound based on [historical data](https://dune.com/queries/3433954/5768623) for proof sizes. - The reason for choosing 600 as the length for the elements of `_withdrawalProof` is that each element is `17 * 32 = 544` bytes long, so adding a 10% margin for RLP encoding and rounding up yields 600 bytes. The same historical data confirms this is a valid bound. - All other symbolic `bytes` arguments that are not part of a `bytes` array have a symbolic length bounded by `2^63`. @@ -165,8 +163,8 @@ Therefore we want to minimize the amount of code executed in Kontrol, and the fa This project uses two different [`foundry.toml` profiles](../../foundry.toml), `kdeploy` and `kprove`, to facilitate usage of this fast summarization feature.: -- `kdeploy`: Used by [`make-summary-deployment.sh`](./scripts/make-summary-deployment.sh) to generate the [`DeploymentSummary.sol`](./proofs/utils/DeploymentSummary.sol) contract based on execution of the [`KontrolDeployment.sol`](./deployment/KontrolDeployment.sol) contract using Foundry's state diff recording cheatcodes. - This is where all necessary [`src/L1`](../../src/L1) files are compiled with their bytecode saved into the [`DeploymentSummaryCode.sol`](./proofs/utils/DeploymentSummaryCode.sol) file, which is inherited by `DeploymentSummary`. +- `kdeploy`: Used by [`make-summary-deployment.sh`](./scripts/make-summary-deployment.sh) to generate the [`DeploymentSummary.sol`](./scripts/DeploymentSummary.sol) contract based on execution of the `KontrolDeployment.sol` contract using Foundry's state diff recording cheatcodes. + This is where all necessary [`src/L1`](../../src/L1) files are compiled with their bytecode saved into the [`DeploymentSummaryCode.sol`](./scripts/DeploymentSummaryCode.sol) file, which is inherited by `DeploymentSummary`. - `kprove`: Used by the [`run-kontrol.sh`](./scripts/run-kontrol.sh) script to execute proofs, which can be run once a `DeploymentSummary.sol` contract is present. This profile's `src` and `script` paths point to a test folder because we only want to compile what is in the `test/kontrol/proofs` folder, since that folder contains all bytecode and proofs. @@ -219,7 +217,7 @@ Method 1: GitHub's `gh` CLI tool Method 2: [Github API](https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28) List the artifacts for a run: -- GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts -- See [documentation](httpshttps://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts) for more details +- GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts -- See [documentation](https://docs.github.com/en/rest/actions/artifacts?apiVersion=2022-11-28#list-workflow-run-artifacts) for more details ```bash curl -L \ -H "Accept: application/vnd.github+json" \ diff --git a/rollup-boost b/rollup-boost deleted file mode 160000 index 196237bab2a..00000000000 --- a/rollup-boost +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 196237bab2a02298de994b439e0455abb1ac512f diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 6d0a2850e79..4ea6e4b073d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -23,7 +23,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -35,7 +35,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -106,9 +106,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.31" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -121,14 +121,14 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728" +checksum = "83447eeb17816e172f1dfc0db1f9dc0b7c5d069bd1f7cecbecceb382bf931015" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-trie", "alloy-tx-macros", "arbitrary", @@ -139,7 +139,7 @@ dependencies = [ "either", "k256", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "serde", "serde_json", @@ -149,24 +149,24 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434" +checksum = "5406343e306856dc2be762700e98a16904de45dee14a07f233e742ce68daff2f" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.5", "arbitrary", "serde", ] [[package]] name = "alloy-dyn-abi" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" +checksum = "a475bb02d9cef2dbb99065c1664ab3fe1f9352e21d6d5ed3f02cdbfc06ed1abc" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -176,7 +176,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow", + "winnow 1.0.1", ] [[package]] @@ -189,7 +189,7 @@ dependencies = [ "alloy-rlp", "arbitrary", "crc", - "rand 0.8.5", + "rand 0.8.6", "serde", "thiserror 2.0.18", ] @@ -204,7 +204,7 @@ dependencies = [ "alloy-rlp", "arbitrary", "borsh", - "rand 0.8.5", + "rand 0.8.6", "serde", ] @@ -219,7 +219,7 @@ dependencies = [ "arbitrary", "borsh", "k256", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_with", "thiserror 2.0.18", @@ -227,22 +227,47 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.3" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" dependencies = [ "alloy-primitives", "alloy-rlp", "arbitrary", "borsh", + "once_cell", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eips" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives", + "alloy-rlp", + "alloy-serde 1.8.3", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", "serde", + "serde_with", + "sha2", ] [[package]] name = "alloy-eips" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" +checksum = "0dca4c89ace90684b4b77366d00631ed498c9af962079af2a5dbc593a0618a77" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -250,52 +275,49 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.5", "arbitrary", "auto_impl", "borsh", "c-kzg", "derive_more", "either", - "ethereum_ssz 0.9.1", - "ethereum_ssz_derive 0.9.1", + "ethereum_ssz", + "ethereum_ssz_derive", "serde", "serde_with", "sha2", - "thiserror 2.0.18", ] [[package]] name = "alloy-evm" -version = "0.27.3" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b991c370ce44e70a3a9e474087e3d65e42e66f967644ad729dc4cec09a21fd09" +checksum = "c1ceeea6dcbbcd4e546b27700763a6f6c3b3fee30054209884f521078b6fda4f" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-hardforks", - "alloy-op-hardforks", "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-sol-types", "auto_impl", "derive_more", - "op-alloy", - "op-revm", "revm", "thiserror 2.0.18", + "tracing", ] [[package]] name = "alloy-genesis" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9cf3b99f46615fbf7dc1add0c96553abb7bf88fc9ec70dfbe7ad0b47ba7fe8" +checksum = "ab0e0fe9e6d1120ad7bb9254c3fc2b9bc80a8df42a033fb626be6559c13d5153" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-trie", "borsh", "serde", @@ -318,9 +340,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" +checksum = "7c36c9d7f9021601b04bfef14a4b64849f6d73116a4e91e071d7fbfe10247901" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -330,9 +352,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7" +checksum = "ec0a82e56b1843bce483942d54fcadea92e676f1bde162e93c7d3b621fabc4e1" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -345,19 +367,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762" +checksum = "a7db7b095b0b1db1d18ce7e91dcd2e82007f2d52bfb8125e6b64633a74a06bc3" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-signer", "alloy-sol-types", "async-trait", @@ -371,23 +393,23 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04" +checksum = "cd28d9bfd11729037d194f2b1d43db8642eb3f342032691f4ca96bb745479c3c" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", - "alloy-serde", + "alloy-serde 2.0.5", "serde", ] [[package]] name = "alloy-op-evm" -version = "0.26.3" +version = "0.32.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-hardforks", "alloy-op-hardforks", @@ -402,7 +424,7 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.4.7" +version = "0.5.0" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -413,9 +435,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" dependencies = [ "alloy-rlp", "arbitrary", @@ -426,32 +448,33 @@ dependencies = [ "fixed-cache", "foldhash 0.2.0", "getrandom 0.4.2", - "hashbrown 0.16.1", - "indexmap 2.13.0", + "hashbrown 0.17.0", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "proptest-derive", - "rand 0.9.2", + "proptest-derive 0.8.0", + "rand 0.9.4", "rapidhash", "rayon", "ruint", "rustc-hash", + "secp256k1 0.31.1", "serde", - "sha3", + "sha3 0.11.0", ] [[package]] name = "alloy-provider" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870" +checksum = "8955ab30418343de57b356de2ea60200f9fb8016a7ea3bc7f5c6176f01a8b1cf" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -475,10 +498,10 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "lru 0.16.3", + "lru", "parking_lot", "pin-project", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "serde_json", "thiserror 2.0.18", @@ -490,9 +513,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8bd82953194dec221aa4cbbbb0b1e2df46066fe9d0333ac25b43a311e122d13" +checksum = "7cd85cfea1fa8ebd20d3475e961fe3a3624c0eb4659ea137715c0c83c8aeaff0" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -505,16 +528,16 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower 0.5.3", + "tower", "tracing", "wasmtimer", ] [[package]] name = "alloy-rlp" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -523,9 +546,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" dependencies = [ "proc-macro2", "quote", @@ -534,9 +557,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f" +checksum = "24f461f091dc8f657e73b5dea18fd63d5c7049720cd252f1eade4a7ebed6a7e1" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -547,12 +570,12 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde", "serde_json", "tokio", "tokio-stream", - "tower 0.5.3", + "tower", "tracing", "url", "wasmtimer", @@ -560,23 +583,23 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bdcbf9dfd5eea8bfeb078b1d906da8cd3a39c4d4dbe7a628025648e323611f6" +checksum = "052c031d1f7c5611997056bbcb8814e5cbf20f7efeee8c3de690555172038cf2" dependencies = [ "alloy-primitives", "alloy-rpc-types-debug", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "serde", ] [[package]] name = "alloy-rpc-types-admin" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42325c117af3a9e49013f881c1474168db57978e02085fc9853a1c89e0562740" +checksum = "ef669b370940e7945a3a384cc4024038cd69ee658b71270d59c20b78dd8d20d4" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -586,39 +609,43 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a3100b76987c1b1dc81f3abe592b7edc29e92b1242067a69d65e0030b35cf9" +checksum = "2ff111a54268dc0bbd3b17f98571a7e27cc661dc081ad2999d91888647eb2e11" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a" +checksum = "0a6561ed4759c974d9c144500a59e3fb8c1d87327a12900d5ce455c0cae6dcb6" dependencies = [ "alloy-consensus-any", + "alloy-network-primitives", + "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", + "serde", + "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a22e13215866f5dfd5d3278f4c41f1fad9410dc68ce39022f58593c873c26f8" +checksum = "9a62f6ce2d95f59ed310bd90d5fd1566a29d1ec45cc219abbc5dcc807d31f136" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", - "ethereum_ssz 0.9.1", - "ethereum_ssz_derive 0.9.1", + "ethereum_ssz", + "ethereum_ssz_derive", "serde", "serde_json", "serde_with", @@ -629,11 +656,12 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b21e1ad18ff1b31ff1030e046462ab8168cf8894e6778cd805c8bdfe2bd649" +checksum = "48b9ad6eee93dd35a9ec0a6c1c6b180892a900ee17a6ed6500921552dd71e846" dependencies = [ "alloy-primitives", + "alloy-rlp", "derive_more", "serde", "serde_with", @@ -641,38 +669,38 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ac61f03f1edabccde1c687b5b25fff28f183afee64eaa2e767def3929e4457" +checksum = "7eba59e1c069f168a01982f42a57797736923b76aa854194df4930be17867e1c" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.5", "arbitrary", "derive_more", - "ethereum_ssz 0.9.1", - "ethereum_ssz_derive 0.9.1", + "ethereum_ssz", + "ethereum_ssz_derive", "jsonwebtoken", - "rand 0.8.5", + "rand 0.8.6", "serde", "strum", ] [[package]] name = "alloy-rpc-types-eth" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9" +checksum = "175a2a5b6017d7f61b5e4b800d21215fe8e94fe729d00828e13bb6d93dcf3492" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-sol-types", "arbitrary", "itertools 0.14.0", @@ -684,28 +712,28 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe85bf3be739126aa593dca9fb3ab13ca93fa7873e6f2247be64d7f2cb15f34a" +checksum = "ed1004c1d68bfaee001712f83356f88031ab74a727b8560fb7fc738d1281ebe5" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-trace" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad79f1e27e161943b5a4f99fe5534ef0849876214be411e0032c12f38e94daa" +checksum = "514b4b1ce3354f65067b4fc7eb75358e0f2ec8be3340c96dea65d6894f9ca435" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "serde", "serde_json", "thiserror 2.0.18", @@ -713,21 +741,32 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d459f902a2313737bc66d18ed094c25d2aeb268b74d98c26bbbda2aa44182ab0" +checksum = "76e34a42ebb4a71ab0bfdebc6d2f3c7bf809f01edf154d08fed159d10d1ef1d4" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", + "serde", +] + +[[package]] +name = "alloy-serde" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" +dependencies = [ + "alloy-primitives", "serde", + "serde_json", ] [[package]] name = "alloy-serde" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" +checksum = "cc21a8772af7d78bba286726aa245bd2ff81cd9abe230afea2e91578996831c9" dependencies = [ "alloy-primitives", "arbitrary", @@ -737,9 +776,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d" +checksum = "8ffbce94c50dd9d4d1f83e044c5595bbd3ada981bd3057ce28b3a5470e77385d" dependencies = [ "alloy-primitives", "async-trait", @@ -752,9 +791,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789" +checksum = "e48366d2c42b8d95ef951101bafa28486590f21b7a1e68b6b2d069746557bbe3" dependencies = [ "alloy-consensus", "alloy-network", @@ -764,16 +803,16 @@ dependencies = [ "coins-bip32", "coins-bip39", "k256", - "rand 0.8.5", + "rand 0.8.6", "thiserror 2.0.18", "zeroize", ] [[package]] name = "alloy-sol-macro" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +checksum = "840128ed2b2971d6d4668a553fe403a82683d3acc646c73e75887e7157408033" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -785,27 +824,27 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +checksum = "63ec265e5d65d725175f6ca7711c970824c90ef9c0d1f1973711d4150ee612dd" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro-error2", "proc-macro2", "quote", - "sha3", + "sha3 0.11.0", "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-macro-input" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +checksum = "89bf01077f18650876cfa682eb1f949967b5cde03f1a51c955c469d2c9b4aa67" dependencies = [ "const-hex", "dunce", @@ -819,19 +858,19 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +checksum = "857b470ecdd2ed38beaf82ad1a38c516a8ff75266750f38b9eeed001d575241b" dependencies = [ "serde", - "winnow", + "winnow 1.0.1", ] [[package]] name = "alloy-sol-types" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +checksum = "384cf252de0db2dec52821eac037a7f57e2aa33fe5b900ce6fe39973402341f1" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -841,9 +880,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53" +checksum = "86052fdcec72d37ca4aa4b66254601e7453c45a6e1c70aa4561033d002fb80cc" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -856,7 +895,7 @@ dependencies = [ "serde_json", "thiserror 2.0.18", "tokio", - "tower 0.5.3", + "tower", "tracing", "url", "wasmtimer", @@ -864,9 +903,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" +checksum = "b273587487921274f4f5d0ef2c7ef36944dcbb75a4e2318e69eae822bd263f91" dependencies = [ "alloy-json-rpc", "alloy-rpc-types-engine", @@ -877,18 +916,18 @@ dependencies = [ "hyper-util", "itertools 0.14.0", "jsonwebtoken", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde_json", - "tower 0.5.3", + "tower", "tracing", "url", ] [[package]] name = "alloy-transport-ipc" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ef85688e5ac2da72afc804e0a1f153a1f309f05a864b1998bbbed7804dbaab" +checksum = "bfb89df168b24773ef603af14f2449c05a7d3f27d05d3eceaea6bf96cccae168" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -906,18 +945,20 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f00445db69d63298e2b00a0ea1d859f00e6424a3144ffc5eba9c31da995e16" +checksum = "33e32e0b47d3b3bf5770b7c132090c614b008d307c5e1544f1925f5b7e9e9af6" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", "http", + "rustls", "serde_json", "tokio", - "tokio-tungstenite 0.26.2", + "tokio-tungstenite", "tracing", + "url", "ws_stream_wasm", ] @@ -934,7 +975,7 @@ dependencies = [ "derive_more", "nybbles", "proptest", - "proptest-derive", + "proptest-derive 0.7.0", "serde", "smallvec", "thiserror 2.0.18", @@ -943,11 +984,11 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.7.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" +checksum = "01a0035943b75fe1e249f52e688492d7a1b1826bc2d19b8e1d5d3c24a2ad8f50" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -997,9 +1038,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -1068,6 +1109,12 @@ dependencies = [ "arbitrary", ] +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" + [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -1335,7 +1382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1345,7 +1392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1355,7 +1402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1595,19 +1642,20 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -1615,64 +1663,6 @@ dependencies = [ "fs_extra", ] -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper", - "tower 0.5.3", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", -] - -[[package]] -name = "backoff" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" -dependencies = [ - "getrandom 0.2.17", - "instant", - "rand 0.8.5", -] - [[package]] name = "backon" version = "1.6.0" @@ -1695,7 +1685,7 @@ dependencies = [ "miniz_oxide 0.8.9", "object", "rustc-demangle", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1785,7 +1775,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1836,14 +1826,20 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "arbitrary", "serde_core", ] +[[package]] +name = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + [[package]] name = "bitvec" version = "1.0.1" @@ -1868,16 +1864,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.3" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "cpufeatures", + "cpufeatures 0.3.0", ] [[package]] @@ -1889,6 +1885,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block-padding" version = "0.3.3" @@ -1912,28 +1917,28 @@ dependencies = [ [[package]] name = "boa_ast" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc119a5ad34c3f459062a96907f53358989b173d104258891bb74f95d93747e8" +checksum = "6339a700715bda376f5ea65c76e8fe8fc880930d8b0638cea68e7f3da6538e0a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "boa_interner", "boa_macros", "boa_string", - "indexmap 2.13.0", + "indexmap 2.14.0", "num-bigint", "rustc-hash", ] [[package]] name = "boa_engine" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e637ec52ea66d76b0ca86180c259d6c7bb6e6a6e14b2f36b85099306d8b00cc3" +checksum = "1521be326f8a5c8887e95d4ce7f002917a002a23f7b93b9a6a2bf50ed4157824" dependencies = [ "aligned-vec", "arrayvec", - "bitflags 2.11.0", + "bitflags 2.11.1", "boa_ast", "boa_gc", "boa_interner", @@ -1952,7 +1957,7 @@ dependencies = [ "futures-lite", "hashbrown 0.16.1", "icu_normalizer", - "indexmap 2.13.0", + "indexmap 2.14.0", "intrusive-collections", "itertools 0.14.0", "num-bigint", @@ -1961,7 +1966,7 @@ dependencies = [ "num_enum", "paste", "portable-atomic", - "rand 0.9.2", + "rand 0.9.4", "regress", "rustc-hash", "ryu-js", @@ -1979,9 +1984,9 @@ dependencies = [ [[package]] name = "boa_gc" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1179f690cbfcbe5364cceee5f1cb577265bb6f07b0be6f210aabe270adcf9da" +checksum = "17323a98cf2e631afacf1a6d659c1212c48a68bacfa85afab0a66ade80582e51" dependencies = [ "boa_macros", "boa_string", @@ -1991,14 +1996,14 @@ dependencies = [ [[package]] name = "boa_interner" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9626505d33dc63d349662437297df1d3afd9d5fc4a2b3ad34e5e1ce879a78848" +checksum = "20510b8b02bcde9b0a01cf34c0c308c56156503d1d91cdab4c8cfbd292b747ea" dependencies = [ "boa_gc", "boa_macros", "hashbrown 0.16.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "once_cell", "phf", "rustc-hash", @@ -2007,9 +2012,9 @@ dependencies = [ [[package]] name = "boa_macros" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f36418a46544b152632c141b0a0b7a453cd69ca150caeef83aee9e2f4b48b7d" +checksum = "5822cb4f146d243060e588bc5a5f2e709683fdad3d7111f42c48e6b5c921d23d" dependencies = [ "cfg-if", "cow-utils", @@ -2021,11 +2026,11 @@ dependencies = [ [[package]] name = "boa_parser" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f99bf5b684f0de946378fcfe5f38c3a0fbd51cbf83a0f39ff773a0e218541f" +checksum = "35bd957fa9fa93e3a001a8aba5a5cd40c2bbfde486378be4c4b472fd304aaddb" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "boa_ast", "boa_interner", "boa_macros", @@ -2039,9 +2044,9 @@ dependencies = [ [[package]] name = "boa_string" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ce9d7aa5563a2e14eab111e2ae1a06a69a812f6c0c3d843196c9d03fbef440" +checksum = "ca2da1d7f4a76fd9040788a122f0d807910800a7b86f5952e9244848c36511de" dependencies = [ "fast-float2", "itoa", @@ -2053,19 +2058,20 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "borsh-derive", + "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" dependencies = [ "once_cell", "proc-macro-crate", @@ -2263,7 +2269,7 @@ checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", "cargo-platform", - "semver 1.0.27", + "semver 1.0.28", "serde", "serde_json", "thiserror 2.0.18", @@ -2286,9 +2292,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -2331,7 +2337,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", ] [[package]] @@ -2341,7 +2358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -2358,7 +2375,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2394,7 +2411,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -2412,9 +2429,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -2434,9 +2451,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -2452,9 +2469,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -2537,7 +2554,7 @@ dependencies = [ "hmac", "once_cell", "pbkdf2", - "rand 0.8.5", + "rand 0.8.6", "sha2", "thiserror 1.0.69", ] @@ -2557,15 +2574,15 @@ dependencies = [ "ripemd", "serde", "sha2", - "sha3", + "sha3 0.10.8", "thiserror 1.0.69", ] [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" @@ -2669,7 +2686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "proptest", "serde_core", ] @@ -2688,11 +2705,12 @@ checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -2747,15 +2765,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "cow-utils" version = "0.1.3" @@ -2771,6 +2780,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -2839,6 +2857,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -2851,7 +2878,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "crossterm_winapi", "derive_more", "document-features", @@ -2901,6 +2928,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -2917,7 +2953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", @@ -2947,16 +2983,6 @@ dependencies = [ "darling_macro 0.20.11", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - [[package]] name = "darling" version = "0.23.0" @@ -2981,21 +3007,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "serde", - "strsim", - "syn 2.0.117", -] - [[package]] name = "darling_core" version = "0.23.0" @@ -3005,6 +3016,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", "syn 2.0.117", ] @@ -3020,17 +3032,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", -] - [[package]] name = "darling_macro" version = "0.23.0" @@ -3148,9 +3149,9 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", @@ -3243,12 +3244,22 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.1", +] + [[package]] name = "dirs" version = "6.0.0" @@ -3293,9 +3304,9 @@ dependencies = [ [[package]] name = "discv5" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f170f4f6ed0e1df52bf43b403899f0081917ecf1500bfe312505cc3b515a8899" +checksum = "4c7999df38d0bd8f688212e1a4fae31fd2fea6d218649b9cd7c40bf3ec1318fc" dependencies = [ "aes", "aes-gcm", @@ -3306,18 +3317,17 @@ dependencies = [ "enr", "fnv", "futures", - "hashlink 0.9.1", + "hashlink 0.11.0", "hex", "hkdf", "lazy_static", "libp2p-identity", - "lru 0.12.5", "more-asserts", "multiaddr", "parking_lot", - "rand 0.8.5", + "rand 0.8.6", "smallvec", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tracing", "uint 0.10.0", @@ -3325,8 +3335,39 @@ dependencies = [ ] [[package]] -name = "displaydoc" -version = "0.2.5" +name = "discv5" +version = "0.10.4" +source = "git+https://github.com/sigp/discv5?rev=7663c00#7663c00ee0837ea98547caaedede95d9d6736f4d" +dependencies = [ + "aes", + "aes-gcm", + "alloy-rlp", + "arrayvec", + "ctr", + "delay_map", + "enr", + "fnv", + "futures", + "hashlink 0.11.0", + "hex", + "hkdf", + "lazy_static", + "libp2p-identity", + "more-asserts", + "multiaddr", + "parking_lot", + "rand 0.8.6", + "smallvec", + "socket2 0.6.3", + "tokio", + "tracing", + "uint 0.10.0", + "zeroize", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ @@ -3337,9 +3378,9 @@ dependencies = [ [[package]] name = "doctest-file" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" +checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359" [[package]] name = "document-features" @@ -3350,12 +3391,6 @@ dependencies = [ "litrs", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "downcast" version = "0.11.0" @@ -3422,7 +3457,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "serde", "signature", ] @@ -3488,12 +3522,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - [[package]] name = "enr" version = "0.13.0" @@ -3507,10 +3535,10 @@ dependencies = [ "hex", "k256", "log", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "serde", - "sha3", + "sha3 0.10.8", "zeroize", ] @@ -3584,11 +3612,11 @@ dependencies = [ [[package]] name = "ethereum_hashing" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" +checksum = "5aa93f58bb1eb3d1e556e4f408ef1dac130bad01ac37db4e7ade45de40d1c86a" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "ring", "sha2", ] @@ -3608,24 +3636,9 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" -dependencies = [ - "alloy-primitives", - "ethereum_serde_utils", - "itertools 0.13.0", - "serde", - "serde_derive", - "smallvec", - "typenum", -] - -[[package]] -name = "ethereum_ssz" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +checksum = "368a4a4e4273b0135111fe9464e35465067766a8f664615b5a86338b73864407" dependencies = [ "alloy-primitives", "ethereum_serde_utils", @@ -3638,21 +3651,9 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "ethereum_ssz_derive" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd596f91cff004fc8d02be44c21c0f9b93140a04b66027ae052f5f8e05b48eba" +checksum = "f2cd82c68120c89361e1a457245cf212f7d9f541bffaffed530c8f2d54a160b2" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -3681,12 +3682,23 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "evmap" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8" +dependencies = [ + "hashbag", + "left-right", + "smallvec", +] + [[package]] name = "example-custom-node" version = "0.0.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-genesis", "alloy-network", @@ -3695,7 +3707,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "async-trait", "derive_more", "eyre", @@ -3709,15 +3721,21 @@ dependencies = [ "reth-db-api", "reth-engine-primitives", "reth-ethereum", + "reth-evm", "reth-network-peers", + "reth-node-api", "reth-node-builder", "reth-op", "reth-optimism-evm", "reth-optimism-flashblocks", "reth-optimism-forks", "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-provider", "reth-rpc-api", "reth-rpc-engine-api", + "reth-trie-common", "revm", "revm-primitives", "serde", @@ -3730,7 +3748,7 @@ version = "0.0.0" dependencies = [ "anyhow", "clap", - "discv5", + "discv5 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "kona-cli", "kona-disc", "tokio", @@ -3769,7 +3787,7 @@ dependencies = [ "anyhow", "async-trait", "clap", - "discv5", + "discv5 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "kona-cli", "kona-disc", "kona-node-service", @@ -3777,7 +3795,6 @@ dependencies = [ "libp2p", "op-alloy-rpc-types-engine", "tokio", - "tokio-util", "tracing", "tracing-subscriber 0.3.23", ] @@ -3822,9 +3839,9 @@ checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fastrlp" @@ -3909,7 +3926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.6", "rustc-hex", "static_assertions", ] @@ -4005,9 +4022,12 @@ dependencies = [ [[package]] name = "fragile" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] [[package]] name = "fs_extra" @@ -4192,8 +4212,8 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link", - "windows-result", + "windows-link 0.2.1", + "windows-result 0.4.1", ] [[package]] @@ -4243,6 +4263,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -4269,7 +4290,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "libgit2-sys", "log", @@ -4330,12 +4351,12 @@ dependencies = [ [[package]] name = "gmp-mpfr-sys" -version = "1.6.8" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394" +checksum = "8cfc928d8ff4ab3767a3674cf55f81186436fb6070866bb1443ffe65a640d2d6" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4361,7 +4382,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -4385,6 +4406,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" +[[package]] +name = "hashbag" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064" + [[package]] name = "hashbrown" version = "0.12.3" @@ -4413,7 +4440,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", - "equivalent", "foldhash 0.1.5", ] @@ -4426,6 +4452,15 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +dependencies = [ + "foldhash 0.2.0", "rayon", "serde", "serde_core", @@ -4449,6 +4484,15 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "hashlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +dependencies = [ + "hashbrown 0.16.1", +] + [[package]] name = "hdrhistogram" version = "7.5.4" @@ -4520,6 +4564,30 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" +[[package]] +name = "hickory-net" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "futures-channel", + "futures-io", + "futures-util", + "hickory-proto 0.26.1", + "idna", + "ipnet", + "jni 0.22.4", + "rand 0.10.1", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + [[package]] name = "hickory-proto" version = "0.25.2" @@ -4536,9 +4604,8 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.2", + "rand 0.9.4", "ring", - "serde", "socket2 0.5.10", "thiserror 2.0.18", "tinyvec", @@ -4547,6 +4614,27 @@ dependencies = [ "url", ] +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni 0.22.4", + "once_cell", + "prefix-trie", + "rand 0.10.1", + "ring", + "serde", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + [[package]] name = "hickory-resolver" version = "0.25.2" @@ -4555,15 +4643,41 @@ checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ "cfg-if", "futures-util", - "hickory-proto", + "hickory-proto 0.25.2", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.4", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hickory-resolver" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-net", + "hickory-proto 0.26.1", "ipconfig", + "ipnet", + "jni 0.22.4", "moka", + "ndk-context", "once_cell", "parking_lot", - "rand 0.9.2", + "rand 0.10.1", "resolv-conf", "serde", "smallvec", + "system-configuration", "thiserror 2.0.18", "tokio", "tracing", @@ -4694,11 +4808,20 @@ dependencies = [ "serde", ] +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -4711,7 +4834,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -4719,9 +4841,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", @@ -4729,11 +4851,9 @@ dependencies = [ "log", "rustls", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", ] [[package]] @@ -4782,7 +4902,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "system-configuration", "tokio", "tower-layer", @@ -4803,7 +4923,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -4830,9 +4950,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -4889,9 +5009,9 @@ checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -4977,7 +5097,7 @@ dependencies = [ "rtnetlink", "system-configuration", "tokio", - "windows", + "windows 0.62.2", ] [[package]] @@ -4995,12 +5115,38 @@ dependencies = [ "hyper", "hyper-util", "log", - "rand 0.9.2", + "rand 0.9.4", "tokio", "url", "xmltree", ] +[[package]] +name = "imbl" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e525189e5f603908d0c6e0d402cb5de9c4b2c8866151fabc4ebd771ed2630a2e" +dependencies = [ + "arbitrary", + "archery", + "bitmaps", + "imbl-sized-chunks", + "rand_core 0.9.5", + "rand_xoshiro", + "serde_core", + "version_check", + "wide", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -5059,13 +5205,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "rayon", "serde", "serde_core", @@ -5086,7 +5232,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "inotify-sys", "libc", ] @@ -5112,9 +5258,9 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling 0.23.0", "indoc", @@ -5123,20 +5269,11 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "interprocess" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" dependencies = [ "doctest-file", "futures-core", @@ -5144,7 +5281,7 @@ dependencies = [ "recvmsg", "tokio", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5158,14 +5295,15 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.5.10", + "socket2 0.6.3", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", ] [[package]] @@ -5179,9 +5317,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -5233,9 +5371,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jemalloc_pprof" @@ -5263,7 +5401,7 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", @@ -5271,43 +5409,97 @@ dependencies = [ ] [[package]] -name = "jni-sys" -version = "0.3.0" +name = "jni" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] [[package]] -name = "jobserver" -version = "0.1.34" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "getrandom 0.3.4", - "libc", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.117", ] [[package]] -name = "js-sys" -version = "0.3.91" +name = "jni-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" dependencies = [ - "once_cell", - "wasm-bindgen", + "jni-sys 0.4.1", ] [[package]] -name = "jsonrpsee" -version = "0.26.0" +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" dependencies = [ - "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-http-client", - "jsonrpsee-proc-macros", - "jsonrpsee-server", - "jsonrpsee-types", + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonrpsee" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a" +dependencies = [ + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-http-client", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", "jsonrpsee-wasm-client", "jsonrpsee-ws-client", "tokio", @@ -5329,7 +5521,7 @@ dependencies = [ "pin-project", "rustls", "rustls-pki-types", - "rustls-platform-verifier", + "rustls-platform-verifier 0.5.3", "soketto", "thiserror 2.0.18", "tokio", @@ -5355,14 +5547,14 @@ dependencies = [ "jsonrpsee-types", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.4", "rustc-hash", "serde", "serde_json", "thiserror 2.0.18", "tokio", "tokio-stream", - "tower 0.5.3", + "tower", "tracing", "wasm-bindgen-futures", ] @@ -5381,12 +5573,12 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "rustls", - "rustls-platform-verifier", + "rustls-platform-verifier 0.5.3", "serde", "serde_json", "thiserror 2.0.18", "tokio", - "tower 0.5.3", + "tower", "url", ] @@ -5426,7 +5618,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "tower 0.5.3", + "tower", "tracing", ] @@ -5451,7 +5643,7 @@ dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", - "tower 0.5.3", + "tower", ] [[package]] @@ -5464,22 +5656,24 @@ dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", - "tower 0.5.3", + "tower", "url", ] [[package]] name = "jsonwebtoken" -version = "9.3.1" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ + "aws-lc-rs", "base64 0.22.1", + "getrandom 0.2.17", "js-sys", "pem", - "ring", "serde", "serde_json", + "signature", "simple_asn1", ] @@ -5500,9 +5694,9 @@ dependencies = [ [[package]] name = "kasuari" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ "hashbrown 0.16.1", "portable-atomic", @@ -5515,14 +5709,24 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", ] [[package]] name = "keccak-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -5539,7 +5743,7 @@ dependencies = [ "kona-registry", "libc", "libp2p", - "metrics-exporter-prometheus 0.18.1", + "metrics-exporter-prometheus", "metrics-process", "rstest", "serde", @@ -5554,7 +5758,7 @@ name = "kona-client" version = "1.0.2" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-op-evm", "alloy-primitives", @@ -5575,7 +5779,7 @@ dependencies = [ "kona-registry", "kona-std-fpvm", "kona-std-fpvm-proc", - "lru 0.16.3", + "lru", "op-alloy-consensus", "op-alloy-rpc-types-engine", "op-revm", @@ -5594,13 +5798,14 @@ name = "kona-derive" version = "0.4.5" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", "async-trait", "kona-genesis", "kona-hardforks", + "kona-interop", "kona-macros", "kona-protocol", "kona-registry", @@ -5624,14 +5829,14 @@ dependencies = [ "alloy-rlp", "backon", "derive_more", - "discv5", + "discv5 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "kona-cli", "kona-genesis", "kona-macros", "kona-peers", "libp2p", "metrics", - "rand 0.9.2", + "rand 0.9.4", "serde_json", "tempfile", "thiserror 2.0.18", @@ -5664,8 +5869,7 @@ name = "kona-engine" version = "0.1.2" dependencies = [ "alloy-consensus", - "alloy-eips", - "alloy-json-rpc", + "alloy-eips 2.0.5", "alloy-network", "alloy-primitives", "alloy-provider", @@ -5677,30 +5881,25 @@ dependencies = [ "arbitrary", "async-trait", "derive_more", - "http", "http-body-util", - "jsonrpsee-types", "kona-genesis", "kona-macros", "kona-protocol", "kona-registry", "metrics", - "metrics-exporter-prometheus 0.18.1", + "metrics-exporter-prometheus", "op-alloy-consensus", "op-alloy-network", "op-alloy-provider", "op-alloy-rpc-types", "op-alloy-rpc-types-engine", - "parking_lot", - "rand 0.9.2", - "rollup-boost", - "rollup-boost-types", + "rand 0.9.4", "rstest", "serde", "serde_json", "thiserror 2.0.18", "tokio", - "tower 0.5.3", + "tower", "tracing", "url", ] @@ -5710,7 +5909,7 @@ name = "kona-executor" version = "0.4.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-op-evm", "alloy-op-hardforks", @@ -5729,7 +5928,7 @@ dependencies = [ "op-alloy-consensus", "op-alloy-rpc-types-engine", "op-revm", - "rand 0.9.2", + "rand 0.9.4", "revm", "rocksdb", "rstest", @@ -5747,7 +5946,7 @@ version = "0.4.5" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-genesis", "alloy-hardforks", "alloy-op-hardforks", @@ -5756,7 +5955,7 @@ dependencies = [ "arbitrary", "derive_more", "op-revm", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde_json", "serde_repr", @@ -5771,13 +5970,13 @@ version = "0.1.2" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", "arbitrary", "derive_more", - "discv5", + "discv5 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "futures", "ipnet", "kona-disc", @@ -5793,7 +5992,7 @@ dependencies = [ "op-alloy-consensus", "op-alloy-rpc-types-engine", "openssl", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde_json", "serde_repr", @@ -5808,12 +6007,16 @@ dependencies = [ name = "kona-hardforks" version = "0.4.5" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", + "anyhow", "kona-protocol", + "once_cell", "op-alloy-consensus", "op-revm", "revm", + "serde", + "serde_json", ] [[package]] @@ -5821,7 +6024,7 @@ name = "kona-host" version = "1.0.2" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-json-rpc", "alloy-op-evm", "alloy-primitives", @@ -5830,7 +6033,7 @@ dependencies = [ "alloy-rpc-client", "alloy-rpc-types", "alloy-rpc-types-beacon", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-transport", "alloy-transport-http", "anyhow", @@ -5843,6 +6046,7 @@ dependencies = [ "kona-driver", "kona-executor", "kona-genesis", + "kona-interop", "kona-mpt", "kona-preimage", "kona-proof", @@ -5859,6 +6063,7 @@ dependencies = [ "rocksdb", "serde", "serde_json", + "tempfile", "thiserror 2.0.18", "tokio", "tracing", @@ -5870,10 +6075,10 @@ name = "kona-interop" version = "0.4.5" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-sol-types", "arbitrary", "async-trait", @@ -5882,7 +6087,7 @@ dependencies = [ "kona-protocol", "kona-registry", "op-alloy-consensus", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde_json", "thiserror 2.0.18", @@ -5908,7 +6113,7 @@ dependencies = [ "codspeed-criterion-compat", "op-alloy-rpc-types-engine", "proptest", - "rand 0.9.2", + "rand 0.9.4", "reqwest 0.13.2", "serde", "thiserror 2.0.18", @@ -5933,9 +6138,8 @@ dependencies = [ "clap", "derive_more", "dirs", - "discv5", + "discv5 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "futures", - "http", "jsonrpsee", "kona-cli", "kona-derive", @@ -5943,6 +6147,7 @@ dependencies = [ "kona-engine", "kona-genesis", "kona-gossip", + "kona-interop", "kona-node-service", "kona-peers", "kona-protocol", @@ -5956,7 +6161,6 @@ dependencies = [ "op-alloy-provider", "op-alloy-rpc-types-engine", "reqwest 0.13.2", - "rollup-boost", "rstest", "serde_json", "strum", @@ -5979,7 +6183,7 @@ version = "0.1.3" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-provider", "alloy-rpc-client", @@ -5995,9 +6199,8 @@ dependencies = [ "async-trait", "backon", "derive_more", - "discv5", + "discv5 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "futures", - "http", "http-body-util", "jsonrpsee", "kona-derive", @@ -6005,6 +6208,7 @@ dependencies = [ "kona-engine", "kona-genesis", "kona-gossip", + "kona-interop", "kona-macros", "kona-peers", "kona-protocol", @@ -6019,15 +6223,14 @@ dependencies = [ "op-alloy-network", "op-alloy-provider", "op-alloy-rpc-types-engine", - "rand 0.9.2", - "rollup-boost", + "rand 0.9.4", "rstest", "strum", "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", - "tower 0.5.3", + "tower", "tracing", "url", ] @@ -6043,7 +6246,7 @@ dependencies = [ "arbtest", "derive_more", "dirs", - "discv5", + "discv5 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "kona-genesis", "kona-registry", "lazy_static", @@ -6069,6 +6272,7 @@ dependencies = [ "async-trait", "rkyv", "serde", + "sha2", "thiserror 2.0.18", "tokio", "tracing", @@ -6079,7 +6283,7 @@ name = "kona-proof" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-op-evm", "alloy-primitives", @@ -6093,16 +6297,17 @@ dependencies = [ "kona-driver", "kona-executor", "kona-genesis", + "kona-interop", "kona-mpt", "kona-preimage", "kona-protocol", "kona-registry", "lazy_static", - "lru 0.16.3", + "lru", "op-alloy-consensus", "op-alloy-rpc-types-engine", "op-revm", - "rand 0.9.2", + "rand 0.9.4", "rayon", "rstest", "serde", @@ -6118,7 +6323,7 @@ name = "kona-proof-interop" version = "0.2.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-op-evm", "alloy-primitives", @@ -6137,12 +6342,13 @@ dependencies = [ "op-alloy-consensus", "op-alloy-rpc-types-engine", "op-revm", - "rand 0.9.2", + "rand 0.9.4", "revm", "serde", "serde_json", "spin 0.10.0", "thiserror 2.0.18", + "tokio", "tracing", ] @@ -6152,13 +6358,13 @@ version = "0.4.5" dependencies = [ "alloc-no-stdlib", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-hardforks", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-sol-types", "ambassador", "arbitrary", @@ -6172,7 +6378,7 @@ dependencies = [ "op-alloy-rpc-types", "op-alloy-rpc-types-engine", "proptest", - "rand 0.9.2", + "rand 0.9.4", "rstest", "serde", "serde_json", @@ -6189,13 +6395,13 @@ name = "kona-providers-alloy" version = "0.3.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-provider", "alloy-rpc-client", "alloy-rpc-types-beacon", "alloy-rpc-types-engine", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-transport", "alloy-transport-http", "async-trait", @@ -6204,17 +6410,19 @@ dependencies = [ "httpmock", "kona-derive", "kona-genesis", + "kona-interop", "kona-macros", "kona-protocol", - "lru 0.16.3", + "lru", "metrics", "op-alloy-consensus", "op-alloy-network", + "reqwest 0.13.2", "serde", "serde_json", "thiserror 2.0.18", "tokio", - "tower 0.5.3", + "tower", "tracing", ] @@ -6223,14 +6431,14 @@ name = "kona-providers-local" version = "0.1.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "async-trait", "kona-derive", "kona-genesis", "kona-macros", "kona-protocol", - "lru 0.16.3", + "lru", "metrics", "op-alloy-consensus", "rstest", @@ -6243,7 +6451,7 @@ name = "kona-registry" version = "0.4.5" dependencies = [ "alloy-chains", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-genesis", "alloy-hardforks", "alloy-op-hardforks", @@ -6260,7 +6468,7 @@ dependencies = [ name = "kona-rpc" version = "0.3.2" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rpc-client", "alloy-rpc-types-engine", @@ -6281,7 +6489,6 @@ dependencies = [ "op-alloy-rpc-jsonrpsee", "op-alloy-rpc-types", "op-alloy-rpc-types-engine", - "rollup-boost", "serde", "serde_json", "thiserror 2.0.18", @@ -6344,6 +6551,21 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "kqueue" version = "1.1.1" @@ -6379,11 +6601,22 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "left-right" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0c21e4c8ff95f487fb34e6f9182875f42c84cef966d29216bf115d9bba835a" +dependencies = [ + "crossbeam-utils", + "loom", + "slab", +] + [[package]] name = "libc" -version = "0.2.183" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "libgit2-sys" @@ -6404,7 +6637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -6485,7 +6718,7 @@ dependencies = [ "parking_lot", "pin-project", "quick-protobuf", - "rand 0.8.5", + "rand 0.8.6", "rw-stream-sink", "thiserror 2.0.18", "tracing", @@ -6501,7 +6734,7 @@ checksum = "0b770c1c8476736ca98c578cba4b505104ff8e842c2876b528925f9766379f9a" dependencies = [ "async-trait", "futures", - "hickory-resolver", + "hickory-resolver 0.25.2", "libp2p-core", "libp2p-identity", "parking_lot", @@ -6511,9 +6744,9 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" -version = "0.49.3" +version = "0.49.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cef64c3bdfaee9561319a289d778e9f8c56bd8e10f5d1059289ebb085ef09d7" +checksum = "a538e571cd38f504f761c61b8f79127489ea7a7d6f05c41ca15d31ffb5726326" dependencies = [ "async-channel", "asynchronous-codec", @@ -6532,7 +6765,7 @@ dependencies = [ "libp2p-swarm", "quick-protobuf", "quick-protobuf-codec", - "rand 0.8.5", + "rand 0.8.6", "regex", "sha2", "tracing", @@ -6573,7 +6806,7 @@ dependencies = [ "k256", "multihash", "quick-protobuf", - "rand 0.8.5", + "rand 0.8.6", "sha2", "thiserror 2.0.18", "tracing", @@ -6587,12 +6820,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66872d0f1ffcded2788683f76931be1c52e27f343edb93bc6d0bcd8887be443" dependencies = [ "futures", - "hickory-proto", + "hickory-proto 0.25.2", "if-watch", "libp2p-core", "libp2p-identity", "libp2p-swarm", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "socket2 0.5.10", "tokio", @@ -6631,7 +6864,7 @@ dependencies = [ "multiaddr", "multihash", "quick-protobuf", - "rand 0.8.5", + "rand 0.8.6", "snow", "static_assertions", "thiserror 2.0.18", @@ -6651,7 +6884,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "rand 0.8.5", + "rand 0.8.6", "tracing", "web-time", ] @@ -6669,7 +6902,7 @@ dependencies = [ "libp2p-identity", "libp2p-tls", "quinn", - "rand 0.8.5", + "rand 0.8.6", "ring", "rustls", "socket2 0.5.10", @@ -6688,7 +6921,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "rand 0.8.5", + "rand 0.8.6", "tracing", ] @@ -6707,7 +6940,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm-derive", "multistream-select", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "tokio", "tracing", @@ -6803,14 +7036,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -6831,9 +7064,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -6843,11 +7076,11 @@ dependencies = [ [[package]] name = "line-clipping" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -6874,9 +7107,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -6915,18 +7148,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -6979,9 +7203,9 @@ checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" [[package]] name = "macro-string" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" dependencies = [ "proc-macro2", "quote", @@ -7021,12 +7245,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "memchr" version = "2.8.0" @@ -7053,12 +7271,12 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", ] [[package]] @@ -7074,39 +7292,19 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" -dependencies = [ - "base64 0.22.1", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "indexmap 2.13.0", - "ipnet", - "metrics", - "metrics-util 0.19.1", - "quanta", - "thiserror 1.0.69", - "tokio", - "tracing", -] - -[[package]] -name = "metrics-exporter-prometheus" -version = "0.18.1" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda" +checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108" dependencies = [ "base64 0.22.1", + "evmap", "http-body-util", "hyper", "hyper-util", - "indexmap 2.13.0", + "indexmap 2.14.0", "ipnet", "metrics", - "metrics-util 0.20.1", + "metrics-util", "quanta", "thiserror 2.0.18", "tokio", @@ -7126,42 +7324,23 @@ dependencies = [ "once_cell", "procfs", "rlimit", - "windows", -] - -[[package]] -name = "metrics-util" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" -dependencies = [ - "aho-corasick", - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.15.5", - "indexmap 2.13.0", - "metrics", - "ordered-float", - "quanta", - "radix_trie", - "rand 0.9.2", - "rand_xoshiro", - "sketches-ddsketch", + "windows 0.62.2", ] [[package]] name = "metrics-util" -version = "0.20.1" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdfb1365fea27e6dd9dc1dbc19f570198bc86914533ad639dae939635f096be4" +checksum = "96f8722f8562635f92f8ed992f26df0532266eb03d5202607c20c0d7e9745e13" dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.16.1", "metrics", "quanta", - "rand 0.9.2", + "rand 0.9.4", "rand_xoshiro", + "rapidhash", "sketches-ddsketch", ] @@ -7209,9 +7388,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -7268,17 +7447,14 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ - "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", - "event-listener", - "futures-util", "parking_lot", "portable-atomic", "smallvec", @@ -7325,11 +7501,11 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +checksum = "89ace881e3f514092ce9efbcb8f413d0ad9763860b828981c2de51ddc666936c" dependencies = [ - "core2", + "no_std_io2", "unsigned-varint 0.8.0", ] @@ -7384,6 +7560,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "netlink-packet-core" version = "0.8.1" @@ -7399,7 +7581,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "log", "netlink-packet-core", @@ -7432,27 +7614,27 @@ dependencies = [ "tokio", ] -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - [[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "no_std_io2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3564ce7035b1e4778d8cb6cacebb5d766b5e8fe5a75b9e441e33fb61a872c6" +dependencies = [ + "memchr", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -7475,7 +7657,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "fsevent-sys", "inotify", "kqueue", @@ -7493,7 +7675,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -7550,9 +7732,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -7607,9 +7789,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -7617,9 +7799,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -7657,7 +7839,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -7712,7 +7894,7 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy" -version = "0.23.1" +version = "2.0.0" dependencies = [ "op-alloy-consensus", "op-alloy-network", @@ -7724,20 +7906,25 @@ dependencies = [ [[package]] name = "op-alloy-consensus" -version = "0.23.1" +version = "2.0.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-network", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-signer", "arbitrary", "bincode 2.0.1", + "bytes", "derive_more", - "rand 0.9.2", + "modular-bitfield", + "rand 0.9.4", + "reth-codecs", + "reth-codecs-derive", + "reth-zstd-compressors", "serde", "serde_json", "serde_with", @@ -7752,21 +7939,19 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.23.1" +version = "2.0.0" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives", "alloy-provider", "alloy-rpc-types-eth", - "alloy-signer", "op-alloy-consensus", "op-alloy-rpc-types", ] [[package]] name = "op-alloy-provider" -version = "0.23.1" +version = "2.0.0" dependencies = [ "alloy-network", "alloy-primitives", @@ -7779,7 +7964,7 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.23.1" +version = "2.0.0" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -7787,19 +7972,22 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.23.1" +version = "2.0.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", + "alloy-network", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", + "alloy-signer", "arbitrary", "derive_more", "jsonrpsee", "op-alloy-consensus", - "rand 0.9.2", + "rand 0.9.4", + "reth-rpc-traits", "serde", "serde_json", "similar-asserts", @@ -7808,19 +7996,18 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" -version = "0.23.1" +version = "2.0.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde", + "alloy-serde 2.0.5", "arbitrary", "arbtest", - "derive_more", - "ethereum_ssz 0.9.1", - "ethereum_ssz_derive 0.9.1", + "ethereum_ssz", + "ethereum_ssz_derive", "op-alloy-consensus", "serde", "serde_json", @@ -7868,13 +8055,16 @@ dependencies = [ [[package]] name = "op-revm" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c92b75162c2ed1661849fa51683b11254a5b661798360a2c24be918edafd40" +version = "20.0.0" dependencies = [ + "alloy-primitives", + "alloy-sol-types", "auto_impl", "revm", + "rstest", "serde", + "serde_json", + "sha2", ] [[package]] @@ -7885,11 +8075,11 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "foreign-types", "libc", @@ -7917,18 +8107,18 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-src" -version = "300.5.5+3.5.5" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" dependencies = [ "cc", "libc", @@ -7937,20 +8127,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "opentelemetry" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e667b670a5cdf90c258f5a55794ec5ac5027e960c224bff8367a59e1e6426" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "pin-project-lite", - "thiserror 2.0.18", - "tracing", -] - [[package]] name = "opentelemetry" version = "0.31.0" @@ -7971,26 +8147,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" dependencies = [ - "opentelemetry 0.31.0", + "opentelemetry", "tracing", "tracing-core", "tracing-subscriber 0.3.23", ] -[[package]] -name = "opentelemetry-http" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253" -dependencies = [ - "async-trait", - "bytes", - "http", - "opentelemetry 0.28.0", - "reqwest 0.12.28", - "tracing", -] - [[package]] name = "opentelemetry-http" version = "0.31.0" @@ -8000,105 +8162,47 @@ dependencies = [ "async-trait", "bytes", "http", - "opentelemetry 0.31.0", + "opentelemetry", "reqwest 0.12.28", ] [[package]] name = "opentelemetry-otlp" -version = "0.28.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91" +checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" dependencies = [ - "async-trait", - "futures-core", "http", - "opentelemetry 0.28.0", - "opentelemetry-http 0.28.0", - "opentelemetry-proto 0.28.0", - "opentelemetry_sdk 0.28.0", - "prost 0.13.5", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", "reqwest 0.12.28", - "serde_json", "thiserror 2.0.18", "tokio", - "tonic 0.12.3", + "tonic", "tracing", ] [[package]] -name = "opentelemetry-otlp" +name = "opentelemetry-proto" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" dependencies = [ - "http", - "opentelemetry 0.31.0", - "opentelemetry-http 0.31.0", - "opentelemetry-proto 0.31.0", - "opentelemetry_sdk 0.31.0", - "prost 0.14.3", - "reqwest 0.12.28", - "thiserror 2.0.18", - "tokio", - "tonic 0.14.5", - "tracing", + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", + "tonic-prost", ] [[package]] -name = "opentelemetry-proto" -version = "0.28.0" +name = "opentelemetry-semantic-conventions" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d" -dependencies = [ - "base64 0.22.1", - "hex", - "opentelemetry 0.28.0", - "opentelemetry_sdk 0.28.0", - "prost 0.13.5", - "serde", - "tonic 0.12.3", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" -dependencies = [ - "opentelemetry 0.31.0", - "opentelemetry_sdk 0.31.0", - "prost 0.14.3", - "tonic 0.14.5", - "tonic-prost", -] - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" - -[[package]] -name = "opentelemetry_sdk" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570" -dependencies = [ - "async-trait", - "futures-channel", - "futures-executor", - "futures-util", - "glob", - "opentelemetry 0.28.0", - "percent-encoding", - "rand 0.8.5", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", -] +checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" [[package]] name = "opentelemetry_sdk" @@ -8109,9 +8213,9 @@ dependencies = [ "futures-channel", "futures-executor", "futures-util", - "opentelemetry 0.31.0", + "opentelemetry", "percent-encoding", - "rand 0.9.2", + "rand 0.9.4", "thiserror 2.0.18", ] @@ -8121,15 +8225,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - [[package]] name = "p256" version = "0.13.2" @@ -8169,7 +8264,6 @@ version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ - "arbitrary", "arrayvec", "bitvec", "byte-slice-cast", @@ -8219,7 +8313,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -8328,18 +8422,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -8370,9 +8464,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -8437,7 +8531,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -8449,7 +8543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -8462,9 +8556,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -8486,7 +8580,7 @@ dependencies = [ "flate2", "num", "paste", - "prost 0.14.3", + "prost", ] [[package]] @@ -8524,6 +8618,17 @@ dependencies = [ "termtree", ] +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -8610,7 +8715,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "chrono", "flate2", "procfs-core", @@ -8623,7 +8728,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "chrono", "hex", ] @@ -8653,15 +8758,15 @@ dependencies = [ [[package]] name = "proptest" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.11.0", + "bitflags 2.11.1", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -8692,13 +8797,14 @@ dependencies = [ ] [[package]] -name = "prost" -version = "0.13.5" +name = "proptest-derive" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "c57924a81864dddafba92e1bf92f9bf82f97096c44489548a60e888e1547549b" dependencies = [ - "bytes", - "prost-derive 0.13.5", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -8708,20 +8814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", - "prost-derive 0.14.3", -] - -[[package]] -name = "prost-derive" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn 2.0.117", + "prost-derive", ] [[package]] @@ -8814,7 +8907,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -8827,10 +8920,11 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -8851,7 +8945,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -8883,16 +8977,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "rancor" version = "0.1.1" @@ -8904,9 +8988,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -8916,15 +9000,26 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", "serde", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -8964,6 +9059,12 @@ dependencies = [ "serde", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -8988,7 +9089,7 @@ version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" dependencies = [ - "rand 0.9.2", + "rand 0.9.4", "rustversion", ] @@ -9010,13 +9111,13 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "compact_str", "hashbrown 0.16.1", "indoc", "itertools 0.14.0", "kasuari", - "lru 0.16.3", + "lru", "strum", "thiserror 2.0.18", "unicode-segmentation", @@ -9042,7 +9143,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.16.1", "indoc", "instability", @@ -9061,14 +9162,14 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -9109,16 +9210,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -9248,16 +9349,13 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tokio-util", - "tower 0.5.3", + "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", - "webpki-roots 1.0.6", ] [[package]] @@ -9268,26 +9366,37 @@ checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", + "futures-channel", "futures-core", + "futures-util", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier 0.6.2", "serde", "serde_json", + "serde_urlencoded", "sync_wrapper", "tokio", - "tower 0.5.3", + "tokio-rustls", + "tokio-util", + "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", ] @@ -9299,16 +9408,17 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "reth-basic-payload-builder" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "futures-core", "futures-util", "metrics", "reth-chain-state", + "reth-execution-cache", "reth-metrics", "reth-payload-builder", "reth-payload-builder-primitives", @@ -9317,17 +9427,19 @@ dependencies = [ "reth-revm", "reth-storage-api", "reth-tasks", + "reth-trie-parallel", + "serde", "tokio", "tracing", ] [[package]] name = "reth-chain-state" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-signer", "alloy-signer-local", @@ -9335,7 +9447,7 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.4", "rayon", "reth-chainspec", "reth-errors", @@ -9344,6 +9456,7 @@ dependencies = [ "reth-metrics", "reth-primitives-traits", "reth-storage-api", + "reth-tasks", "reth-trie", "revm-database", "revm-state", @@ -9355,12 +9468,12 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -9375,8 +9488,8 @@ dependencies = [ [[package]] name = "reth-cli" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-genesis", "clap", @@ -9384,21 +9497,21 @@ dependencies = [ "reth-cli-runner", "reth-db", "serde_json", - "shellexpand", ] [[package]] name = "reth-cli-commands" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "arbitrary", "backon", + "blake3", "clap", "comfy-table", "crossterm", @@ -9414,7 +9527,8 @@ dependencies = [ "proptest", "proptest-arbitrary-interop", "ratatui", - "reqwest 0.12.28", + "rayon", + "reqwest 0.13.2", "reth-chainspec", "reth-cli", "reth-cli-runner", @@ -9475,8 +9589,8 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "reth-tasks", "tokio", @@ -9485,38 +9599,40 @@ dependencies = [ [[package]] name = "reth-cli-util" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "cfg-if", "eyre", "libc", - "rand 0.8.5", + "rand 0.8.6", "reth-fs-util", "reth-tracing", "secp256k1 0.30.0", "serde", "thiserror 2.0.18", + "tikv-jemalloc-sys", "tikv-jemallocator", "tracy-client", ] [[package]] name = "reth-codecs" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce542a96bf888f31854803e80b3340bc233927743aa580838014e8a88fe0d66" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-genesis", "alloy-primitives", "alloy-trie", "arbitrary", "bytes", "modular-bitfield", - "op-alloy-consensus", + "parity-scale-codec", "reth-codecs-derive", "reth-zstd-compressors", "serde", @@ -9525,8 +9641,9 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c90f1cc0f9887680ca785b0b21aa961070b9465917bf65afaec56a6d005bb" dependencies = [ "proc-macro2", "quote", @@ -9535,8 +9652,8 @@ dependencies = [ [[package]] name = "reth-config" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "eyre", "humantime-serde", @@ -9551,10 +9668,11 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", + "alloy-eip7928", "alloy-primitives", "auto_impl", "reth-execution-types", @@ -9564,11 +9682,12 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", + "alloy-primitives", "reth-chainspec", "reth-consensus", "reth-primitives-traits", @@ -9576,11 +9695,11 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-json-rpc", "alloy-primitives", "alloy-provider", @@ -9590,9 +9709,8 @@ dependencies = [ "derive_more", "eyre", "futures", - "reqwest 0.12.28", + "reqwest 0.13.2", "reth-node-api", - "reth-primitives-traits", "reth-tracing", "ringbuffer", "serde", @@ -9602,15 +9720,17 @@ dependencies = [ [[package]] name = "reth-db" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "derive_more", "eyre", + "libc", "metrics", "page_size", "parking_lot", + "quanta", "reth-db-api", "reth-fs-util", "reth-libmdbx", @@ -9629,11 +9749,10 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-genesis", "alloy-primitives", "arbitrary", "arrayvec", @@ -9641,8 +9760,6 @@ dependencies = [ "derive_more", "metrics", "modular-bitfield", - "op-alloy-consensus", - "parity-scale-codec", "proptest", "reth-codecs", "reth-db-models", @@ -9658,8 +9775,8 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -9688,10 +9805,10 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "arbitrary", "bytes", @@ -9703,16 +9820,16 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "alloy-rlp", - "discv5", + "discv5 0.10.4 (git+https://github.com/sigp/discv5?rev=7663c00)", "enr", "itertools 0.14.0", "parking_lot", - "rand 0.8.5", + "rand 0.8.6", "reth-ethereum-forks", "reth-net-banlist", "reth-net-nat", @@ -9728,18 +9845,18 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "alloy-rlp", "derive_more", - "discv5", + "discv5 0.10.4 (git+https://github.com/sigp/discv5?rev=7663c00)", "enr", "futures", "itertools 0.14.0", "metrics", - "rand 0.9.2", + "rand 0.9.4", "reth-chainspec", "reth-ethereum-forks", "reth-metrics", @@ -9752,14 +9869,14 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "dashmap", "data-encoding", "enr", - "hickory-resolver", + "hickory-resolver 0.26.1", "linked_hash_set", "reth-ethereum-forks", "reth-network-peers", @@ -9776,11 +9893,11 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "async-compression", @@ -9811,11 +9928,11 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-network", "alloy-primitives", "alloy-provider", @@ -9847,7 +9964,6 @@ dependencies = [ "reth-payload-builder", "reth-payload-builder-primitives", "reth-payload-primitives", - "reth-primitives", "reth-primitives-traits", "reth-provider", "reth-rpc-api", @@ -9869,8 +9985,8 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "aes", "alloy-primitives", @@ -9884,7 +10000,7 @@ dependencies = [ "futures", "hmac", "pin-project", - "rand 0.8.5", + "rand 0.8.6", "reth-network-peers", "secp256k1 0.30.0", "sha2", @@ -9897,15 +10013,14 @@ dependencies = [ [[package]] name = "reth-engine-local" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rpc-types-engine", "eyre", "futures-util", - "op-alloy-rpc-types-engine", "reth-chainspec", "reth-engine-primitives", "reth-ethereum-engine-primitives", @@ -9921,11 +10036,11 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -9944,44 +10059,22 @@ dependencies = [ "tokio", ] -[[package]] -name = "reth-engine-service" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" -dependencies = [ - "futures", - "pin-project", - "reth-chainspec", - "reth-consensus", - "reth-engine-primitives", - "reth-engine-tree", - "reth-evm", - "reth-network-p2p", - "reth-node-types", - "reth-payload-builder", - "reth-provider", - "reth-prune", - "reth-stages-api", - "reth-tasks", - "reth-trie-db", -] - [[package]] name = "reth-engine-tree" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-eip7928", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", "crossbeam-channel", "derive_more", - "fixed-cache", "futures", + "indexmap 2.14.0", "metrics", "moka", "parking_lot", @@ -9994,6 +10087,7 @@ dependencies = [ "reth-errors", "reth-ethereum-primitives", "reth-evm", + "reth-execution-cache", "reth-execution-types", "reth-metrics", "reth-network-p2p", @@ -10016,8 +10110,8 @@ dependencies = [ "reth-trie-sparse", "revm", "revm-primitives", + "revm-state", "schnellru", - "smallvec", "thiserror 2.0.18", "tokio", "tracing", @@ -10025,10 +10119,12 @@ dependencies = [ [[package]] name = "reth-engine-util" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", + "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "eyre", "futures", @@ -10053,29 +10149,30 @@ dependencies = [ [[package]] name = "reth-era" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", - "ethereum_ssz 0.10.1", - "ethereum_ssz_derive 0.10.1", + "ethereum_ssz", + "ethereum_ssz_derive", + "sha2", "snap", "thiserror 2.0.18", ] [[package]] name = "reth-era-downloader" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "bytes", "eyre", "futures-util", - "reqwest 0.12.28", + "reqwest 0.13.2", "reth-era", "reth-fs-util", "sha2", @@ -10084,8 +10181,8 @@ dependencies = [ [[package]] name = "reth-era-utils" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -10106,8 +10203,8 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -10117,8 +10214,8 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-chains", "alloy-primitives", @@ -10146,12 +10243,13 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eip7928", + "alloy-eips 2.0.5", "alloy-hardforks", "alloy-primitives", "alloy-rlp", @@ -10170,8 +10268,8 @@ dependencies = [ [[package]] name = "reth-ethereum" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -10211,8 +10309,8 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "clap", "eyre", @@ -10234,11 +10332,11 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -10250,26 +10348,24 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", - "alloy-rlp", "alloy-rpc-types-engine", "reth-engine-primitives", "reth-ethereum-primitives", "reth-payload-primitives", "reth-primitives-traits", "serde", - "sha2", "thiserror 2.0.18", ] [[package]] name = "reth-ethereum-forks" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -10282,11 +10378,11 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", @@ -10297,6 +10393,7 @@ dependencies = [ "reth-ethereum-primitives", "reth-evm", "reth-evm-ethereum", + "reth-execution-cache", "reth-payload-builder", "reth-payload-builder-primitives", "reth-payload-primitives", @@ -10311,28 +10408,22 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", - "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde", - "arbitrary", - "modular-bitfield", "reth-codecs", "reth-primitives-traits", - "reth-zstd-compressors", "serde", - "serde_with", ] [[package]] name = "reth-etl" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "rayon", "reth-db-api", @@ -10341,11 +10432,11 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-primitives", "auto_impl", @@ -10365,16 +10456,14 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", - "derive_more", - "parking_lot", "reth-chainspec", "reth-ethereum-forks", "reth-ethereum-primitives", @@ -10385,10 +10474,28 @@ dependencies = [ "revm", ] +[[package]] +name = "reth-execution-cache" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives", + "fixed-cache", + "metrics", + "parking_lot", + "reth-errors", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-trie", + "tracing", +] + [[package]] name = "reth-execution-errors" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-evm", "alloy-primitives", @@ -10400,13 +10507,14 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-primitives", + "alloy-rlp", "derive_more", "reth-ethereum-primitives", "reth-primitives-traits", @@ -10418,11 +10526,11 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "eyre", "futures", @@ -10456,10 +10564,10 @@ dependencies = [ [[package]] name = "reth-exex-test-utils" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "eyre", "futures-util", "reth-chainspec", @@ -10488,10 +10596,10 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "reth-chain-state", "reth-execution-types", @@ -10502,8 +10610,8 @@ dependencies = [ [[package]] name = "reth-fs-util" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "serde", "serde_json", @@ -10512,8 +10620,8 @@ dependencies = [ [[package]] name = "reth-invalid-block-hooks" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -10540,8 +10648,8 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "bytes", "futures", @@ -10554,17 +10662,18 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "tower 0.5.3", + "tower", "tracing", ] [[package]] name = "reth-libmdbx" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", + "crossbeam-queue", "dashmap", "derive_more", "parking_lot", @@ -10576,8 +10685,8 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "bindgen", "cc", @@ -10585,20 +10694,21 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "futures", "metrics", "metrics-derive", + "reth-primitives-traits", "tokio", "tokio-util", ] [[package]] name = "reth-net-banlist" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "ipnet", @@ -10606,12 +10716,12 @@ dependencies = [ [[package]] name = "reth-net-nat" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "futures-util", "if-addrs 0.14.0", - "reqwest 0.12.28", + "reqwest 0.13.2", "serde_with", "thiserror 2.0.18", "tokio", @@ -10620,25 +10730,25 @@ dependencies = [ [[package]] name = "reth-network" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "aquamarine", "auto_impl", "derive_more", - "discv5", + "discv5 0.10.4 (git+https://github.com/sigp/discv5?rev=7663c00)", "enr", "futures", "itertools 0.14.0", "metrics", "parking_lot", "pin-project", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "rayon", "reth-chainspec", "reth-consensus", @@ -10668,6 +10778,7 @@ dependencies = [ "secp256k1 0.30.0", "serde", "smallvec", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -10677,8 +10788,8 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -10702,11 +10813,11 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "auto_impl", "derive_more", @@ -10725,8 +10836,8 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -10740,8 +10851,8 @@ dependencies = [ [[package]] name = "reth-network-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -10754,8 +10865,8 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "anyhow", "bincode 1.3.3", @@ -10771,8 +10882,8 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -10795,11 +10906,11 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-provider", "alloy-rpc-types", @@ -10823,7 +10934,6 @@ dependencies = [ "reth-downloaders", "reth-engine-local", "reth-engine-primitives", - "reth-engine-service", "reth-engine-tree", "reth-engine-util", "reth-evm", @@ -10864,11 +10974,11 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rpc-types-engine", "clap", @@ -10878,7 +10988,7 @@ dependencies = [ "futures", "humantime", "ipnet", - "rand 0.9.2", + "rand 0.9.4", "reth-chainspec", "reth-cli-util", "reth-config", @@ -10902,12 +11012,12 @@ dependencies = [ "reth-stages-types", "reth-storage-api", "reth-storage-errors", + "reth-tasks", "reth-tracing", "reth-tracing-otlp", "reth-transaction-pool", "secp256k1 0.30.0", "serde", - "shellexpand", "strum", "thiserror 2.0.18", "toml", @@ -10919,14 +11029,18 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-network", + "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", + "ethereum_ssz", "eyre", + "http-body-util", + "jsonrpsee", "reth-chainspec", "reth-engine-local", "reth-engine-primitives", @@ -10953,12 +11067,13 @@ dependencies = [ "reth-transaction-pool", "revm", "tokio", + "tower", ] [[package]] name = "reth-node-ethstats" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -10974,18 +11089,18 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-stream", - "tokio-tungstenite 0.28.0", + "tokio-tungstenite", "tracing", "url", ] [[package]] name = "reth-node-events" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -11005,8 +11120,8 @@ dependencies = [ [[package]] name = "reth-node-metrics" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "bytes", "eyre", @@ -11016,26 +11131,26 @@ dependencies = [ "jsonrpsee-server", "mappings", "metrics", - "metrics-exporter-prometheus 0.18.1", + "metrics-exporter-prometheus", "metrics-process", - "metrics-util 0.20.1", + "metrics-util", "pprof_util", "procfs", - "reqwest 0.12.28", + "reqwest 0.13.2", "reth-fs-util", "reth-metrics", "reth-tasks", "tempfile", "tikv-jemalloc-ctl", "tokio", - "tower 0.5.3", + "tower", "tracing", ] [[package]] name = "reth-node-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "reth-chainspec", "reth-db-api", @@ -11091,7 +11206,7 @@ version = "1.11.3" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-genesis", "alloy-hardforks", "alloy-op-hardforks", @@ -11118,7 +11233,7 @@ name = "reth-optimism-cli" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "clap", @@ -11155,6 +11270,7 @@ dependencies = [ "reth-stages", "reth-static-file", "reth-static-file-types", + "reth-tasks", "reth-tracing", "serde", "tempfile", @@ -11169,7 +11285,7 @@ version = "1.11.3" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-trie", "op-alloy-consensus", @@ -11199,7 +11315,7 @@ name = "reth-optimism-evm" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-genesis", "alloy-op-evm", @@ -11229,7 +11345,7 @@ name = "reth-optimism-exex" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "eyre", "futures", "futures-util", @@ -11256,7 +11372,7 @@ name = "reth-optimism-flashblocks" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-network", "alloy-primitives", "alloy-rpc-types", @@ -11291,7 +11407,7 @@ dependencies = [ "serde_json", "test-case", "tokio", - "tokio-tungstenite 0.28.0", + "tokio-tungstenite", "tracing", "url", ] @@ -11316,13 +11432,13 @@ dependencies = [ "alloy-op-evm", "alloy-op-hardforks", "alloy-primitives", + "alloy-rpc-types-admin", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "clap", "eyre", "futures", "futures-util", - "humantime", "op-alloy-consensus", "op-alloy-network", "op-alloy-rpc-types-engine", @@ -11332,7 +11448,6 @@ dependencies = [ "reth-db", "reth-db-api", "reth-e2e-test-utils", - "reth-engine-local", "reth-evm", "reth-network", "reth-node-api", @@ -11379,25 +11494,25 @@ name = "reth-optimism-payload-builder" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-debug", "alloy-rpc-types-engine", "derive_more", - "either", "op-alloy-consensus", "op-alloy-rpc-types-engine", + "op-revm", "reth-basic-payload-builder", "reth-chainspec", "reth-evm", "reth-execution-types", + "reth-optimism-chainspec", "reth-optimism-evm", "reth-optimism-forks", "reth-optimism-primitives", "reth-optimism-txpool", - "reth-payload-builder", "reth-payload-builder-primitives", "reth-payload-primitives", "reth-payload-util", @@ -11418,7 +11533,7 @@ name = "reth-optimism-primitives" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "arbitrary", @@ -11428,8 +11543,8 @@ dependencies = [ "op-alloy-consensus", "proptest", "proptest-arbitrary-interop", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "reth-codecs", "reth-primitives-traits", "reth-zstd-compressors", @@ -11445,7 +11560,7 @@ name = "reth-optimism-rpc" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-json-rpc", "alloy-op-evm", "alloy-op-hardforks", @@ -11455,7 +11570,7 @@ dependencies = [ "alloy-rpc-types-debug", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-transport", "alloy-transport-http", "async-trait", @@ -11508,7 +11623,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-stream", - "tower 0.5.3", + "tower", "tracing", ] @@ -11529,12 +11644,13 @@ name = "reth-optimism-trie" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-genesis", "alloy-primitives", "auto_impl", "bincode 2.0.1", "bytes", + "crossbeam-channel", "derive_more", "eyre", "metrics", @@ -11551,6 +11667,7 @@ dependencies = [ "reth-execution-errors", "reth-metrics", "reth-node-api", + "reth-optimism-trie", "reth-primitives-traits", "reth-provider", "reth-revm", @@ -11574,12 +11691,12 @@ name = "reth-optimism-txpool" version = "1.11.3" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-json-rpc", "alloy-primitives", "alloy-rpc-client", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "c-kzg", "derive_more", "futures-util", @@ -11591,7 +11708,9 @@ dependencies = [ "parking_lot", "reth-chain-state", "reth-chainspec", + "reth-eth-wire-types", "reth-evm", + "reth-execution-types", "reth-metrics", "reth-optimism-chainspec", "reth-optimism-evm", @@ -11609,20 +11728,23 @@ dependencies = [ [[package]] name = "reth-payload-builder" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rpc-types", + "derive_more", "futures-util", "metrics", "reth-chain-state", "reth-ethereum-engine-primitives", + "reth-execution-cache", "reth-metrics", "reth-payload-builder-primitives", "reth-payload-primitives", "reth-primitives-traits", + "reth-trie-parallel", "tokio", "tokio-stream", "tracing", @@ -11630,8 +11752,8 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "pin-project", "reth-payload-primitives", @@ -11642,31 +11764,31 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "auto_impl", "either", - "op-alloy-rpc-types-engine", - "reth-chain-state", "reth-chainspec", "reth-errors", "reth-execution-types", "reth-primitives-traits", "reth-trie-common", "serde", + "sha2", "thiserror 2.0.18", "tokio", ] [[package]] name = "reth-payload-util" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -11675,50 +11797,37 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", "reth-primitives-traits", ] -[[package]] -name = "reth-primitives" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" -dependencies = [ - "alloy-consensus", - "once_cell", - "reth-ethereum-forks", - "reth-ethereum-primitives", - "reth-primitives-traits", - "reth-static-file-types", -] - [[package]] name = "reth-primitives-traits" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee12e304adbacbb32248c9806ebafbe1e2811fbfefe53c5e5b710a8438b7ec0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", "alloy-trie", "arbitrary", - "auto_impl", "byteorder", "bytes", "dashmap", "derive_more", "modular-bitfield", "once_cell", - "op-alloy-consensus", "proptest", "proptest-arbitrary-interop", + "quanta", "rayon", "reth-codecs", "revm-bytecode", @@ -11726,17 +11835,18 @@ dependencies = [ "revm-state", "secp256k1 0.30.0", "serde", - "serde_with", "thiserror 2.0.18", ] [[package]] name = "reth-provider" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eip7928", + "alloy-eips 2.0.5", + "alloy-genesis", "alloy-primitives", "alloy-rpc-types-engine", "eyre", @@ -11765,11 +11875,13 @@ dependencies = [ "reth-storage-api", "reth-storage-errors", "reth-tasks", + "reth-tokio-util", "reth-trie", "reth-trie-db", "revm-database", "revm-state", "rocksdb", + "smallvec", "strum", "tokio", "tracing", @@ -11777,11 +11889,11 @@ dependencies = [ [[package]] name = "reth-prune" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "itertools 0.14.0", "metrics", @@ -11806,8 +11918,8 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "arbitrary", @@ -11822,10 +11934,12 @@ dependencies = [ [[package]] name = "reth-revm" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-debug", "reth-primitives-traits", "reth-storage-api", "reth-storage-errors", @@ -11835,13 +11949,12 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eip7928", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-genesis", "alloy-network", @@ -11857,7 +11970,7 @@ dependencies = [ "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-signer", "alloy-signer-local", "async-trait", @@ -11912,11 +12025,10 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eip7928", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-genesis", "alloy-json-rpc", "alloy-primitives", @@ -11930,20 +12042,21 @@ dependencies = [ "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde", + "alloy-serde 2.0.5", "jsonrpsee", "reth-chain-state", "reth-engine-primitives", "reth-network-peers", "reth-rpc-eth-api", "reth-trie-common", + "serde", "serde_json", ] [[package]] name = "reth-rpc-builder" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-network", "alloy-provider", @@ -11961,9 +12074,11 @@ dependencies = [ "reth-metrics", "reth-network-api", "reth-node-core", + "reth-payload-primitives", "reth-primitives-traits", "reth-rpc", "reth-rpc-api", + "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", "reth-rpc-layer", @@ -11976,15 +12091,15 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-util", - "tower 0.5.3", + "tower", "tower-http", "tracing", ] [[package]] name = "reth-rpc-convert" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-evm", @@ -11992,26 +12107,23 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-signer", "auto_impl", "dyn-clone", "jsonrpsee-types", - "op-alloy-consensus", - "op-alloy-network", - "op-alloy-rpc-types", - "reth-ethereum-primitives", "reth-evm", "reth-primitives-traits", + "reth-rpc-traits", "thiserror 2.0.18", ] [[package]] name = "reth-rpc-engine-api" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "async-trait", "jsonrpsee-core", @@ -12037,12 +12149,13 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eips", + "alloy-eip7928", + "alloy-eips 2.0.5", "alloy-evm", "alloy-json-rpc", "alloy-network", @@ -12050,7 +12163,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "alloy-rpc-types-mev", - "alloy-serde", + "alloy-serde 2.0.5", "async-trait", "auto_impl", "dyn-clone", @@ -12075,17 +12188,18 @@ dependencies = [ "reth-trie-common", "revm", "revm-inspectors", + "serde_json", "tokio", "tracing", ] [[package]] name = "reth-rpc-eth-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-evm", "alloy-network", "alloy-primitives", @@ -12099,8 +12213,8 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "metrics", - "rand 0.9.2", - "reqwest 0.12.28", + "rand 0.9.4", + "reqwest 0.13.2", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -12129,24 +12243,24 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-rpc-types-engine", "http", "jsonrpsee-http-client", "pin-project", - "tower 0.5.3", + "tower", "tower-http", "tracing", ] [[package]] name = "reth-rpc-server-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rpc-types-engine", "jsonrpsee-core", @@ -12157,21 +12271,37 @@ dependencies = [ "strum", ] +[[package]] +name = "reth-rpc-traits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "860fe223501a76ff14aa3bf164f739f31008c2a2905ac85708bfd88f042e6151" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-signer", + "reth-primitives-traits", + "thiserror 2.0.18", +] + [[package]] name = "reth-stages" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", - "bincode 1.3.3", + "alloy-rlp", "eyre", "futures-util", "itertools 0.14.0", "num-traits", + "page_size", "rayon", - "reqwest 0.12.28", + "reqwest 0.13.2", "reth-chainspec", "reth-codecs", "reth-config", @@ -12187,6 +12317,7 @@ dependencies = [ "reth-execution-types", "reth-exex", "reth-fs-util", + "reth-libmdbx", "reth-network-p2p", "reth-primitives-traits", "reth-provider", @@ -12209,15 +12340,16 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "aquamarine", "auto_impl", "futures-util", "metrics", + "reth-codecs", "reth-consensus", "reth-errors", "reth-metrics", @@ -12236,8 +12368,8 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "arbitrary", @@ -12250,8 +12382,8 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "parking_lot", @@ -12270,8 +12402,8 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "clap", @@ -12285,11 +12417,11 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -12302,6 +12434,7 @@ dependencies = [ "reth-prune-types", "reth-stages-types", "reth-storage-errors", + "reth-tokio-util", "reth-trie-common", "revm-database", "serde_json", @@ -12309,13 +12442,14 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "derive_more", + "reth-codecs", "reth-primitives-traits", "reth-prune-types", "reth-static-file-types", @@ -12326,17 +12460,20 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ - "auto_impl", - "dyn-clone", + "crossbeam-utils", + "dashmap", "futures-util", + "libc", "metrics", + "parking_lot", "pin-project", "rayon", "reth-metrics", "thiserror 2.0.18", + "thread-priority", "tokio", "tracing", "tracing-futures", @@ -12344,15 +12481,15 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-genesis", "alloy-primitives", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "reth-ethereum-primitives", "reth-primitives-traits", "secp256k1 0.30.0", @@ -12360,8 +12497,8 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "tokio", "tokio-stream", @@ -12370,8 +12507,8 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "clap", "eyre", @@ -12389,42 +12526,43 @@ dependencies = [ [[package]] name = "reth-tracing-otlp" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "clap", "eyre", - "opentelemetry 0.31.0", + "opentelemetry", "opentelemetry-appender-tracing", - "opentelemetry-otlp 0.31.0", + "opentelemetry-otlp", "opentelemetry-semantic-conventions", - "opentelemetry_sdk 0.31.0", + "opentelemetry_sdk", "tracing", - "tracing-opentelemetry 0.32.1", + "tracing-opentelemetry", "tracing-subscriber 0.3.23", "url", ] [[package]] name = "reth-transaction-pool" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "aquamarine", "auto_impl", - "bitflags 2.11.0", + "bitflags 2.11.1", "futures-util", + "imbl", "metrics", "parking_lot", "paste", "pin-project", "proptest", "proptest-arbitrary-interop", - "rand 0.9.2", + "rand 0.9.4", "reth-chain-state", "reth-chainspec", "reth-eth-wire-types", @@ -12453,11 +12591,11 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.5", "alloy-primitives", "alloy-rlp", "alloy-trie", @@ -12479,14 +12617,14 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.5", "alloy-trie", "arbitrary", "arrayvec", @@ -12506,8 +12644,8 @@ dependencies = [ [[package]] name = "reth-trie-db" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "metrics", @@ -12526,12 +12664,15 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ + "alloy-eip7928", + "alloy-evm", "alloy-primitives", "alloy-rlp", "crossbeam-channel", + "crossbeam-utils", "derive_more", "itertools 0.14.0", "metrics", @@ -12543,44 +12684,48 @@ dependencies = [ "reth-storage-errors", "reth-tasks", "reth-trie", - "reth-trie-common", "reth-trie-sparse", + "revm-state", "thiserror 2.0.18", "tracing", ] [[package]] name = "reth-trie-sparse" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-trie", - "auto_impl", + "either", "metrics", "rayon", "reth-execution-errors", "reth-metrics", "reth-primitives-traits", "reth-trie-common", + "serde", + "serde_json", + "slotmap", "smallvec", "tracing", ] [[package]] name = "reth-zstd-compressors" -version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c12fafa33d2f420a9d39249a3e0357b1928d09429f30758b85280409092873b2" dependencies = [ "zstd", ] [[package]] name = "revm" -version = "34.0.0" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" +checksum = "91202d39dbe8e8d10e9e8f2b76c30da68ecd1d25be69ba6d853ad0d03a3a398a" dependencies = [ "revm-bytecode", "revm-context", @@ -12597,9 +12742,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "8.0.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" +checksum = "bdbb3a3d735efa94c91f2ef6bf20a35f99a77bc78f3e25bd758336901bdf9661" dependencies = [ "bitvec", "phf", @@ -12609,9 +12754,9 @@ dependencies = [ [[package]] name = "revm-context" -version = "13.0.0" +version = "16.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "892ff3e6a566cf8d72ffb627fdced3becebbd9ba64089c25975b9b028af326a5" +checksum = "c5f68d928d8b228e0faeb1c6ed75c4fde7d124f1ddf9119b67e7a0ad4041237d" dependencies = [ "bitvec", "cfg-if", @@ -12626,9 +12771,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "14.0.0" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f61cc6d23678c4840af895b19f8acfbbd546142ec8028b6526c53cc1c16c98" +checksum = "1f3758e6167c4ba7a59a689c519a047edaefcd4c37d74f279b93ed87bc8aece4" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -12642,11 +12787,11 @@ dependencies = [ [[package]] name = "revm-database" -version = "10.0.0" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" +checksum = "c281a1f11d3bcb8c0bba1199ed6bcb001d1aeb3d4fb366819e14f88723989a4e" dependencies = [ - "alloy-eips", + "alloy-eips 1.8.3", "revm-bytecode", "revm-database-interface", "revm-primitives", @@ -12656,9 +12801,9 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "9.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7bf93ac5b91347c057610c0d96e923db8c62807e03f036762d03e981feddc1d" +checksum = "d89efb9832a4e3742bb4ded5f7fe5bf905e8860e69427d4dfec153484fc6d304" dependencies = [ "auto_impl", "either", @@ -12668,11 +12813,21 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "revm-ee-tests" +version = "0.2.0" +dependencies = [ + "op-revm", + "revm", + "serde", + "serde_json", +] + [[package]] name = "revm-handler" -version = "15.0.0" +version = "18.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" +checksum = "783e903d6922b7f5f9a940d1bb229530502d2924b1aed9d5ca5a94ebf065d460" dependencies = [ "auto_impl", "derive-where", @@ -12689,9 +12844,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "15.0.0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" +checksum = "8216ad58422090d0daa9eb430e0a081f7ad07e7fd30681dee71f8420c99624e0" dependencies = [ "auto_impl", "either", @@ -12707,9 +12862,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.34.2" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e435414e9de50a1b930da602067c76365fea2fea11e80ceb50783c94ddd127f" +checksum = "731b682530a732ef9c189ef831589128e2ce34d4a306c956322ae2dffe009715" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -12727,9 +12882,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "32.0.0" +version = "35.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" +checksum = "1ece9f41b69658c15d748288a4dbdfc06a63f3ce93d983af440de3f1631dce6a" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -12740,9 +12895,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "32.1.0" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ec11f45deec71e4945e1809736bb20d454285f9167ab53c5159dae1deb603f" +checksum = "a346a8cc6c8c39bd65306641c692191299c0a7b63d38810e39e8fe9b92378660" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -12751,23 +12906,26 @@ dependencies = [ "ark-serialize 0.5.0", "arrayref", "aurora-engine-modexp", + "aws-lc-rs", "blst", "c-kzg", "cfg-if", "gmp-mpfr-sys", "k256", "p256", + "revm-context-interface", "revm-primitives", "ripemd", "secp256k1 0.31.1", "sha2", + "substrate-bn", ] [[package]] name = "revm-primitives" -version = "22.1.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfb5ce6cf18b118932bcdb7da05cd9c250f2cb9f64131396b55f3fe3537c35" +checksum = "0c99bda77d9661521ba0b4bc04558c6692074f01e65dd420fa3b893033d9b8a2" dependencies = [ "alloy-primitives", "num_enum", @@ -12777,12 +12935,12 @@ dependencies = [ [[package]] name = "revm-state" -version = "9.0.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" +checksum = "c32490ed687dba31c3c882beb8c20408bdd30ef96690d8f145b0ee9a87040bfe" dependencies = [ "alloy-eip7928", - "bitflags 2.11.0", + "bitflags 2.11.1", "revm-bytecode", "revm-primitives", "serde", @@ -12808,7 +12966,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -12829,14 +12987,14 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" +checksum = "73389e0c99e664f919275ab5b5b0471391fe9a8de61e1dff9b1eaf56a90f16e3" dependencies = [ "bytecheck", "bytes", - "hashbrown 0.16.1", - "indexmap 2.13.0", + "hashbrown 0.17.0", + "indexmap 2.14.0", "munge", "ptr_meta", "rancor", @@ -12848,9 +13006,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" +checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6" dependencies = [ "proc-macro2", "quote", @@ -12897,9 +13055,9 @@ dependencies = [ [[package]] name = "roaring" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" dependencies = [ "bytemuck", "byteorder", @@ -12924,87 +13082,6 @@ dependencies = [ "chrono", ] -[[package]] -name = "rollup-boost" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d1d7c635dec67c86346eb871e8a22dd1596c33d4a96a9a4926b4d2fd703b63" -dependencies = [ - "alloy-primitives", - "alloy-rlp", - "alloy-rpc-types-engine", - "alloy-rpc-types-eth", - "alloy-serde", - "backoff", - "blake3", - "bytes", - "clap", - "dashmap", - "dotenvy", - "ed25519-dalek", - "eyre", - "futures", - "hex", - "http", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "jsonrpsee", - "lru 0.16.3", - "metrics", - "metrics-derive", - "metrics-exporter-prometheus 0.16.2", - "metrics-util 0.19.1", - "moka", - "op-alloy-rpc-types-engine", - "opentelemetry 0.28.0", - "opentelemetry-otlp 0.28.0", - "opentelemetry_sdk 0.28.0", - "parking_lot", - "paste", - "rollup-boost-types", - "rustls", - "serde", - "serde_json", - "sha2", - "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.26.2", - "tokio-util", - "tower 0.5.3", - "tower-http", - "tracing", - "tracing-opentelemetry 0.29.0", - "tracing-subscriber 0.3.23", - "url", - "uuid", - "vergen", - "vergen-git2", -] - -[[package]] -name = "rollup-boost-types" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756f32c7f241ab6d91d823e94d20f6e0729bfcaec3b545bd30f33b24e50f5821" -dependencies = [ - "alloy-primitives", - "alloy-rlp", - "alloy-rpc-types-engine", - "alloy-rpc-types-eth", - "alloy-serde", - "blake3", - "ed25519-dalek", - "futures", - "moka", - "op-alloy-rpc-types-engine", - "serde", - "serde_json", - "thiserror 2.0.18", - "tracing", -] - [[package]] name = "route-recognizer" version = "0.3.1" @@ -13078,8 +13155,8 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "rlp", "ruint-macro", "serde_core", @@ -13101,11 +13178,11 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" dependencies = [ - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -13138,7 +13215,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.27", + "semver 1.0.28", ] [[package]] @@ -13156,7 +13233,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -13165,9 +13242,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "aws-lc-rs", "log", @@ -13209,7 +13286,7 @@ checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", - "jni", + "jni 0.21.1", "log", "once_cell", "rustls", @@ -13222,6 +13299,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.21.1", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs 1.0.7", + "windows-sys 0.61.2", +] + [[package]] name = "rustls-platform-verifier-android" version = "0.1.1" @@ -13230,14 +13328,14 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -13281,6 +13379,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -13383,7 +13490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", + "rand 0.8.6", "secp256k1-sys 0.10.1", "serde", ] @@ -13395,7 +13502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.2", + "rand 0.9.4", "secp256k1-sys 0.11.0", ] @@ -13423,7 +13530,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -13460,9 +13567,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -13531,7 +13638,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -13562,9 +13669,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -13583,15 +13690,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -13602,11 +13709,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -13655,7 +13762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -13666,7 +13773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -13677,14 +13784,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest 0.10.7", - "keccak", + "keccak 0.1.6", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", ] [[package]] name = "sha3-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" +checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" dependencies = [ "cc", "cfg-if", @@ -13699,15 +13816,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shellexpand" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" -dependencies = [ - "dirs", -] - [[package]] name = "shlex" version = "1.3.0" @@ -13757,9 +13865,19 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] [[package]] name = "simdutf8" @@ -13818,6 +13936,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "small_btree" version = "0.1.0" @@ -13892,7 +14019,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand 0.8.6", "sha1", ] @@ -13966,12 +14093,31 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.6", + "rustc-hex", +] + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -13996,9 +14142,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +checksum = "ec005042c7d952febc1a3ef5b0f6674e9054aa836877a31c90b20e25b3d31744" dependencies = [ "paste", "proc-macro2", @@ -14037,7 +14183,7 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows", + "windows 0.62.2", ] [[package]] @@ -14046,7 +14192,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -14114,9 +14260,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" dependencies = [ "filetime", "libc", @@ -14129,7 +14275,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715f9a4586706a61c571cb5ee1c3ac2bbb2cf63e15bce772307b95befef5f5ee" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "log", "num-traits", ] @@ -14197,9 +14343,9 @@ dependencies = [ [[package]] name = "thin-vec" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" +checksum = "259cdf8ed4e4aca6f1e9d011e10bd53f524a2d0637d7b28450f6c64ac298c4c6" [[package]] name = "thiserror" @@ -14241,6 +14387,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "thread-priority" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210811179577da3d54eb69ab0b50490ee40491a25d95b8c6011ba40771cb721" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.61.3", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -14298,7 +14458,6 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", - "js-sys", "libc", "num-conv", "num_threads", @@ -14326,9 +14485,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "serde_core", @@ -14347,9 +14506,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -14362,9 +14521,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -14379,9 +14538,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -14422,34 +14581,21 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", - "native-tls", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-native-tls", "tokio-rustls", - "tungstenite 0.26.2", + "tungstenite", "webpki-roots 0.26.11", ] -[[package]] -name = "tokio-tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.28.0", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -14471,13 +14617,13 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde_core", "serde_spanned", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -14491,69 +14637,39 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.0.0+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.1", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - -[[package]] -name = "tonic" -version = "0.12.3" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.22.1", - "bytes", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "prost 0.13.5", - "socket2 0.5.10", - "tokio", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tonic" @@ -14575,7 +14691,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-stream", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", @@ -14588,28 +14704,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" dependencies = [ "bytes", - "prost 0.14.3", - "tonic 0.14.5", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "indexmap 1.9.3", - "pin-project", - "pin-project-lite", - "rand 0.8.5", - "slab", - "tokio", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", + "prost", + "tonic", ] [[package]] @@ -14621,7 +14717,7 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap 2.13.0", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -14640,7 +14736,7 @@ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", "base64 0.22.1", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-core", "futures-util", @@ -14656,7 +14752,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower 0.5.3", + "tower", "tower-layer", "tower-service", "tracing", @@ -14689,11 +14785,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber 0.3.23", @@ -14754,9 +14851,9 @@ dependencies = [ [[package]] name = "tracing-logfmt" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1f47d22deb79c3f59fcf2a1f00f60cbdc05462bf17d1cd356c1fefa3f444bd" +checksum = "a250055a3518b5efba928a18ffac8d32d42ea607a9affff4532144cd5b2e378e" dependencies = [ "time", "tracing", @@ -14764,24 +14861,6 @@ dependencies = [ "tracing-subscriber 0.3.23", ] -[[package]] -name = "tracing-opentelemetry" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721f2d2569dce9f3dfbbddee5906941e953bfcdf736a62da3377f5751650cc36" -dependencies = [ - "js-sys", - "once_cell", - "opentelemetry 0.28.0", - "opentelemetry_sdk 0.28.0", - "smallvec", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber 0.3.23", - "web-time", -] - [[package]] name = "tracing-opentelemetry" version = "0.32.1" @@ -14789,7 +14868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" dependencies = [ "js-sys", - "opentelemetry 0.31.0", + "opentelemetry", "smallvec", "tracing", "tracing-core", @@ -14889,24 +14968,24 @@ dependencies = [ [[package]] name = "tree_hash" -version = "0.10.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" +checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" dependencies = [ "alloy-primitives", "ethereum_hashing", - "ethereum_ssz 0.9.1", + "ethereum_ssz", "smallvec", "typenum", ] [[package]] name = "tree_hash_derive" -version = "0.10.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bee2ea1551f90040ab0e34b6fb7f2fa3bad8acc925837ac654f2c78a13e3089" +checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -14928,26 +15007,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "native-tls", - "rand 0.9.2", - "rustls", - "rustls-pki-types", - "sha1", - "thiserror 2.0.18", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.28.0" @@ -14959,7 +15018,9 @@ dependencies = [ "http", "httparse", "log", - "rand 0.9.2", + "rand 0.9.4", + "rustls", + "rustls-pki-types", "sha1", "thiserror 2.0.18", "utf-8", @@ -14973,9 +15034,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -15027,9 +15088,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-truncate" @@ -15060,7 +15121,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -15076,6 +15137,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -15127,9 +15194,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -15248,11 +15315,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -15261,14 +15328,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -15279,23 +15346,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -15303,9 +15366,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -15316,9 +15379,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -15340,16 +15403,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -15364,10 +15427,10 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver 1.0.27", + "indexmap 2.14.0", + "semver 1.0.28", ] [[package]] @@ -15386,9 +15449,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -15410,14 +15473,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.6", + "webpki-root-certs 1.0.7", ] [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -15428,18 +15491,28 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.2.1" @@ -15477,16 +15550,38 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -15495,7 +15590,20 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core", + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -15506,9 +15614,20 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", ] [[package]] @@ -15517,9 +15636,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core", - "windows-link", - "windows-threading", + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -15544,20 +15663,36 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core", - "windows-link", + "windows-core 0.62.2", + "windows-link 0.2.1", ] [[package]] @@ -15566,9 +15701,18 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", ] [[package]] @@ -15577,34 +15721,34 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] name = "windows-strings" -version = "0.5.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.42.2", + "windows-link 0.2.1", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.42.2", ] [[package]] @@ -15640,7 +15784,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -15658,21 +15802,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -15695,7 +15824,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -15706,13 +15835,22 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -15721,12 +15859,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -15745,12 +15877,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -15769,12 +15895,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -15805,12 +15925,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -15829,12 +15943,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -15853,12 +15961,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -15877,12 +15979,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -15900,18 +15996,14 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] [[package]] -name = "winreg" -version = "0.50.0" +name = "winnow" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "memchr", ] [[package]] @@ -15923,6 +16015,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -15942,7 +16040,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -15972,8 +16070,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -15992,9 +16090,9 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", - "semver 1.0.27", + "semver 1.0.28", "serde", "serde_derive", "serde_json", @@ -16010,9 +16108,9 @@ checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "ws_stream_wasm" @@ -16113,7 +16211,7 @@ dependencies = [ "nohash-hasher", "parking_lot", "pin-project", - "rand 0.8.5", + "rand 0.8.6", "static_assertions", ] @@ -16128,7 +16226,7 @@ dependencies = [ "nohash-hasher", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.4", "static_assertions", "web-time", ] @@ -16150,9 +16248,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -16161,9 +16259,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -16173,18 +16271,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -16193,18 +16291,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -16234,20 +16332,21 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", "zerofrom", + "zerovec", ] [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "serde", "yoke", @@ -16257,9 +16356,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 8344feb368e..da02b56de49 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [workspace.package] edition = "2024" license = "MIT OR Apache-2.0" -rust-version = "1.92" +rust-version = "1.94" authors = ["Op Stack Contributors"] homepage = "https://github.com/ethereum-optimism/optimism" repository = "https://github.com/ethereum-optimism/optimism" @@ -49,6 +49,12 @@ members = [ # Alloy OP Hardforks "alloy-op-hardforks/", + + # Op-Revm + "op-revm/", + + # Revm execution-environment integration tests + "revm-ee-tests/", ] default-members = [ "kona/bin/host", @@ -175,6 +181,15 @@ rand_chacha.opt-level = 3 rand_xorshift.opt-level = 3 unarray.opt-level = 3 +[profile.fast-build] +inherits = "dev" +opt-level = 0 +debug = "none" +split-debuginfo = "off" +debug-assertions = false +incremental = true +codegen-units = 256 + [profile.bench] debug = true @@ -286,108 +301,115 @@ reth-optimism-trie = { path = "op-reth/crates/trie/" } reth-optimism-txpool = { path = "op-reth/crates/txpool/" } # ==================== OP-ALLOY INTERNAL CRATES ==================== -op-alloy-consensus = { version = "0.23.1", path = "op-alloy/crates/consensus", default-features = false } -op-alloy-network = { version = "0.23.1", path = "op-alloy/crates/network", default-features = false } -op-alloy-provider = { version = "0.23.1", path = "op-alloy/crates/provider", default-features = false } -op-alloy-rpc-types = { version = "0.23.1", path = "op-alloy/crates/rpc-types", default-features = false } -op-alloy-rpc-types-engine = { version = "0.23.1", path = "op-alloy/crates/rpc-types-engine", default-features = false } -op-alloy-rpc-jsonrpsee = { version = "0.23.1", path = "op-alloy/crates/rpc-jsonrpsee", default-features = false } +op-alloy-consensus = { version = "2.0.0", path = "op-alloy/crates/consensus", default-features = false } +op-alloy-network = { version = "2.0.0", path = "op-alloy/crates/network", default-features = false } +op-alloy-provider = { version = "2.0.0", path = "op-alloy/crates/provider", default-features = false } +op-alloy-rpc-types = { version = "2.0.0", path = "op-alloy/crates/rpc-types", default-features = false } +op-alloy-rpc-types-engine = { version = "2.0.0", path = "op-alloy/crates/rpc-types-engine", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "2.0.0", path = "op-alloy/crates/rpc-jsonrpsee", default-features = false } # ==================== ALLOY-OP-EVM / ALLOY-OP-HARDFORKS ==================== -alloy-op-evm = { version = "0.26.3", path = "alloy-op-evm/", default-features = false } -alloy-op-hardforks = { version = "0.4.7", path = "alloy-op-hardforks/", default-features = false } - -# ==================== RETH CRATES (v1.11.3) ==================== -reth = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-chain-state = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-cli-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-codecs = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-db-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-db-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-downloaders = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-e2e-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-engine-local = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-eth-wire = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-ethereum-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-ethereum-forks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-exex = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-exex-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-execution-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-fs-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-metrics = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-network = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-network-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-network-peers = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-core = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-events = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-metrics = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-payload-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-payload-validator = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-prune = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-prune-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-revm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-stages = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-stages-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-static-file = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-static-file-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-storage-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-tasks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-tracing = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-trie = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-trie-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } -reth-trie-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-zstd-compressors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3", default-features = false } +alloy-op-evm = { version = "0.32.0", path = "alloy-op-evm/", default-features = false } +alloy-op-hardforks = { version = "0.5.0", path = "alloy-op-hardforks/", default-features = false } + +# ==================== RETH CRATES (crates.io) ==================== +reth-codecs = { version = "0.3.1", default-features = false, features = [ + "alloy", +] } +reth-codecs-derive = "0.3.1" +reth-primitives-traits = { version = "0.3.1", default-features = false } +reth-rpc-traits = { version = "0.3.1", default-features = false } +reth-zstd-compressors = { version = "0.3.1", default-features = false } + +# ==================== RETH CRATES (git @ 88505c7fcbfdebfd3b56d88c86b62e950043c6c4) ==================== +reth = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-chain-state = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-cli = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-cli-util = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-db = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-db-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-db-common = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-downloaders = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-e2e-test-utils = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-engine-local = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-errors = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-eth-wire = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-eth-wire-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-ethereum-cli = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-ethereum-forks = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-exex = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-exex-test-utils = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-fs-util = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-metrics = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-network = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-network-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-core = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-events = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-metrics = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-util = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-validator = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-prune = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-prune-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-revm = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-rpc = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-stages = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-stages-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-static-file = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-static-file-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-storage-errors = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-tracing = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-trie = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-trie-common = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", default-features = false } +reth-trie-db = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } # ==================== REVM (latest: op-reth versions) ==================== -revm = { version = "34.0.0", default-features = false } -revm-bytecode = { version = "8.0.0", default-features = false } -revm-database = { version = "10.0.0", default-features = false } -revm-state = { version = "9.0.0", default-features = false } -revm-primitives = { version = "22.0.0", default-features = false } -revm-interpreter = { version = "32.0.0", default-features = false } -revm-database-interface = { version = "9.0.0", default-features = false } -op-revm = { version = "15.0.0", default-features = false } -revm-inspectors = "0.34.2" +revm = { version = "38.0.0", default-features = false } +revm-bytecode = { version = "10.0.0", default-features = false } +revm-database = { version = "13.0.0", default-features = false } +revm-state = { version = "11.0.0", default-features = false } +revm-primitives = { version = "23.0.0", default-features = false } +revm-interpreter = { version = "35.0.0", default-features = false } +revm-database-interface = { version = "11.0.0", default-features = false } +op-revm = { version = "20.0.0", path = "op-revm/", default-features = false } +revm-inspectors = "0.39.0" # ==================== ALLOY ==================== -alloy-chains = { version = "0.2.30", default-features = false } +alloy-chains = { version = "0.2.33", default-features = false } alloy-dyn-abi = "1.5.6" alloy-eip2124 = { version = "0.2.0", default-features = false } -alloy-eip7928 = { version = "0.3.2", default-features = false } -alloy-evm = { version = "0.27.2", default-features = false } +alloy-eip7928 = { version = "0.3.4", default-features = false } +alloy-evm = { version = "0.34.0", default-features = false } alloy-primitives = { version = "1.5.6", default-features = false, features = [ "map-foldhash", ] } @@ -400,44 +422,44 @@ alloy-trie = { version = "0.9.4", default-features = false } alloy-hardforks = { version = "0.4.7", default-features = false } -alloy-consensus = { version = "1.6.3", default-features = false } -alloy-contract = { version = "1.6.3", default-features = false } -alloy-eips = { version = "1.6.3", default-features = false } -alloy-genesis = { version = "1.6.3", default-features = false } -alloy-json-rpc = { version = "1.6.3", default-features = false } -alloy-network = { version = "1.6.3", default-features = false } -alloy-network-primitives = { version = "1.6.3", default-features = false } -alloy-node-bindings = { version = "1.6.3", default-features = false } -alloy-provider = { version = "1.6.3", features = [ +alloy-consensus = { version = "2.0.4", default-features = false } +alloy-contract = { version = "2.0.4", default-features = false } +alloy-eips = { version = "2.0.4", default-features = false } +alloy-genesis = { version = "2.0.4", default-features = false } +alloy-json-rpc = { version = "2.0.4", default-features = false } +alloy-network = { version = "2.0.4", default-features = false } +alloy-network-primitives = { version = "2.0.4", default-features = false } +alloy-node-bindings = { version = "2.0.4", default-features = false } +alloy-provider = { version = "2.0.4", features = [ "reqwest", "debug-api", ], default-features = false } -alloy-pubsub = { version = "1.6.3", default-features = false } -alloy-rpc-client = { version = "1.6.3", default-features = false } -alloy-rpc-types = { version = "1.6.3", features = [ +alloy-pubsub = { version = "2.0.4", default-features = false } +alloy-rpc-client = { version = "2.0.4", default-features = false } +alloy-rpc-types = { version = "2.0.4", features = [ "eth", ], default-features = false } -alloy-rpc-types-admin = { version = "1.6.3", default-features = false } -alloy-rpc-types-anvil = { version = "1.6.3", default-features = false } -alloy-rpc-types-beacon = { version = "1.6.3", default-features = false } -alloy-rpc-types-debug = { version = "1.6.3", default-features = false } -alloy-rpc-types-engine = { version = "1.6.3", default-features = false } -alloy-rpc-types-eth = { version = "1.6.3", default-features = false } -alloy-rpc-types-mev = { version = "1.6.3", default-features = false } -alloy-rpc-types-trace = { version = "1.6.3", default-features = false } -alloy-rpc-types-txpool = { version = "1.6.3", default-features = false } -alloy-serde = { version = "1.6.3", default-features = false } -alloy-signer = { version = "1.6.3", default-features = false } -alloy-signer-local = { version = "1.6.3", default-features = false } -alloy-transport = { version = "1.6.3" } -alloy-transport-http = { version = "1.6.3", features = [ +alloy-rpc-types-admin = { version = "2.0.4", default-features = false } +alloy-rpc-types-anvil = { version = "2.0.4", default-features = false } +alloy-rpc-types-beacon = { version = "2.0.4", default-features = false } +alloy-rpc-types-debug = { version = "2.0.4", default-features = false } +alloy-rpc-types-engine = { version = "2.0.4", default-features = false } +alloy-rpc-types-eth = { version = "2.0.4", default-features = false } +alloy-rpc-types-mev = { version = "2.0.4", default-features = false } +alloy-rpc-types-trace = { version = "2.0.4", default-features = false } +alloy-rpc-types-txpool = { version = "2.0.4", default-features = false } +alloy-serde = { version = "2.0.4", default-features = false } +alloy-signer = { version = "2.0.4", default-features = false } +alloy-signer-local = { version = "2.0.4", default-features = false } +alloy-transport = { version = "2.0.4" } +alloy-transport-http = { version = "2.0.4", features = [ "reqwest-rustls-tls", ], default-features = false } -alloy-transport-ipc = { version = "1.6.3", default-features = false } -alloy-transport-ws = { version = "1.6.3", default-features = false } +alloy-transport-ipc = { version = "2.0.4", default-features = false } +alloy-transport-ws = { version = "2.0.4", default-features = false } # ==================== OP-ALLOY (from crates.io) ==================== -op-alloy = { version = "0.23.1", path = "op-alloy/crates/op-alloy", default-features = false } +op-alloy = { version = "2.0.0", path = "op-alloy/crates/op-alloy", default-features = false } op-alloy-flz = { version = "0.13.1", default-features = false } # ==================== ASYNC ==================== @@ -484,8 +506,8 @@ ark-bls12-381 = { version = "0.5.0", default-features = false } # ==================== SERIALIZATION ==================== bincode = { version = "2.0.1", features = ["serde"] } -ethereum_ssz = "0.9.1" -ethereum_ssz_derive = "0.9.1" +ethereum_ssz = "0.10" +ethereum_ssz_derive = "0.10" rkyv = "0.8.15" serde_repr = "0.1.20" serde_with = { version = "3.16", default-features = false, features = [ @@ -570,6 +592,7 @@ cfg-if = "1.0.4" chrono = "0.4.43" clap = "4.5.58" color-eyre = "0.6.5" +crossbeam-channel = "0.5.13" crossterm = "0.29.0" dashmap = "6.1" derive_more = { version = "2.1.1", default-features = false, features = [ @@ -613,10 +636,8 @@ rand = { version = "0.9.2", default-features = false } rand_08 = { package = "rand", version = "0.8" } ratatui = "0.30.0" rayon = "1.11.0" -reqwest = { version = "0.13.2", default-features = false } +reqwest = { version = "0.13.2", default-features = false, features = ["query"] } ringbuffer = "0.16.0" -rollup-boost = "0.7.13" -rollup-boost-types = "0.1.0" rustc-hash = { version = "2.1", default-features = false } rustls = { version = "0.23", default-features = false } rustls-pemfile = { version = "2.2", default-features = false } @@ -646,18 +667,3 @@ snmalloc-rs = { version = "0.3.8", features = ["build_cc"] } # K/V database rocksdb = { version = "0.24.0", default-features = false } - -[patch.crates-io] -# Duplicated by: reth-payload-primitives, reth-engine-local (reth git), rollup-boost, -# rollup-boost-types (crates.io) -op-alloy-rpc-types-engine = { path = "op-alloy/crates/rpc-types-engine" } -# Duplicated by: reth-codecs, reth-db-api, reth-primitives-traits, reth-rpc-convert (reth git) -op-alloy-consensus = { path = "op-alloy/crates/consensus" } -# Duplicated by: reth-rpc-convert (reth git) -op-alloy-network = { path = "op-alloy/crates/network" } -# Duplicated by: reth-rpc-convert (reth git) -op-alloy-rpc-types = { path = "op-alloy/crates/rpc-types" } -# Duplicated by: alloy-evm (crates.io) -op-alloy = { path = "op-alloy/crates/op-alloy" } -# Duplicated by: alloy-evm (crates.io) -alloy-op-hardforks = { path = "alloy-op-hardforks/" } diff --git a/rust/UPDATING-RETH.md b/rust/UPDATING-RETH.md new file mode 100644 index 00000000000..434fda67d69 --- /dev/null +++ b/rust/UPDATING-RETH.md @@ -0,0 +1,123 @@ +# Updating the reth dependency + +The Rust workspace pins ~70 `reth-*` crates from `paradigmxyz/reth` to a single +git rev in [`rust/Cargo.toml`](Cargo.toml). This guide describes how to bump +that rev safely. + +## When to update + +- Picking up a fix or feature merged upstream that op-reth depends on. +- Periodic catch-up to limit drift before it becomes painful. +- After your own reth-side change has merged upstream. + +Prefer the latest upstream release tag when one exists that contains the +changes you need. Tags are stable artifacts with corresponding upstream +releases and the most predictable downstream behavior. Fall back to a merge +commit on `main` only when no tagged release covers the needed change yet +(e.g. urgently picking up a fix). Avoid pinning to an unmerged PR branch for +anything we want to land — the merge commit on `main` is at least the version +main's CI actually validated. + +## Procedure + +1. Pick the new rev. For a specific upstream PR, take its merge commit (see + `gh pr view --repo paradigmxyz/reth --json mergeCommit`). Otherwise take + the current head of `paradigmxyz/reth` `main`. + +2. Update the rev. All ~70 references share a single rev string, so a single + replacement covers them: + + ```bash + cd rust + sed -i 's/rev = ""/rev = ""/g' Cargo.toml + ``` + +3. Refresh `Cargo.lock`. `cargo update -p reth` does **not** work — there is no + top-level crate literally named `reth` in the dep graph; the workspace + depends on `reth-*` subcrates. Pass any real reth subcrate; cargo cascades + to every git dep sharing the same source: + + ```bash + mise exec -- cargo update reth-chainspec + ``` + +4. Compile and adapt: + + ```bash + mise exec -- cargo check --workspace --tests + ``` + + Fix each compile error, then re-run. Don't try to predict the full set of + breakages in advance — let the compiler walk the dep graph. Each pass + surfaces the next crate's errors. + +5. Build, format, and test before pushing: + + ```bash + mise exec -- cargo build -p op-reth + just fmt-fix && just lint + just test-unit + ``` + +## Expect upstream churn beyond your target change + +A rev bump is rarely "just a rev change." Upstream reth iterates trait +signatures, struct fields, re-exports, and feature flags between commits, and +any of them can require op-reth-side adaptation. Recent examples observed in +the wild: + +- Trait methods gaining new parameters (e.g. `FullConsensus::validate_block_post_execution` + gained `block_access_list_hash: Option`; `PayloadTypes::block_to_payload` + gained `bal: Option`). +- Associated-type bounds (e.g. `PayloadTypes::ExecutionData` gaining a + `From` bound, requiring a new `From` impl). +- Provider trait additions (e.g. `BalProvider` becoming a required bound on + the engine API's `Provider`). +- Struct destructuring (e.g. `BlockBuilderOutcome` gaining a + `block_access_list` field; `BuiltPayloadExecutedBlock.hashed_state` / + `trie_updates` losing their `Either, _>` wrappers). +- Renames (e.g. `ComputedTrieData::without_trie_input` → `::new`). +- Removed re-exports — sometimes the trait itself is deleted upstream while + op-reth (and the wider OP Stack) still relies on it. In that case the + smallest correct fix is to vendor the trait locally in the consuming crate + (with a comment pointing at the upstream PR that removed it). See + `rust/op-reth/crates/rpc/src/witness.rs` for an example. +- Type changes in test fixtures (e.g. struct fields flipping from `u16` to + `Option`). + +None of these are deep redesigns — most fixes are one to three lines — but +they need to be done before CI will pass. + +## Picking the right target commit + +In order of preference: + +1. **Latest upstream release tag** that contains the change you need. Tags + correspond to actual upstream releases and have the most predictable + downstream behavior. List them with `git -C tag --sort=-v:refname | head` + (or `gh release list --repo paradigmxyz/reth`). + +2. **Merge commit on `main`** of a specific PR — for cases where the needed + change has been merged but isn't in a release yet (e.g. urgent fix). Find + it via `gh pr view --repo paradigmxyz/reth --json mergeCommit`. The + merge commit is stable (PR rebases no longer affect it) and is the version + main's CI actually validated. + +3. **Current `main` HEAD** — for periodic catch-up bumps when no specific PR + is the trigger. + +Avoid pinning to an unmerged PR branch tip for anything we want to land. It +moves under us when the PR rebases, may sit on a much newer main than our +current pin, and isn't a version any upstream CI has signed off on as a +release artifact. + +If the only upstream change you need is a single PR but the PR branch is based +on much newer main than our pin, consider asking the upstream author to rebase +onto an older base, or accept the broader catch-up work as part of the bump. + +## See also + +- `docs/ai/rust-dev.md` — broader Rust workflow (build, test, lint). +- `rust/Cargo.toml` — where the rev string lives (~70 occurrences). +- `rust/op-reth/crates/rpc/src/witness.rs` — example of vendoring a trait that + upstream removed. diff --git a/rust/alloy-op-evm/Cargo.toml b/rust/alloy-op-evm/Cargo.toml index d64db38c865..b9dd70e7bc4 100644 --- a/rust/alloy-op-evm/Cargo.toml +++ b/rust/alloy-op-evm/Cargo.toml @@ -2,9 +2,9 @@ name = "alloy-op-evm" description = "OP EVM implementation" -version = "0.26.3" +version = "0.32.0" edition = "2021" -rust-version = "1.92" +rust-version = "1.94" authors = ["Alloy Contributors", "OpLabsPBC"] license.workspace = true homepage = "https://github.com/ethereum-optimism/optimism" @@ -14,7 +14,7 @@ repository = "https://github.com/ethereum-optimism/optimism" workspace = true [dependencies] -alloy-evm = { workspace = true, features = ["op"] } +alloy-evm = { workspace = true } alloy-eips = { workspace = true } alloy-consensus = { workspace = true } @@ -50,3 +50,10 @@ gmp = ["alloy-evm/gmp"] engine = ["op-alloy/rpc-types-engine"] rpc = ["alloy-evm/rpc", "op-alloy/rpc-types"] asm-keccak = ["alloy-evm/asm-keccak", "alloy-primitives/asm-keccak", "revm/asm-keccak"] +arbitrary = [ + "alloy-consensus/arbitrary", + "alloy-eips/arbitrary", + "alloy-primitives/arbitrary", + "op-alloy/arbitrary", + "revm/arbitrary", +] diff --git a/rust/alloy-op-evm/release.toml b/rust/alloy-op-evm/release.toml index 60e8b772197..07326d0705d 100644 --- a/rust/alloy-op-evm/release.toml +++ b/rust/alloy-op-evm/release.toml @@ -1,11 +1,8 @@ # Configuration file for [`cargo-release`](https://github.com/crate-ci/cargo-release) # See: https://github.com/crate-ci/cargo-release/blob/master/docs/reference.md -allow-branch = ["main"] +allow-branch = ["develop"] sign-commit = true sign-tag = true -shared-version = true -pre-release-commit-message = "chore: release {{version}}" -tag-prefix = "" # tag only once instead of per every crate -pre-release-hook = ["sh", "-c", "$WORKSPACE_ROOT/scripts/changelog.sh --tag {{version}}"] -owners = ["github:alloy-rs:core"] +pre-release-commit-message = "chore(alloy-op-evm): release {{version}}" +tag-prefix = "" # tag only once instead of per every crate diff --git a/rust/alloy-op-evm/src/block/mod.rs b/rust/alloy-op-evm/src/block/mod.rs index 4334c9bd72f..f89c65712b8 100644 --- a/rust/alloy-op-evm/src/block/mod.rs +++ b/rust/alloy-op-evm/src/block/mod.rs @@ -1,33 +1,49 @@ //! Block executor for Optimism. -use crate::OpEvmFactory; -use alloc::{borrow::Cow, boxed::Box, vec::Vec}; +use crate::{OpEvmFactory, spec_by_timestamp_after_bedrock}; +use alloc::{ + borrow::Cow, boxed::Box, collections::BTreeMap, format, string::String, vec, vec::Vec, +}; use alloy_consensus::{Eip658Value, Header, Transaction, TransactionEnvelope, TxReceipt}; -use alloy_eips::{Encodable2718, Typed2718}; +use alloy_eips::{Encodable2718, Typed2718, eip7685::Requests}; use alloy_evm::{ - Database, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded, RecoveredTx, + Database, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, RecoveredTx, block::{ BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, - BlockExecutorFor, BlockValidationError, ExecutableTx, OnStateHook, - StateChangePostBlockSource, StateChangeSource, StateDB, SystemCaller, TxResult, + BlockValidationError, ExecutableTx, GasOutput, OnStateHook, StateChangePostBlockSource, + StateChangeSource, StateDB, SystemCaller, TxResult, state_changes::{balance_increment_state, post_block_balance_increments}, }, eth::{EthTxResult, receipt_builder::ReceiptBuilderCtx}, }; use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; -use alloy_primitives::{Address, B256, Bytes}; +use alloy_primitives::{Address, B256, Bytes, U256}; use canyon::ensure_create2_deployer; -use op_alloy::consensus::OpDepositReceipt; +use op_alloy::consensus::{ + OpDepositReceipt, OpTransaction as OpConsensusTransaction, POST_EXEC_TX_TYPE_ID, + PostExecPayload, SDMGasEntry, +}; use op_revm::{ - L1BlockInfo, OpTransaction, constants::L1_BLOCK_CONTRACT, estimate_tx_compressed_size, + L1BlockInfo, OpTransaction, + constants::{BASE_FEE_RECIPIENT, L1_BLOCK_CONTRACT, OPERATOR_FEE_RECIPIENT}, + estimate_tx_compressed_size, transaction::deposit::DEPOSIT_TRANSACTION_TYPE, }; pub use receipt_builder::OpAlloyReceiptBuilder; use receipt_builder::OpReceiptBuilder; use revm::{ Database as _, DatabaseCommit, Inspector, - context::{Block, result::ResultAndState}, - database::{DatabaseCommitExt, State}, + context::{ + Block, TxEnv, + result::{ExecutionResult, Output, ResultAndState, SuccessReason}, + }, + database::DatabaseCommitExt, + state::{Account, AccountStatus, EvmState}, +}; + +use crate::post_exec::{ + PostExecEvm, PostExecEvmFactoryAdapter, PostExecEvmFactoryHooks, PostExecTxContext, + PostExecTxKind, }; mod canyon; @@ -46,6 +62,161 @@ impl OpTxEnv for OpTransaction { } } +/// Canonical post-exec execution mode for an OP block. +#[derive(Debug, Default, Clone)] +pub enum PostExecMode { + /// Execute with legacy gas accounting. + #[default] + Disabled, + /// Produce canonical post-exec refunds locally and append them to the block later. + Produce, + /// Verify canonical gas accounting using an post-exec payload embedded in the block. + Verify(PostExecPayload), +} + +/// Per-block post-exec state carried by [`OpBlockExecutor`]. +#[derive(Debug)] +pub enum PostExecState { + /// Execute with legacy gas accounting. + Disabled, + /// Produce canonical post-exec refunds locally and append them to the block later. + Producing { + /// Accumulated per-tx warming refunds for post-exec tx assembly. + entries: Vec, + }, + /// Verify canonical gas accounting using a post-exec payload embedded in the block. + /// + /// `payload` and `remaining` are not redundant: `payload` is the immutable verifier input + /// (kept for byte-equality comparison against the actual `0x7D` tx and for the block-number + /// re-check), while `remaining` is the mutable working set drained as txs are matched. + Verifying { + /// Decoded post-exec payload being verified. + payload: PostExecPayload, + /// Verifier payload entries not yet consumed, indexed by original tx index. + remaining: BTreeMap, + /// Invalid verifier payload reason, if any. + invalid_reason: Option, + /// Whether the block's synthetic post-exec transaction has been seen during execution. + saw_post_exec_tx: bool, + }, +} + +impl PostExecState { + fn new(mode: PostExecMode) -> Self { + match mode { + PostExecMode::Disabled => Self::Disabled, + PostExecMode::Produce => Self::Producing { entries: Vec::new() }, + PostExecMode::Verify(payload) => { + let mut remaining = BTreeMap::new(); + let mut invalid_reason = None; + + for entry in &payload.gas_refund_entries { + if entry.gas_refund == 0 { + invalid_reason = Some(format!( + "zero post-exec payload refund for tx index {}", + entry.index + )); + break; + } + if remaining.insert(entry.index, entry.gas_refund).is_some() { + invalid_reason = Some(format!( + "duplicate post-exec payload entry for tx index {}", + entry.index + )); + break; + } + } + + Self::Verifying { payload, remaining, invalid_reason, saw_post_exec_tx: false } + } + } + } + + const fn is_producing(&self) -> bool { + matches!(self, Self::Producing { .. }) + } + + const fn is_verifying(&self) -> bool { + matches!(self, Self::Verifying { .. }) + } + + const fn invalid_reason(&self) -> Option<&str> { + match self { + Self::Verifying { invalid_reason: Some(reason), .. } => Some(reason.as_str()), + _ => None, + } + } + + fn verify_block_number(&self, block_number: u64) -> Option { + match self { + Self::Verifying { payload, .. } if payload.block_number != block_number => { + Some(format!( + "payload block number {} does not match block number {}", + payload.block_number, block_number, + )) + } + _ => None, + } + } + + const fn produced_entries_mut(&mut self) -> Option<&mut Vec> { + match self { + Self::Producing { entries } => Some(entries), + _ => None, + } + } + + fn take_entries(&mut self) -> Vec { + match self { + Self::Producing { entries } => core::mem::take(entries), + _ => Vec::new(), + } + } + + fn verifier_refund(&self, tx_index: u64) -> Option { + match self { + Self::Verifying { remaining, .. } => remaining.get(&tx_index).copied(), + _ => None, + } + } + + fn consume_verifier_entry(&mut self, tx_index: u64) { + if let Self::Verifying { remaining, .. } = self { + remaining.remove(&tx_index); + } + } + + fn verify_post_exec_tx( + &mut self, + tx_index: u64, + payload: &PostExecPayload, + ) -> Result<(), String> { + match self { + Self::Verifying { payload: expected, saw_post_exec_tx, .. } => { + if *saw_post_exec_tx { + return Err(format!("duplicate post-exec tx at index {tx_index}")); + } + if payload != expected { + return Err(format!("post-exec tx payload mismatch at index {tx_index}")); + } + *saw_post_exec_tx = true; + Ok(()) + } + Self::Producing { .. } => Ok(()), + Self::Disabled => Err(format!( + "unexpected post-exec tx at index {tx_index}: SDM not active for this block" + )), + } + } + + fn remaining_verifier_indexes(&self) -> Vec { + match self { + Self::Verifying { remaining, .. } => remaining.keys().copied().collect(), + _ => Vec::new(), + } + } +} + /// Context for OP block execution. #[derive(Debug, Default, Clone)] pub struct OpBlockExecutionCtx { @@ -55,6 +226,29 @@ pub struct OpBlockExecutionCtx { pub parent_beacon_block_root: Option, /// The block's extra data. pub extra_data: Bytes, + /// Canonical post-exec execution mode for this block. + pub post_exec_mode: PostExecMode, +} + +/// Balance patch that reconciles fee distribution with the post-refund gas used. +/// +/// The EVM has already paid fees out based on `evm_gas_used`, so just lowering `gas_used` in the +/// receipt isn't enough — the sender, beneficiary, base-fee recipient, and operator-fee recipient +/// all need their balances rolled back to match the canonical gas. This struct carries the +/// per-recipient debits, plus the matching credit to the sender (which equals their sum). +#[derive(Debug, Default, Clone)] +pub struct PostExecAdjustment { + /// Refund amount subtracted from `evm_gas_used` to produce `canonical_gas_used`. + pub refund: u64, + /// Wei to credit back to the sender (sum of the three recipient deltas below). + pub sender_balance_delta: U256, + /// Wei to debit from the block beneficiary — priority-fee share of the refund. + pub beneficiary_balance_delta: U256, + /// Wei to debit from the base-fee recipient — base-fee share of the refund. + pub base_fee_balance_delta: U256, + /// Wei to debit from the operator-fee recipient — operator-fee share of the refund + /// (post-Isthmus). + pub operator_fee_balance_delta: U256, } /// The result of executing an OP transaction. @@ -64,16 +258,35 @@ pub struct OpTxResult { pub inner: EthTxResult, /// Whether the transaction is a deposit transaction. pub is_deposit: bool, + /// Whether the transaction is a post-exec transaction. + pub is_post_exec: bool, /// The sender of the transaction. pub sender: Address, + /// Gas used returned by normal EVM execution, before any canonical post-exec adjustment. + pub evm_gas_used: u64, + /// Canonical gas used after any post-exec adjustment. + pub canonical_gas_used: u64, + /// Canonical post-exec adjustment, if any. + pub post_exec: Option, + /// Cached depositor nonce — looked up during execute so commit can be infallible. + /// `Some` only for regolith deposit transactions. + pub depositor_nonce: Option, } -impl TxResult for OpTxResult { +impl TxResult for OpTxResult +where + H: Send + 'static, + T: Send + 'static, +{ type HaltReason = H; fn result(&self) -> &ResultAndState { &self.inner.result } + + fn into_result(self) -> ResultAndState { + self.inner.result + } } /// Block executor for Optimism. @@ -100,6 +313,10 @@ pub struct OpBlockExecutor { pub is_regolith: bool, /// Utility to call system smart contracts. pub system_caller: SystemCaller, + /// Cached L1 block info for the current block. + pub l1_block_info: Option, + /// Post-exec execution state (mode and producer/verifier working state). + pub post_exec: PostExecState, } impl OpBlockExecutor @@ -110,6 +327,7 @@ where { /// Creates a new [`OpBlockExecutor`]. pub fn new(evm: E, ctx: OpBlockExecutionCtx, spec: Spec, receipt_builder: R) -> Self { + let post_exec = PostExecState::new(ctx.post_exec_mode.clone()); Self { is_regolith: spec .is_regolith_active_at_timestamp(evm.block().timestamp().saturating_to()), @@ -121,8 +339,31 @@ where gas_used: 0, da_footprint_used: 0, ctx, + l1_block_info: None, + post_exec, } } + + /// Set the post-exec execution mode for the executor. + #[must_use] + pub fn with_post_exec_mode(mut self, post_exec_mode: PostExecMode) -> Self { + self.set_post_exec_mode(post_exec_mode); + self + } + + /// Set the post-exec execution mode for the executor. + /// + /// This is primarily intended for tests and replay tooling that need to override the + /// block-context default after construction. + pub fn set_post_exec_mode(&mut self, post_exec_mode: PostExecMode) { + self.post_exec = PostExecState::new(post_exec_mode); + } + + /// Take the accumulated post-exec entries (sequencer mode). + /// Returns the entries and clears the internal state. + pub fn take_post_exec_entries(&mut self) -> Vec { + self.post_exec.take_entries() + } } /// Custom errors that can occur during OP block execution. @@ -146,6 +387,19 @@ pub enum OpBlockExecutionError { /// The available block DA footprint. available_block_da_footprint: u64, }, + + /// The block contained an invalid post-exec payload. + #[error("invalid post-exec payload: {0}")] + InvalidPostExecPayload(String), + + /// Canonical post-exec settlement would underflow an account balance. + #[error("canonical post-exec settlement underflow for {address}: delta {delta}")] + PostExecSettlementUnderflow { + /// Account whose balance would underflow. + address: Address, + /// Delta that could not be removed from the account. + delta: U256, + }, } impl OpBlockExecutor @@ -154,7 +408,10 @@ where DB: Database + DatabaseCommit + StateDB, Tx: FromRecoveredTx + FromTxWithEncoded + OpTxEnv, >, - R: OpReceiptBuilder, + R: OpReceiptBuilder< + Transaction: Transaction + Encodable2718 + OpConsensusTransaction, + Receipt: TxReceipt, + >, Spec: OpHardforks, { fn jovian_da_footprint_estimation( @@ -181,15 +438,250 @@ where Ok(encoded.saturating_mul(da_footprint_gas_scalar)) } + + fn invalid_post_exec_payload(reason: impl Into) -> BlockExecutionError { + BlockExecutionError::Validation(BlockValidationError::Other(Box::new( + OpBlockExecutionError::InvalidPostExecPayload(reason.into()), + ))) + } + + fn verifier_post_exec_refund_for_tx( + &self, + tx_index: u64, + is_deposit: bool, + is_post_exec: bool, + evm_gas_used: u64, + ) -> Result { + // Entry-existence first: deposit and post-exec txs are called with this helper + // unconditionally to validate their tx index against the payload, so we can only + // raise the deposit/post-exec error when the payload actually targets them. + let Some(refund) = self.post_exec.verifier_refund(tx_index) else { + return Ok(0); + }; + + if is_deposit { + return Err(Self::invalid_post_exec_payload(format!( + "payload entry targets deposit tx index {tx_index}" + ))); + } + + if is_post_exec { + return Err(Self::invalid_post_exec_payload(format!( + "payload entry targets post-exec tx index {tx_index}" + ))); + } + + if refund > evm_gas_used { + return Err(Self::invalid_post_exec_payload(format!( + "payload refund {refund} exceeds evm_gas_used {evm_gas_used} for tx index {tx_index}" + ))); + } + + Ok(refund) + } + + const fn canonicalize_result_gas( + result: &mut ExecutionResult, + post_exec_refund: u64, + ) { + if post_exec_refund == 0 { + return; + } + + match result { + ExecutionResult::Success { gas, .. } => { + *gas = gas + .with_total_gas_spent(gas.total_gas_spent().saturating_sub(post_exec_refund)) + .with_refunded(gas.inner_refunded().saturating_add(post_exec_refund)); + } + ExecutionResult::Revert { gas, .. } | ExecutionResult::Halt { gas, .. } => { + *gas = gas + .with_total_gas_spent(gas.total_gas_spent().saturating_sub(post_exec_refund)); + } + } + } + + fn state_account_mut<'a>( + db: &mut E::DB, + state: &'a mut EvmState, + address: Address, + ) -> Result<&'a mut Account, BlockExecutionError> { + use revm::primitives::hash_map::Entry; + + match state.entry(address) { + Entry::Occupied(entry) => Ok(entry.into_mut()), + Entry::Vacant(entry) => { + let info = + db.basic(address).map_err(BlockExecutionError::other)?.unwrap_or_default(); + let original_info = info.clone(); + Ok(entry.insert(Account { + info, + // The original_info is not used by State::commit — the + // CacheAccount tracks its own previous state for building + // transitions. Setting it equal to current info is safe. + original_info: Box::new(original_info), + status: AccountStatus::Touched, + ..Default::default() + })) + } + } + } + + fn add_state_balance( + db: &mut E::DB, + state: &mut EvmState, + address: Address, + delta: U256, + ) -> Result<(), BlockExecutionError> { + if delta.is_zero() { + return Ok(()); + } + + let account = Self::state_account_mut(db, state, address)?; + account.mark_touch(); + account.info.balance = account.info.balance.saturating_add(delta); + Ok(()) + } + + fn sub_state_balance( + db: &mut E::DB, + state: &mut EvmState, + address: Address, + delta: U256, + ) -> Result<(), BlockExecutionError> { + if delta.is_zero() { + return Ok(()); + } + + let account = Self::state_account_mut(db, state, address)?; + account.mark_touch(); + account.info.balance = account.info.balance.checked_sub(delta).ok_or_else(|| { + BlockExecutionError::Validation(BlockValidationError::Other(Box::new( + OpBlockExecutionError::PostExecSettlementUnderflow { address, delta }, + ))) + })?; + Ok(()) + } + + fn l1_block_info( + &mut self, + spec_id: op_revm::OpSpecId, + ) -> Result { + if let Some(l1_block_info) = &self.l1_block_info { + return Ok(l1_block_info.clone()); + } + + let block_number = self.evm.block().number(); + let l1_block_info = L1BlockInfo::try_fetch(self.evm.db_mut(), block_number, spec_id) + .map_err(BlockExecutionError::other)?; + self.l1_block_info = Some(l1_block_info.clone()); + Ok(l1_block_info) + } + + /// Computes the fee-settlement patch required after canonicalizing post-exec gas. + /// + /// `evm.transact` has already charged the sender and paid fee recipients according to + /// `evm_gas_used`. Lowering only the receipt's `gas_used` would leave those balance changes + /// in place. This translates the refunded gas back into the exact per-recipient deltas + /// `execute_transaction_without_commit` then applies before state is committed. + fn post_exec_settlement_deltas( + &mut self, + tx: impl RecoveredTx, + evm_gas_used: u64, + post_exec_refund: u64, + is_deposit: bool, + is_post_exec: bool, + ) -> Result { + if is_deposit || is_post_exec || post_exec_refund == 0 { + return Ok(PostExecAdjustment::default()); + } + + let gas_delta_u256 = U256::from(post_exec_refund); + let basefee = u128::from(self.evm.block().basefee()); + let spec_id = spec_by_timestamp_after_bedrock( + &self.spec, + self.evm.block().timestamp().saturating_to(), + ); + let effective_gas_price = tx.tx().effective_gas_price(Some(self.evm.block().basefee())); + // SDM/PostExec is only enabled on forks after Karst, which is already post-London. + // A saturating_sub landing at zero is intentional and consensus-valid: a legacy tx + // with a gas price equal to the basefee pays zero priority fee, so the beneficiary + // delta below must be zero as well — we credit back only what the beneficiary + // actually received for the refunded gas, which is the (effective_price - basefee) + // component. + let beneficiary_gas_price = effective_gas_price.saturating_sub(basefee); + + let base_fee_balance_delta = gas_delta_u256.saturating_mul(U256::from(basefee)); + let beneficiary_balance_delta = + gas_delta_u256.saturating_mul(U256::from(beneficiary_gas_price)); + + let canonical_gas_used = evm_gas_used.saturating_sub(post_exec_refund); + let l1_block_info = self.l1_block_info(spec_id)?; + let encoded = tx.tx().encoded_2718(); + let raw_fee = + l1_block_info.operator_fee_charge(encoded.as_ref(), U256::from(evm_gas_used), spec_id); + let canonical_fee = l1_block_info.operator_fee_charge( + encoded.as_ref(), + U256::from(canonical_gas_used), + spec_id, + ); + let operator_fee_balance_delta = raw_fee.saturating_sub(canonical_fee); + + let sender_balance_delta = gas_delta_u256 + .saturating_mul(U256::from(effective_gas_price)) + .saturating_add(operator_fee_balance_delta); + + Ok(PostExecAdjustment { + refund: post_exec_refund, + sender_balance_delta, + beneficiary_balance_delta, + base_fee_balance_delta, + operator_fee_balance_delta, + }) + } + + fn apply_post_exec_refund_to_state( + &mut self, + state: &mut EvmState, + sender: Address, + deltas: &PostExecAdjustment, + ) -> Result<(), BlockExecutionError> { + let beneficiary = self.evm.block().beneficiary(); + Self::add_state_balance(self.evm.db_mut(), state, sender, deltas.sender_balance_delta)?; + Self::sub_state_balance( + self.evm.db_mut(), + state, + beneficiary, + deltas.beneficiary_balance_delta, + )?; + Self::sub_state_balance( + self.evm.db_mut(), + state, + BASE_FEE_RECIPIENT, + deltas.base_fee_balance_delta, + )?; + Self::sub_state_balance( + self.evm.db_mut(), + state, + OPERATOR_FEE_RECIPIENT, + deltas.operator_fee_balance_delta, + )?; + + Ok(()) + } } impl BlockExecutor for OpBlockExecutor where - E: Evm< + E: PostExecEvm< DB: Database + DatabaseCommit + StateDB, Tx: FromRecoveredTx + FromTxWithEncoded + OpTxEnv, + HaltReason: Send + 'static, + >, + R: OpReceiptBuilder< + Transaction: Transaction + Encodable2718 + OpConsensusTransaction, + Receipt: TxReceipt, >, - R: OpReceiptBuilder, Spec: OpHardforks, { type Transaction = R::Transaction; @@ -198,10 +690,13 @@ where type Result = OpTxResult::TxType>; fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { - // Set state clear flag if the block is after the Spurious Dragon hardfork. - let state_clear_flag = - self.spec.is_spurious_dragon_active_at_block(self.evm.block().number().saturating_to()); - self.evm.db_mut().set_state_clear_flag(state_clear_flag); + if let Some(reason) = self.post_exec.invalid_reason() { + return Err(Self::invalid_post_exec_payload(String::from(reason))); + } + let block_number = self.evm.block().number().saturating_to::(); + if let Some(reason) = self.post_exec.verify_block_number(block_number) { + return Err(Self::invalid_post_exec_payload(reason)); + } self.system_caller.apply_blockhashes_contract_call(self.ctx.parent_hash, &mut self.evm)?; self.system_caller @@ -227,6 +722,8 @@ where ) -> Result { let (tx_env, tx) = tx.into_parts(); let is_deposit = tx.tx().ty() == DEPOSIT_TRANSACTION_TYPE; + let is_post_exec = tx.tx().ty() == POST_EXEC_TX_TYPE_ID; + let tx_index = self.receipts.len() as u64; // The sum of the transaction's gas limit, Tg, and the gas utilized in this block prior, // must be no greater than the block's gasLimit. @@ -239,6 +736,42 @@ where .into()); } + if is_post_exec { + let payload = + tx.tx().as_post_exec().map(|tx| &tx.inner().payload).ok_or_else(|| { + Self::invalid_post_exec_payload(format!( + "transaction at index {tx_index} has post-exec type but no post-exec payload", + )) + })?; + if let Err(reason) = self.post_exec.verify_post_exec_tx(tx_index, payload) { + return Err(Self::invalid_post_exec_payload(reason)); + } + // Validates that no Verify payload entry targets this tx index; refund is always 0. + self.verifier_post_exec_refund_for_tx(tx_index, false, true, 0)?; + return Ok(OpTxResult { + inner: EthTxResult { + result: ResultAndState::new( + ExecutionResult::Success { + reason: SuccessReason::Stop, + gas: revm::context::result::ResultGas::default(), + logs: vec![], + output: Output::Call(Bytes::default()), + }, + EvmState::default(), + ), + blob_gas_used: 0, + tx_type: tx.tx().tx_type(), + }, + is_deposit: false, + is_post_exec: true, + sender: *tx.signer(), + evm_gas_used: 0, + canonical_gas_used: 0, + post_exec: None, + depositor_nonce: None, + }); + } + let da_footprint_used = if self .spec .is_jovian_active_at_timestamp(self.evm.block().timestamp().saturating_to()) && @@ -262,12 +795,68 @@ where 0 }; + if self.post_exec.is_producing() { + self.evm.begin_post_exec_tx(PostExecTxContext { + tx_index, + kind: if is_deposit { PostExecTxKind::Deposit } else { PostExecTxKind::Normal }, + }); + } + // Execute transaction and return the result - let result = self.evm.transact(tx_env).map_err(|err| { + let mut result = self.evm.transact(tx_env).map_err(|err| { let hash = tx.tx().trie_hash(); BlockExecutionError::evm(err, hash) })?; + let evm_gas_used = result.result.tx_gas_used(); + let post_exec_refund = if self.post_exec.is_producing() { + let refund = self.evm.take_last_post_exec_tx_result().refund_total; + // The inspector's accumulated refund must never exceed the tx's evm_gas_used. If + // it does, we'd emit an `SDMGasEntry` that any honest verifier would reject + // at pre-execution ("payload refund exceeds evm_gas_used"), so the sequencer + // would ship a block it can't verify itself. Fail here with a loud error + // instead of letting `saturating_sub` mask the discrepancy. + if refund > evm_gas_used { + return Err(Self::invalid_post_exec_payload(format!( + "produced refund {refund} exceeds evm_gas_used {evm_gas_used} for tx index {tx_index}", + ))); + } + refund + } else { + self.verifier_post_exec_refund_for_tx(tx_index, is_deposit, false, evm_gas_used)? + }; + let canonical_gas_used = evm_gas_used.saturating_sub(post_exec_refund); + let deltas = self.post_exec_settlement_deltas( + &tx, + evm_gas_used, + post_exec_refund, + is_deposit, + false, + )?; + let post_exec = (post_exec_refund > 0).then_some(deltas); + + // Pre-compute depositor nonce here so `commit_transaction` can be infallible. + // Only post-regolith deposit transactions need the depositor account from DB. + let sender = *tx.signer(); + let depositor_nonce = if self.is_regolith && is_deposit { + let account = self + .evm + .db_mut() + .basic(sender) + .map_err(BlockExecutionError::other)? + .unwrap_or_default(); + Some(account.nonce) + } else { + None + }; + + // Canonicalize the result gas and apply any post-exec refund to state in-place. Both + // operations must run before commit so commit_transaction stays infallible. + Self::canonicalize_result_gas(&mut result.result, post_exec_refund); + if let Some(deltas) = post_exec.as_ref() { + self.apply_post_exec_refund_to_state(&mut result.state, sender, deltas)?; + } + Ok(OpTxResult { inner: EthTxResult { result, @@ -275,36 +864,48 @@ where tx_type: tx.tx().tx_type(), }, is_deposit, - sender: *tx.signer(), + is_post_exec: false, + sender, + evm_gas_used, + canonical_gas_used, + post_exec, + depositor_nonce, }) } - fn commit_transaction(&mut self, output: Self::Result) -> Result { + fn commit_transaction(&mut self, output: Self::Result) -> GasOutput { + let tx_index = self.receipts.len() as u64; let OpTxResult { inner: EthTxResult { result: ResultAndState { result, state }, blob_gas_used, tx_type }, is_deposit, - sender, + is_post_exec, + sender: _, + evm_gas_used: _, + canonical_gas_used, + post_exec, + depositor_nonce, } = output; - // Fetch the depositor account from the database for the deposit nonce. - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor = (self.is_regolith && is_deposit) - .then(|| self.evm.db_mut().basic(sender).map(|acc| acc.unwrap_or_default())) - .transpose() - .map_err(BlockExecutionError::other)?; + let post_exec_refund = post_exec.as_ref().map(|d| d.refund).unwrap_or(0); - self.system_caller.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); + if !is_deposit && !is_post_exec && post_exec_refund > 0 { + if let Some(entries) = self.post_exec.produced_entries_mut() { + entries.push(SDMGasEntry { index: tx_index, gas_refund: post_exec_refund }); + } + } + if self.post_exec.is_verifying() && post_exec_refund > 0 { + self.post_exec.consume_verifier_entry(tx_index); + } - let gas_used = result.gas_used(); + self.system_caller.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); - // append gas used - self.gas_used += gas_used; + // add canonical gas used + self.gas_used += canonical_gas_used; // Update DA footprint if Jovian is active if self.spec.is_jovian_active_at_timestamp(self.evm.block().timestamp().saturating_to()) && - !is_deposit + !is_deposit && + !is_post_exec { // Add to DA footprint used self.da_footprint_used = self.da_footprint_used.saturating_add(blob_gas_used); @@ -330,7 +931,7 @@ where self.receipt_builder.build_deposit_receipt(OpDepositReceipt { inner: receipt, - deposit_nonce: depositor.map(|account| account.nonce), + deposit_nonce: depositor_nonce, // The deposit receipt version was introduced in Canyon to indicate an // update to how receipt hashes should be computed // when set. The state transition process ensures @@ -348,12 +949,21 @@ where self.evm.db_mut().commit(state); - Ok(gas_used) + GasOutput::new(canonical_gas_used) } fn finish( mut self, ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { + let indexes = self.post_exec.remaining_verifier_indexes(); + if !indexes.is_empty() { + return Err(Self::invalid_post_exec_payload(format!( + "{} unconsumed post-exec payload entries for tx indexes {:?}", + indexes.len(), + indexes, + ))); + } + let balance_increments = post_block_balance_increments::
(&self.spec, self.evm.block(), &[], None); // increment balances @@ -371,15 +981,12 @@ where }) })?; - let legacy_gas_used = - self.receipts.last().map(|r| r.cumulative_gas_used()).unwrap_or_default(); - Ok(( self.evm, BlockExecutionResult { receipts: self.receipts, - requests: Default::default(), - gas_used: legacy_gas_used, + requests: Requests::default(), + gas_used: self.gas_used, blob_gas_used: self.da_footprint_used, }, )) @@ -440,19 +1047,31 @@ impl OpBlockExecutorFactory { } } -impl BlockExecutorFactory for OpBlockExecutorFactory +impl BlockExecutorFactory + for OpBlockExecutorFactory> where - R: OpReceiptBuilder, - Spec: OpHardforks, - EvmF: EvmFactory< - Tx: FromRecoveredTx + FromTxWithEncoded + OpTxEnv, - >, + R: OpReceiptBuilder< + Transaction: Transaction + Encodable2718 + OpConsensusTransaction, + Receipt: TxReceipt, + > + 'static, + Spec: OpHardforks + 'static, + F: PostExecEvmFactoryHooks + 'static, + F::Tx: FromRecoveredTx + FromTxWithEncoded + OpTxEnv, Self: 'static, { - type EvmFactory = EvmF; + type EvmFactory = PostExecEvmFactoryAdapter; type ExecutionCtx<'a> = OpBlockExecutionCtx; type Transaction = R::Transaction; type Receipt = R::Receipt; + type TxExecutionResult = OpTxResult< + as EvmFactory>::HaltReason, + ::TxType, + >; + type Executor< + 'a, + DB: StateDB, + I: Inspector< as EvmFactory>::Context>, + > = OpBlockExecutor< as EvmFactory>::Evm, &'a R, &'a Spec>; fn evm_factory(&self) -> &Self::EvmFactory { &self.evm_factory @@ -460,300 +1079,62 @@ where fn create_executor<'a, DB, I>( &'a self, - evm: EvmF::Evm<&'a mut State, I>, + evm: as EvmFactory>::Evm, ctx: Self::ExecutionCtx<'a>, - ) -> impl BlockExecutorFor<'a, Self, DB, I> + ) -> Self::Executor<'a, DB, I> where - DB: Database + 'a, - I: Inspector>> + 'a, + DB: StateDB, + I: Inspector< as EvmFactory>::Context>, { OpBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) } } -#[cfg(test)] -mod tests { - use alloc::{string::ToString, vec}; - use alloy_consensus::{SignableTransaction, TxLegacy, transaction::Recovered}; - use alloy_eips::eip2718::WithEncoded; - use alloy_evm::{EvmEnv, ToTxEnv}; - use alloy_hardforks::ForkCondition; - use alloy_op_hardforks::OpHardfork; - use alloy_primitives::{Address, Signature, U256, uint}; - use op_alloy::consensus::OpTxEnvelope; - use op_revm::{ - DefaultOp, L1BlockInfo, OpBuilder, OpSpecId, - constants::{ - BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, - L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT, - }, - }; - use revm::{ - Context, - context::BlockEnv, - database::{CacheDB, EmptyDB, InMemoryDB}, - inspector::NoOpInspector, - primitives::HashMap, - state::AccountInfo, - }; - - use crate::OpEvm; - - use super::*; - - #[test] - fn test_with_encoded() { - let executor_factory = OpBlockExecutorFactory::new( - OpAlloyReceiptBuilder::default(), - OpChainHardforks::op_mainnet(), - OpEvmFactory::>::default(), - ); - let mut db = State::builder().with_database(CacheDB::::default()).build(); - let evm = executor_factory.evm_factory.create_evm(&mut db, EvmEnv::default()); - let mut executor = executor_factory.create_executor(evm, OpBlockExecutionCtx::default()); - let tx = Recovered::new_unchecked( - OpTxEnvelope::Legacy(TxLegacy::default().into_signed(Signature::new( - Default::default(), - Default::default(), - Default::default(), - ))), - Address::ZERO, - ); - let tx_with_encoded = WithEncoded::new(tx.encoded_2718().into(), tx.clone()); - - // make sure we can use both `WithEncoded` and transaction itself as inputs. - let _ = executor.execute_transaction(&tx); - let _ = executor.execute_transaction(&tx_with_encoded); - } - - fn prepare_jovian_db(da_footprint_gas_scalar: u16) -> State { - const L1_BASE_FEE: U256 = uint!(1_U256); - const L1_BLOB_BASE_FEE: U256 = uint!(2_U256); - const L1_BASE_FEE_SCALAR: u64 = 3; - const L1_BLOB_BASE_FEE_SCALAR: u64 = 4; - const L1_FEE_SCALARS: U256 = U256::from_limbs([ - 0, - (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR, - 0, - 0, - ]); - const OPERATOR_FEE_SCALAR: u8 = 5; - const OPERATOR_FEE_CONST: u8 = 6; - let da_footprint_gas_scalar_bytes = da_footprint_gas_scalar.to_be_bytes(); - let mut operator_fee_and_da_footprint = [0u8; 32]; - operator_fee_and_da_footprint[31] = OPERATOR_FEE_CONST; - operator_fee_and_da_footprint[23] = OPERATOR_FEE_SCALAR; - operator_fee_and_da_footprint[19] = da_footprint_gas_scalar_bytes[1]; - operator_fee_and_da_footprint[18] = da_footprint_gas_scalar_bytes[0]; - let operator_fee_and_da_footprint_u256 = U256::from_be_bytes(operator_fee_and_da_footprint); - - let mut db = State::builder().with_database(InMemoryDB::default()).build(); - - db.insert_account_with_storage( - L1_BLOCK_CONTRACT, - Default::default(), - HashMap::from_iter([ - (L1_BASE_FEE_SLOT, L1_BASE_FEE), - (ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS), - (ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE), - (OPERATOR_FEE_SCALARS_SLOT, operator_fee_and_da_footprint_u256), - ]), - ); - - db.insert_account( - Address::ZERO, - AccountInfo { balance: U256::from(400_000_000), ..Default::default() }, - ); - - db - } - - fn build_executor<'a>( - db: &'a mut State, - receipt_builder: &'a OpAlloyReceiptBuilder, - op_chain_hardforks: &'a OpChainHardforks, - gas_limit: u64, - jovian_timestamp: u64, - ) -> OpBlockExecutor< - OpEvm<&'a mut State, NoOpInspector>, - &'a OpAlloyReceiptBuilder, - &'a OpChainHardforks, - > { - let ctx = Context::op() - .with_db(db) - .with_chain(L1BlockInfo { - operator_fee_scalar: Some(U256::from(2)), - operator_fee_constant: Some(U256::from(50)), - ..Default::default() - }) - .with_block(BlockEnv { - timestamp: U256::from(jovian_timestamp), - gas_limit, - ..Default::default() - }) - .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN); - - let evm = OpEvm::new(ctx.build_op_with_inspector(NoOpInspector {}), true); - - OpBlockExecutor::new( - evm, - OpBlockExecutionCtx::default(), - op_chain_hardforks, - receipt_builder, - ) - } - - #[test] - fn test_jovian_da_footprint_estimation() { - const DA_FOOTPRINT_GAS_SCALAR: u16 = 7; - const GAS_LIMIT: u64 = 100_000; - const JOVIAN_TIMESTAMP: u64 = 1746806402; - - let mut db = prepare_jovian_db(DA_FOOTPRINT_GAS_SCALAR); - let op_chain_hardforks = OpChainHardforks::new( - OpHardfork::op_mainnet() - .into_iter() - .chain(vec![(OpHardfork::Jovian, ForkCondition::Timestamp(JOVIAN_TIMESTAMP))]), - ); - - let receipt_builder = OpAlloyReceiptBuilder::default(); - let mut executor = build_executor( - &mut db, - &receipt_builder, - &op_chain_hardforks, - GAS_LIMIT, - JOVIAN_TIMESTAMP, - ); - - let tx_inner = TxLegacy { gas_limit: GAS_LIMIT, ..Default::default() }; - - let tx = Recovered::new_unchecked( - OpTxEnvelope::Legacy(tx_inner.into_signed(Signature::new( - Default::default(), - Default::default(), - Default::default(), - ))), - Address::ZERO, - ); - let tx_env = tx.to_tx_env(); - - assert!(executor.da_footprint_used == 0); - - let expected_da_footprint = executor.jovian_da_footprint_estimation(&tx_env, &tx).unwrap(); - - // make sure we can use both `WithEncoded` and transaction itself as inputs. - let res = executor.execute_transaction(&tx); - assert!(res.is_ok()); - - assert!(executor.da_footprint_used == expected_da_footprint); - } - - #[test] - fn test_jovian_da_footprint_estimation_out_of_gas() { - const DA_FOOTPRINT_GAS_SCALAR: u16 = 7; - const JOVIAN_TIMESTAMP: u64 = 1746806402; - const GAS_LIMIT: u64 = 100; - - let mut db = prepare_jovian_db(DA_FOOTPRINT_GAS_SCALAR); - let op_chain_hardforks = OpChainHardforks::new( - OpHardfork::op_mainnet() - .into_iter() - .chain(vec![(OpHardfork::Jovian, ForkCondition::Timestamp(JOVIAN_TIMESTAMP))]), - ); - - let receipt_builder = OpAlloyReceiptBuilder::default(); - let mut executor = build_executor( - &mut db, - &receipt_builder, - &op_chain_hardforks, - GAS_LIMIT, - JOVIAN_TIMESTAMP, - ); - - let tx_inner = TxLegacy { gas_limit: GAS_LIMIT, ..Default::default() }; +impl BlockExecutorFactory for OpBlockExecutorFactory> +where + R: OpReceiptBuilder< + Transaction: Transaction + Encodable2718 + OpConsensusTransaction, + Receipt: TxReceipt, + > + 'static, + Spec: OpHardforks + 'static, + Tx: IntoTxEnv + + Into> + + Default + + Clone + + core::fmt::Debug + + FromRecoveredTx + + FromTxWithEncoded + + OpTxEnv + + 'static, + Self: 'static, +{ + type EvmFactory = OpEvmFactory; + type ExecutionCtx<'a> = OpBlockExecutionCtx; + type Transaction = R::Transaction; + type Receipt = R::Receipt; + type TxExecutionResult = OpTxResult< + as EvmFactory>::HaltReason, + ::TxType, + >; + type Executor<'a, DB: StateDB, I: Inspector< as EvmFactory>::Context>> = + OpBlockExecutor< as EvmFactory>::Evm, &'a R, &'a Spec>; - let tx = Recovered::new_unchecked( - OpTxEnvelope::Legacy(tx_inner.into_signed(Signature::new( - Default::default(), - Default::default(), - Default::default(), - ))), - Address::ZERO, - ); - let tx_env = tx.to_tx_env(); - - assert!(executor.da_footprint_used == 0); - - let expected_da_footprint = executor.jovian_da_footprint_estimation(&tx_env, &tx).unwrap(); - - // make sure we can use both `WithEncoded` and transaction itself as inputs. - let res = executor.execute_transaction(&tx); - assert!(res.is_err()); - let err = res.unwrap_err(); - match err { - BlockExecutionError::Validation(BlockValidationError::Other(err)) => { - assert_eq!( - err.to_string(), - OpBlockExecutionError::TransactionDaFootprintAboveGasLimit { - transaction_da_footprint: expected_da_footprint, - available_block_da_footprint: GAS_LIMIT, - } - .to_string(), - ); - } - _ => panic!("expected TransactionDaFootprintAboveGasLimit error"), - } + fn evm_factory(&self) -> &Self::EvmFactory { + &self.evm_factory } - #[test] - fn test_jovian_da_footprint_estimation_maxed_out_da_footprint() { - const DA_FOOTPRINT_GAS_SCALAR: u16 = 2000; - const JOVIAN_TIMESTAMP: u64 = 1746806402; - const GAS_LIMIT: u64 = 200_000; - - let mut db = prepare_jovian_db(DA_FOOTPRINT_GAS_SCALAR); - let op_chain_hardforks = OpChainHardforks::new( - OpHardfork::op_mainnet() - .into_iter() - .chain(vec![(OpHardfork::Jovian, ForkCondition::Timestamp(JOVIAN_TIMESTAMP))]), - ); - - let receipt_builder = OpAlloyReceiptBuilder::default(); - let mut executor = build_executor( - &mut db, - &receipt_builder, - &op_chain_hardforks, - GAS_LIMIT, - JOVIAN_TIMESTAMP, - ); - - let tx_inner = TxLegacy { gas_limit: GAS_LIMIT, ..Default::default() }; - - let tx = Recovered::new_unchecked( - OpTxEnvelope::Legacy(tx_inner.into_signed(Signature::new( - Default::default(), - Default::default(), - Default::default(), - ))), - Address::ZERO, - ); - let tx_env = tx.to_tx_env(); - - assert!(executor.da_footprint_used == 0); - - let expected_da_footprint = executor.jovian_da_footprint_estimation(&tx_env, &tx).unwrap(); - - // make sure we can use both `WithEncoded` and transaction itself as inputs. - let gas_used_tx = executor.execute_transaction(&tx).expect("failed to execute transaction"); - - // The gas used when executing the transaction should be the legacy value... - assert!(gas_used_tx < expected_da_footprint); - - // The gas used when finishing the executor should be the DA footprint since this is higher - // than the legacy gas used and jovian is active... - let (_, result) = executor.finish().expect("failed to finish executor"); - assert_eq!(result.blob_gas_used, expected_da_footprint); - assert_eq!(result.gas_used, gas_used_tx); - assert!(result.blob_gas_used > result.gas_used); + fn create_executor<'a, DB, I>( + &'a self, + evm: as EvmFactory>::Evm, + ctx: Self::ExecutionCtx<'a>, + ) -> Self::Executor<'a, DB, I> + where + DB: StateDB, + I: Inspector< as EvmFactory>::Context>, + { + OpBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) } } + +#[cfg(test)] +mod tests; diff --git a/rust/alloy-op-evm/src/block/receipt_builder.rs b/rust/alloy-op-evm/src/block/receipt_builder.rs index 7fd04314c7f..dc9e3f82dac 100644 --- a/rust/alloy-op-evm/src/block/receipt_builder.rs +++ b/rust/alloy-op-evm/src/block/receipt_builder.rs @@ -10,7 +10,10 @@ use op_alloy::consensus::{OpDepositReceipt, OpReceiptEnvelope, OpTxEnvelope, OpT #[auto_impl::auto_impl(&, Arc)] pub trait OpReceiptBuilder: Debug { /// Transaction type. - type Transaction: TransactionEnvelope; + /// + /// `TxType: Send + 'static` is required so that `OpTxResult` can satisfy the + /// upstream `TxResult` trait bound (`Self: Send + 'static`). + type Transaction: TransactionEnvelope; /// Receipt type. type Receipt; @@ -58,6 +61,7 @@ impl OpReceiptBuilder for OpAlloyReceiptBuilder { OpTxType::Eip2930 => OpReceiptEnvelope::Eip2930(receipt), OpTxType::Eip1559 => OpReceiptEnvelope::Eip1559(receipt), OpTxType::Eip7702 => OpReceiptEnvelope::Eip7702(receipt), + OpTxType::PostExec => OpReceiptEnvelope::PostExec(receipt), OpTxType::Deposit => unreachable!(), }) } diff --git a/rust/alloy-op-evm/src/block/tests.rs b/rust/alloy-op-evm/src/block/tests.rs new file mode 100644 index 00000000000..a9cb67f1808 --- /dev/null +++ b/rust/alloy-op-evm/src/block/tests.rs @@ -0,0 +1,528 @@ +use alloc::{string::ToString, vec}; +use alloy_consensus::{SignableTransaction, TxLegacy, transaction::Recovered}; +use alloy_eips::eip2718::WithEncoded; +use alloy_evm::{EvmEnv, ToTxEnv}; +use alloy_hardforks::ForkCondition; +use alloy_op_hardforks::OpHardfork; +use alloy_primitives::{Address, Signature, U256, uint}; +use op_alloy::consensus::OpTxEnvelope; +use op_revm::{ + L1BlockInfo, OpBuilder, OpSpecId, OpTransaction, + constants::{ + BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, + L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT, + }, +}; +use revm::{ + Context, MainContext, + context::{BlockEnv, CfgEnv}, + database::{CacheDB, EmptyDB, InMemoryDB, State}, + inspector::NoOpInspector, + primitives::HashMap, + state::AccountInfo, +}; + +use crate::OpEvm; + +use super::*; + +/// Wraps a `TxLegacy` in an `OpTxEnvelope::Legacy` recovered with a zero signer. +fn recovered_legacy(tx: TxLegacy) -> Recovered { + Recovered::new_unchecked( + OpTxEnvelope::Legacy(tx.into_signed(Signature::new( + Default::default(), + Default::default(), + Default::default(), + ))), + Address::ZERO, + ) +} + +/// Build the standard verifier payload (version 1) used by every test. +fn post_exec_payload(block_number: u64, gas_refund_entries: Vec) -> PostExecPayload { + PostExecPayload { version: 1, block_number, gas_refund_entries } +} + +#[test] +fn test_with_encoded() { + let executor_factory = OpBlockExecutorFactory::new( + OpAlloyReceiptBuilder::default(), + OpChainHardforks::op_mainnet(), + OpEvmFactory::::default(), + ); + let mut db = State::builder().with_database(CacheDB::::default()).build(); + let evm = executor_factory.evm_factory.create_evm(&mut db, EvmEnv::default()); + let mut executor = executor_factory.create_executor(evm, OpBlockExecutionCtx::default()); + let tx = recovered_legacy(TxLegacy::default()); + let tx_with_encoded = WithEncoded::new(tx.encoded_2718().into(), tx.clone()); + + // make sure we can use both `WithEncoded` and transaction itself as inputs. + let _ = executor.execute_transaction(&tx); + let _ = executor.execute_transaction(&tx_with_encoded); +} + +fn prepare_jovian_db(da_footprint_gas_scalar: u16) -> State { + const L1_BASE_FEE: U256 = uint!(1_U256); + const L1_BLOB_BASE_FEE: U256 = uint!(2_U256); + const L1_BASE_FEE_SCALAR: u64 = 3; + const L1_BLOB_BASE_FEE_SCALAR: u64 = 4; + const L1_FEE_SCALARS: U256 = U256::from_limbs([ + 0, + (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR, + 0, + 0, + ]); + const OPERATOR_FEE_SCALAR: u8 = 5; + const OPERATOR_FEE_CONST: u8 = 6; + let da_footprint_gas_scalar_bytes = da_footprint_gas_scalar.to_be_bytes(); + let mut operator_fee_and_da_footprint = [0u8; 32]; + operator_fee_and_da_footprint[31] = OPERATOR_FEE_CONST; + operator_fee_and_da_footprint[23] = OPERATOR_FEE_SCALAR; + operator_fee_and_da_footprint[19] = da_footprint_gas_scalar_bytes[1]; + operator_fee_and_da_footprint[18] = da_footprint_gas_scalar_bytes[0]; + let operator_fee_and_da_footprint_u256 = U256::from_be_bytes(operator_fee_and_da_footprint); + + let mut db = State::builder().with_database(InMemoryDB::default()).build(); + + db.insert_account_with_storage( + L1_BLOCK_CONTRACT, + Default::default(), + HashMap::from_iter([ + (L1_BASE_FEE_SLOT, L1_BASE_FEE), + (ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS), + (ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE), + (OPERATOR_FEE_SCALARS_SLOT, operator_fee_and_da_footprint_u256), + ]), + ); + + db.insert_account( + Address::ZERO, + AccountInfo { balance: U256::from(400_000_000), ..Default::default() }, + ); + + db +} + +type SDMTestExecutor<'a> = OpBlockExecutor< + OpEvm< + &'a mut State, + NoOpInspector, + op_revm::precompiles::OpPrecompiles, + crate::OpTx, + >, + &'a OpAlloyReceiptBuilder, + &'a OpChainHardforks, +>; + +const DEFAULT_DA_FOOTPRINT_GAS_SCALAR: u16 = 7; +const DEFAULT_GAS_LIMIT: u64 = 100_000; +const JOVIAN_TIMESTAMP: u64 = 1_746_806_402; + +fn build_executor<'a>( + db: &'a mut State, + receipt_builder: &'a OpAlloyReceiptBuilder, + op_chain_hardforks: &'a OpChainHardforks, + gas_limit: u64, + jovian_timestamp: u64, +) -> SDMTestExecutor<'a> { + let ctx = Context::mainnet() + .with_tx(crate::OpTx(OpTransaction::builder().build_fill())) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .with_chain(L1BlockInfo::default()) + .with_db(db) + .with_chain(L1BlockInfo { + operator_fee_scalar: Some(U256::from(2)), + operator_fee_constant: Some(U256::from(50)), + ..Default::default() + }) + .with_block(BlockEnv { + timestamp: U256::from(jovian_timestamp), + gas_limit, + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN); + + let evm = OpEvm::new(ctx.build_op_with_inspector(NoOpInspector {}), true); + + OpBlockExecutor::new(evm, OpBlockExecutionCtx::default(), op_chain_hardforks, receipt_builder) +} + +struct SDMExecutorFixture { + db: State, + receipt_builder: OpAlloyReceiptBuilder, + op_chain_hardforks: OpChainHardforks, + gas_limit: u64, + jovian_timestamp: u64, +} + +impl SDMExecutorFixture { + fn new(da_footprint_gas_scalar: u16, gas_limit: u64, jovian_timestamp: u64) -> Self { + Self { + db: prepare_jovian_db(da_footprint_gas_scalar), + receipt_builder: OpAlloyReceiptBuilder::default(), + op_chain_hardforks: OpChainHardforks::new( + OpHardfork::op_mainnet() + .into_iter() + .chain(vec![(OpHardfork::Jovian, ForkCondition::Timestamp(jovian_timestamp))]), + ), + gas_limit, + jovian_timestamp, + } + } + + fn executor(&mut self) -> SDMTestExecutor<'_> { + build_executor( + &mut self.db, + &self.receipt_builder, + &self.op_chain_hardforks, + self.gas_limit, + self.jovian_timestamp, + ) + } + + fn executor_with_post_exec_mode( + &mut self, + post_exec_mode: PostExecMode, + ) -> SDMTestExecutor<'_> { + let mut executor = self.executor(); + executor.set_post_exec_mode(post_exec_mode); + executor + } + + /// Shorthand for an executor in `Verify` mode against `post_exec_payload(block, entries)`. + fn verifier(&mut self, block_number: u64, entries: Vec) -> SDMTestExecutor<'_> { + self.executor_with_post_exec_mode(PostExecMode::Verify(post_exec_payload( + block_number, + entries, + ))) + } +} + +impl Default for SDMExecutorFixture { + fn default() -> Self { + Self::new(DEFAULT_DA_FOOTPRINT_GAS_SCALAR, DEFAULT_GAS_LIMIT, JOVIAN_TIMESTAMP) + } +} + +#[test] +fn test_jovian_da_footprint_estimation() { + let mut fixture = SDMExecutorFixture::default(); + let mut executor = fixture.executor(); + let tx = recovered_legacy(TxLegacy { gas_limit: DEFAULT_GAS_LIMIT, ..Default::default() }); + let tx_env = tx.to_tx_env(); + + let expected_da_footprint = executor.jovian_da_footprint_estimation(&tx_env, &tx).unwrap(); + + executor.execute_transaction(&tx).expect("legacy tx executes"); + assert_eq!(executor.da_footprint_used, expected_da_footprint); +} + +#[test] +fn test_jovian_da_footprint_estimation_out_of_gas() { + const GAS_LIMIT: u64 = 100; + + let mut fixture = + SDMExecutorFixture::new(DEFAULT_DA_FOOTPRINT_GAS_SCALAR, GAS_LIMIT, JOVIAN_TIMESTAMP); + let mut executor = fixture.executor(); + let tx = recovered_legacy(TxLegacy { gas_limit: GAS_LIMIT, ..Default::default() }); + let tx_env = tx.to_tx_env(); + + let expected_da_footprint = executor.jovian_da_footprint_estimation(&tx_env, &tx).unwrap(); + + let err = executor.execute_transaction(&tx).expect_err("must reject when DA exceeds limit"); + match err { + BlockExecutionError::Validation(BlockValidationError::Other(err)) => { + assert_eq!( + err.to_string(), + OpBlockExecutionError::TransactionDaFootprintAboveGasLimit { + transaction_da_footprint: expected_da_footprint, + available_block_da_footprint: GAS_LIMIT, + } + .to_string(), + ); + } + _ => panic!("expected TransactionDaFootprintAboveGasLimit error"), + } +} + +#[test] +fn test_jovian_da_footprint_estimation_maxed_out_da_footprint() { + const DA_FOOTPRINT_GAS_SCALAR: u16 = 2000; + const GAS_LIMIT: u64 = 200_000; + + let mut fixture = SDMExecutorFixture::new(DA_FOOTPRINT_GAS_SCALAR, GAS_LIMIT, JOVIAN_TIMESTAMP); + let mut executor = fixture.executor(); + let tx = recovered_legacy(TxLegacy { gas_limit: GAS_LIMIT, ..Default::default() }); + let tx_env = tx.to_tx_env(); + + let expected_da_footprint = executor.jovian_da_footprint_estimation(&tx_env, &tx).unwrap(); + let gas_used_tx = + executor.execute_transaction(&tx).expect("failed to execute transaction").tx_gas_used(); + + // The legacy gas used must stay below the DA-derived footprint so the latter dominates. + assert!(gas_used_tx < expected_da_footprint); + + // After Jovian, `blob_gas_used` reports the DA footprint when it exceeds the legacy gas used. + let (_, result) = executor.finish().expect("failed to finish executor"); + assert_eq!(result.blob_gas_used, expected_da_footprint); + assert_eq!(result.gas_used, gas_used_tx); + assert!(result.blob_gas_used > result.gas_used); +} + +mod sdm { + use super::*; + use alloy_consensus::Sealable; + use op_alloy::consensus::build_post_exec_tx; + + /// Builds a recovered post-exec (0x7D) tx with a zero signer. + fn recovered_post_exec( + block_number: u64, + entries: Vec, + ) -> Recovered { + Recovered::new_unchecked( + OpTxEnvelope::PostExec(build_post_exec_tx(block_number, entries).seal_slow()), + Address::ZERO, + ) + } + + fn legacy_tx(nonce: u64, to: Address) -> Recovered { + recovered_legacy(TxLegacy { + nonce, + gas_limit: 50_000, + to: alloy_primitives::TxKind::Call(to), + ..Default::default() + }) + } + + fn assert_invalid_post_exec(err: BlockExecutionError, expected_reason: &str) { + match err { + BlockExecutionError::Validation(BlockValidationError::Other(err)) => { + match err.downcast_ref::() { + Some(OpBlockExecutionError::InvalidPostExecPayload(reason)) => { + assert_eq!(reason, expected_reason); + } + other => panic!("expected invalid post-exec payload error, got: {other:?}"), + } + } + other => panic!("expected invalid post-exec payload error, got: {other:?}"), + } + } + + #[test] + fn test_settlement_state_account_preserves_original_info() { + type TestExecutor<'a> = OpBlockExecutor< + OpEvm<&'a mut State, NoOpInspector>, + &'a OpAlloyReceiptBuilder, + &'a OpChainHardforks, + >; + + let mut backing_db = InMemoryDB::default(); + backing_db.insert_account_info( + BASE_FEE_RECIPIENT, + AccountInfo { balance: U256::from(10), ..Default::default() }, + ); + let mut db = State::builder().with_database(backing_db).with_bundle_update().build(); + revm::Database::basic(&mut db, BASE_FEE_RECIPIENT) + .expect("failed to load base fee recipient into cache"); + + let mut credited_account = + Account::from(AccountInfo { balance: U256::from(15), ..Default::default() }); + credited_account.mark_touch(); + revm::DatabaseCommit::commit( + &mut db, + HashMap::from_iter([(BASE_FEE_RECIPIENT, credited_account)]), + ); + + let mut state = EvmState::default(); + let mut db_ref = &mut db; + let account = TestExecutor::state_account_mut(&mut db_ref, &mut state, BASE_FEE_RECIPIENT) + .expect("failed to materialize settlement account"); + assert_eq!(account.info.balance, U256::from(15)); + // original_info mirrors current info here — State::commit computes the + // true previous value from its own cache, so the bundle stays correct. + assert_eq!(account.original_info.balance, U256::from(15)); + + account.info.balance = account.info.balance.saturating_sub(U256::from(3)); + revm::DatabaseCommit::commit(&mut db, state); + db.merge_transitions(revm::database::states::bundle_state::BundleRetention::Reverts); + + let bundle = db.take_bundle(); + let bundle_account = bundle + .account(&BASE_FEE_RECIPIENT) + .expect("bundle must contain the base fee recipient"); + assert_eq!(bundle_account.original_info.as_ref().unwrap().balance, U256::from(10)); + assert_eq!(bundle_account.info.as_ref().unwrap().balance, U256::from(12)); + } + + // End-to-end executor coverage for SDM: a producer emits refund entries and appends a + // post-exec tx, then a verifier replays the same tx stream and consumes the payload. + #[test] + fn test_post_exec_producer_verifier_roundtrip() { + let target = Address::from([0x11; 20]); + let user_txs = vec![legacy_tx(0, target), legacy_tx(1, target)]; + + let mut producer_fixture = SDMExecutorFixture::default(); + let mut producer = producer_fixture.executor_with_post_exec_mode(PostExecMode::Produce); + let first_user_gas = producer + .execute_transaction(&user_txs[0]) + .expect("producer executes first user tx") + .tx_gas_used(); + let second_user_gas = producer + .execute_transaction(&user_txs[1]) + .expect("producer executes second user tx") + .tx_gas_used(); + assert!(second_user_gas < first_user_gas, "second user tx should receive an SDM refund"); + + let entries = producer.take_post_exec_entries(); + assert!(!entries.is_empty(), "producer should emit at least one SDM refund entry"); + assert_eq!(entries[0].index, 1, "the second tx reuses block-warmed addresses"); + assert!(entries[0].gas_refund > 0); + + let post_exec_recovered = recovered_post_exec(0, entries.clone()); + assert_eq!(producer.execute_transaction(&post_exec_recovered).unwrap().tx_gas_used(), 0); + let (_, produced) = producer.finish().expect("producer finishes block"); + + let mut verifier_fixture = SDMExecutorFixture::default(); + let mut verifier = verifier_fixture.verifier(0, entries); + for tx in &user_txs { + verifier.execute_transaction(tx).expect("verifier executes user tx"); + } + assert_eq!(verifier.execute_transaction(&post_exec_recovered).unwrap().tx_gas_used(), 0); + let (_, verified) = verifier.finish().expect("verifier consumes all entries"); + + assert_eq!(verified.gas_used, produced.gas_used); + assert_eq!(verified.blob_gas_used, produced.blob_gas_used); + assert_eq!(verified.receipts, produced.receipts); + assert_eq!(verified.receipts.len(), user_txs.len() + 1); + } + + #[test] + fn test_mismatched_payload_block_number_fails_pre_execution() { + // build_executor configures BlockEnv with block number 0; a payload anchored to a + // different block must be rejected before any tx runs. + let mut fixture = SDMExecutorFixture::default(); + let mut executor = fixture.verifier(42, vec![]); + + let err = + executor.apply_pre_execution_changes().expect_err("mismatched block number must fail"); + assert_invalid_post_exec(err, "payload block number 42 does not match block number 0"); + } + + #[test] + fn test_duplicate_payload_index_fails_pre_execution() { + // Two entries colliding on tx index 3 — the second insert must be flagged at construction + // and surface as a pre-execution failure. + let mut fixture = SDMExecutorFixture::default(); + let mut executor = fixture.verifier( + 0, + vec![ + SDMGasEntry { index: 3, gas_refund: 10 }, + SDMGasEntry { index: 3, gas_refund: 20 }, + ], + ); + + let err = executor + .apply_pre_execution_changes() + .expect_err("duplicate payload index must fail pre-execution"); + assert_invalid_post_exec(err, "duplicate post-exec payload entry for tx index 3"); + } + + #[test] + fn test_verifier_rejects_payload_targeting_non_normal_tx() { + for (tx_index, is_deposit, is_post_exec, evm_gas_used, expected_reason) in [ + (0, true, false, 21_000, "payload entry targets deposit tx index 0"), + (4, false, true, 0, "payload entry targets post-exec tx index 4"), + ] { + let mut fixture = SDMExecutorFixture::default(); + let executor = + fixture.verifier(0, vec![SDMGasEntry { index: tx_index, gas_refund: 1 }]); + + let err = executor + .verifier_post_exec_refund_for_tx(tx_index, is_deposit, is_post_exec, evm_gas_used) + .expect_err("payload entries must not target non-normal txs"); + assert_invalid_post_exec(err, expected_reason); + } + } + + #[test] + fn test_verifier_rejects_refund_exceeding_evm_gas() { + let mut fixture = SDMExecutorFixture::default(); + let executor = fixture.verifier(0, vec![SDMGasEntry { index: 2, gas_refund: 50_000 }]); + + // evm_gas_used < payload refund — a refund that exceeds the tx's EVM-reported cost is + // impossible under SDM semantics and must be rejected, otherwise canonical_gas_used + // would underflow to a bogus value via saturating_sub. + let err = executor + .verifier_post_exec_refund_for_tx(2, false, false, 40_000) + .expect_err("refund greater than evm_gas_used must be rejected"); + assert_invalid_post_exec( + err, + "payload refund 50000 exceeds evm_gas_used 40000 for tx index 2", + ); + + // Boundary: refund == evm_gas_used is permitted (canonical_gas_used ends up at zero). + let ok = executor + .verifier_post_exec_refund_for_tx(2, false, false, 50_000) + .expect("refund equal to evm_gas_used is permitted"); + assert_eq!(ok, 50_000); + } + + #[test] + fn test_verifier_returns_zero_when_no_entry_for_tx() { + // Deposit and post-exec cases guard against the inverse-ordering regression: every + // block calls this helper for every deposit and for the synthetic 0x7D tx, so the + // is_deposit / is_post_exec error branches must only fire when a payload entry actually + // targets that tx index. If those branches are checked before the entry-existence guard, + // every block fails at its first deposit (and at the synthetic tx). + for (label, tx_index, is_deposit, is_post_exec) in [ + ("normal tx with no payload entry", 3, false, false), + ("deposit tx with no payload entry", 3, true, false), + ("post-exec tx with no payload entry", 3, false, true), + ] { + let mut fixture = SDMExecutorFixture::default(); + let executor = fixture.verifier(0, vec![SDMGasEntry { index: 7, gas_refund: 42 }]); + + let refund = executor + .verifier_post_exec_refund_for_tx(tx_index, is_deposit, is_post_exec, 21_000) + .unwrap_or_else(|err| panic!("{label}: expected no refund, got error: {err:?}")); + assert_eq!(refund, 0, "{label}"); + } + } + + #[test] + fn test_finish_reports_all_unconsumed_post_exec_entries() { + let mut fixture = SDMExecutorFixture::default(); + let executor = fixture.verifier( + 0, + vec![SDMGasEntry { index: 2, gas_refund: 7 }, SDMGasEntry { index: 5, gas_refund: 11 }], + ); + + let Err(err) = executor.finish() else { + panic!("unconsumed verifier entries must fail"); + }; + assert_invalid_post_exec( + err, + "2 unconsumed post-exec payload entries for tx indexes [2, 5]", + ); + } + + /// Followers running with SDM disabled must reject any block that carries a post-exec + /// 0x7D tx. Silently short-circuiting the tx (which is what the pre-guard code did) would + /// let a producer ship a payload with arbitrary refund entries that no follower validates, + /// and the two nodes' states would diverge without anyone noticing. + #[test] + fn test_disabled_mode_rejects_post_exec_tx() { + let mut fixture = SDMExecutorFixture::default(); + // build_executor leaves post_exec_mode at the default (Disabled). + let mut executor = fixture.executor(); + assert!(matches!(executor.post_exec, PostExecState::Disabled)); + + let tx = recovered_post_exec(0, vec![]); + let err = + executor.execute_transaction(&tx).expect_err("0x7D tx in Disabled mode must fail"); + assert_invalid_post_exec( + err, + "unexpected post-exec tx at index 0: SDM not active for this block", + ); + } +} diff --git a/rust/alloy-op-evm/src/env.rs b/rust/alloy-op-evm/src/env.rs index 089f31c406a..a289cb455a1 100644 --- a/rust/alloy-op-evm/src/env.rs +++ b/rust/alloy-op-evm/src/env.rs @@ -35,6 +35,7 @@ pub fn spec_by_timestamp_after_bedrock(chain_spec: impl OpHardforks, timestamp: }; } check_forks! { + is_karst_active_at_timestamp => KARST, is_interop_active_at_timestamp => INTEROP, is_jovian_active_at_timestamp => JOVIAN, is_isthmus_active_at_timestamp => ISTHMUS, @@ -139,6 +140,9 @@ fn evm_env_for_op( basefee: input.base_fee_per_gas, // EIP-4844 excess blob gas of this block, introduced in Cancun blob_excess_gas_and_price, + // EIP-7843 slot number. Not yet used in the OP Stack. + // TODO(optimism#19853): populate from block header once EIP-7843 is enabled. + slot_num: 0, }; EvmEnv::new(cfg_env, block_env) @@ -200,6 +204,7 @@ mod tests { fake_hardfork_constructors! { timestamp: + karst => Karst, interop => Interop, jovian => Jovian, isthmus => Isthmus, @@ -225,6 +230,7 @@ mod tests { } } + #[test_case::test_case(FakeHardfork::karst(), OpSpecId::KARST; "Karst")] #[test_case::test_case(FakeHardfork::interop(), OpSpecId::INTEROP; "Interop")] #[test_case::test_case(FakeHardfork::jovian(), OpSpecId::JOVIAN; "Jovian")] #[test_case::test_case(FakeHardfork::isthmus(), OpSpecId::ISTHMUS; "Isthmus")] diff --git a/rust/alloy-op-evm/src/error.rs b/rust/alloy-op-evm/src/error.rs index 024df4ed069..08d1754232e 100644 --- a/rust/alloy-op-evm/src/error.rs +++ b/rust/alloy-op-evm/src/error.rs @@ -37,5 +37,6 @@ pub fn map_op_err(err: EVMError) -> EVMError EVMError::Database(e), EVMError::Header(e) => EVMError::Header(e), EVMError::Custom(e) => EVMError::Custom(e), + EVMError::CustomAny(e) => EVMError::CustomAny(e), } } diff --git a/rust/alloy-op-evm/src/lib.rs b/rust/alloy-op-evm/src/lib.rs index c76bbafbd1f..901412a7236 100644 --- a/rust/alloy-op-evm/src/lib.rs +++ b/rust/alloy-op-evm/src/lib.rs @@ -27,20 +27,32 @@ use core::{ ops::{Deref, DerefMut}, }; use op_revm::{ - DefaultOp, OpBuilder, OpContext, OpHaltReason, OpSpecId, OpTransaction, + L1BlockInfo, OpBuilder, OpHaltReason, OpSpecId, OpTransaction, + constants::{BASE_FEE_RECIPIENT, L1_FEE_RECIPIENT, OPERATOR_FEE_RECIPIENT}, precompiles::OpPrecompiles, }; use revm::{ - Context, ExecuteEvm, InspectEvm, Inspector, SystemCallEvm, - context::{BlockEnv, TxEnv}, - context_interface::result::{EVMError, ResultAndState}, + Context, ExecuteEvm, InspectEvm, Inspector, Journal, MainContext, SystemCallEvm, + context::{BlockEnv, CfgEnv, TxEnv}, + context_interface::{ + Transaction, + result::{EVMError, ResultAndState}, + }, handler::{PrecompileProvider, instructions::EthInstructions}, inspector::NoOpInspector, interpreter::{InterpreterResult, interpreter::EthInterpreter}, }; +pub mod tx; +pub use tx::OpTx; + pub mod block; -pub use block::{OpBlockExecutionCtx, OpBlockExecutor, OpBlockExecutorFactory}; +pub use block::{OpBlockExecutionCtx, OpBlockExecutor, OpBlockExecutorFactory, PostExecMode}; + +pub mod post_exec; + +/// The OP EVM context type. +pub type OpEvmContext = Context, DB, Journal, L1BlockInfo>; /// OP EVM implementation. /// @@ -49,24 +61,51 @@ pub use block::{OpBlockExecutionCtx, OpBlockExecutor, OpBlockExecutorFactory}; /// [`OpEvm`](op_revm::OpEvm) type. /// /// The `Tx` type parameter controls the transaction environment type. By default it uses -/// [`OpTransaction`] directly, but consumers can provide a newtype wrapper to -/// satisfy additional trait bounds (e.g. `FromRecoveredTx`, -/// `TransactionEnv`). +/// [`OpTx`] which wraps [`OpTransaction`] and implements the necessary foreign traits. #[allow(missing_debug_implementations)] // missing revm::OpContext Debug impl -pub struct OpEvm> { - inner: op_revm::OpEvm, I, EthInstructions>, P>, +pub struct OpEvm { + inner: op_revm::OpEvm< + OpEvmContext, + post_exec::PostExecCompositeInspector, + EthInstructions>, + P, + >, inspect: bool, + post_exec_tracking_active: bool, + last_tx_warming_savings: u64, _tx: PhantomData, } impl OpEvm { + /// Consumes self and return the inner EVM instance. + pub fn into_inner( + self, + ) -> op_revm::OpEvm, I, EthInstructions>, P> + { + let op_revm::OpEvm(revm::context::Evm { + ctx, + inspector, + instruction, + precompiles, + frame_stack, + }) = self.inner; + + op_revm::OpEvm(revm::context::Evm { + ctx, + inspector: inspector.into_inner(), + instruction, + precompiles, + frame_stack, + }) + } + /// Provides a reference to the EVM context. - pub const fn ctx(&self) -> &OpContext { + pub const fn ctx(&self) -> &OpEvmContext { &self.inner.0.ctx } /// Provides a mutable reference to the EVM context. - pub const fn ctx_mut(&mut self) -> &mut OpContext { + pub const fn ctx_mut(&mut self) -> &mut OpEvmContext { &mut self.inner.0.ctx } } @@ -76,16 +115,94 @@ impl OpEvm { /// /// The `inspect` argument determines whether the configured [`Inspector`] of the given /// [`OpEvm`](op_revm::OpEvm) should be invoked on [`Evm::transact`]. - pub const fn new( - evm: op_revm::OpEvm, I, EthInstructions>, P>, + pub fn new( + evm: op_revm::OpEvm< + OpEvmContext, + I, + EthInstructions>, + P, + >, inspect: bool, ) -> Self { - Self { inner: evm, inspect, _tx: PhantomData } + let op_revm::OpEvm(revm::context::Evm { + ctx, + inspector, + instruction, + precompiles, + frame_stack, + }) = evm; + + Self { + inner: op_revm::OpEvm(revm::context::Evm { + ctx, + inspector: post_exec::PostExecCompositeInspector::new(inspector), + instruction, + precompiles, + frame_stack, + }), + inspect, + post_exec_tracking_active: false, + last_tx_warming_savings: 0, + _tx: PhantomData, + } + } + + /// Begin post-exec tracking for the next transaction. + pub fn begin_post_exec_tx(&mut self, ctx: post_exec::PostExecTxContext) { + self.post_exec_tracking_active = true; + self.inner.0.inspector.begin_post_exec_tx(ctx); + } + + fn note_post_exec_account_touch(&mut self, address: Address) { + self.inner.0.inspector.note_account_touch(address); + } + + /// Take the extracted post-exec result for the most recently executed transaction. + pub fn take_last_post_exec_tx_result(&mut self) -> post_exec::PostExecExecutedTx { + post_exec::PostExecExecutedTx { + refund_total: core::mem::take(&mut self.last_tx_warming_savings), + } + } +} + +impl post_exec::PostExecEvm for OpEvm +where + Self: Evm, +{ + fn begin_post_exec_tx(&mut self, ctx: post_exec::PostExecTxContext) { + Self::begin_post_exec_tx(self, ctx); + } + + fn take_last_post_exec_tx_result(&mut self) -> post_exec::PostExecExecutedTx { + Self::take_last_post_exec_tx_result(self) + } +} + +impl post_exec::PostExecEvmFactoryHooks for OpEvmFactory +where + Tx: IntoTxEnv + Into> + Default + Clone + Debug, +{ + fn begin_post_exec_tx(evm: &mut Self::Evm, ctx: post_exec::PostExecTxContext) + where + DB: Database, + I: Inspector>, + { + evm.begin_post_exec_tx(ctx); + } + + fn take_last_post_exec_tx_result( + evm: &mut Self::Evm, + ) -> post_exec::PostExecExecutedTx + where + DB: Database, + I: Inspector>, + { + evm.take_last_post_exec_tx_result() } } impl Deref for OpEvm { - type Target = OpContext; + type Target = OpEvmContext; #[inline] fn deref(&self) -> &Self::Target { @@ -103,8 +220,8 @@ impl DerefMut for OpEvm { impl Evm for OpEvm where DB: Database, - I: Inspector>, - P: PrecompileProvider, Output = InterpreterResult>, + I: Inspector>, + P: PrecompileProvider, Output = InterpreterResult>, Tx: IntoTxEnv + Into>, { type DB = DB; @@ -120,6 +237,10 @@ where &self.block } + fn cfg_env(&self) -> &CfgEnv { + &self.cfg + } + fn chain_id(&self) -> u64 { self.cfg.chain_id } @@ -128,12 +249,31 @@ where &mut self, tx: Self::Tx, ) -> Result, Self::Error> { - let inner_tx: OpTransaction = tx.into(); - let result = if self.inspect { - self.inner.inspect_tx(inner_tx) + self.last_tx_warming_savings = 0; + + let track_post_exec = self.post_exec_tracking_active; + let result = if self.inspect || track_post_exec { + self.inner.inspect_tx(OpTx(tx.into())) } else { - self.inner.transact(inner_tx) + self.inner.transact(OpTx(tx.into())) }; + + if track_post_exec { + if self.inner.0.ctx.tx.tx_type() != + op_revm::transaction::deposit::DEPOSIT_TRANSACTION_TYPE + { + self.note_post_exec_account_touch(L1_FEE_RECIPIENT); + self.note_post_exec_account_touch(BASE_FEE_RECIPIENT); + if self.inner.0.ctx.cfg.spec.is_enabled_in(OpSpecId::ISTHMUS) { + self.note_post_exec_account_touch(OPERATOR_FEE_RECIPIENT); + } + } + + let post_exec_result = self.inner.0.inspector.finish_post_exec_tx(); + self.last_tx_warming_savings = post_exec_result.refund_total; + self.post_exec_tracking_active = false; + } + result.map_err(map_op_err) } @@ -146,7 +286,7 @@ where self.inner.system_call_with_caller(caller, contract, data).map_err(map_op_err) } - fn finish(self) -> (Self::DB, EvmEnv) { + fn finish(self) -> (Self::DB, EvmEnv) { let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.0.ctx; (journaled_state.database, EvmEnv { block_env, cfg_env }) @@ -159,7 +299,7 @@ where fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { ( &self.inner.0.ctx.journaled_state.database, - &self.inner.0.inspector, + self.inner.0.inspector.inner(), &self.inner.0.precompiles, ) } @@ -167,7 +307,7 @@ where fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { ( &mut self.inner.0.ctx.journaled_state.database, - &mut self.inner.0.inspector, + self.inner.0.inspector.inner_mut(), &mut self.inner.0.precompiles, ) } @@ -176,10 +316,10 @@ where /// Factory producing [`OpEvm`]s. /// /// The `Tx` type parameter controls the transaction type used by the created EVMs. -/// By default it uses [`OpTransaction`] directly, but consumers can specify a newtype -/// wrapper to satisfy additional trait bounds. +/// By default it uses [`OpTx`] which wraps [`OpTransaction`] and implements +/// the necessary foreign traits. #[derive(Debug)] -pub struct OpEvmFactory>(PhantomData); +pub struct OpEvmFactory(PhantomData); impl Clone for OpEvmFactory { fn clone(&self) -> Self { @@ -199,8 +339,8 @@ impl EvmFactory for OpEvmFactory where Tx: IntoTxEnv + Into> + Default + Clone + Debug, { - type Evm>> = OpEvm; - type Context = OpContext; + type Evm>> = OpEvm; + type Context = OpEvmContext; type Tx = Tx; type Error = EVMError; type HaltReason = OpHaltReason; @@ -211,61 +351,116 @@ where fn create_evm( &self, db: DB, - input: EvmEnv, + input: EvmEnv, ) -> Self::Evm { let spec_id = input.cfg_env.spec; - OpEvm { - inner: Context::op() - .with_db(db) - .with_block(input.block_env) - .with_cfg(input.cfg_env) - .build_op_with_inspector(NoOpInspector {}) - .with_precompiles(PrecompilesMap::from_static( - OpPrecompiles::new_with_spec(spec_id).precompiles(), - )), - inspect: false, - _tx: PhantomData, - } + let inner = Context::mainnet() + .with_tx(OpTx(OpTransaction::builder().build_fill())) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .with_chain(L1BlockInfo::default()) + .with_db(db) + .with_block(input.block_env) + .with_cfg(input.cfg_env) + .build_op_with_inspector(NoOpInspector {}) + .with_precompiles(PrecompilesMap::from_static( + OpPrecompiles::new_with_spec(spec_id).precompiles(), + )); + + OpEvm::new(inner, false) } fn create_evm_with_inspector>>( &self, db: DB, - input: EvmEnv, + input: EvmEnv, inspector: I, ) -> Self::Evm { let spec_id = input.cfg_env.spec; - OpEvm { - inner: Context::op() - .with_db(db) - .with_block(input.block_env) - .with_cfg(input.cfg_env) - .build_op_with_inspector(inspector) - .with_precompiles(PrecompilesMap::from_static( - OpPrecompiles::new_with_spec(spec_id).precompiles(), - )), - inspect: true, - _tx: PhantomData, - } + let inner = Context::mainnet() + .with_tx(OpTx(OpTransaction::builder().build_fill())) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .with_chain(L1BlockInfo::default()) + .with_db(db) + .with_block(input.block_env) + .with_cfg(input.cfg_env) + .build_op_with_inspector(inspector) + .with_precompiles(PrecompilesMap::from_static( + OpPrecompiles::new_with_spec(spec_id).precompiles(), + )); + + OpEvm::new(inner, true) } } #[cfg(test)] mod tests { - use alloc::{string::ToString, vec}; + use alloc::vec; + use alloy_consensus::{SignableTransaction, TxLegacy}; use alloy_evm::{ - EvmInternals, + EvmInternals, FromRecoveredTx, precompiles::{Precompile, PrecompileInput}, }; - use alloy_primitives::U256; + use alloy_primitives::{Signature, TxKind, U256}; use op_revm::precompiles::{bls12_381, bn254_pair}; - use revm::{context::CfgEnv, database::EmptyDB, precompile::PrecompileError}; + use revm::{ + context::CfgEnv, + database::{EmptyDB, InMemoryDB}, + precompile::PrecompileHalt, + state::AccountInfo, + }; use super::*; + fn legacy_op_tx(nonce: u64, caller: Address, target: Address) -> OpTx { + let tx = + TxLegacy { nonce, gas_limit: 100_000, to: TxKind::Call(target), ..Default::default() } + .into_signed(Signature::new( + Default::default(), + Default::default(), + Default::default(), + )); + + OpTx::from_recovered_tx(&tx, caller) + } + + // Verifies the raw OpEvm post-exec hook: this test enables SDM tracking directly with + // `begin_post_exec_tx` rather than via node config, and confirms it forces the inspector path + // even when normal tracing is disabled. + #[test] + fn op_evm_post_exec_tracking_runs_when_inspector_is_otherwise_disabled() { + let caller = Address::ZERO; + let target = Address::from([0x22; 20]); + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { balance: U256::from(1_000_000_000u64), ..Default::default() }, + ); + let mut evm = OpEvmFactory::::default().create_evm( + db, + EvmEnv::new( + CfgEnv::new_with_spec(OpSpecId::JOVIAN), + BlockEnv { gas_limit: 1_000_000, ..Default::default() }, + ), + ); + assert!(!evm.inspect, "factory-created EVM should start with user inspection disabled"); + + let mut tracked_refund = |tx_index| { + evm.begin_post_exec_tx(post_exec::PostExecTxContext { + tx_index, + kind: post_exec::PostExecTxKind::Normal, + }); + // `transact_raw` does not commit state in this low-level test, so reuse nonce 0. + evm.transact_raw(legacy_op_tx(0, caller, target)).expect("tx executes"); + evm.take_last_post_exec_tx_result().refund_total + }; + + assert_eq!(tracked_refund(0), 0); + assert!(tracked_refund(1) > 0, "second tx should observe block-warmed addresses"); + } + #[test] fn test_precompiles_jovian_fail() { - let mut evm = OpEvmFactory::>::default().create_evm( + let mut evm = OpEvmFactory::::default().create_evm( EmptyDB::default(), EvmEnv::new(CfgEnv::new_with_spec(OpSpecId::JOVIAN), BlockEnv::default()), ); @@ -273,69 +468,90 @@ mod tests { let (precompiles, ctx) = (&mut evm.inner.0.precompiles, &mut evm.inner.0.ctx); let jovian_precompile = precompiles.get(bn254_pair::JOVIAN.address()).unwrap(); - let result = jovian_precompile.call(PrecompileInput { - data: &vec![0; bn254_pair::JOVIAN_MAX_INPUT_SIZE + 1], - gas: u64::MAX, - caller: Address::ZERO, - value: U256::ZERO, - is_static: false, - target_address: Address::ZERO, - bytecode_address: Address::ZERO, - internals: EvmInternals::from_context(ctx), - }); - - assert!(result.is_err()); - assert!(matches!(result.unwrap_err(), PrecompileError::Bn254PairLength)); + let result = jovian_precompile + .call(PrecompileInput { + data: &vec![0; bn254_pair::JOVIAN_MAX_INPUT_SIZE + 1], + gas: u64::MAX, + reservoir: 0, + caller: Address::ZERO, + value: U256::ZERO, + is_static: false, + target_address: Address::ZERO, + bytecode_address: Address::ZERO, + internals: EvmInternals::from_context(ctx), + }) + .unwrap(); + + assert!(result.is_halt()); + assert!(matches!(result.halt_reason(), Some(&PrecompileHalt::Bn254PairLength))); let jovian_precompile = precompiles.get(bls12_381::JOVIAN_G1_MSM.address()).unwrap(); - let result = jovian_precompile.call(PrecompileInput { - data: &vec![0; bls12_381::JOVIAN_G1_MSM_MAX_INPUT_SIZE + 1], - gas: u64::MAX, - caller: Address::ZERO, - value: U256::ZERO, - is_static: false, - target_address: Address::ZERO, - bytecode_address: Address::ZERO, - internals: EvmInternals::from_context(ctx), - }); - - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("G1MSM input length too long")); + let result = jovian_precompile + .call(PrecompileInput { + data: &vec![0; bls12_381::JOVIAN_G1_MSM_MAX_INPUT_SIZE + 1], + gas: u64::MAX, + reservoir: 0, + caller: Address::ZERO, + value: U256::ZERO, + is_static: false, + target_address: Address::ZERO, + bytecode_address: Address::ZERO, + internals: EvmInternals::from_context(ctx), + }) + .unwrap(); + + assert!(result.is_halt()); + assert!(matches!( + result.halt_reason(), + Some(PrecompileHalt::Other(msg)) if msg.contains("G1MSM input length too long") + )); let jovian_precompile = precompiles.get(bls12_381::JOVIAN_G2_MSM.address()).unwrap(); - let result = jovian_precompile.call(PrecompileInput { - data: &vec![0; bls12_381::JOVIAN_G2_MSM_MAX_INPUT_SIZE + 1], - gas: u64::MAX, - caller: Address::ZERO, - value: U256::ZERO, - is_static: false, - target_address: Address::ZERO, - bytecode_address: Address::ZERO, - internals: EvmInternals::from_context(ctx), - }); - - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("G2MSM input length too long")); + let result = jovian_precompile + .call(PrecompileInput { + data: &vec![0; bls12_381::JOVIAN_G2_MSM_MAX_INPUT_SIZE + 1], + gas: u64::MAX, + reservoir: 0, + caller: Address::ZERO, + value: U256::ZERO, + is_static: false, + target_address: Address::ZERO, + bytecode_address: Address::ZERO, + internals: EvmInternals::from_context(ctx), + }) + .unwrap(); + + assert!(result.is_halt()); + assert!(matches!( + result.halt_reason(), + Some(PrecompileHalt::Other(msg)) if msg.contains("G2MSM input length too long") + )); let jovian_precompile = precompiles.get(bls12_381::JOVIAN_PAIRING.address()).unwrap(); - let result = jovian_precompile.call(PrecompileInput { - data: &vec![0; bls12_381::JOVIAN_PAIRING_MAX_INPUT_SIZE + 1], - gas: u64::MAX, - caller: Address::ZERO, - value: U256::ZERO, - is_static: false, - target_address: Address::ZERO, - bytecode_address: Address::ZERO, - internals: EvmInternals::from_context(ctx), - }); - - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Pairing input length too long")); + let result = jovian_precompile + .call(PrecompileInput { + data: &vec![0; bls12_381::JOVIAN_PAIRING_MAX_INPUT_SIZE + 1], + gas: u64::MAX, + reservoir: 0, + caller: Address::ZERO, + value: U256::ZERO, + is_static: false, + target_address: Address::ZERO, + bytecode_address: Address::ZERO, + internals: EvmInternals::from_context(ctx), + }) + .unwrap(); + + assert!(result.is_halt()); + assert!(matches!( + result.halt_reason(), + Some(PrecompileHalt::Other(msg)) if msg.contains("Pairing input length too long") + )); } #[test] fn test_precompiles_jovian() { - let mut evm = OpEvmFactory::>::default().create_evm( + let mut evm = OpEvmFactory::::default().create_evm( EmptyDB::default(), EvmEnv::new(CfgEnv::new_with_spec(OpSpecId::JOVIAN), BlockEnv::default()), ); @@ -344,6 +560,7 @@ mod tests { let result = jovian_precompile.call(PrecompileInput { data: &vec![0; bn254_pair::JOVIAN_MAX_INPUT_SIZE], gas: u64::MAX, + reservoir: 0, caller: Address::ZERO, value: U256::ZERO, is_static: false, @@ -358,6 +575,7 @@ mod tests { let result = jovian_precompile.call(PrecompileInput { data: &vec![0; bls12_381::JOVIAN_G1_MSM_MAX_INPUT_SIZE], gas: u64::MAX, + reservoir: 0, caller: Address::ZERO, value: U256::ZERO, is_static: false, @@ -372,6 +590,7 @@ mod tests { let result = jovian_precompile.call(PrecompileInput { data: &vec![0; bls12_381::JOVIAN_G2_MSM_MAX_INPUT_SIZE], gas: u64::MAX, + reservoir: 0, caller: Address::ZERO, value: U256::ZERO, is_static: false, @@ -386,6 +605,7 @@ mod tests { let result = jovian_precompile.call(PrecompileInput { data: &vec![0; bls12_381::JOVIAN_PAIRING_MAX_INPUT_SIZE], gas: u64::MAX, + reservoir: 0, caller: Address::ZERO, value: U256::ZERO, is_static: false, diff --git a/rust/alloy-op-evm/src/post_exec/inspector.rs b/rust/alloy-op-evm/src/post_exec/inspector.rs new file mode 100644 index 00000000000..5a523f69a5f --- /dev/null +++ b/rust/alloy-op-evm/src/post_exec/inspector.rs @@ -0,0 +1,451 @@ +use alloy_primitives::{Address, B256, map::HashSet}; +use revm::{ + Inspector, + bytecode::opcode, + context::Block, + context_interface::{ + ContextTr, CreateScheme, JournalTr, Transaction, + transaction::{AccessListItemTr, AuthorizationTr}, + }, + inspector::JournalExt, + interpreter::{ + CallInputs, CreateInputs, Interpreter, + interpreter_types::{InputsTr, Jumps}, + }, + primitives::TxKind, +}; + +// EIP-2929 repeat-access savings. SDM refunds the cold-access premium when a tx +// re-touches something a prior tx in the same block already warmed, making canonical gas +// reflect block-level warming. +// +// Values are derived from EIP-2929 cold-access premiums: +// account touch: COLD_ACCOUNT_ACCESS_COST (2600) - WARM_STORAGE_READ_COST (100) = 2500 +// SLOAD: COLD_SLOAD_COST (2100) - WARM_STORAGE_READ_COST (100) = 2000 +// SSTORE: cold storage-key surcharge is the full COLD_SLOAD_COST = 2100 +// (EIP-2929 uses this SLOAD-named constant for cold SSTORE too) + +/// Refund for re-touching an account warmed earlier in the block (BALANCE, EXTCODE*, CALL, …). +const ACCOUNT_REWARM_REFUND: u64 = 2500; +/// Refund for re-touching a storage slot warmed earlier in the block via SLOAD. +const SLOAD_REWARM_REFUND: u64 = 2000; +/// Refund for re-touching a storage slot warmed earlier in the block via SSTORE. +/// +/// Higher than the SLOAD refund because EIP-2929 charges cold SSTORE the full +/// `COLD_SLOAD_COST` surcharge too, despite the SLOAD-specific constant name. +const SSTORE_REWARM_REFUND: u64 = 2100; + +/// Classification for the currently executing transaction. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PostExecTxKind { + /// Regular user transaction that can claim post-exec refunds. + Normal, + /// Deposit transaction: warms for later txs, but never claims refunds. + Deposit, + /// Post-exec tx: never claims refunds. + PostExec, +} + +impl PostExecTxKind { + const fn claims_refunds(self) -> bool { + matches!(self, Self::Normal) + } +} + +/// Metadata supplied before executing a transaction. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PostExecTxContext { + /// Replay-local transaction index. + pub tx_index: u64, + /// Transaction classification. + pub kind: PostExecTxKind, +} + +/// Extracted result for the most recently executed transaction. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct PostExecExecutedTx { + /// Total refund for the tx. + pub refund_total: u64, +} + +#[derive(Debug, Clone, Default)] +struct CurrentTxState { + kind: Option, + initialized_top_level: bool, + refund_total: u64, + touched_accounts: HashSet
, + touched_slots: HashSet<(Address, B256)>, + intrinsic_warm_accounts: HashSet
, + intrinsic_warm_slots: HashSet<(Address, B256)>, +} + +impl CurrentTxState { + fn begin(&mut self, ctx: PostExecTxContext) { + self.kind = Some(ctx.kind); + self.initialized_top_level = false; + self.refund_total = 0; + self.touched_accounts.clear(); + self.touched_slots.clear(); + self.intrinsic_warm_accounts.clear(); + self.intrinsic_warm_slots.clear(); + } + + const fn kind(&self) -> Option { + self.kind + } + + fn finish(&mut self) -> PostExecExecutedTx { + self.kind = None; + self.initialized_top_level = false; + PostExecExecutedTx { refund_total: core::mem::take(&mut self.refund_total) } + } + + fn add_refund(&mut self, amount: u64) { + if self.kind.is_some_and(PostExecTxKind::claims_refunds) { + self.refund_total = self.refund_total.saturating_add(amount); + } + } +} + +/// Lightweight inspector that computes post-exec block-warming refunds. +#[derive(Debug, Clone, Default)] +pub struct SDMWarmingInspector { + warmed_accounts: HashSet
, + warmed_slots: HashSet<(Address, B256)>, + current_tx: CurrentTxState, + last_tx: PostExecExecutedTx, +} + +impl SDMWarmingInspector { + /// Begins tracking for the next transaction. + pub fn begin_tx(&mut self, ctx: PostExecTxContext) { + self.current_tx.begin(ctx); + } + + /// Notes an account touch that happened outside opcode stepping. + pub fn note_account_touch(&mut self, address: Address) { + self.observe_account_touch(address, true); + } + + /// Finishes the current transaction and stores the extracted result. + pub fn finish_tx(&mut self) -> PostExecExecutedTx { + let last = self.current_tx.finish(); + self.last_tx = last; + last + } + + /// Takes the extracted result for the most recently finished transaction. + pub fn take_last_tx_result(&mut self) -> PostExecExecutedTx { + core::mem::take(&mut self.last_tx) + } + + fn ensure_top_level_initialized(&mut self, context: &CTX) + where + CTX: ContextTr, + { + if self.current_tx.kind().is_none() || self.current_tx.initialized_top_level { + return; + } + + self.current_tx.initialized_top_level = true; + self.collect_intrinsic_warmth(context); + + let caller = context.tx().caller(); + self.observe_account_touch(caller, true); + + if let TxKind::Call(target) = context.tx().kind() { + self.observe_account_touch(target, true); + } + } + + fn collect_intrinsic_warmth(&mut self, context: &CTX) + where + CTX: ContextTr, + { + self.current_tx.intrinsic_warm_accounts.insert(context.block().beneficiary()); + self.current_tx + .intrinsic_warm_accounts + .extend(context.journal_ref().precompile_addresses().iter().copied()); + + if let Some(access_list) = context.tx().access_list() { + for item in access_list { + let address = *item.address(); + self.current_tx.intrinsic_warm_accounts.insert(address); + for slot in item.storage_slots() { + self.current_tx.intrinsic_warm_slots.insert((address, *slot)); + } + } + } + + for authority in context.tx().authorization_list() { + if let Some(authority) = authority.authority() { + self.current_tx.intrinsic_warm_accounts.insert(authority); + } + } + } + + fn observe_account_touch(&mut self, address: Address, allow_refund: bool) { + if self.current_tx.kind().is_none() { + return; + } + + if self.current_tx.touched_accounts.insert(address) && + allow_refund && + !self.current_tx.intrinsic_warm_accounts.contains(&address) && + self.warmed_accounts.contains(&address) + { + self.current_tx.add_refund(ACCOUNT_REWARM_REFUND); + } + + self.warmed_accounts.insert(address); + } + + fn observe_slot_touch(&mut self, address: Address, slot: B256, is_sstore: bool) { + if self.current_tx.kind().is_none() { + return; + } + + // Storage accesses should never also claim the account refund. + self.observe_account_touch(address, false); + + if self.current_tx.touched_slots.insert((address, slot)) && + !self.current_tx.intrinsic_warm_slots.contains(&(address, slot)) && + self.warmed_slots.contains(&(address, slot)) + { + self.current_tx.add_refund(if is_sstore { + SSTORE_REWARM_REFUND + } else { + SLOAD_REWARM_REFUND + }); + } + + self.warmed_slots.insert((address, slot)); + } +} + +impl Inspector for SDMWarmingInspector +where + CTX: ContextTr, +{ + fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) { + self.ensure_top_level_initialized(context); + + match interp.bytecode.opcode() { + opcode::SLOAD | opcode::SSTORE => { + if let Ok(slot) = interp.stack.peek(0) { + let slot = B256::from(slot.to_be_bytes()); + self.observe_slot_touch( + interp.input.target_address(), + slot, + interp.bytecode.opcode() == opcode::SSTORE, + ); + } + } + opcode::EXTCODECOPY | + opcode::EXTCODEHASH | + opcode::EXTCODESIZE | + opcode::BALANCE | + opcode::SELFDESTRUCT => { + if let Ok(word) = interp.stack.peek(0) { + self.observe_account_touch( + Address::from_word(B256::from(word.to_be_bytes())), + true, + ); + } + } + _ => {} + } + } + + fn call( + &mut self, + context: &mut CTX, + inputs: &mut CallInputs, + ) -> Option { + if context.journal().depth() == 0 { + self.ensure_top_level_initialized(context); + } + self.observe_account_touch(inputs.bytecode_address, true); + None + } + + fn create( + &mut self, + context: &mut CTX, + inputs: &mut CreateInputs, + ) -> Option { + if context.journal().depth() == 0 { + self.ensure_top_level_initialized(context); + } + + let caller = inputs.caller(); + self.observe_account_touch(caller, true); + + let created_address = match inputs.scheme() { + CreateScheme::Create => { + let nonce = context + .journal_ref() + .evm_state() + .get(&caller) + .map(|account| account.info.nonce) + .unwrap_or_default(); + inputs.created_address(nonce) + } + _ => inputs.created_address(0), + }; + self.observe_account_touch(created_address, true); + None + } + + fn selfdestruct( + &mut self, + _contract: Address, + target: Address, + _value: alloy_primitives::U256, + ) { + self.observe_account_touch(target, true); + } +} + +/// Composite inspector that always includes the [`SDMWarmingInspector`] alongside a +/// caller-provided inner inspector, fanning every hook to both. +#[derive(Debug, Clone)] +pub struct PostExecCompositeInspector { + inner: I, + post_exec: SDMWarmingInspector, +} + +impl PostExecCompositeInspector { + /// Creates a new composite inspector. + pub fn new(inner: I) -> Self { + Self { inner, post_exec: SDMWarmingInspector::default() } + } + + /// Returns the wrapped user inspector. + pub const fn inner(&self) -> &I { + &self.inner + } + + /// Returns the wrapped user inspector mutably. + pub const fn inner_mut(&mut self) -> &mut I { + &mut self.inner + } + + /// Consumes the composite inspector and returns the wrapped user inspector. + pub fn into_inner(self) -> I { + self.inner + } + + /// Begin tracking the next transaction. + pub fn begin_post_exec_tx(&mut self, ctx: PostExecTxContext) { + self.post_exec.begin_tx(ctx); + } + + /// Notes an account touch that happened outside opcode stepping. + pub fn note_account_touch(&mut self, address: Address) { + self.post_exec.note_account_touch(address); + } + + /// Finish tracking the current transaction. + pub fn finish_post_exec_tx(&mut self) -> PostExecExecutedTx { + self.post_exec.finish_tx() + } +} + +impl Inspector for PostExecCompositeInspector +where + INTR: revm::interpreter::InterpreterTypes, + I: Inspector, + SDMWarmingInspector: Inspector, +{ + fn initialize_interp(&mut self, interp: &mut Interpreter, context: &mut CTX) { + self.inner.initialize_interp(interp, context); + self.post_exec.initialize_interp(interp, context); + } + + fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) { + self.inner.step(interp, context); + self.post_exec.step(interp, context); + } + + fn step_end(&mut self, interp: &mut Interpreter, context: &mut CTX) { + self.inner.step_end(interp, context); + self.post_exec.step_end(interp, context); + } + + fn log(&mut self, context: &mut CTX, log: alloy_primitives::Log) { + self.inner.log(context, log.clone()); + self.post_exec.log(context, log); + } + + fn log_full( + &mut self, + interp: &mut Interpreter, + context: &mut CTX, + log: alloy_primitives::Log, + ) { + self.inner.log_full(interp, context, log.clone()); + self.post_exec.log_full(interp, context, log); + } + + fn call( + &mut self, + context: &mut CTX, + inputs: &mut CallInputs, + ) -> Option { + // Always run both inspectors: the warming inspector's first-touch observations drive + // block-scoped refund attribution and must not be gated on whether the user inspector + // short-circuits the frame. The warming inspector is expected to never synthesize an + // outcome, so inner's return value is authoritative. + let inner = self.inner.call(context, inputs); + let post_exec = self.post_exec.call(context, inputs); + debug_assert!( + post_exec.is_none(), + "SDMWarmingInspector must not synthesize a call outcome", + ); + inner + } + + fn call_end( + &mut self, + context: &mut CTX, + inputs: &CallInputs, + outcome: &mut revm::interpreter::CallOutcome, + ) { + self.inner.call_end(context, inputs, outcome); + self.post_exec.call_end(context, inputs, outcome); + } + + fn create( + &mut self, + context: &mut CTX, + inputs: &mut CreateInputs, + ) -> Option { + // See `call` above: always observe; inner's outcome wins. + let inner = self.inner.create(context, inputs); + let post_exec = self.post_exec.create(context, inputs); + debug_assert!( + post_exec.is_none(), + "SDMWarmingInspector must not synthesize a create outcome", + ); + inner + } + + fn create_end( + &mut self, + context: &mut CTX, + inputs: &CreateInputs, + outcome: &mut revm::interpreter::CreateOutcome, + ) { + self.inner.create_end(context, inputs, outcome); + self.post_exec.create_end(context, inputs, outcome); + } + + fn selfdestruct(&mut self, contract: Address, target: Address, value: alloy_primitives::U256) { + self.inner.selfdestruct(contract, target, value); + self.post_exec.selfdestruct(contract, target, value); + } +} + +#[cfg(test)] +mod tests; diff --git a/rust/alloy-op-evm/src/post_exec/inspector/tests.rs b/rust/alloy-op-evm/src/post_exec/inspector/tests.rs new file mode 100644 index 00000000000..81f1e6c8026 --- /dev/null +++ b/rust/alloy-op-evm/src/post_exec/inspector/tests.rs @@ -0,0 +1,87 @@ +use super::*; +use alloy_primitives::{address, b256}; + +const ACCOUNT_A: Address = address!("00000000000000000000000000000000000000aa"); +const ACCOUNT_B: Address = address!("00000000000000000000000000000000000000bb"); +const SLOT_1: B256 = b256!("0000000000000000000000000000000000000000000000000000000000000001"); + +fn run_account( + insp: &mut SDMWarmingInspector, + tx_index: u64, + kind: PostExecTxKind, + addr: Address, +) -> u64 { + insp.begin_tx(PostExecTxContext { tx_index, kind }); + insp.observe_account_touch(addr, true); + insp.finish_tx().refund_total +} + +fn run_slot( + insp: &mut SDMWarmingInspector, + tx_index: u64, + addr: Address, + slot: B256, + is_sstore: bool, +) -> u64 { + insp.begin_tx(PostExecTxContext { tx_index, kind: PostExecTxKind::Normal }); + insp.observe_slot_touch(addr, slot, is_sstore); + insp.finish_tx().refund_total +} + +#[test] +fn repeated_account_touch_refunds_once() { + let mut insp = SDMWarmingInspector::default(); + + assert_eq!(run_account(&mut insp, 0, PostExecTxKind::Normal, ACCOUNT_A), 0); + assert_eq!(run_account(&mut insp, 1, PostExecTxKind::Normal, ACCOUNT_A), ACCOUNT_REWARM_REFUND,); +} + +#[test] +fn repeated_storage_refunds_without_account_double_count() { + for (is_sstore, expected) in [(false, SLOAD_REWARM_REFUND), (true, SSTORE_REWARM_REFUND)] { + let mut insp = SDMWarmingInspector::default(); + + assert_eq!(run_slot(&mut insp, 0, ACCOUNT_A, SLOT_1, is_sstore), 0); + assert_eq!(run_slot(&mut insp, 1, ACCOUNT_A, SLOT_1, is_sstore), expected); + } +} + +#[test] +fn deposit_warms_but_does_not_claim() { + let mut insp = SDMWarmingInspector::default(); + + assert_eq!(run_account(&mut insp, 0, PostExecTxKind::Deposit, ACCOUNT_B), 0); + assert_eq!(run_account(&mut insp, 1, PostExecTxKind::Normal, ACCOUNT_B), ACCOUNT_REWARM_REFUND,); +} + +#[test] +fn post_exec_tx_never_claims_refunds() { + let mut insp = SDMWarmingInspector::default(); + + let _ = run_account(&mut insp, 0, PostExecTxKind::Normal, ACCOUNT_A); + assert_eq!(run_account(&mut insp, 1, PostExecTxKind::PostExec, ACCOUNT_A), 0); +} + +#[test] +fn intrinsic_access_list_warmth_does_not_claim() { + let mut insp = SDMWarmingInspector::default(); + + insp.begin_tx(PostExecTxContext { tx_index: 0, kind: PostExecTxKind::Normal }); + insp.current_tx.intrinsic_warm_accounts.insert(ACCOUNT_A); + insp.current_tx.intrinsic_warm_slots.insert((ACCOUNT_A, SLOT_1)); + insp.observe_slot_touch(ACCOUNT_A, SLOT_1, false); + assert_eq!(insp.finish_tx().refund_total, 0); + + assert_eq!(run_slot(&mut insp, 1, ACCOUNT_A, SLOT_1, false), SLOAD_REWARM_REFUND); +} + +#[test] +fn take_last_tx_result_round_trips() { + let mut insp = SDMWarmingInspector::default(); + + let _ = run_account(&mut insp, 0, PostExecTxKind::Normal, ACCOUNT_A); + let _ = run_account(&mut insp, 1, PostExecTxKind::Normal, ACCOUNT_A); + + assert_eq!(insp.take_last_tx_result().refund_total, ACCOUNT_REWARM_REFUND); + assert_eq!(insp.take_last_tx_result().refund_total, 0); +} diff --git a/rust/alloy-op-evm/src/post_exec/mod.rs b/rust/alloy-op-evm/src/post_exec/mod.rs new file mode 100644 index 00000000000..812aefb17fe --- /dev/null +++ b/rust/alloy-op-evm/src/post_exec/mod.rs @@ -0,0 +1,234 @@ +//! Post-exec execution extensions. + +mod inspector; + +use alloc::vec::Vec; +use alloy_evm::{Database, Evm, EvmEnv, EvmFactory}; +use core::{ + marker::PhantomData, + ops::{Deref, DerefMut}, +}; +use op_alloy::consensus::post_exec::SDMGasEntry; +use revm::{Inspector, inspector::NoOpInspector}; + +pub use inspector::{ + PostExecCompositeInspector, PostExecExecutedTx, PostExecTxContext, PostExecTxKind, + SDMWarmingInspector, +}; + +use crate::block::{OpBlockExecutor, receipt_builder::OpReceiptBuilder}; + +/// Extension trait for EVMs that can track post-exec per-transaction warming results. +pub trait PostExecEvm: alloy_evm::Evm { + /// Begin post-exec tracking for the next transaction. + fn begin_post_exec_tx(&mut self, ctx: PostExecTxContext); + + /// Take the extracted post-exec result for the most recently executed transaction. + fn take_last_post_exec_tx_result(&mut self) -> PostExecExecutedTx; +} + +/// Extension trait for EVM factories whose produced EVMs support post-exec tracking. +/// +/// This bridges generic custom [`EvmFactory`] implementations into the concrete [`PostExecEvm`] +/// bound used by the OP block executor. The adapter keeps post-exec capability explicit on the EVM +/// itself without requiring the compiler to prove that every `EvmFactory::Evm` associated +/// type directly implements [`PostExecEvm`]. +pub trait PostExecEvmFactoryHooks: EvmFactory { + /// Begin post-exec tracking for the next transaction. + fn begin_post_exec_tx(evm: &mut Self::Evm, ctx: PostExecTxContext) + where + DB: Database, + I: Inspector>; + + /// Take the extracted post-exec result for the most recently executed transaction. + fn take_last_post_exec_tx_result(evm: &mut Self::Evm) -> PostExecExecutedTx + where + DB: Database, + I: Inspector>; +} + +/// EVM wrapper that makes factory-provided post-exec hooks visible as a [`PostExecEvm`]. +#[derive(Debug, Clone)] +pub struct PostExecEvmAdapter { + inner: E, + _factory: PhantomData, +} + +impl PostExecEvmAdapter { + /// Creates a new post-exec EVM adapter. + pub const fn new(inner: E) -> Self { + Self { inner, _factory: PhantomData } + } + + /// Consumes the adapter and returns the wrapped EVM. + pub fn into_inner(self) -> E { + self.inner + } +} + +impl Deref for PostExecEvmAdapter { + type Target = E; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for PostExecEvmAdapter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Evm for PostExecEvmAdapter +where + E: Evm, +{ + type DB = E::DB; + type Tx = E::Tx; + type Error = E::Error; + type HaltReason = E::HaltReason; + type Spec = E::Spec; + type BlockEnv = E::BlockEnv; + type Precompiles = E::Precompiles; + type Inspector = E::Inspector; + + fn block(&self) -> &Self::BlockEnv { + self.inner.block() + } + + fn cfg_env(&self) -> &revm::context::CfgEnv { + self.inner.cfg_env() + } + + fn chain_id(&self) -> u64 { + self.inner.chain_id() + } + + fn transact_raw( + &mut self, + tx: Self::Tx, + ) -> Result, Self::Error> + { + self.inner.transact_raw(tx) + } + + fn transact_system_call( + &mut self, + caller: alloy_primitives::Address, + contract: alloy_primitives::Address, + data: alloy_primitives::Bytes, + ) -> Result, Self::Error> + { + self.inner.transact_system_call(caller, contract, data) + } + + fn finish(self) -> (Self::DB, EvmEnv) { + self.inner.finish() + } + + fn set_inspector_enabled(&mut self, enabled: bool) { + self.inner.set_inspector_enabled(enabled); + } + + fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { + self.inner.components() + } + + fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { + self.inner.components_mut() + } +} + +impl PostExecEvm for PostExecEvmAdapter, F, DB, I> +where + F: PostExecEvmFactoryHooks, + DB: Database, + I: Inspector>, +{ + fn begin_post_exec_tx(&mut self, ctx: PostExecTxContext) { + F::begin_post_exec_tx(&mut self.inner, ctx); + } + + fn take_last_post_exec_tx_result(&mut self) -> PostExecExecutedTx { + F::take_last_post_exec_tx_result(&mut self.inner) + } +} + +/// EVM factory adapter that wraps produced EVMs in [`PostExecEvmAdapter`]. +/// +/// This is needed for generic/custom [`EvmFactory`] implementations because +/// [`BlockExecutorFactory::create_executor`](alloy_evm::block::BlockExecutorFactory::create_executor) +/// is handed an already-produced EVM value, not the factory that produced it. Without this wrapper, +/// generic executor code cannot assume that every `F::Evm` associated type implements +/// [`PostExecEvm`], even when `F` knows how to drive post-exec hooks for those EVMs. +#[derive(Debug, Clone, Copy)] +pub struct PostExecEvmFactoryAdapter { + inner: F, +} + +impl PostExecEvmFactoryAdapter { + /// Creates a new post-exec EVM factory adapter. + pub const fn new(inner: F) -> Self { + Self { inner } + } + + /// Returns the wrapped EVM factory. + pub const fn inner(&self) -> &F { + &self.inner + } + + /// Consumes the adapter and returns the wrapped EVM factory. + pub fn into_inner(self) -> F { + self.inner + } +} + +impl EvmFactory for PostExecEvmFactoryAdapter +where + F: PostExecEvmFactoryHooks, +{ + type Evm>> = + PostExecEvmAdapter, F, DB, I>; + type Context = F::Context; + type Tx = F::Tx; + type Error = F::Error; + type HaltReason = F::HaltReason; + type Spec = F::Spec; + type BlockEnv = F::BlockEnv; + type Precompiles = F::Precompiles; + + fn create_evm( + &self, + db: DB, + input: EvmEnv, + ) -> Self::Evm { + PostExecEvmAdapter::new(self.inner.create_evm(db, input)) + } + + fn create_evm_with_inspector>>( + &self, + db: DB, + input: EvmEnv, + inspector: I, + ) -> Self::Evm { + PostExecEvmAdapter::new(self.inner.create_evm_with_inspector(db, input, inspector)) + } +} + +/// Extension trait for block executors that collect post-exec payload entries. +pub trait PostExecExecutorExt { + /// Take the accumulated post-exec entries for the current block. + fn take_post_exec_entries(&mut self) -> Vec; +} + +impl PostExecExecutorExt for OpBlockExecutor +where + E: alloy_evm::Evm, + R: OpReceiptBuilder, + Spec: alloy_op_hardforks::OpHardforks + Clone, +{ + fn take_post_exec_entries(&mut self) -> Vec { + Self::take_post_exec_entries(self) + } +} diff --git a/rust/alloy-op-evm/src/tx.rs b/rust/alloy-op-evm/src/tx.rs new file mode 100644 index 00000000000..7d21857841a --- /dev/null +++ b/rust/alloy-op-evm/src/tx.rs @@ -0,0 +1,274 @@ +//! [`OpTx`] newtype wrapper around [`OpTransaction`]. + +use crate::block::OpTxEnv; +use alloy_consensus::{ + Signed, Transaction, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, TxLegacy, +}; +use alloy_eips::{Encodable2718, Typed2718, eip7594::Encodable7594}; +use alloy_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, TransactionEnvMut}; +use alloy_primitives::{Address, B256, Bytes, TxKind, U256}; +use core::ops::{Deref, DerefMut}; +use op_alloy::consensus::{OpTxEnvelope, TxDeposit, TxPostExec}; +use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; +use revm::context::TxEnv; + +/// Helper to convert a deposit transaction into a [`TxEnv`]. +fn deposit_tx_env(tx: &TxDeposit, caller: Address) -> TxEnv { + TxEnv { + tx_type: tx.ty(), + caller, + gas_limit: tx.gas_limit, + kind: tx.to, + value: tx.value, + data: tx.input.clone(), + ..Default::default() + } +} + +/// Newtype wrapper around [`OpTransaction`] that allows implementing foreign traits. +#[derive(Clone, Debug, Default)] +pub struct OpTx(pub OpTransaction); + +impl From for OpTransaction { + fn from(tx: OpTx) -> Self { + tx.0 + } +} + +impl Deref for OpTx { + type Target = OpTransaction; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for OpTx { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl IntoTxEnv for OpTx { + fn into_tx_env(self) -> Self { + self + } +} + +impl OpTxEnv for OpTx { + fn encoded_bytes(&self) -> Option<&Bytes> { + self.0.enveloped_tx.as_ref() + } +} + +impl revm::context::Transaction for OpTx { + type AccessListItem<'a> + = as revm::context::Transaction>::AccessListItem<'a> + where + Self: 'a; + type Authorization<'a> + = as revm::context::Transaction>::Authorization<'a> + where + Self: 'a; + + fn tx_type(&self) -> u8 { + self.0.tx_type() + } + fn caller(&self) -> Address { + self.0.caller() + } + fn gas_limit(&self) -> u64 { + self.0.gas_limit() + } + fn value(&self) -> U256 { + self.0.value() + } + fn input(&self) -> &Bytes { + self.0.input() + } + fn nonce(&self) -> u64 { + revm::context::Transaction::nonce(&self.0) + } + fn kind(&self) -> TxKind { + self.0.kind() + } + fn chain_id(&self) -> Option { + self.0.chain_id() + } + fn gas_price(&self) -> u128 { + self.0.gas_price() + } + fn access_list(&self) -> Option>> { + self.0.access_list() + } + fn blob_versioned_hashes(&self) -> &[B256] { + self.0.blob_versioned_hashes() + } + fn max_fee_per_blob_gas(&self) -> u128 { + self.0.max_fee_per_blob_gas() + } + fn authorization_list_len(&self) -> usize { + self.0.authorization_list_len() + } + fn authorization_list(&self) -> impl Iterator> { + self.0.authorization_list() + } + fn max_priority_fee_per_gas(&self) -> Option { + self.0.max_priority_fee_per_gas() + } +} + +impl revm::handler::SystemCallTx for OpTx { + fn new_system_tx_with_caller( + caller: Address, + system_contract_address: Address, + data: Bytes, + ) -> Self { + Self(OpTransaction::new_system_tx_with_caller(caller, system_contract_address, data)) + } +} + +impl op_revm::transaction::OpTxTr for OpTx { + fn enveloped_tx(&self) -> Option<&Bytes> { + self.0.enveloped_tx() + } + + fn source_hash(&self) -> Option { + self.0.source_hash() + } + + fn mint(&self) -> Option { + self.0.mint() + } + + fn is_system_transaction(&self) -> bool { + self.0.is_system_transaction() + } +} + +impl FromRecoveredTx for OpTx { + fn from_recovered_tx(tx: &OpTxEnvelope, sender: Address) -> Self { + let encoded = tx.encoded_2718(); + Self::from_encoded_tx(tx, sender, encoded.into()) + } +} + +impl FromTxWithEncoded for OpTx { + fn from_encoded_tx(tx: &OpTxEnvelope, caller: Address, encoded: Bytes) -> Self { + match tx { + OpTxEnvelope::Legacy(tx) => Self::from_encoded_tx(tx, caller, encoded), + OpTxEnvelope::Eip1559(tx) => Self::from_encoded_tx(tx, caller, encoded), + OpTxEnvelope::Eip2930(tx) => Self::from_encoded_tx(tx, caller, encoded), + OpTxEnvelope::Eip7702(tx) => Self::from_encoded_tx(tx, caller, encoded), + OpTxEnvelope::Deposit(tx) => Self::from_encoded_tx(tx.inner(), caller, encoded), + OpTxEnvelope::PostExec(tx) => Self::from_encoded_tx(tx.inner(), caller, encoded), + } + } +} + +/// Generates [`FromRecoveredTx`] and [`FromTxWithEncoded`] impls for [`OpTx`] from a +/// `Signed<$tx>` and bare `$tx` type. The bare type conversion creates the [`TxEnv`] via +/// [`FromRecoveredTx`] and wraps it in an [`OpTransaction`]. +macro_rules! impl_from_tx { + ($($tx:ty),+ $(,)?) => { + $( + impl FromRecoveredTx> for OpTx { + fn from_recovered_tx(tx: &Signed<$tx>, sender: Address) -> Self { + let encoded = tx.encoded_2718(); + Self::from_encoded_tx(tx, sender, encoded.into()) + } + } + + impl FromTxWithEncoded> for OpTx { + fn from_encoded_tx(tx: &Signed<$tx>, caller: Address, encoded: Bytes) -> Self { + Self::from_encoded_tx(tx.tx(), caller, encoded) + } + } + + impl FromTxWithEncoded<$tx> for OpTx { + fn from_encoded_tx(tx: &$tx, caller: Address, encoded: Bytes) -> Self { + let base = TxEnv::from_recovered_tx(tx, caller); + Self(OpTransaction { + base, + enveloped_tx: Some(encoded), + deposit: Default::default(), + }) + } + } + )+ + }; +} + +impl_from_tx!(TxLegacy, TxEip2930, TxEip1559, TxEip4844, TxEip7702); + +/// `TxEip4844Variant` conversion is not necessary for `OpTx`, but it's useful +/// sugar for Foundry. +impl FromRecoveredTx>> for OpTx +where + T: Encodable7594 + Send + Sync, +{ + fn from_recovered_tx(tx: &Signed>, sender: Address) -> Self { + let encoded = tx.encoded_2718(); + Self::from_encoded_tx(tx, sender, encoded.into()) + } +} + +impl FromTxWithEncoded>> for OpTx { + fn from_encoded_tx(tx: &Signed>, caller: Address, encoded: Bytes) -> Self { + Self::from_encoded_tx(tx.tx(), caller, encoded) + } +} + +impl FromTxWithEncoded> for OpTx { + fn from_encoded_tx(tx: &TxEip4844Variant, caller: Address, encoded: Bytes) -> Self { + let base = TxEnv::from_recovered_tx(tx, caller); + Self(OpTransaction { base, enveloped_tx: Some(encoded), deposit: Default::default() }) + } +} + +impl FromRecoveredTx for OpTx { + fn from_recovered_tx(tx: &TxDeposit, sender: Address) -> Self { + let encoded = tx.encoded_2718(); + Self::from_encoded_tx(tx, sender, encoded.into()) + } +} + +impl FromTxWithEncoded for OpTx { + fn from_encoded_tx(tx: &TxDeposit, caller: Address, encoded: Bytes) -> Self { + let base = deposit_tx_env(tx, caller); + let deposit = DepositTransactionParts { + source_hash: tx.source_hash, + mint: Some(tx.mint), + is_system_transaction: tx.is_system_transaction, + }; + Self(OpTransaction { base, enveloped_tx: Some(encoded), deposit }) + } +} + +impl FromRecoveredTx for OpTx { + fn from_recovered_tx(tx: &TxPostExec, _sender: Address) -> Self { + let encoded = tx.encoded_2718(); + Self::from_encoded_tx(tx, Address::ZERO, encoded.into()) + } +} + +impl FromTxWithEncoded for OpTx { + fn from_encoded_tx(tx: &TxPostExec, caller: Address, encoded: Bytes) -> Self { + let base = TxEnv { tx_type: tx.ty(), caller, kind: tx.kind(), ..Default::default() }; + Self(OpTransaction { base, enveloped_tx: Some(encoded), deposit: Default::default() }) + } +} + +impl TransactionEnvMut for OpTx { + fn set_gas_limit(&mut self, gas_limit: u64) { + self.0.base.gas_limit = gas_limit; + } + + fn set_nonce(&mut self, nonce: u64) { + self.0.base.nonce = nonce; + } + + fn set_access_list(&mut self, access_list: alloy_eips::eip2930::AccessList) { + self.0.base.access_list = access_list; + } +} diff --git a/rust/alloy-op-hardforks/Cargo.toml b/rust/alloy-op-hardforks/Cargo.toml index 338c707e4d5..0f9e9a10bab 100644 --- a/rust/alloy-op-hardforks/Cargo.toml +++ b/rust/alloy-op-hardforks/Cargo.toml @@ -2,9 +2,9 @@ name = "alloy-op-hardforks" description = "Bindings for named OP hardforks" -version = "0.4.7" +version = "0.5.0" edition = "2024" -rust-version = "1.92" +rust-version = "1.94" authors = ["Alloy Contributors", "OpLabsPBC"] license.workspace = true homepage = "https://github.com/ethereum-optimism/optimism" diff --git a/rust/alloy-op-hardforks/LICENSE-MIT b/rust/alloy-op-hardforks/LICENSE-MIT index 2114a1fbfaf..f443e847f8f 100644 --- a/rust/alloy-op-hardforks/LICENSE-MIT +++ b/rust/alloy-op-hardforks/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Hemi Labs, Inc. +Copyright (c) 2024 Alloy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/rust/alloy-op-hardforks/src/lib.rs b/rust/alloy-op-hardforks/src/lib.rs index 756ed70e426..850c406f321 100644 --- a/rust/alloy-op-hardforks/src/lib.rs +++ b/rust/alloy-op-hardforks/src/lib.rs @@ -166,21 +166,6 @@ impl OpHardfork { ] } - /// Devnet list of hardforks. - pub const fn devnet() -> [(Self, ForkCondition); 9] { - [ - (Self::Bedrock, ForkCondition::ZERO_BLOCK), - (Self::Regolith, ForkCondition::ZERO_TIMESTAMP), - (Self::Canyon, ForkCondition::ZERO_TIMESTAMP), - (Self::Ecotone, ForkCondition::ZERO_TIMESTAMP), - (Self::Fjord, ForkCondition::ZERO_TIMESTAMP), - (Self::Granite, ForkCondition::ZERO_TIMESTAMP), - (Self::Holocene, ForkCondition::ZERO_TIMESTAMP), - (Self::Isthmus, ForkCondition::ZERO_TIMESTAMP), - (Self::Jovian, ForkCondition::Timestamp(1762185600)), - ] - } - /// Returns index of `self` in sorted canonical array. pub const fn idx(&self) -> usize { *self as usize @@ -303,11 +288,6 @@ impl OpChainHardforks { Self::new(OpHardfork::base_sepolia()) } - /// Creates a new [`OpChainHardforks`] with devnet configuration. - pub fn devnet() -> Self { - Self::new(OpHardfork::devnet()) - } - /// Returns `true` if this is an OP mainnet instance. pub fn is_op_mainnet(&self) -> bool { self[OpHardfork::Bedrock] == ForkCondition::Block(OP_MAINNET_BEDROCK_BLOCK) @@ -316,8 +296,8 @@ impl OpChainHardforks { impl EthereumHardforks for OpChainHardforks { fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition { - use EthereumHardfork::{Cancun, Prague, Shanghai}; - use OpHardfork::{Canyon, Ecotone, Isthmus}; + use EthereumHardfork::{Cancun, Osaka, Prague, Shanghai}; + use OpHardfork::{Canyon, Ecotone, Isthmus, Karst}; if self.forks.is_empty() { return ForkCondition::Never; @@ -329,6 +309,7 @@ impl EthereumHardforks for OpChainHardforks { Shanghai if forks_len <= Canyon.idx() => ForkCondition::Never, Cancun if forks_len <= Ecotone.idx() => ForkCondition::Never, Prague if forks_len <= Isthmus.idx() => ForkCondition::Never, + Osaka if forks_len <= Karst.idx() => ForkCondition::Never, _ => self[fork], } } @@ -378,11 +359,11 @@ impl Index for OpChainHardforks { Constantinople, Dao, Frontier, GrayGlacier, Homestead, Istanbul, London, MuirGlacier, Osaka, Paris, Petersburg, Prague, Shanghai, SpuriousDragon, Tangerine, }; - use OpHardfork::{Bedrock, Canyon, Ecotone, Isthmus}; + use OpHardfork::{Bedrock, Canyon, Ecotone, Isthmus, Karst}; match hf { // Dao Hardfork is not needed for OpChainHardforks - Dao | Osaka | Bpo1 | Bpo2 | Bpo3 | Bpo4 | Bpo5 | Amsterdam => &ForkCondition::Never, + Dao | Bpo1 | Bpo2 | Bpo3 | Bpo4 | Bpo5 | Amsterdam => &ForkCondition::Never, Berlin if self.is_op_mainnet() => &ForkCondition::Block(OP_MAINNET_BERLIN_BLOCK), Frontier | Homestead | Tangerine | SpuriousDragon | Byzantium | Constantinople | Petersburg | Istanbul | MuirGlacier | Berlin => &ForkCondition::ZERO_BLOCK, @@ -400,6 +381,7 @@ impl Index for OpChainHardforks { Shanghai => &self[Canyon], Cancun => &self[Ecotone], Prague => &self[Isthmus], + Osaka => &self[Karst], _ => unreachable!(), } } diff --git a/rust/clippy.toml b/rust/clippy.toml index 2b69e81286b..058aa492e5f 100644 --- a/rust/clippy.toml +++ b/rust/clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.92" +msrv = "1.94" too-large-for-stack = 128 doc-valid-idents = [ "P2P", diff --git a/rust/deny.toml b/rust/deny.toml index 461d1eda193..5b4be4aa3eb 100644 --- a/rust/deny.toml +++ b/rust/deny.toml @@ -12,11 +12,14 @@ yanked = "warn" ignore = [ # paste crate is no longer maintained. "RUSTSEC-2024-0436", - # https://rustsec.org/advisories/RUSTSEC-2024-0384 used by sse example - "RUSTSEC-2024-0384", - "RUSTSEC-2025-0012", # bincode is unmaintained but still functional; transitive dep from reth-nippy-jar and test-fuzz. "RUSTSEC-2025-0141", + # hickory-proto <0.26.1 NSEC3 closest-encloser DNSSEC validation unbounded loop; + # transitive via reth-network -> reth-dns-discovery. Pending upstream bump. + "RUSTSEC-2026-0118", + # hickory-proto <0.26.1 O(n^2) name-compression CPU exhaustion in BinEncoder; + # transitive via reth-network -> reth-dns-discovery. Pending upstream bump. + "RUSTSEC-2026-0119", ] # This section is considered when running `cargo deny check bans`. @@ -65,8 +68,9 @@ exceptions = [ # aws-lc-sys includes OpenSSL in its composite license expression { allow = ["OpenSSL"], name = "aws-lc-sys" }, # gmp feature (optional, LGPL-licensed) + # gmp-mpfr-sys uses deprecated "LGPL-3.0+" identifier; equivalent to "LGPL-3.0-or-later" { allow = ["LGPL-3.0-or-later"], crate = "rug" }, - { allow = ["LGPL-3.0-or-later"], crate = "gmp-mpfr-sys" }, + { allow = ["LGPL-3.0-or-later", "LGPL-3.0"], crate = "gmp-mpfr-sys" }, # Grandfathered MPL-2.0 exceptions from op-reth { allow = ["MPL-2.0"], name = "option-ext" }, { allow = ["MPL-2.0"], name = "webpki-root-certs" }, @@ -103,6 +107,7 @@ allow-git = [ "https://github.com/paradigmxyz/revm-inspectors", "https://github.com/foundry-rs/block-explorers", "https://github.com/flashbots/rollup-boost", + "https://github.com/sigp/discv5", ] unknown-registry = "warn" unknown-git = "deny" diff --git a/rust/docs/docs/components/CodeGroup.tsx b/rust/docs/docs/components/CodeGroup.tsx deleted file mode 100644 index f508a4ce583..00000000000 --- a/rust/docs/docs/components/CodeGroup.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useState } from 'react' - -interface CodeProps { - title: string - code: string -} - -interface CodeGroupProps { - children: React.ReactElement[] -} - -export function CodeGroup({ children }: CodeGroupProps) { - const [activeTab, setActiveTab] = useState(0) - - return ( -
-
- {children.map((child, index) => ( - - ))} -
-
-
-          
-            {children[activeTab].props.code}
-          
-        
-
-
- ) -} - -export function Code({ title, code }: CodeProps) { - return null // This component is only used as a child of CodeGroup -} \ No newline at end of file diff --git a/rust/docs/docs/components/SdkShowcase.tsx b/rust/docs/docs/components/SdkShowcase.tsx deleted file mode 100644 index 3b5be6c29e2..00000000000 --- a/rust/docs/docs/components/SdkShowcase.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react' - -interface SdkProject { - name: string - description: string - linesOfCode: string - githubUrl: string -} - -const projects: SdkProject[] = [ - { - name: 'Kona Client', - description: 'Fault proof program for rollup state transitions', - linesOfCode: '~3K LoC', - githubUrl: 'https://github.com/ethereum-optimism/optimism/tree/develop/rust/kona/bin/client' - }, - { - name: 'Kona Node', - description: 'Modular OP Stack rollup node implementation', - linesOfCode: '~8K LoC', - githubUrl: 'https://github.com/ethereum-optimism/optimism/tree/develop/rust/kona/bin/node' - }, - { - name: 'OP Succinct', - description: 'zkVM-based proof system using Kona', - linesOfCode: '~2K LoC', - githubUrl: 'https://github.com/succinctlabs/op-succinct' - }, - { - name: 'Kailua', - description: 'zkVM-based proof system using Kona', - linesOfCode: '~5K LoC', - githubUrl: 'https://github.com/boundless-xyz/kailua' - } -] - -export function SdkShowcase() { - return ( -
- {projects.map((project, index) => ( -
-
-

{project.name}

- - - - - -
-

{project.description}

-
- {project.linesOfCode} -
-
- ))} -
- ) -} diff --git a/rust/docs/docs/components/TrustedBy.tsx b/rust/docs/docs/components/TrustedBy.tsx deleted file mode 100644 index bbb60c2efe6..00000000000 --- a/rust/docs/docs/components/TrustedBy.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' - -interface TrustedCompany { - name: string - logoUrl: string -} - -const companies: TrustedCompany[] = [ - { - name: 'OP Labs', - logoUrl: 'https://avatars.githubusercontent.com/u/109625874?s=200&v=4' - }, - { - name: 'Base', - logoUrl: 'https://avatars.githubusercontent.com/u/106747352?s=200&v=4' - }, - { - name: 'Conduit', - logoUrl: 'https://avatars.githubusercontent.com/u/108968326?s=200&v=4' - }, - { - name: 'Lattice', - logoUrl: 'https://avatars.githubusercontent.com/u/17163988?s=200&v=4' - } -] - -export function TrustedBy() { - return ( -
- {companies.map((company, index) => ( -
-
- {`${company.name} -
-
- ))} -
- ) -} \ No newline at end of file diff --git a/rust/docs/docs/pages/index.mdx b/rust/docs/docs/pages/index.mdx deleted file mode 100644 index 888d0283dda..00000000000 --- a/rust/docs/docs/pages/index.mdx +++ /dev/null @@ -1,115 +0,0 @@ ---- -layout: landing ---- - -import { HomePage } from 'vocs/components' -import { SdkShowcase } from '../components/SdkShowcase' -import { CodeGroup, Code } from '../components/CodeGroup' - -
-
-
-
-

- OP Stack Rust -

-

- Rust implementations for the OP Stack -

-

- A unified documentation site for OP Stack Rust components: Kona (rollup node & fault proofs), - op-reth (execution client), and op-alloy (types & providers). - Built by OP Labs. -

-
-
-
- - - - -
-
-
- - - - - - -
-
-

Built with Kona SDK

-

- Production implementations using Kona's modular architecture -

- -
-
diff --git a/rust/docs/docs/pages/kona/node/faq/overview.mdx b/rust/docs/docs/pages/kona/node/faq/overview.mdx deleted file mode 100644 index b3c662040f2..00000000000 --- a/rust/docs/docs/pages/kona/node/faq/overview.mdx +++ /dev/null @@ -1,6 +0,0 @@ -# FAQ - -1. [Ports](/kona/node/faq/ports) - Detailed account of ports used by the `kona-node` for P2P communication, JSON-RPC APIs, and the Engine API for execution layer communication. - -2. [Profiling](/kona/node/faq/profiling) - Profile performance of the Kona node including CPU profiling and memory analysis. - diff --git a/rust/docs/docs/pages/kona/node/faq/profiling.mdx b/rust/docs/docs/pages/kona/node/faq/profiling.mdx deleted file mode 100644 index ab92ba0690e..00000000000 --- a/rust/docs/docs/pages/kona/node/faq/profiling.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# Profiling the Node - -Coming soon... diff --git a/rust/docs/docs/pages/kona/sdk/examples/intro.mdx b/rust/docs/docs/pages/kona/sdk/examples/intro.mdx deleted file mode 100644 index 496497492d6..00000000000 --- a/rust/docs/docs/pages/kona/sdk/examples/intro.mdx +++ /dev/null @@ -1,10 +0,0 @@ -# Examples - -Examples for working with `kona` crates. - -- [Load a Rollup Config for a Chain ID](/kona/sdk/examples/load-a-rollup-config) -- [Transform Frames to a Batch](/kona/sdk/examples/frames-to-batch) -- [Create a new L1BlockInfoTx Hardfork Variant](/kona/sdk/examples/new-l1-block-info-tx-hardfork) -- [Create a new `kona-executor` test fixture](/kona/sdk/examples/executor-test-fixtures) -- [Configuring P2P Network Peer Scoring](/kona/sdk/examples/p2p-peer-scoring) -- [Custom Derivation Pipeline with New Stage](/kona/sdk/examples/custom-derivation-pipeline) diff --git a/rust/docs/docs/pages/kona/sdk/fpp-dev/io.mdx b/rust/docs/docs/pages/kona/sdk/fpp-dev/io.mdx deleted file mode 100644 index 1b3628b3b01..00000000000 --- a/rust/docs/docs/pages/kona/sdk/fpp-dev/io.mdx +++ /dev/null @@ -1 +0,0 @@ -# IO diff --git a/rust/docs/docs/styles.css b/rust/docs/docs/styles.css deleted file mode 100644 index 2645d84abb3..00000000000 --- a/rust/docs/docs/styles.css +++ /dev/null @@ -1,131 +0,0 @@ -@import 'tailwindcss'; - -@layer components { - .dark { - --vocs-color-accent: #87ceeb; - } -} - -.vocs_LandingPage_button { - border-radius: 4px; - height: 36px; -} - -.vocs_LandingPage_codeGroup { - display: flex; - flex-direction: column; - gap: 16px; - flex: 1; -} - -.vocs_DesktopTopNav, .vocs_DesktopTopNav_withLogo { - padding: 0 2rem; -} - -.vocs_DesktopTopNav_logoWrapper { - display: none; -} - -.vocs_LandingPage_install .vocs_CodeBlock_content { - font-size: 18px; -} - -.vocs_accent_border { - border-color: var(--vocs-color-accent); -} - -/* Override ALL vocs layout constraints */ -.vocs_LandingPage_root, -.vocs_LandingPage_root > div, -.vocs_LandingPage_root > div > div, -.vocs_LandingPage_root * { - max-width: none !important; -} - -/* Force full width on the main content area */ -.vocs_Content_root { - max-width: none !important; -} - -.vocs_Content_main { - max-width: none !important; -} - -/* Override any container classes */ -[class*="container"] { - max-width: none !important; -} - -/* Force full width on our custom sections */ -.full-width { - width: 100vw !important; - max-width: none !important; - margin-left: calc(-50vw + 50%) !important; - margin-right: calc(-50vw + 50%) !important; -} - -.full-width-inner { - max-width: 1400px !important; - margin: 0 auto !important; - padding: 0 8rem !important; -} - -@media (max-width: 1440px) { - .full-width-inner { - padding: 0 6rem !important; - } -} - -@media (max-width: 1200px) { - .full-width-inner { - padding: 0 4rem !important; - } -} - -@media (max-width: 1024px) { - .full-width-inner { - padding: 0 3rem !important; - } -} - -@media (max-width: 768px) { - .full-width-inner { - padding: 0 2rem !important; - } -} - -@media (max-width: 480px) { - .full-width-inner { - padding: 0 1rem !important; - } -} - -/* Hide only the logo in the navbar, keep navigation */ -.vocs_Header_logo { - display: none !important; -} - -.vocs_Header_logoContainer { - display: none !important; -} - -/* Hide only the logo in mobile navigation, keep the rest */ -.vocs_MobileTopNav_group .vocs_Header_logo, -.vocs_MobileTopNav_group .vocs_Header_logoContainer, -.vocs_MobileTopNav_group a[href="/"] { - display: none !important; -} - -/* Hide the home link that contains the logo */ -.vocs_Header_root a[href="/"] { - display: none !important; -} - -/* Adjust navbar layout without logo */ -.vocs_Header_nav { - justify-content: flex-start !important; -} - -/* .vocs_Header_root { */ -/* padding-left: 2rem !important; */ -/* } */ diff --git a/rust/docs/justfile b/rust/docs/justfile deleted file mode 100644 index 0150a6f1204..00000000000 --- a/rust/docs/justfile +++ /dev/null @@ -1,15 +0,0 @@ -# Install documentation dependencies -docs-install: - bun install - -# Start the documentation development server -docs-dev: docs-install - bun run dev - -# Build the documentation for production -docs-build: docs-install - bun run build - -# Preview the built documentation -docs-preview: docs-build - bun run preview diff --git a/rust/docs/package-lock.json b/rust/docs/package-lock.json deleted file mode 100644 index b1886b90ce8..00000000000 --- a/rust/docs/package-lock.json +++ /dev/null @@ -1,10036 +0,0 @@ -{ - "name": "op-stack-rust-docs", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "op-stack-rust-docs", - "version": "0.0.0", - "dependencies": { - "react": "19.2.1", - "react-dom": "19.2.1", - "vocs": "1.2.1" - }, - "devDependencies": { - "@types/node": "latest", - "@types/react": "latest", - "tailwindcss": "^4.1.11", - "typescript": "latest" - } - }, - "node_modules/@antfu/install-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", - "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", - "license": "MIT", - "dependencies": { - "package-manager-detector": "^1.3.0", - "tinyexec": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@braintree/sanitize-url": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", - "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", - "license": "MIT" - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/gast/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", - "license": "Apache-2.0" - }, - "node_modules/@clack/core": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.5.tgz", - "integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==", - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, - "node_modules/@clack/prompts": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz", - "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==", - "bundleDependencies": [ - "is-unicode-supported" - ], - "license": "MIT", - "dependencies": { - "@clack/core": "^0.3.3", - "is-unicode-supported": "*", - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, - "node_modules/@clack/prompts/node_modules/is-unicode-supported": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.27.17", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.17.tgz", - "integrity": "sha512-LGVZKHwmWGg6MRHjLLgsfyaX2y2aCNgnD1zT/E6B+/h+vxg+nIJUqHPAlTzsHDyqdgEpJ1Np5kxWuFEErXzoGg==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.7", - "@floating-ui/utils": "^0.2.10", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=17.0.0", - "react-dom": ">=17.0.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", - "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.5" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@fortawesome/fontawesome-free": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz", - "integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==", - "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", - "engines": { - "node": ">=6" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", - "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@iconify/types": "^2.0.0", - "mlly": "^1.8.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@mdx-js/mdx": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", - "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "acorn": "^8.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "recma-build-jsx": "^1.0.0", - "recma-jsx": "^1.0.0", - "recma-stringify": "^1.0.0", - "rehype-recma": "^1.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@mdx-js/react": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", - "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", - "license": "MIT", - "dependencies": { - "@types/mdx": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" - } - }, - "node_modules/@mdx-js/rollup": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/rollup/-/rollup-3.1.1.tgz", - "integrity": "sha512-v8satFmBB+DqDzYohnm1u2JOvxx6Hl3pUvqzJvfs2Zk/ngZ1aRUhsWpXvwPkNeGN9c2NCm/38H29ZqXQUjf8dw==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@rollup/pluginutils": "^5.0.0", - "source-map": "^0.7.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "rollup": ">=2" - } - }, - "node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", - "license": "MIT", - "dependencies": { - "langium": "3.3.1" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@radix-ui/colors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", - "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==", - "license": "MIT" - }, - "node_modules/@radix-ui/number": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", - "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", - "license": "MIT" - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-accessible-icon": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", - "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-accordion": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", - "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collapsible": "1.1.12", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", - "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dialog": "1.1.15", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-aspect-ratio": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", - "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", - "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-is-hydrated": "0.1.0", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", - "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", - "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context-menu": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", - "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-form": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", - "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-label": "2.1.7", - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-form/node_modules/@radix-ui/react-label": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", - "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-hover-card": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", - "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-icons": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", - "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", - "license": "MIT", - "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", - "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menubar": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", - "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-navigation-menu": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", - "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-one-time-password-field": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", - "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-is-hydrated": "0.1.0", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-password-toggle-field": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", - "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-is-hydrated": "0.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", - "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", - "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", - "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", - "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", - "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slider": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", - "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", - "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", - "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", - "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle-group": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", - "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-toggle": "1.1.10", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toolbar": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", - "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-separator": "1.1.7", - "@radix-ui/react-toggle-group": "1.1.11" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", - "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", - "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.5.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", - "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", - "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", - "license": "MIT" - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", - "license": "MIT" - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@shikijs/core": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.29.2.tgz", - "integrity": "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==", - "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz", - "integrity": "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "oniguruma-to-es": "^2.2.0" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", - "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" - } - }, - "node_modules/@shikijs/langs": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.29.2.tgz", - "integrity": "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2" - } - }, - "node_modules/@shikijs/rehype": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-1.29.2.tgz", - "integrity": "sha512-sxi53HZe5XDz0s2UqF+BVN/kgHPMS9l6dcacM4Ra3ZDzCJa5rDGJ+Ukpk4LxdD1+MITBM6hoLbPfGv9StV8a5Q==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2", - "@types/hast": "^3.0.4", - "hast-util-to-string": "^3.0.1", - "shiki": "1.29.2", - "unified": "^11.0.5", - "unist-util-visit": "^5.0.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.29.2.tgz", - "integrity": "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "1.29.2" - } - }, - "node_modules/@shikijs/transformers": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.29.2.tgz", - "integrity": "sha512-NHQuA+gM7zGuxGWP9/Ub4vpbwrYCrho9nQCLcCPfOe3Yc7LOYwmSuhElI688oiqIXk9dlZwDiyAG9vPBTuPJMA==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.2", - "@shikijs/types": "1.29.2" - } - }, - "node_modules/@shikijs/twoslash": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/twoslash/-/twoslash-1.29.2.tgz", - "integrity": "sha512-2S04ppAEa477tiaLfGEn1QJWbZUmbk8UoPbAEw4PifsrxkBXtAtOflIZJNtuCwz8ptc/TPxy7CO7gW4Uoi6o/g==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.2", - "@shikijs/types": "1.29.2", - "twoslash": "^0.2.12" - } - }, - "node_modules/@shikijs/twoslash/node_modules/twoslash": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/twoslash/-/twoslash-0.2.12.tgz", - "integrity": "sha512-tEHPASMqi7kqwfJbkk7hc/4EhlrKCSLcur+TcvYki3vhIfaRMXnXjaYFgXpoZRbT6GdprD4tGuVBEmTpUgLBsw==", - "license": "MIT", - "dependencies": { - "@typescript/vfs": "^1.6.0", - "twoslash-protocol": "0.2.12" - }, - "peerDependencies": { - "typescript": "*" - } - }, - "node_modules/@shikijs/twoslash/node_modules/twoslash-protocol": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/twoslash-protocol/-/twoslash-protocol-0.2.12.tgz", - "integrity": "sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg==", - "license": "MIT" - }, - "node_modules/@shikijs/types": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", - "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "license": "MIT" - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.15.tgz", - "integrity": "sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw==", - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.0", - "lightningcss": "1.30.2", - "magic-string": "^0.30.19", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.15" - } - }, - "node_modules/@tailwindcss/node/node_modules/tailwindcss": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.15.tgz", - "integrity": "sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==", - "license": "MIT" - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.15.tgz", - "integrity": "sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ==", - "license": "MIT", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.15", - "@tailwindcss/oxide-darwin-arm64": "4.1.15", - "@tailwindcss/oxide-darwin-x64": "4.1.15", - "@tailwindcss/oxide-freebsd-x64": "4.1.15", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.15", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.15", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.15", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.15", - "@tailwindcss/oxide-linux-x64-musl": "4.1.15", - "@tailwindcss/oxide-wasm32-wasi": "4.1.15", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.15", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.15" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.15.tgz", - "integrity": "sha512-TkUkUgAw8At4cBjCeVCRMc/guVLKOU1D+sBPrHt5uVcGhlbVKxrCaCW9OKUIBv1oWkjh4GbunD/u/Mf0ql6kEA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.15.tgz", - "integrity": "sha512-xt5XEJpn2piMSfvd1UFN6jrWXyaKCwikP4Pidcf+yfHTSzSpYhG3dcMktjNkQO3JiLCp+0bG0HoWGvz97K162w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.15.tgz", - "integrity": "sha512-TnWaxP6Bx2CojZEXAV2M01Yl13nYPpp0EtGpUrY+LMciKfIXiLL2r/SiSRpagE5Fp2gX+rflp/Os1VJDAyqymg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.15.tgz", - "integrity": "sha512-quISQDWqiB6Cqhjc3iWptXVZHNVENsWoI77L1qgGEHNIdLDLFnw3/AfY7DidAiiCIkGX/MjIdB3bbBZR/G2aJg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.15.tgz", - "integrity": "sha512-ObG76+vPlab65xzVUQbExmDU9FIeYLQ5k2LrQdR2Ud6hboR+ZobXpDoKEYXf/uOezOfIYmy2Ta3w0ejkTg9yxg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.15.tgz", - "integrity": "sha512-4WbBacRmk43pkb8/xts3wnOZMDKsPFyEH/oisCm2q3aLZND25ufvJKcDUpAu0cS+CBOL05dYa8D4U5OWECuH/Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.15.tgz", - "integrity": "sha512-AbvmEiteEj1nf42nE8skdHv73NoR+EwXVSgPY6l39X12Ex8pzOwwfi3Kc8GAmjsnsaDEbk+aj9NyL3UeyHcTLg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.15.tgz", - "integrity": "sha512-+rzMVlvVgrXtFiS+ES78yWgKqpThgV19ISKD58Ck+YO5pO5KjyxLt7AWKsWMbY0R9yBDC82w6QVGz837AKQcHg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.15.tgz", - "integrity": "sha512-fPdEy7a8eQN9qOIK3Em9D3TO1z41JScJn8yxl/76mp4sAXFDfV4YXxsiptJcOwy6bGR+70ZSwFIZhTXzQeqwQg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.15.tgz", - "integrity": "sha512-sJ4yd6iXXdlgIMfIBXuVGp/NvmviEoMVWMOAGxtxhzLPp9LOj5k0pMEMZdjeMCl4C6Up+RM8T3Zgk+BMQ0bGcQ==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.15.tgz", - "integrity": "sha512-sJGE5faXnNQ1iXeqmRin7Ds/ru2fgCiaQZQQz3ZGIDtvbkeV85rAZ0QJFMDg0FrqsffZG96H1U9AQlNBRLsHVg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.15.tgz", - "integrity": "sha512-NLeHE7jUV6HcFKS504bpOohyi01zPXi2PXmjFfkzTph8xRxDdxkRsXm/xDO5uV5K3brrE1cCwbUYmFUSHR3u1w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.15.tgz", - "integrity": "sha512-B6s60MZRTUil+xKoZoGe6i0Iar5VuW+pmcGlda2FX+guDuQ1G1sjiIy1W0frneVpeL/ZjZ4KEgWZHNrIm++2qA==", - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.1.15", - "@tailwindcss/oxide": "4.1.15", - "tailwindcss": "4.1.15" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" - } - }, - "node_modules/@tailwindcss/vite/node_modules/tailwindcss": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.15.tgz", - "integrity": "sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==", - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", - "license": "MIT", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", - "license": "MIT" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", - "license": "MIT" - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", - "license": "MIT" - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", - "license": "MIT" - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", - "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@typescript/vfs": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.2.tgz", - "integrity": "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1" - }, - "peerDependencies": { - "typescript": "*" - } - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "license": "ISC" - }, - "node_modules/@vanilla-extract/babel-plugin-debug-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vanilla-extract/babel-plugin-debug-ids/-/babel-plugin-debug-ids-1.2.2.tgz", - "integrity": "sha512-MeDWGICAF9zA/OZLOKwhoRlsUW+fiMwnfuOAqFVohL31Agj7Q/RBWAYweqjHLgFBCsdnr6XIfwjJnmb2znEWxw==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.23.9" - } - }, - "node_modules/@vanilla-extract/compiler": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@vanilla-extract/compiler/-/compiler-0.3.4.tgz", - "integrity": "sha512-W9HXf9EAccpE1vEIATvSoBVj/bQnmHfYHfDJjUN8dcOHW6oMcnoGTqweDM9I66BHqlNH4d0IsaeZKSViOv7K4w==", - "license": "MIT", - "dependencies": { - "@vanilla-extract/css": "^1.18.0", - "@vanilla-extract/integration": "^8.0.7", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", - "vite-node": "^3.2.2" - } - }, - "node_modules/@vanilla-extract/css": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.18.0.tgz", - "integrity": "sha512-/p0dwOjr0o8gE5BRQ5O9P0u/2DjUd6Zfga2JGmE4KaY7ZITWMszTzk4x4CPlM5cKkRr2ZGzbE6XkuPNfp9shSQ==", - "license": "MIT", - "dependencies": { - "@emotion/hash": "^0.9.0", - "@vanilla-extract/private": "^1.0.9", - "css-what": "^6.1.0", - "cssesc": "^3.0.0", - "csstype": "^3.2.3", - "dedent": "^1.5.3", - "deep-object-diff": "^1.1.9", - "deepmerge": "^4.2.2", - "lru-cache": "^10.4.3", - "media-query-parser": "^2.0.2", - "modern-ahocorasick": "^1.0.0", - "picocolors": "^1.0.0" - } - }, - "node_modules/@vanilla-extract/dynamic": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@vanilla-extract/dynamic/-/dynamic-2.1.5.tgz", - "integrity": "sha512-QGIFGb1qyXQkbzx6X6i3+3LMc/iv/ZMBttMBL+Wm/DetQd36KsKsFg5CtH3qy+1hCA/5w93mEIIAiL4fkM8ycw==", - "license": "MIT", - "dependencies": { - "@vanilla-extract/private": "^1.0.9" - } - }, - "node_modules/@vanilla-extract/integration": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/@vanilla-extract/integration/-/integration-8.0.7.tgz", - "integrity": "sha512-ILob4F9cEHXpbWAVt3Y2iaQJpqYq/c/5TJC8Fz58C2XmX3QW2Y589krvViiyJhQfydCGK3EbwPQhVFjQaBeKfg==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/plugin-syntax-typescript": "^7.23.3", - "@vanilla-extract/babel-plugin-debug-ids": "^1.2.2", - "@vanilla-extract/css": "^1.18.0", - "dedent": "^1.5.3", - "esbuild": "npm:esbuild@>=0.17.6 <0.28.0", - "eval": "0.1.8", - "find-up": "^5.0.0", - "javascript-stringify": "^2.0.1", - "mlly": "^1.4.2" - } - }, - "node_modules/@vanilla-extract/private": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.9.tgz", - "integrity": "sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA==", - "license": "MIT" - }, - "node_modules/@vanilla-extract/vite-plugin": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@vanilla-extract/vite-plugin/-/vite-plugin-5.1.4.tgz", - "integrity": "sha512-fTYNKUK3n4ApkUf2FEcO7mpqNKEHf9kDGg8DXlkqHtPxgwPhjuaajmDfQCSBsNgnA2SLI+CB5EO6kLQuKsw2Rw==", - "license": "MIT", - "dependencies": { - "@vanilla-extract/compiler": "^0.3.4", - "@vanilla-extract/integration": "^8.0.7" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz", - "integrity": "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.2", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "license": "MIT", - "bin": { - "astring": "bin/astring" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.24", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", - "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001766", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/bcp-47-match": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", - "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001768", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", - "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^11.0.0" - } - }, - "node_modules/chevrotain/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/chroma-js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-3.2.0.tgz", - "integrity": "sha512-os/OippSlX1RlWWr+QDPcGUZs0uoqr32urfxESG9U93lhUfbnlyckte84Q8P1UQY/qth983AS1JONKmLS4T0nw==", - "license": "(BSD-3-Clause AND Apache-2.0)" - }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", - "license": "MIT", - "dependencies": { - "layout-base": "^1.0.0" - } - }, - "node_modules/create-vocs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/create-vocs/-/create-vocs-1.0.0.tgz", - "integrity": "sha512-Lv1Bd3WZEgwG4nrogkM54m8viW+TWPlGivLyEi7aNb3cuKPsEfMDZ/kTbo87fzOGtsZ2yh7scO54ZmVhhgBgTw==", - "dependencies": { - "@clack/prompts": "^0.7.0", - "cac": "^6.7.14", - "detect-package-manager": "^3.0.2", - "fs-extra": "^11.3.0", - "picocolors": "^1.1.1" - }, - "bin": { - "create-vocs": "_lib/bin.js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-selector-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", - "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^1.0.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "license": "MIT", - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT" - }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause" - }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dagre-d3-es": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", - "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "lodash-es": "^4.17.21" - } - }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-object-diff": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", - "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==", - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, - "node_modules/detect-package-manager": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-3.0.2.tgz", - "integrity": "sha512-8JFjJHutStYrfWwzfretQoyNGoZVW1Fsrp4JO9spa7h/fBfwgTMEIy4/LBzRDGsxwVPHU0q+T9YvwLDJoOApLQ==", - "license": "MIT", - "dependencies": { - "execa": "^5.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/direction": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", - "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", - "license": "MIT", - "bin": { - "direction": "cli.js" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT" - }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", - "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-scope": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", - "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-value-to-estree": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", - "integrity": "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - } - }, - "node_modules/estree-util-visit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eval": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", - "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", - "dependencies": { - "@types/node": "*", - "require-like": ">= 0.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fault": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", - "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/github-slugger": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", - "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", - "license": "ISC" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/hachure-fill": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", - "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", - "license": "MIT" - }, - "node_modules/hast-util-classnames": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-classnames/-/hast-util-classnames-3.0.0.tgz", - "integrity": "sha512-tI3JjoGDEBVorMAWK4jNRsfLMYmih1BUOG3VV36pH36njs1IEl7xkNrVTD2mD2yYHmQCa5R/fj61a8IAF4bRaQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-dom": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", - "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", - "license": "ISC", - "dependencies": { - "@types/hast": "^3.0.0", - "hastscript": "^9.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-dom/node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-html": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", - "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-html-isomorphic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", - "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-dom": "^5.0.0", - "hast-util-from-html": "^2.0.0", - "unist-util-remove-position": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5/node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-has-property": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", - "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-heading-rank": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", - "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-element": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", - "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-select": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", - "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "bcp-47-match": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "css-selector-parser": "^3.0.0", - "devlop": "^1.0.0", - "direction": "^2.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-to-string": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "nth-check": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-estree": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", - "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-string": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", - "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", - "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", - "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript/node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hono": { - "version": "4.11.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", - "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/inline-style-parser": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", - "license": "MIT" - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/javascript-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", - "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", - "license": "MIT" - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/katex": { - "version": "0.16.28", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz", - "integrity": "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/khroma": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" - }, - "node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", - "license": "MIT", - "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/layout-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", - "license": "MIT" - }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "license": "MIT", - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "license": "MIT" - }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/mdast-util-directive": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", - "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-frontmatter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", - "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "escape-string-regexp": "^5.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/media-query-parser": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz", - "integrity": "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/mermaid": { - "version": "11.12.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", - "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", - "@types/d3": "^7.4.3", - "cytoscape": "^3.29.3", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.2.0", - "d3": "^7.9.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", - "khroma": "^2.1.0", - "lodash-es": "^4.17.21", - "marked": "^16.2.1", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" - } - }, - "node_modules/mermaid-isomorphic": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/mermaid-isomorphic/-/mermaid-isomorphic-3.0.4.tgz", - "integrity": "sha512-XQTy7H1XwHK3DPEHf+ZNWiqUEd9BwX3Xws38R9Fj2gx718srmgjlZoUzHr+Tca+O+dqJOJsAJaKzCoP65QDfDg==", - "license": "MIT", - "dependencies": { - "@fortawesome/fontawesome-free": "^6.0.0", - "mermaid": "^11.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - }, - "peerDependencies": { - "playwright": "1" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": true - } - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-frontmatter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", - "license": "MIT", - "dependencies": { - "fault": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", - "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", - "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "license": "MIT", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", - "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", - "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mini-svg-data-uri": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", - "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", - "license": "MIT", - "bin": { - "mini-svg-data-uri": "cli.js" - } - }, - "node_modules/minisearch": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", - "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/modern-ahocorasick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/modern-ahocorasick/-/modern-ahocorasick-1.1.0.tgz", - "integrity": "sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "license": "MIT" - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/oniguruma-to-es": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", - "integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==", - "license": "MIT", - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "regex": "^5.1.1", - "regex-recursion": "^5.1.1" - } - }, - "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-manager-detector": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", - "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", - "license": "MIT" - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-data-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", - "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/playwright": { - "version": "1.58.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", - "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.58.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.58.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", - "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", - "license": "MIT" - }, - "node_modules/points-on-path": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", - "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", - "license": "MIT", - "dependencies": { - "path-data-parser": "0.1.0", - "points-on-curve": "0.2.0" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/radix-ui": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", - "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-accessible-icon": "1.1.7", - "@radix-ui/react-accordion": "1.2.12", - "@radix-ui/react-alert-dialog": "1.1.15", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-aspect-ratio": "1.1.7", - "@radix-ui/react-avatar": "1.1.10", - "@radix-ui/react-checkbox": "1.3.3", - "@radix-ui/react-collapsible": "1.1.12", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-context-menu": "2.2.16", - "@radix-ui/react-dialog": "1.1.15", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-dropdown-menu": "2.1.16", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-form": "0.1.8", - "@radix-ui/react-hover-card": "1.1.15", - "@radix-ui/react-label": "2.1.7", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-menubar": "1.1.16", - "@radix-ui/react-navigation-menu": "1.2.14", - "@radix-ui/react-one-time-password-field": "0.1.8", - "@radix-ui/react-password-toggle-field": "0.1.3", - "@radix-ui/react-popover": "1.1.15", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-progress": "1.1.7", - "@radix-ui/react-radio-group": "1.3.8", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-scroll-area": "1.2.10", - "@radix-ui/react-select": "2.2.6", - "@radix-ui/react-separator": "1.1.7", - "@radix-ui/react-slider": "1.3.6", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-switch": "1.2.6", - "@radix-ui/react-tabs": "1.1.13", - "@radix-ui/react-toast": "1.2.15", - "@radix-ui/react-toggle": "1.1.10", - "@radix-ui/react-toggle-group": "1.1.11", - "@radix-ui/react-toolbar": "1.1.11", - "@radix-ui/react-tooltip": "1.2.8", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-escape-keydown": "1.1.1", - "@radix-ui/react-use-is-hydrated": "0.1.0", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/radix-ui/node_modules/@radix-ui/react-label": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", - "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/react": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", - "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", - "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.1" - } - }, - "node_modules/react-intersection-observer": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz", - "integrity": "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==", - "license": "MIT", - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-remove-scroll": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-router": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", - "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/recma-build-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", - "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-jsx": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", - "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", - "license": "MIT", - "dependencies": { - "acorn-jsx": "^5.0.0", - "estree-util-to-js": "^2.0.0", - "recma-parse": "^1.0.0", - "recma-stringify": "^1.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/recma-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", - "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "esast-util-from-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", - "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-to-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/regex": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", - "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz", - "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", - "license": "MIT", - "dependencies": { - "regex": "^5.1.1", - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "license": "MIT" - }, - "node_modules/rehype-autolink-headings": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", - "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-heading-rank": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unified": "^11.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-class-names": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rehype-class-names/-/rehype-class-names-2.0.0.tgz", - "integrity": "sha512-jldCIiAEvXKdq8hqr5f5PzNdIDkvHC6zfKhwta9oRoMu7bn0W7qLES/JrrjBvr9rKz3nJ8x4vY1EWI+dhjHVZQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-classnames": "^3.0.0", - "hast-util-select": "^6.0.0", - "unified": "^11.0.4" - } - }, - "node_modules/rehype-mermaid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rehype-mermaid/-/rehype-mermaid-3.0.0.tgz", - "integrity": "sha512-fxrD5E4Fa1WXUjmjNDvLOMT4XB1WaxcfycFIWiYU0yEMQhcTDElc9aDFnbDFRLxG1Cfo1I3mfD5kg4sjlWaB+Q==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-html-isomorphic": "^2.0.0", - "hast-util-to-text": "^4.0.0", - "mermaid-isomorphic": "^3.0.0", - "mini-svg-data-uri": "^1.0.0", - "space-separated-tokens": "^2.0.0", - "unified": "^11.0.0", - "unist-util-visit-parents": "^6.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - }, - "peerDependencies": { - "playwright": "1" - }, - "peerDependenciesMeta": { - "playwright": { - "optional": true - } - } - }, - "node_modules/rehype-recma": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", - "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "hast-util-to-estree": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-slug": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", - "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "github-slugger": "^2.0.0", - "hast-util-heading-rank": "^3.0.0", - "hast-util-to-string": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-directive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", - "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-directive": "^3.0.0", - "micromark-extension-directive": "^3.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-frontmatter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", - "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-frontmatter": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-mdx": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", - "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", - "license": "MIT", - "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-mdx-frontmatter": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/remark-mdx-frontmatter/-/remark-mdx-frontmatter-5.2.0.tgz", - "integrity": "sha512-U/hjUYTkQqNjjMRYyilJgLXSPF65qbLPdoESOkXyrwz2tVyhAnm4GUKhfXqOOS9W34M3545xEMq+aMpHgVjEeQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "estree-util-value-to-estree": "^3.0.0", - "toml": "^3.0.0", - "unified": "^11.0.0", - "unist-util-mdx-define": "^1.0.0", - "yaml": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", - "engines": { - "node": "*" - } - }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" - }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/roughjs": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", - "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", - "license": "MIT", - "dependencies": { - "hachure-fill": "^0.5.2", - "path-data-parser": "^0.1.0", - "points-on-curve": "^0.2.0", - "points-on-path": "^0.2.1" - } - }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shiki": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.29.2.tgz", - "integrity": "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==", - "license": "MIT", - "dependencies": { - "@shikijs/core": "1.29.2", - "@shikijs/engine-javascript": "1.29.2", - "@shikijs/engine-oniguruma": "1.29.2", - "@shikijs/langs": "1.29.2", - "@shikijs/themes": "1.29.2", - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1", - "@types/hast": "^3.0.4" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", - "license": "MIT", - "dependencies": { - "bl": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/style-to-js": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", - "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.14" - } - }, - "node_modules/style-to-object": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", - "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.7" - } - }, - "node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" - }, - "node_modules/tabbable": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", - "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", - "license": "MIT" - }, - "node_modules/tailwindcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", - "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/toml": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", - "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", - "license": "MIT" - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "license": "MIT", - "engines": { - "node": ">=6.10" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/twoslash": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/twoslash/-/twoslash-0.3.6.tgz", - "integrity": "sha512-VuI5OKl+MaUO9UIW3rXKoPgHI3X40ZgB/j12VY6h98Ae1mCBihjPvhOPeJWlxCYcmSbmeZt5ZKkK0dsVtp+6pA==", - "license": "MIT", - "dependencies": { - "@typescript/vfs": "^1.6.2", - "twoslash-protocol": "0.3.6" - }, - "peerDependencies": { - "typescript": "^5.5.0" - } - }, - "node_modules/twoslash-protocol": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/twoslash-protocol/-/twoslash-protocol-0.3.6.tgz", - "integrity": "sha512-FHGsJ9Q+EsNr5bEbgG3hnbkvEBdW5STgPU824AHUjB4kw0Dn4p8tABT7Ncg1Ie6V0+mDg3Qpy41VafZXcQhWMA==", - "license": "MIT" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ua-parser-js": { - "version": "1.0.41", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", - "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "license": "MIT", - "bin": { - "ua-parser-js": "script/cli.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-find-after": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", - "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-mdx-define": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-mdx-define/-/unist-util-mdx-define-1.1.2.tgz", - "integrity": "sha512-9ncH7i7TN5Xn7/tzX5bE3rXgz1X/u877gYVAUB3mLeTKYJmQHmqKTDBi6BTGXV7AeolBCI9ErcVsOt2qryoD0g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-matter": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/vfile-matter/-/vfile-matter-5.0.1.tgz", - "integrity": "sha512-o6roP82AiX0XfkyTHyRCMXgHfltUNlXSEqCIS80f+mbAyiQBE2fxtDVMtseyytGx75sihiJFo/zR6r/4LTs2Cw==", - "license": "MIT", - "dependencies": { - "vfile": "^6.0.0", - "yaml": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/vocs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/vocs/-/vocs-1.2.1.tgz", - "integrity": "sha512-rQ5aoD68+UJQeJ9G/nPcqcwhbBpMFZnHJ9ZkIsRHaeqBdiA4S86ufplJRKxmX56XZLEpY+wlU+TGz8Qsxtb8Sw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.27.16", - "@hono/node-server": "^1.19.5", - "@mdx-js/mdx": "^3.1.1", - "@mdx-js/react": "^3.1.1", - "@mdx-js/rollup": "^3.1.1", - "@noble/hashes": "^1.7.1", - "@radix-ui/colors": "^3.0.0", - "@radix-ui/react-accordion": "^1.2.3", - "@radix-ui/react-dialog": "^1.1.6", - "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.2", - "@radix-ui/react-navigation-menu": "^1.2.5", - "@radix-ui/react-popover": "^1.1.6", - "@radix-ui/react-tabs": "^1.1.3", - "@shikijs/rehype": "^1", - "@shikijs/transformers": "^1", - "@shikijs/twoslash": "^1", - "@tailwindcss/vite": "4.1.15", - "@vanilla-extract/css": "^1.17.4", - "@vanilla-extract/dynamic": "^2.1.5", - "@vanilla-extract/vite-plugin": "^5.1.1", - "@vitejs/plugin-react": "^5.0.4", - "autoprefixer": "^10.4.21", - "cac": "^6.7.14", - "chroma-js": "^3.1.2", - "clsx": "^2.1.1", - "compression": "^1.8.1", - "create-vocs": "^1.0.0-alpha.5", - "cross-spawn": "^7.0.6", - "fs-extra": "^11.3.2", - "hastscript": "^8.0.0", - "hono": "^4.10.3", - "mark.js": "^8.11.1", - "mdast-util-directive": "^3.1.0", - "mdast-util-from-markdown": "^2.0.2", - "mdast-util-frontmatter": "^2.0.1", - "mdast-util-gfm": "^3.1.0", - "mdast-util-mdx": "^3.0.0", - "mdast-util-mdx-jsx": "^3.2.0", - "mdast-util-to-hast": "^13.2.0", - "mdast-util-to-markdown": "^2.1.2", - "minisearch": "^7.2.0", - "nuqs": "^2.7.2", - "ora": "^7.0.1", - "p-limit": "^5.0.0", - "picomatch": "^4.0.3", - "playwright": "^1.52.0", - "postcss": "^8.5.2", - "radix-ui": "^1.1.3", - "react-intersection-observer": "^9.15.1", - "react-router": "^7.9.4", - "rehype-autolink-headings": "^7.1.0", - "rehype-class-names": "^2.0.0", - "rehype-mermaid": "^3.0.0", - "rehype-slug": "^6.0.0", - "remark-directive": "^3.0.1", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.1", - "remark-mdx": "^3.1.1", - "remark-mdx-frontmatter": "^5.2.0", - "remark-parse": "^11.0.0", - "serve-static": "^1.16.2", - "shiki": "^1", - "toml": "^3.0.0", - "twoslash": "~0.3.4", - "ua-parser-js": "^1.0.40", - "unified": "^11.0.5", - "unist-util-visit": "^5.0.0", - "vfile-matter": "^5.0.1", - "vite": "^7.1.11", - "yaml": "^2.8.1" - }, - "bin": { - "vocs": "_lib/cli/index.js" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "react": "^19", - "react-dom": "^19" - } - }, - "node_modules/vocs/node_modules/nuqs": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-2.8.8.tgz", - "integrity": "sha512-LF5sw9nWpHyPWzMMu9oho3r9C5DvkpmBIg4LQN78sexIzGaeRx8DWr0uy3YiFx5i2QGZN1Qqcb+OAtEVRa2bnA==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/franky47" - }, - "peerDependencies": { - "@remix-run/react": ">=2", - "@tanstack/react-router": "^1", - "next": ">=14.2.0", - "react": ">=18.2.0 || ^19.0.0-0", - "react-router": "^5 || ^6 || ^7", - "react-router-dom": "^5 || ^6 || ^7" - }, - "peerDependenciesMeta": { - "@remix-run/react": { - "optional": true - }, - "@tanstack/react-router": { - "optional": true - }, - "next": { - "optional": true - }, - "react-router": { - "optional": true - }, - "react-router-dom": { - "optional": true - } - } - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "license": "MIT" - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/rust/docs/package.json b/rust/docs/package.json deleted file mode 100644 index 482e9a21334..00000000000 --- a/rust/docs/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "op-stack-rust-docs", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vocs dev", - "build": "vocs build", - "preview": "vocs preview" - }, - "dependencies": { - "react": "19.2.1", - "react-dom": "19.2.1", - "vocs": "1.2.1" - }, - "devDependencies": { - "@types/node": "latest", - "@types/react": "latest", - "tailwindcss": "^4.1.11", - "typescript": "latest" - } -} diff --git a/rust/docs/sidebar-cli-op-reth.ts b/rust/docs/sidebar-cli-op-reth.ts deleted file mode 100644 index 775b163a625..00000000000 --- a/rust/docs/sidebar-cli-op-reth.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { SidebarItem } from "vocs"; - -export const opRethCliSidebar: SidebarItem = { - text: "op-reth", - link: "/op-reth/cli/op-reth", - collapsed: false, - items: [ - { - text: "op-reth node", - link: "/op-reth/cli/op-reth/node" - }, - { - text: "op-reth init", - link: "/op-reth/cli/op-reth/init" - }, - { - text: "op-reth init-state", - link: "/op-reth/cli/op-reth/init-state" - }, - { - text: "op-reth import-op", - link: "/op-reth/cli/op-reth/import-op" - }, - { - text: "op-reth import-receipts-op", - link: "/op-reth/cli/op-reth/import-receipts-op" - }, - { - text: "op-reth dump-genesis", - link: "/op-reth/cli/op-reth/dump-genesis" - }, - { - text: "op-reth db", - link: "/op-reth/cli/op-reth/db", - collapsed: true, - items: [ - { - text: "op-reth db stats", - link: "/op-reth/cli/op-reth/db/stats" - }, - { - text: "op-reth db list", - link: "/op-reth/cli/op-reth/db/list" - }, - { - text: "op-reth db checksum", - link: "/op-reth/cli/op-reth/db/checksum", - collapsed: true, - items: [ - { - text: "op-reth db checksum mdbx", - link: "/op-reth/cli/op-reth/db/checksum/mdbx" - }, - { - text: "op-reth db checksum static-file", - link: "/op-reth/cli/op-reth/db/checksum/static-file" - } - ] - }, - { - text: "op-reth db diff", - link: "/op-reth/cli/op-reth/db/diff" - }, - { - text: "op-reth db get", - link: "/op-reth/cli/op-reth/db/get", - collapsed: true, - items: [ - { - text: "op-reth db get mdbx", - link: "/op-reth/cli/op-reth/db/get/mdbx" - }, - { - text: "op-reth db get static-file", - link: "/op-reth/cli/op-reth/db/get/static-file" - } - ] - }, - { - text: "op-reth db drop", - link: "/op-reth/cli/op-reth/db/drop" - }, - { - text: "op-reth db clear", - link: "/op-reth/cli/op-reth/db/clear", - collapsed: true, - items: [ - { - text: "op-reth db clear mdbx", - link: "/op-reth/cli/op-reth/db/clear/mdbx" - }, - { - text: "op-reth db clear static-file", - link: "/op-reth/cli/op-reth/db/clear/static-file" - } - ] - }, - { - text: "op-reth db repair-trie", - link: "/op-reth/cli/op-reth/db/repair-trie" - }, - { - text: "op-reth db static-file-header", - link: "/op-reth/cli/op-reth/db/static-file-header", - collapsed: true, - items: [ - { - text: "op-reth db static-file-header block", - link: "/op-reth/cli/op-reth/db/static-file-header/block" - }, - { - text: "op-reth db static-file-header path", - link: "/op-reth/cli/op-reth/db/static-file-header/path" - } - ] - }, - { - text: "op-reth db version", - link: "/op-reth/cli/op-reth/db/version" - }, - { - text: "op-reth db path", - link: "/op-reth/cli/op-reth/db/path" - }, - { - text: "op-reth db settings", - link: "/op-reth/cli/op-reth/db/settings", - collapsed: true, - items: [ - { - text: "op-reth db settings get", - link: "/op-reth/cli/op-reth/db/settings/get" - }, - { - text: "op-reth db settings set", - link: "/op-reth/cli/op-reth/db/settings/set", - collapsed: true, - items: [ - { - text: "op-reth db settings set receipts", - link: "/op-reth/cli/op-reth/db/settings/set/receipts" - }, - { - text: "op-reth db settings set transaction_senders", - link: "/op-reth/cli/op-reth/db/settings/set/transaction_senders" - }, - { - text: "op-reth db settings set account_changesets", - link: "/op-reth/cli/op-reth/db/settings/set/account_changesets" - }, - { - text: "op-reth db settings set storages_history", - link: "/op-reth/cli/op-reth/db/settings/set/storages_history" - }, - { - text: "op-reth db settings set transaction_hash_numbers", - link: "/op-reth/cli/op-reth/db/settings/set/transaction_hash_numbers" - }, - { - text: "op-reth db settings set account_history", - link: "/op-reth/cli/op-reth/db/settings/set/account_history" - }, - { - text: "op-reth db settings set storage_changesets", - link: "/op-reth/cli/op-reth/db/settings/set/storage_changesets" - } - ] - } - ] - }, - { - text: "op-reth db account-storage", - link: "/op-reth/cli/op-reth/db/account-storage" - } - ] - }, - { - text: "op-reth stage", - link: "/op-reth/cli/op-reth/stage", - collapsed: true, - items: [ - { - text: "op-reth stage run", - link: "/op-reth/cli/op-reth/stage/run" - }, - { - text: "op-reth stage drop", - link: "/op-reth/cli/op-reth/stage/drop" - }, - { - text: "op-reth stage dump", - link: "/op-reth/cli/op-reth/stage/dump", - collapsed: true, - items: [ - { - text: "op-reth stage dump execution", - link: "/op-reth/cli/op-reth/stage/dump/execution" - }, - { - text: "op-reth stage dump storage-hashing", - link: "/op-reth/cli/op-reth/stage/dump/storage-hashing" - }, - { - text: "op-reth stage dump account-hashing", - link: "/op-reth/cli/op-reth/stage/dump/account-hashing" - }, - { - text: "op-reth stage dump merkle", - link: "/op-reth/cli/op-reth/stage/dump/merkle" - } - ] - }, - { - text: "op-reth stage unwind", - link: "/op-reth/cli/op-reth/stage/unwind", - collapsed: true, - items: [ - { - text: "op-reth stage unwind to-block", - link: "/op-reth/cli/op-reth/stage/unwind/to-block" - }, - { - text: "op-reth stage unwind num-blocks", - link: "/op-reth/cli/op-reth/stage/unwind/num-blocks" - } - ] - } - ] - }, - { - text: "op-reth p2p", - link: "/op-reth/cli/op-reth/p2p", - collapsed: true, - items: [ - { - text: "op-reth p2p header", - link: "/op-reth/cli/op-reth/p2p/header" - }, - { - text: "op-reth p2p body", - link: "/op-reth/cli/op-reth/p2p/body" - }, - { - text: "op-reth p2p rlpx", - link: "/op-reth/cli/op-reth/p2p/rlpx", - collapsed: true, - items: [ - { - text: "op-reth p2p rlpx ping", - link: "/op-reth/cli/op-reth/p2p/rlpx/ping" - } - ] - }, - { - text: "op-reth p2p bootnode", - link: "/op-reth/cli/op-reth/p2p/bootnode" - } - ] - }, - { - text: "op-reth config", - link: "/op-reth/cli/op-reth/config" - }, - { - text: "op-reth prune", - link: "/op-reth/cli/op-reth/prune" - }, - { - text: "op-reth re-execute", - link: "/op-reth/cli/op-reth/re-execute" - } - ] -}; diff --git a/rust/docs/sidebar-kona.ts b/rust/docs/sidebar-kona.ts deleted file mode 100644 index 8d82d55c0e3..00000000000 --- a/rust/docs/sidebar-kona.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { SidebarItem } from "vocs"; - -export const konaSidebar: SidebarItem[] = [ - { - text: "Introduction", - items: [ - { text: "Overview", link: "/kona/intro/overview" }, - { text: "Why Kona?", link: "/kona/intro/why" }, - { text: "Contributing", link: "/kona/intro/contributing" }, - { text: "Kona Lore", link: "/kona/intro/lore" } - ] - }, - { - text: "Kona for Node Operators", - items: [ - { text: "System Requirements", link: "/kona/node/requirements" }, - { - text: "Installation", - collapsed: true, - items: [ - { - text: "Prerequisites", - link: "/kona/node/install/overview" - }, - { - text: "Pre-Built Binaries", - link: "/kona/node/install/binaries" - }, - { - text: "Docker", - link: "/kona/node/install/docker" - }, - { - text: "Build from Source", - link: "/kona/node/install/source" - } - ] - }, - { - text: "Run a Node", - items: [ - { - text: "Overview", - link: "/kona/node/run/overview", - }, - { - text: "Binary", - link: "/kona/node/run/binary", - }, - { - text: "Docker", - link: "/kona/node/run/docker", - }, - { - text: "How it Works", - link: "/kona/node/run/mechanics", - } - ] - }, - { - text: "JSON-RPC Reference", - items: [ - { - text: "Overview", - link: "/kona/node/rpc/overview", - }, - { - text: "p2p", - link: "/kona/node/rpc/p2p", - }, - { - text: "rollup", - link: "/kona/node/rpc/rollup", - }, - { - text: "admin", - link: "/kona/node/rpc/admin", - } - ] - }, - { text: "Configuration", link: "/kona/node/configuration" }, - { text: "Kurtosis Integration", link: "/kona/kurtosis/overview" }, - { text: "Monitoring", link: "/kona/node/monitoring" }, - { text: "Subcommands", link: "/kona/node/subcommands" }, - { - text: "FAQ", - link: "/kona/node/faq/overview", - collapsed: true, - items: [ - { - text: "Ports", - link: "/kona/node/faq/ports" - }, - { - text: "Profiling", - link: "/kona/node/faq/profiling" - } - ] - } - ] - }, - { - text: "Kona as a Library", - items: [ - { text: "Overview", link: "/kona/sdk/overview" }, - { - text: "Node SDK", - items: [ - { text: "Introduction", link: "/kona/node/design/intro" }, - { text: "Derivation", link: "/kona/node/design/derivation" }, - { text: "Engine", link: "/kona/node/design/engine" }, - { text: "P2P", link: "/kona/node/design/p2p" }, - { text: "Sequencer", link: "/kona/node/design/sequencer" } - ] - }, - { - text: "Proof SDK", - items: [ - { text: "Introduction", link: "/kona/sdk/proof/intro" }, - { text: "FPVM Backend", link: "/kona/sdk/proof/fpvm-backend" }, - { text: "Custom Backend", link: "/kona/sdk/proof/custom-backend" }, - { text: "kona-executor Extensions", link: "/kona/sdk/proof/exec-ext" } - ] - }, - { - text: "Fault Proof Program Development", - collapsed: true, - items: [ - { text: "Introduction", link: "/kona/sdk/fpp-dev/intro" }, - { text: "Environment", link: "/kona/sdk/fpp-dev/env" }, - { text: "Supported Targets", link: "/kona/sdk/fpp-dev/targets" }, - { text: "Prologue", link: "/kona/sdk/fpp-dev/prologue" }, - { text: "Execution", link: "/kona/sdk/fpp-dev/execution" }, - { text: "Epilogue", link: "/kona/sdk/fpp-dev/epilogue" } - ] - }, - { - text: "Protocol Libraries", - collapsed: true, - items: [ - { text: "Introduction", link: "/kona/sdk/protocol/intro" }, - { text: "Registry", link: "/kona/sdk/protocol/registry" }, - { text: "Interop", link: "/kona/sdk/protocol/interop" }, - { text: "Hardforks", link: "/kona/sdk/protocol/hardforks" }, - { - text: "Derivation", - collapsed: true, - items: [ - { text: "Introduction", link: "/kona/sdk/protocol/derive/intro" }, - { text: "Custom Providers", link: "/kona/sdk/protocol/derive/providers" }, - { text: "Stage Swapping", link: "/kona/sdk/protocol/derive/stages" }, - { text: "Signaling", link: "/kona/sdk/protocol/derive/signaling" } - ] - }, - { - text: "Genesis", - collapsed: true, - items: [ - { text: "Introduction", link: "/kona/sdk/protocol/genesis/intro" }, - { text: "Rollup Config", link: "/kona/sdk/protocol/genesis/rollup-config" }, - { text: "System Config", link: "/kona/sdk/protocol/genesis/system-config" } - ] - }, - { - text: "Protocol", - collapsed: true, - items: [ - { text: "Introduction", link: "/kona/sdk/protocol/protocol/intro" }, - { text: "BlockInfo", link: "/kona/sdk/protocol/protocol/block-info" }, - { text: "L2BlockInfo", link: "/kona/sdk/protocol/protocol/l2-block-info" }, - { text: "Frames", link: "/kona/sdk/protocol/protocol/frames" }, - { text: "Channels", link: "/kona/sdk/protocol/protocol/channels" }, - { text: "Batches", link: "/kona/sdk/protocol/protocol/batches" } - ] - } - ] - }, - { - text: "Examples", - collapsed: true, - items: [ - { text: "Introduction", link: "/kona/sdk/examples/intro" }, - { text: "Load a Rollup Config", link: "/kona/sdk/examples/load-a-rollup-config" }, - { text: "Transform Frames to a Batch", link: "/kona/sdk/examples/frames-to-batch" }, - { text: "Transform a Batch into Frames", link: "/kona/sdk/examples/batch-to-frames" }, - { text: "Create a new L1BlockInfoTx Hardfork Variant", link: "/kona/sdk/examples/new-l1-block-info-tx-hardfork" }, - { text: "Create a new kona-executor test fixture", link: "/kona/sdk/examples/executor-test-fixtures" }, - { text: "Configuring P2P Network Peer Scoring", link: "/kona/sdk/examples/p2p-peer-scoring" }, - { text: "Custom Derivation Pipeline with New Stage", link: "/kona/sdk/examples/custom-derivation-pipeline" }, - { text: "Testing Kona Sequencing with Kurtosis", link: "/kona/sdk/examples/kurtosis-sequencing-test" } - ] - } - ] - }, - { - text: "RFC", - link: "/kona/rfc/active/intro", - items: [ - { - text: "Active RFCs", - items: [ ] - }, - { - text: "Archived RFCs", - collapsed: true, - items: [ - { text: "Umbrellas", link: "/kona/rfc/archived/umbrellas" }, - { text: "Monorepo", link: "/kona/rfc/archived/monorepo" } - ] - } - ] - } -]; diff --git a/rust/docs/sidebar-op-alloy.ts b/rust/docs/sidebar-op-alloy.ts deleted file mode 100644 index adb3d94a322..00000000000 --- a/rust/docs/sidebar-op-alloy.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { SidebarItem } from "vocs"; - -export const opAlloySidebar: SidebarItem[] = [ - { - text: "Introduction", - items: [ - { - text: "Overview", - link: "/op-alloy/intro" - }, - { - text: "Getting Started", - link: "/op-alloy/starting" - } - ] - }, - { - text: "Building", - items: [ - { - text: "Overview", - link: "/op-alloy/building" - }, - { - text: "Consensus", - link: "/op-alloy/building/consensus" - }, - { - text: "Engine RPC Types", - link: "/op-alloy/building/engine" - } - ] - }, - { - text: "Reference", - items: [ - { - text: "Glossary", - link: "/op-alloy/glossary" - } - ] - } -]; diff --git a/rust/docs/sidebar-op-reth.ts b/rust/docs/sidebar-op-reth.ts deleted file mode 100644 index c11642058d5..00000000000 --- a/rust/docs/sidebar-op-reth.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { SidebarItem } from "vocs"; -import { opRethCliSidebar } from "./sidebar-cli-op-reth"; - -export const opRethSidebar: SidebarItem[] = [ - { - text: "Introduction", - items: [ - { - text: "Overview", - link: "/op-reth/" - } - ] - }, - { - text: "Running op-reth", - items: [ - { - text: "OP Stack", - link: "/op-reth/run/opstack" - }, - { - text: "FAQ", - collapsed: true, - items: [ - { - text: "Sync OP Mainnet", - link: "/op-reth/run/faq/sync-op-mainnet" - } - ] - } - ] - }, - { - text: "CLI Reference", - link: "/op-reth/cli/op-reth", - collapsed: false, - items: [ - opRethCliSidebar - ] - }, -]; diff --git a/rust/docs/sidebar.ts b/rust/docs/sidebar.ts deleted file mode 100644 index d16b2c74358..00000000000 --- a/rust/docs/sidebar.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SidebarItem } from "vocs"; -import { konaSidebar } from "./sidebar-kona"; -import { opRethSidebar } from "./sidebar-op-reth"; -import { opAlloySidebar } from "./sidebar-op-alloy"; - -export const sidebar = { - "/kona/": konaSidebar, - "/op-reth/": opRethSidebar, - "/op-alloy/": opAlloySidebar, -} satisfies Record; diff --git a/rust/docs/tsconfig.json b/rust/docs/tsconfig.json deleted file mode 100644 index d2636aac47e..00000000000 --- a/rust/docs/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["**/*.ts", "**/*.tsx"] -} diff --git a/rust/docs/vocs.config.ts b/rust/docs/vocs.config.ts deleted file mode 100644 index dfdd26063d4..00000000000 --- a/rust/docs/vocs.config.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { defineConfig } from 'vocs' -import { sidebar } from './sidebar' - -export default defineConfig({ - title: 'OP Stack Rust', - description: 'Rust implementations for the OP Stack: Kona, op-reth, and op-alloy', - logoUrl: '/logo.png', - iconUrl: '/logo.png', - sidebar, - search: { - fuzzy: true - }, - topNav: [ - { - text: 'Kona', - items: [ - { text: 'Overview', link: '/kona/intro/overview' }, - { text: 'Run a Node', link: '/kona/node/run/overview' }, - { text: 'SDK', link: '/kona/sdk/overview' }, - { text: 'Rustdocs', link: 'https://docs.rs/kona-node/latest/' }, - ] - }, - { - text: 'op-reth', - items: [ - { text: 'Overview', link: '/op-reth/' }, - { text: 'Run', link: '/op-reth/run/opstack' }, - { text: 'CLI Reference', link: '/op-reth/cli/op-reth' }, - ] - }, - { - text: 'op-alloy', - items: [ - { text: 'Overview', link: '/op-alloy/intro' }, - { text: 'Getting Started', link: '/op-alloy/starting' }, - { text: 'Building', link: '/op-alloy/building' }, - ] - }, - { text: 'GitHub', link: 'https://github.com/ethereum-optimism/optimism/tree/develop/rust' }, - ], - socials: [ - { - icon: 'github', - link: 'https://github.com/ethereum-optimism/optimism', - }, - ], - theme: { - accentColor: { - light: '#ff0420', - dark: '#ff0420', - } - }, - editLink: { - pattern: "https://github.com/ethereum-optimism/optimism/edit/develop/rust/docs/:path", - }, - sponsors: [ - { - name: 'Supporters', - height: 120, - items: [ - [ - { - name: 'OP Labs', - link: 'https://oplabs.co', - image: 'https://avatars.githubusercontent.com/u/109625874?s=200&v=4', - } - ] - ] - } - ] -}) diff --git a/rust/justfile b/rust/justfile index 46d0fc21ec6..04f1279b19d 100644 --- a/rust/justfile +++ b/rust/justfile @@ -1,6 +1,6 @@ set positional-arguments -NIGHTLY := "nightly-2026-02-20" +NIGHTLY := `grep -oE 'nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}' ../mise.toml | head -1` # Aliases alias t := test @@ -16,7 +16,7 @@ default: # Install the pinned nightly toolchain install-nightly: - rustup toolchain install {{NIGHTLY}} --component rustfmt + rustup toolchain install {{NIGHTLY}} --component rustfmt --component rust-src ############################### Build ############################### @@ -24,18 +24,50 @@ install-nightly: build *args='': cargo build --workspace {{args}} +# Build the workspace, excluding example crates, with a fast compile-only profile by default +build-no-examples *args='': + #!/usr/bin/env bash + set -euo pipefail + + profile_args="--profile fast-build" + case " {{args}} " in + *" --release "*|*" --profile "*|*" --profile="*) + profile_args="" + ;; + esac + + exclude_args="" + while IFS= read -r package; do + exclude_args="$exclude_args --exclude $package" + done < <( + cargo metadata --no-deps --format-version 1 \ + | jq -r '.packages[] | select(.manifest_path | contains("/examples/")) | .name' + ) + + cargo build --workspace $profile_args $exclude_args {{args}} + # Build the workspace in release mode build-release *args='': cargo build --workspace --release {{args}} -# Build the rollup node -build-node: +# Build kona-node +build-kona-node: cargo build --release --bin kona-node +# Build kona-node in debug mode (faster compilation for local E2E test iteration) +build-kona-node-debug: + cargo build --bin kona-node + +alias build-node := build-kona-node + # Build op-reth build-op-reth: cargo build --release --bin op-reth +# Build op-reth in debug mode (faster compilation for local E2E test iteration) +build-op-reth-debug: + cargo build --bin op-reth + ############################### Test ################################ # Run all tests (unit + doc tests) @@ -51,7 +83,7 @@ test-online: # Run doc tests test-docs: - cargo test --doc --workspace --locked + cargo test --doc --workspace --locked --all-features ############################### Lint ################################ @@ -161,6 +193,105 @@ hack partition="" shuffle="false" seed="default": cargo hack check --each-feature --no-dev-deps $PKG_FLAGS {{ if partition != "" { "--partition " + partition } else { "" } }} +########################### Release ################################# + +# Release crates at the given version. +# target: either "workspace" (releases every subdir under rust/ except op-reth, +# in dependency order) or one of the subdir names (op-alloy, +# alloy-op-hardforks, alloy-op-evm, op-revm, kona). +# mode: "dry" (default, prints changes without applying) or "execute" +# Extra args are forwarded to `cargo release`. +# kona is split into two topologically-ordered batches to stay under the +# crates.io "existing crates" rate limit of 30. +# Example: just release workspace 1.0.0 +# Example: just release kona 1.0.0 execute +# Example: just release op-alloy 1.0.0 execute --no-confirm +release TARGET VERSION MODE="dry" *ARGS="": + #!/usr/bin/env bash + set -euo pipefail + + if ! command -v cargo-release >/dev/null 2>&1; then + echo "cargo-release not found, installing..." + cargo install cargo-release + fi + + case "{{MODE}}" in + dry) EXTRA="" ;; + execute) EXTRA="--execute" ;; + *) echo "error: mode must be 'dry' or 'execute', got '{{MODE}}'" >&2; exit 1 ;; + esac + + METADATA=$(cargo metadata --format-version=1 --no-deps) + + # Hardcoded split for kona: 32 crates exceeds crates.io's "existing crates" + # rate limit of 30, so we publish in two topologically-ordered batches. + # When adding a new crate under rust/kona/, append it to whichever batch + # leaves both <= 30 (preserving topo order: deps before dependents). + KONA_BATCH_1="kona-genesis kona-macros kona-mpt kona-preimage kona-serde kona-sources kona-registry kona-std-fpvm kona-cli kona-peers kona-protocol kona-std-fpvm-proc kona-disc kona-engine kona-executor kona-hardforks" + KONA_BATCH_2="kona-interop example-discovery kona-gossip execution-fixture kona-derive kona-rpc kona-driver kona-providers-alloy kona-providers-local kona-proof kona-node-service kona-proof-interop example-gossip kona-node kona-client kona-host" + + ACTUAL_KONA=$(echo "$METADATA" | jq -r '.packages[] | select(.manifest_path | contains("/rust/kona/")) | .name' | sort -u) + EXPECTED_KONA=$(echo "$KONA_BATCH_1 $KONA_BATCH_2" | tr ' ' '\n' | sort -u) + if [[ "$ACTUAL_KONA" != "$EXPECTED_KONA" ]]; then + echo "error: hardcoded kona batches don't match actual kona crates." >&2 + echo " diff (actual vs expected):" >&2 + diff <(echo "$ACTUAL_KONA") <(echo "$EXPECTED_KONA") >&2 || true + echo " Update KONA_BATCH_1/KONA_BATCH_2 in rust/justfile." >&2 + exit 1 + fi + + # Release order is dependency-driven: foundational crates first, kona last. + ALL_SUBDIRS="op-alloy alloy-op-hardforks alloy-op-evm op-revm kona" + + EXPECTED_SUBDIRS=$(echo "$ALL_SUBDIRS" | tr ' ' '\n' | sort -u) + ACTUAL_SUBDIRS=$(echo "$METADATA" \ + | jq -r '.packages[].manifest_path' \ + | sed -nE 's|.*/rust/([^/]+)/.*|\1|p' \ + | grep -vx 'op-reth' \ + | sort -u) + if [[ "$EXPECTED_SUBDIRS" != "$ACTUAL_SUBDIRS" ]]; then + echo "error: hardcoded ALL_SUBDIRS list doesn't match actual subdirs under rust/." >&2 + echo " diff (expected vs actual):" >&2 + diff <(echo "$EXPECTED_SUBDIRS") <(echo "$ACTUAL_SUBDIRS") >&2 || true + echo " Update ALL_SUBDIRS in rust/justfile." >&2 + exit 1 + fi + + case "{{TARGET}}" in + workspace) SUBDIRS="$ALL_SUBDIRS" ;; + *) + if ! echo " $ALL_SUBDIRS " | grep -q " {{TARGET}} "; then + echo "error: unknown target '{{TARGET}}'. Must be 'workspace' or one of: $ALL_SUBDIRS" >&2 + exit 1 + fi + SUBDIRS="{{TARGET}}" + ;; + esac + + run_release() { + local label="$1"; shift + local pkgs + pkgs=$(printf -- '-p %s ' "$@") + echo "=== $label → version {{VERSION}} (mode: {{MODE}}) ===" + printf '%s\n' "$@" + echo + cargo release {{VERSION}} $pkgs $EXTRA {{ARGS}} + } + + for subdir in $SUBDIRS; do + if [[ "$subdir" == "kona" ]]; then + run_release "rust/kona/ batch 1/2" $KONA_BATCH_1 + run_release "rust/kona/ batch 2/2" $KONA_BATCH_2 + else + PKGS_LIST=$(echo "$METADATA" \ + | jq -r --arg dir "/rust/$subdir/" \ + '.packages[] | select(.manifest_path | contains($dir)) | .name' \ + | sort -u) + [[ -z "$PKGS_LIST" ]] && continue + run_release "rust/$subdir/" $PKGS_LIST + fi + done + ######################### Documentation ################################ DOCS_DIR := justfile_directory() / "docs" @@ -180,8 +311,55 @@ docs-preview: ######################### Kona Prestates ############################## KONA_DIR := justfile_directory() / "kona" +MIPS64_TARGET_SPEC := justfile_directory() / "kona/docker/cannon/mips64-unknown-none.json" + +# Build kona-client for the MIPS64 cannon target +build-kona-client-elf VARIANT: + #!/usr/bin/env bash + set -euo pipefail + + # Ensure nightly toolchain with rust-src is installed + just install-nightly + + # Cross-compilation environment + export CC_mips64_unknown_none=mips64-linux-gnuabi64-gcc + export CXX_mips64_unknown_none=mips64-linux-gnuabi64-g++ + export CARGO_TARGET_MIPS64_UNKNOWN_NONE_LINKER=mips64-linux-gnuabi64-gcc + export RUSTFLAGS="-Clink-arg=-e_start -Cllvm-args=-mno-check-zero-division" + export CARGO_BUILD_TARGET="{{MIPS64_TARGET_SPEC}}" + export RUSTUP_TOOLCHAIN="{{NIGHTLY}}" + + # Custom configs support + if [[ -n "${KONA_CUSTOM_CONFIGS_DIR:-}" ]]; then + export KONA_CUSTOM_CONFIGS=true + fi + + echo "Building kona-client ELF (variant: {{VARIANT}})..." + cargo build \ + -Zbuild-std=core,alloc \ + -Zjson-target-spec \ + -p kona-client \ + --bin {{VARIANT}} \ + --locked \ + --profile release-client-lto + +# Lint kona-std-fpvm for the MIPS64 cannon target +lint-kona-cannon: + #!/usr/bin/env bash + set -euo pipefail + + just install-nightly + + export CC_mips64_unknown_none=mips64-linux-gnuabi64-gcc + export CXX_mips64_unknown_none=mips64-linux-gnuabi64-g++ + export CARGO_TARGET_MIPS64_UNKNOWN_NONE_LINKER=mips64-linux-gnuabi64-gcc + export RUSTFLAGS="-Clink-arg=-e_start -Cllvm-args=-mno-check-zero-division" + export CARGO_BUILD_TARGET="{{MIPS64_TARGET_SPEC}}" + export RUSTUP_TOOLCHAIN="{{NIGHTLY}}" -# Build all kona prestates + cargo clippy -p kona-std-fpvm --all-features -Zbuild-std=core,alloc -Zjson-target-spec -- -D warnings + +# Build all kona prestates (runs natively — use build-kona-reproducible-prestate for Docker) build-kona-prestates: build-kona-cannon-prestate build-kona-interop-prestate build-kona-cannon-prestate: @@ -190,23 +368,96 @@ build-kona-cannon-prestate: build-kona-interop-prestate: @just build-kona-prestate kona-client-int prestate-artifacts-cannon-interop +# Build a single kona prestate variant build-kona-prestate VARIANT OUTPUT_DIR: #!/usr/bin/env bash set -euo pipefail - echo "Building prestate for {{VARIANT}}..." - cd "{{KONA_DIR}}/docker/fpvm-prestates" - just cannon {{VARIANT}} "{{KONA_DIR}}/{{OUTPUT_DIR}}" - - cd "{{KONA_DIR}}" + OUTPUT="{{KONA_DIR}}/{{OUTPUT_DIR}}" + mkdir -p "$OUTPUT" + + echo "=== Building cannon ===" + # cannon/justfile imports ../justfiles/go.just which imports git.just. + # These relative imports resolve from cannon/'s directory, so we cd there + # and call just directly — NOT via rust/justfile delegation. + cd "{{justfile_directory()}}/../cannon" + just cannon + CANNON_BIN="$(pwd)/bin/cannon" + + echo "=== Building kona-client ELF (variant: {{VARIANT}}) ===" + cd "{{justfile_directory()}}" + just build-kona-client-elf {{VARIANT}} + + # Locate the built ELF + ELF="{{justfile_directory()}}/target/mips64-unknown-none/release-client-lto/{{VARIANT}}" + + echo "=== Generating prestate ===" + "$CANNON_BIN" load-elf \ + --path="$ELF" \ + --out="$OUTPUT/prestate.bin.gz" \ + --meta="$OUTPUT/meta.json" \ + --type multithreaded64-5 + + "$CANNON_BIN" run \ + --proof-at "=0" \ + --stop-at "=1" \ + --input "$OUTPUT/prestate.bin.gz" \ + --meta "$OUTPUT/meta.json" \ + --proof-fmt "$OUTPUT/%d.json" \ + --output "" + + mv "$OUTPUT/0.json" "$OUTPUT/prestate-proof.json" # Copy with hash-based name for challenger lookup - HASH=$(jq -r .pre "{{OUTPUT_DIR}}/prestate-proof.json") - cp "{{OUTPUT_DIR}}/prestate.bin.gz" "{{OUTPUT_DIR}}/${HASH}.bin.gz" + HASH=$(jq -r .pre "$OUTPUT/prestate-proof.json") + cp "$OUTPUT/prestate.bin.gz" "$OUTPUT/${HASH}.bin.gz" + echo "Prestate for {{VARIANT}}: ${HASH}" + +# Build a single reproducible kona prestate variant via Docker. +# Cannon is built from source as a stage within the Dockerfile. +# Build context is the monorepo root (same pattern as op-program). +# Set KONA_CUSTOM_CONFIGS_DIR to bake custom chain configs into the prestate. +build-kona-reproducible-prestate-variant VARIANT OUTPUT_DIR: + #!/usr/bin/env bash + set -euo pipefail + + MONOREPO_ROOT="{{justfile_directory()}}/.." + OUTPUT="{{KONA_DIR}}/{{OUTPUT_DIR}}" + + # The Dockerfile always copies from the `kona-custom-configs` named build + # context, so point it at an empty temp dir when no configs are requested. + if [[ -n "${KONA_CUSTOM_CONFIGS_DIR:-}" ]]; then + if [[ ! -d "${KONA_CUSTOM_CONFIGS_DIR}" ]]; then + echo "KONA_CUSTOM_CONFIGS_DIR=${KONA_CUSTOM_CONFIGS_DIR} is not a directory" >&2 + exit 1 + fi + CUSTOM_CONFIGS_CONTEXT="${KONA_CUSTOM_CONFIGS_DIR}" + CUSTOM_CONFIGS_FLAG=true + else + CUSTOM_CONFIGS_CONTEXT=$(mktemp -d) + trap 'rm -rf "${CUSTOM_CONFIGS_CONTEXT}"' EXIT + CUSTOM_CONFIGS_FLAG=false + fi + + docker build \ + --platform linux/amd64 \ + --build-arg VARIANT={{VARIANT}} \ + --build-arg KONA_CUSTOM_CONFIGS="${CUSTOM_CONFIGS_FLAG}" \ + --build-context kona-custom-configs="${CUSTOM_CONFIGS_CONTEXT}" \ + --output "$OUTPUT" \ + --progress plain \ + -f "{{KONA_DIR}}/docker/fpvm-prestates/cannon-repro.dockerfile" \ + "$MONOREPO_ROOT" + + # Add hash-named copy for challenger lookup + HASH=$(jq -r .pre "$OUTPUT/prestate-proof.json") + cp "$OUTPUT/prestate.bin.gz" "$OUTPUT/${HASH}.bin.gz" echo "Prestate for {{VARIANT}}: ${HASH}" +# Build all reproducible kona prestates via Docker build-kona-reproducible-prestate: - @just build-kona-prestates + @just build-kona-reproducible-prestate-variant kona-client prestate-artifacts-cannon + @just build-kona-reproducible-prestate-variant kona-client-int prestate-artifacts-cannon-interop output-kona-prestate-hash: @echo "-------------------- Kona Prestates --------------------" diff --git a/rust/kona/README.md b/rust/kona/README.md index f3c86b4be84..d5719f1198b 100644 --- a/rust/kona/README.md +++ b/rust/kona/README.md @@ -55,7 +55,6 @@ getting started with building your own programs, and a reference for the librari - [`driver`](./crates/proof/driver): Stateful derivation pipeline driver. - [`interop`](./crates/protocol/interop): Core functionality and primitives for the [Interop feature](https://specs.optimism.io/interop/overview.html) of the OP Stack. - [`registry`](./crates/protocol/registry): Rust bindings for the [superchain-registry][superchain-registry]. -- [`comp`](./crates/batcher/comp): Compression types for the OP Stack. - [`hardforks`](./crates/protocol/hardforks): Consensus layer hardfork types for the OP Stack including network upgrade transactions. **Proof** diff --git a/rust/kona/bin/client/justfile b/rust/kona/bin/client/justfile index 3aaf2b3c45b..64b7b20f420 100644 --- a/rust/kona/bin/client/justfile +++ b/rust/kona/bin/client/justfile @@ -81,6 +81,92 @@ run-client-native-offline block_number l2_claim l2_output_root l2_head l1_head l --data-dir ./data \ {{verbosity}} +# Regenerate the preimage witness tar used by the offline host+client tests +# (see `.circleci/continue/rust-ci.yml` -> kona-host-client-offline). +# Runs the host in native mode against real RPCs to collect preimages, then +# packages `rust/kona/data` into a zstd-compressed tar. Outputs the exact +# boundary hashes so CI configs can be updated if regenerating for a new block. +# Arguments mirror run-client-native; output_tar defaults to +# rust/kona/bin/client/testdata/witness--.tar.zst +generate-witness-tar block_number l1_rpc l1_beacon_rpc l2_rpc rollup_node_rpc output_tar='' rollup_config_path='': + #!/usr/bin/env bash + set -o errexit -o nounset -o pipefail + + L1_NODE_ADDRESS="{{l1_rpc}}" + L1_BEACON_ADDRESS="{{l1_beacon_rpc}}" + L2_NODE_ADDRESS="{{l2_rpc}}" + OP_NODE_ADDRESS="{{rollup_node_rpc}}" + + L2_CHAIN_ID=$(cast chain-id --rpc-url $L2_NODE_ADDRESS) + if [ -z "{{rollup_config_path}}" ]; then + CHAIN_ID_OR_ROLLUP_CONFIG_ARG="--l2-chain-id $L2_CHAIN_ID" + else + CHAIN_ID_OR_ROLLUP_CONFIG_ARG="--rollup-config-path $(realpath {{rollup_config_path}})" + fi + + CLAIMED_L2_BLOCK_NUMBER={{block_number}} + echo "Resolving boundary state for block #$CLAIMED_L2_BLOCK_NUMBER on chain $L2_CHAIN_ID..." + + CLAIMED_L2_OUTPUT_ROOT=$(cast rpc --rpc-url $OP_NODE_ADDRESS "optimism_outputAtBlock" $(cast 2h $CLAIMED_L2_BLOCK_NUMBER) | jq -r .outputRoot) + AGREED_L2_OUTPUT_ROOT=$(cast rpc --rpc-url $OP_NODE_ADDRESS "optimism_outputAtBlock" $(cast 2h $((CLAIMED_L2_BLOCK_NUMBER - 1))) | jq -r .outputRoot) + AGREED_L2_HEAD_HASH=$(cast block --rpc-url $L2_NODE_ADDRESS $((CLAIMED_L2_BLOCK_NUMBER - 1)) --json | jq -r .hash) + L1_ORIGIN_NUM=$(cast rpc --rpc-url $OP_NODE_ADDRESS "optimism_outputAtBlock" $(cast 2h $((CLAIMED_L2_BLOCK_NUMBER - 1))) | jq -r .blockRef.l1origin.number) + L1_HEAD=$(cast block --rpc-url $L1_NODE_ADDRESS $((L1_ORIGIN_NUM + 30)) --json | jq -r .hash) + + OUTPUT_TAR="{{output_tar}}" + if [ -z "$OUTPUT_TAR" ]; then + OUTPUT_TAR="{{KONA_CLIENT_ROOT}}/testdata/witness-${L2_CHAIN_ID}-${CLAIMED_L2_BLOCK_NUMBER}.tar.zst" + fi + mkdir -p "$(dirname "$OUTPUT_TAR")" + + cd {{KONA_CLIENT_ROOT}}/../.. + + echo "Gathering preimages via native host..." + echo " L1_HEAD=$L1_HEAD" + echo " L2_HEAD=$AGREED_L2_HEAD_HASH" + echo " L2_OUTPUT_ROOT=$AGREED_L2_OUTPUT_ROOT" + echo " L2_CLAIM=$CLAIMED_L2_OUTPUT_ROOT" + + rm -rf ./data + cargo r --bin kona-host --release -- \ + single \ + --l1-head $L1_HEAD \ + --agreed-l2-head-hash $AGREED_L2_HEAD_HASH \ + --claimed-l2-output-root $CLAIMED_L2_OUTPUT_ROOT \ + --agreed-l2-output-root $AGREED_L2_OUTPUT_ROOT \ + --claimed-l2-block-number $CLAIMED_L2_BLOCK_NUMBER \ + --l1-node-address $L1_NODE_ADDRESS \ + --l1-beacon-address $L1_BEACON_ADDRESS \ + --l2-node-address $L2_NODE_ADDRESS \ + --native \ + --data-dir ./data \ + $CHAIN_ID_OR_ROLLUP_CONFIG_ARG + + echo "Packing preimages into $OUTPUT_TAR..." + tar --zstd -cf "$OUTPUT_TAR" data + + echo "" + echo "Done. Witness tar:" + ls -l "$OUTPUT_TAR" + echo "" + echo "Offline test parameters (copy into .circleci/continue/rust-ci.yml if regenerating for a new block):" + echo " BLOCK_NUMBER=$CLAIMED_L2_BLOCK_NUMBER" + echo " L2_CLAIM=$CLAIMED_L2_OUTPUT_ROOT" + echo " L2_OUTPUT_ROOT=$AGREED_L2_OUTPUT_ROOT" + echo " L2_HEAD=$AGREED_L2_HEAD_HASH" + echo " L1_HEAD=$L1_HEAD" + echo " L2_CHAIN_ID=$L2_CHAIN_ID" + echo "" + echo "Upload the new tar to chain-test-data (tag format: kona-YYYY-MM-DD):" + echo " TAG=kona-$(date +%Y-%m-%d)" + echo " gh release create \"\$TAG\" --repo ethereum-optimism/chain-test-data \\" + echo " --title \"kona offline witness \$TAG\" \\" + echo " --notes \"op-sepolia block $CLAIMED_L2_BLOCK_NUMBER\" \\" + echo " \"$OUTPUT_TAR\"" + echo "" + echo "Then bump the witness_tar / witness_tar_url defaults on" + echo "run-client-cannon-offline in this justfile to match." + # Run the client program on cannon with the host in detached server mode. run-client-cannon block_number l1_rpc l1_beacon_rpc l2_rpc rollup_node_rpc rollup_config_path='' verbosity='': #!/usr/bin/env bash @@ -147,8 +233,23 @@ run-client-cannon block_number l1_rpc l1_beacon_rpc l2_rpc rollup_node_rpc rollu --data-dir ./data \ {{verbosity}} +# Fetch the offline witness tar from chain-test-data and decompress it into +# rust/kona/data. Etag caching in `scripts/fetch-witness-tar.sh` keeps repeat +# invocations cheap. +prepare-witness-data witness_tar witness_tar_url: + #!/usr/bin/env bash + set -o errexit -o nounset -o pipefail + {{KONA_CLIENT_ROOT}}/scripts/fetch-witness-tar.sh "{{witness_tar}}" "{{witness_tar_url}}" + cd {{KONA_CLIENT_ROOT}}/../.. + tar --zstd -xf ./bin/client/testdata/{{witness_tar}} -C . + # Run the client program on cannon with the host program detached, in offline mode. -run-client-cannon-offline block_number l2_claim l2_output_root l2_head l1_head l2_chain_id verbosity='': +# +# The witness preimages are fetched from the chain-test-data release +# `kona-YYYY-MM-DD` by the `prepare-witness-data` dependency. When regenerating +# for a new block, upload a new release (see `generate-witness-tar`) and bump +# the `witness_tar` / `witness_tar_url` defaults. +run-client-cannon-offline block_number l2_claim l2_output_root l2_head l1_head l2_chain_id witness_tar='op-sepolia-42427365-witness.tar.zst' witness_tar_url='https://github.com/ethereum-optimism/chain-test-data/releases/download/kona-2026-04-21' verbosity='': (prepare-witness-data witness_tar witness_tar_url) #!/usr/bin/env bash HOST_BIN_PATH="{{KONA_CLIENT_ROOT}}/../../../target/debug/kona-host" diff --git a/rust/kona/bin/client/scripts/fetch-witness-tar.sh b/rust/kona/bin/client/scripts/fetch-witness-tar.sh new file mode 100755 index 00000000000..2bafe93a88e --- /dev/null +++ b/rust/kona/bin/client/scripts/fetch-witness-tar.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Download a preimage witness tar from chain-test-data releases with etag caching. +# Mirrors op-program/scripts/run-compat.sh — skips redownload when the release asset +# is unchanged so local re-runs are fast. +# +# Usage: fetch-witness-tar.sh +# +# Invoked via the `prepare-witness-data` just recipe, which supplies the +# canonical tar name and release URL. The tar is written to +# rust/kona/bin/client/testdata/, alongside an etag sidecar that +# enables conditional GETs on subsequent invocations. +set -o errexit -o nounset -o pipefail + +SCRIPTS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +TESTDATA_DIR="${SCRIPTS_DIR}/../testdata" + +TAR_NAME="${1?Must specify tar name (e.g. op-sepolia-42427365-witness.tar.zst)}" +BASE_URL="${2?Must specify chain-test-data release base URL (e.g. https://github.com/ethereum-optimism/chain-test-data/releases/download/kona-YYYY-MM-DD)}" + +URL="${BASE_URL}/${TAR_NAME}" +TAR_PATH="${TESTDATA_DIR}/${TAR_NAME}" +ETAG_PATH="${TESTDATA_DIR}/${TAR_NAME}.etag" + +mkdir -p "${TESTDATA_DIR}" +curl --etag-save "${ETAG_PATH}" --etag-compare "${ETAG_PATH}" \ + -L --fail -o "${TAR_PATH}" "${URL}" diff --git a/rust/kona/bin/client/src/fpvm_evm/factory.rs b/rust/kona/bin/client/src/fpvm_evm/factory.rs index d12cd27c0f1..c3c2bfcc64e 100644 --- a/rust/kona/bin/client/src/fpvm_evm/factory.rs +++ b/rust/kona/bin/client/src/fpvm_evm/factory.rs @@ -2,13 +2,15 @@ use super::{precompiles::OpFpvmPrecompiles, tx::FpvmOpTx}; use alloy_evm::{Database, EvmEnv, EvmFactory}; -use alloy_op_evm::{OpEvm, OpTxError}; +use alloy_op_evm::{ + OpEvm, OpEvmContext, OpTx, OpTxError, + post_exec::{PostExecEvmFactoryHooks, PostExecExecutedTx, PostExecTxContext}, +}; use kona_preimage::{HintWriterClient, PreimageOracleClient}; -use op_revm::{DefaultOp, OpContext, OpEvm as RevmOpEvm, OpHaltReason, OpSpecId}; +use op_revm::{L1BlockInfo, OpBuilder, OpHaltReason, OpSpecId, OpTransaction}; use revm::{ - Context, Inspector, - context::{BlockEnv, Evm as RevmEvm, FrameStack, result::EVMError}, - handler::instructions::EthInstructions, + Context, Inspector, MainContext, + context::{BlockEnv, CfgEnv, result::EVMError}, inspector::NoOpInspector, }; @@ -42,14 +44,36 @@ where } } +impl PostExecEvmFactoryHooks for FpvmOpEvmFactory +where + H: HintWriterClient + Clone + Send + Sync + 'static, + O: PreimageOracleClient + Clone + Send + Sync + 'static, +{ + fn begin_post_exec_tx(evm: &mut Self::Evm, ctx: PostExecTxContext) + where + DB: Database, + I: Inspector>, + { + evm.begin_post_exec_tx(ctx); + } + + fn take_last_post_exec_tx_result(evm: &mut Self::Evm) -> PostExecExecutedTx + where + DB: Database, + I: Inspector>, + { + evm.take_last_post_exec_tx_result() + } +} + impl EvmFactory for FpvmOpEvmFactory where H: HintWriterClient + Clone + Send + Sync + 'static, O: PreimageOracleClient + Clone + Send + Sync + 'static, { - type Evm>> = + type Evm>> = OpEvm, FpvmOpTx>; - type Context = OpContext; + type Context = OpEvmContext; type Tx = FpvmOpTx; type Error = EVMError; type HaltReason = OpHaltReason; @@ -63,18 +87,19 @@ where input: EvmEnv, ) -> Self::Evm { let spec_id = *input.spec_id(); - let ctx = Context::op().with_db(db).with_block(input.block_env).with_cfg(input.cfg_env); - let revm_evm = RevmOpEvm(RevmEvm { - ctx, - inspector: NoOpInspector {}, - instruction: EthInstructions::new_mainnet(), - precompiles: OpFpvmPrecompiles::new_with_spec( + let revm_evm = Context::mainnet() + .with_tx(OpTx(OpTransaction::builder().build_fill())) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .with_chain(L1BlockInfo::default()) + .with_db(db) + .with_block(input.block_env) + .with_cfg(input.cfg_env) + .build_op_with_inspector(NoOpInspector {}) + .with_precompiles(OpFpvmPrecompiles::new_with_spec( spec_id, self.hint_writer.clone(), self.oracle_reader.clone(), - ), - frame_stack: FrameStack::new(), - }); + )); OpEvm::new(revm_evm, false) } @@ -86,18 +111,19 @@ where inspector: I, ) -> Self::Evm { let spec_id = *input.spec_id(); - let ctx = Context::op().with_db(db).with_block(input.block_env).with_cfg(input.cfg_env); - let revm_evm = RevmOpEvm(RevmEvm { - ctx, - inspector, - instruction: EthInstructions::new_mainnet(), - precompiles: OpFpvmPrecompiles::new_with_spec( + let revm_evm = Context::mainnet() + .with_tx(OpTx(OpTransaction::builder().build_fill())) + .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .with_chain(L1BlockInfo::default()) + .with_db(db) + .with_block(input.block_env) + .with_cfg(input.cfg_env) + .build_op_with_inspector(inspector) + .with_precompiles(OpFpvmPrecompiles::new_with_spec( spec_id, self.hint_writer.clone(), self.oracle_reader.clone(), - ), - frame_stack: FrameStack::new(), - }); + )); OpEvm::new(revm_evm, true) } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g1_add.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g1_add.rs index 0db40ef09ae..2eee2848ea4 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g1_add.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g1_add.rs @@ -10,7 +10,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use revm::precompile::{ - PrecompileError, PrecompileOutput, PrecompileResult, bls12_381, + EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, bls12_381, bls12_381_const::{G1_ADD_BASE_GAS_FEE, G1_ADD_INPUT_LENGTH}, }; @@ -23,18 +23,21 @@ pub(crate) fn fpvm_bls12_g1_add( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { + /// Oracle/L1 staticcall gas required by [EIP-7904](https://eips.ethereum.org/EIPS/eip-7904). + const G1_ADD_ORACLE_GAS: u64 = 643; + if G1_ADD_BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } let input_len = input.len(); if input_len != G1_ADD_INPUT_LENGTH { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!( "G1 addition input length should be {G1_ADD_INPUT_LENGTH} bytes, was {input_len}" ) @@ -47,11 +50,11 @@ where let result_data = kona_proof::block_on(precompile_run! { hint_writer, oracle_reader, - &[precompile.address().as_slice(), &G1_ADD_BASE_GAS_FEE.to_be_bytes(), input] + &[precompile.address().as_slice(), &G1_ADD_ORACLE_GAS.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(G1_ADD_BASE_GAS_FEE, result_data.into())) + Ok(EthPrecompileOutput::new(G1_ADD_BASE_GAS_FEE, result_data.into())) } #[cfg(test)] @@ -59,6 +62,7 @@ mod test { use super::*; use crate::fpvm_evm::precompiles::test_utils::{ execute_native_precompile, test_accelerated_precompile, + test_accelerated_precompile_capture_hint, }; #[tokio::test(flavor = "multi_thread")] @@ -86,7 +90,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_g1_add(&[], u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -96,8 +100,29 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_g1_add(&[], 0, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_bls12_381_g1_add_oracle_gas_carries_l1_cost() { + let recorded_gas_used: std::sync::Arc>> = + std::sync::Arc::new(std::sync::Mutex::new(None)); + let recorded_gas_used_in = recorded_gas_used.clone(); + + let captured = + test_accelerated_precompile_capture_hint(move |hint_writer, oracle_reader| { + let input = [0u8; G1_ADD_INPUT_LENGTH]; + let result = + fpvm_bls12_g1_add(&input, u64::MAX, hint_writer, oracle_reader).unwrap(); + *recorded_gas_used_in.lock().unwrap() = Some(result.gas_used); + }) + .await; + + // Oracle hint must carry the current L1 G1Add cost (EIP-7904: 643). + assert_eq!(captured.oracle_gas(), 643); + // L2 charge must remain unchanged (G1_ADD_BASE_GAS_FEE = 375). + assert_eq!(recorded_gas_used.lock().unwrap().unwrap(), G1_ADD_BASE_GAS_FEE); + } } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g1_msm.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g1_msm.rs index cf118851e08..19fb8443805 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g1_msm.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g1_msm.rs @@ -10,7 +10,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use revm::precompile::{ - PrecompileError, PrecompileOutput, PrecompileResult, bls12_381, + EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, bls12_381, bls12_381_const::{DISCOUNT_TABLE_G1_MSM, G1_MSM_BASE_GAS_FEE, G1_MSM_INPUT_LENGTH}, bls12_381_utils::msm_required_gas, }; @@ -29,13 +29,13 @@ pub(crate) fn fpvm_bls12_g1_msm( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { if input.len() > BLS12_MAX_G1_MSM_SIZE_ISTHMUS { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!("G1MSM input length must be at most {BLS12_MAX_G1_MSM_SIZE_ISTHMUS}") .into(), )); @@ -43,7 +43,7 @@ where let input_len = input.len(); if input_len == 0 || !input_len.is_multiple_of(G1_MSM_INPUT_LENGTH) { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!( "G1MSM input length should be multiple of {G1_MSM_INPUT_LENGTH}, was {input_len}" ) @@ -54,7 +54,7 @@ where let k = input_len / G1_MSM_INPUT_LENGTH; let required_gas = msm_required_gas(k, &DISCOUNT_TABLE_G1_MSM, G1_MSM_BASE_GAS_FEE); if required_gas > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } let precompile = bls12_381::g1_msm::PRECOMPILE; @@ -64,9 +64,9 @@ where oracle_reader, &[precompile.address().as_slice(), &required_gas.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(required_gas, result_data.into())) + Ok(EthPrecompileOutput::new(required_gas, result_data.into())) } /// Performs an FPVM-accelerated `bls12` g1 msm check precompile call after the Jovian Hardfork. @@ -75,13 +75,13 @@ pub(crate) fn fpvm_bls12_g1_msm_jovian( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { if input.len() > BLS12_MAX_G1_MSM_SIZE_JOVIAN { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!("G1MSM input length must be at most {BLS12_MAX_G1_MSM_SIZE_JOVIAN}") .into(), )); @@ -136,7 +136,7 @@ mod test { oracle_reader, ) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -146,7 +146,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_g1_msm(&[], u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -161,7 +161,7 @@ mod test { oracle_reader, ) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -172,7 +172,7 @@ mod test { let accelerated_result = fpvm_bls12_g1_msm(&[0u8; G1_MSM_INPUT_LENGTH], 0, hint_writer, oracle_reader) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } @@ -202,7 +202,7 @@ mod test { let input = [0u8; INPUT_SIZE]; let accelerated_result = fpvm_bls12_g1_msm_jovian(&input, u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g2_add.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g2_add.rs index be23342861a..5be10421cbc 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g2_add.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g2_add.rs @@ -10,7 +10,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use revm::precompile::{ - PrecompileError, PrecompileOutput, PrecompileResult, bls12_381, + EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, bls12_381, bls12_381_const::{G2_ADD_BASE_GAS_FEE, G2_ADD_INPUT_LENGTH}, }; @@ -23,18 +23,21 @@ pub(crate) fn fpvm_bls12_g2_add( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { + /// Oracle/L1 staticcall gas required by [EIP-7904](https://eips.ethereum.org/EIPS/eip-7904). + const G2_ADD_ORACLE_GAS: u64 = 765; + if G2_ADD_BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } let input_len = input.len(); if input_len != G2_ADD_INPUT_LENGTH { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!( "G2 addition input length should be {G2_ADD_INPUT_LENGTH} bytes, was {input_len}" ) @@ -47,11 +50,11 @@ where let result_data = kona_proof::block_on(precompile_run! { hint_writer, oracle_reader, - &[precompile.address().as_slice(), &G2_ADD_BASE_GAS_FEE.to_be_bytes(), input] + &[precompile.address().as_slice(), &G2_ADD_ORACLE_GAS.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(G2_ADD_BASE_GAS_FEE, result_data.into())) + Ok(EthPrecompileOutput::new(G2_ADD_BASE_GAS_FEE, result_data.into())) } #[cfg(test)] @@ -59,6 +62,7 @@ mod test { use super::*; use crate::fpvm_evm::precompiles::test_utils::{ execute_native_precompile, test_accelerated_precompile, + test_accelerated_precompile_capture_hint, }; #[tokio::test(flavor = "multi_thread")] @@ -86,7 +90,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_g2_add(&[], u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -96,8 +100,29 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_g2_add(&[], 0, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_bls12_381_g2_add_oracle_gas_carries_l1_cost() { + let recorded_gas_used: std::sync::Arc>> = + std::sync::Arc::new(std::sync::Mutex::new(None)); + let recorded_gas_used_in = recorded_gas_used.clone(); + + let captured = + test_accelerated_precompile_capture_hint(move |hint_writer, oracle_reader| { + let input = [0u8; G2_ADD_INPUT_LENGTH]; + let result = + fpvm_bls12_g2_add(&input, u64::MAX, hint_writer, oracle_reader).unwrap(); + *recorded_gas_used_in.lock().unwrap() = Some(result.gas_used); + }) + .await; + + // Oracle hint must carry the current L1 G2Add cost (EIP-7904: 765). + assert_eq!(captured.oracle_gas(), 765); + // L2 charge must remain unchanged (G2_ADD_BASE_GAS_FEE = 600). + assert_eq!(recorded_gas_used.lock().unwrap().unwrap(), G2_ADD_BASE_GAS_FEE); + } } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g2_msm.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g2_msm.rs index f19d2c15335..bde67f24abe 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g2_msm.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_g2_msm.rs @@ -10,7 +10,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use revm::precompile::{ - PrecompileError, PrecompileOutput, PrecompileResult, bls12_381, + EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, bls12_381, bls12_381_const::{DISCOUNT_TABLE_G2_MSM, G2_MSM_BASE_GAS_FEE, G2_MSM_INPUT_LENGTH}, bls12_381_utils::msm_required_gas, }; @@ -29,7 +29,7 @@ pub(crate) fn fpvm_bls12_g2_msm( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, @@ -37,14 +37,14 @@ where let input_len = input.len(); if input_len > BLS12_MAX_G2_MSM_SIZE_ISTHMUS { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!("G2MSM input length must be at most {BLS12_MAX_G2_MSM_SIZE_ISTHMUS}") .into(), )); } if input_len == 0 || !input_len.is_multiple_of(G2_MSM_INPUT_LENGTH) { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!( "G2MSM input length should be multiple of {G2_MSM_INPUT_LENGTH}, was {input_len}" ) @@ -55,7 +55,7 @@ where let k = input_len / G2_MSM_INPUT_LENGTH; let required_gas = msm_required_gas(k, &DISCOUNT_TABLE_G2_MSM, G2_MSM_BASE_GAS_FEE); if required_gas > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } let precompile = bls12_381::g2_msm::PRECOMPILE; @@ -65,9 +65,9 @@ where oracle_reader, &[precompile.address().as_slice(), &required_gas.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(required_gas, result_data.into())) + Ok(EthPrecompileOutput::new(required_gas, result_data.into())) } /// Performs an FPVM-accelerated BLS12-381 G2 msm check after the Jovian Hardfork. @@ -76,13 +76,13 @@ pub(crate) fn fpvm_bls12_g2_msm_jovian( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { if input.len() > BLS12_MAX_G2_MSM_SIZE_JOVIAN { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!("G2MSM input length must be at most {BLS12_MAX_G2_MSM_SIZE_JOVIAN}") .into(), )); @@ -137,7 +137,7 @@ mod test { oracle_reader, ) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -147,7 +147,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_g2_msm(&[], u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -158,7 +158,7 @@ mod test { let accelerated_result = fpvm_bls12_g2_msm(&[0u8; G2_MSM_INPUT_LENGTH], 0, hint_writer, oracle_reader) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } @@ -188,7 +188,7 @@ mod test { let input = [0u8; INPUT_SIZE]; let accelerated_result = fpvm_bls12_g2_msm_jovian(&input, u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_map_fp.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_map_fp.rs index 935462123b8..936f8048425 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_map_fp.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_map_fp.rs @@ -10,7 +10,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use revm::precompile::{ - PrecompileError, PrecompileOutput, PrecompileResult, bls12_381, + EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, bls12_381, bls12_381_const::{MAP_FP_TO_G1_BASE_GAS_FEE, PADDED_FP_LENGTH}, }; @@ -23,17 +23,17 @@ pub(crate) fn fpvm_bls12_map_fp( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { if MAP_FP_TO_G1_BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } if input.len() != PADDED_FP_LENGTH { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!( "MAP_FP_TO_G1 input should be {PADDED_FP_LENGTH} bytes, was {}", input.len() @@ -49,9 +49,9 @@ where oracle_reader, &[precompile.address().as_slice(), &MAP_FP_TO_G1_BASE_GAS_FEE.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(MAP_FP_TO_G1_BASE_GAS_FEE, result_data.into())) + Ok(EthPrecompileOutput::new(MAP_FP_TO_G1_BASE_GAS_FEE, result_data.into())) } #[cfg(test)] @@ -85,7 +85,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_map_fp(&[], 0, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } @@ -95,7 +95,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_map_fp(&[], u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_map_fp2.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_map_fp2.rs index 44d111be66e..6a000e7fbb4 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_map_fp2.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_map_fp2.rs @@ -10,7 +10,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use revm::precompile::{ - PrecompileError, PrecompileOutput, PrecompileResult, bls12_381, + EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, bls12_381, bls12_381_const::{MAP_FP2_TO_G2_BASE_GAS_FEE, PADDED_FP2_LENGTH}, }; @@ -23,17 +23,17 @@ pub(crate) fn fpvm_bls12_map_fp2( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { if MAP_FP2_TO_G2_BASE_GAS_FEE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } if input.len() != PADDED_FP2_LENGTH { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!( "MAP_FP2_TO_G2 input should be {PADDED_FP2_LENGTH} bytes, was {}", input.len() @@ -49,9 +49,9 @@ where oracle_reader, &[precompile.address().as_slice(), &MAP_FP2_TO_G2_BASE_GAS_FEE.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(MAP_FP2_TO_G2_BASE_GAS_FEE, result_data.into())) + Ok(EthPrecompileOutput::new(MAP_FP2_TO_G2_BASE_GAS_FEE, result_data.into())) } #[cfg(test)] @@ -85,7 +85,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_map_fp2(&[], 0, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } @@ -95,7 +95,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_bls12_map_fp2(&[], u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_pair.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_pair.rs index 3536ff78497..ba6cb1804f5 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_pair.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/bls12_pair.rs @@ -10,7 +10,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use revm::precompile::{ - PrecompileError, PrecompileOutput, PrecompileResult, bls12_381, + EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, bls12_381, bls12_381_const::{PAIRING_INPUT_LENGTH, PAIRING_MULTIPLIER_BASE, PAIRING_OFFSET_BASE}, }; @@ -26,7 +26,7 @@ pub(crate) fn fpvm_bls12_pairing( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, @@ -34,14 +34,14 @@ where let input_len = input.len(); if input_len > BLS12_MAX_PAIRING_SIZE_ISTHMUS { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!("Pairing input length must be at most {BLS12_MAX_PAIRING_SIZE_ISTHMUS}") .into(), )); } if !input_len.is_multiple_of(PAIRING_INPUT_LENGTH) { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!( "Pairing input length should be multiple of {PAIRING_INPUT_LENGTH}, was {input_len}" ) @@ -52,7 +52,7 @@ where let k = input_len / PAIRING_INPUT_LENGTH; let required_gas: u64 = PAIRING_MULTIPLIER_BASE * k as u64 + PAIRING_OFFSET_BASE; if required_gas > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } let precompile = bls12_381::pairing::PRECOMPILE; @@ -62,9 +62,9 @@ where oracle_reader, &[precompile.address().as_slice(), &required_gas.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(required_gas, result_data.into())) + Ok(EthPrecompileOutput::new(required_gas, result_data.into())) } /// Performs an FPVM-accelerated BLS12-381 pairing check after the Jovian Hardfork. @@ -73,13 +73,13 @@ pub(crate) fn fpvm_bls12_pairing_jovian( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { if input.len() > BLS12_MAX_PAIRING_SIZE_JOVIAN { - return Err(PrecompileError::Other( + return Err(PrecompileHalt::Other( alloc::format!("Pairing input length must be at most {BLS12_MAX_PAIRING_SIZE_JOVIAN}") .into(), )); @@ -132,7 +132,7 @@ mod test { oracle_reader, ) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -143,7 +143,7 @@ mod test { let accelerated_result = fpvm_bls12_pairing(&[0u8; PAIRING_INPUT_LENGTH - 1], 0, hint_writer, oracle_reader) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } @@ -154,7 +154,7 @@ mod test { let accelerated_result = fpvm_bls12_pairing(&[0u8; PAIRING_INPUT_LENGTH], 0, hint_writer, oracle_reader) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } @@ -185,7 +185,7 @@ mod test { let accelerated_result = fpvm_bls12_pairing_jovian(&input, u64::MAX, hint_writer, oracle_reader) .unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Other(_))); + assert!(matches!(accelerated_result, PrecompileHalt::Other(_))); }) .await; } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/bn128_pair.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/bn128_pair.rs index aba64db74bd..88a909412a1 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/bn128_pair.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/bn128_pair.rs @@ -4,7 +4,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use revm::precompile::{ - PrecompileError, PrecompileOutput, PrecompileResult, + EthPrecompileOutput, EthPrecompileResult, PrecompileHalt, bn254::{ PAIR_ELEMENT_LEN, pair::{self, ISTANBUL_PAIR_BASE, ISTANBUL_PAIR_PER_POINT}, @@ -13,6 +13,11 @@ use revm::precompile::{ const BN256_MAX_PAIRING_SIZE_GRANITE: usize = 112_687; const BN256_MAX_PAIRING_SIZE_JOVIAN: usize = 81_984; +const BN256_MAX_PAIRING_SIZE_KARST: usize = 57_600; + +/// Per-pair oracle/L1 gas required by [EIP-7904](https://eips.ethereum.org/EIPS/eip-7904). +/// `ISTANBUL_PAIR_BASE` (45_000) is unchanged; only the per-pair rate differs (34_000 → 34_103). +const GLAMSTERDAM_PAIR_PER_POINT: u64 = 34_103; /// Runs the FPVM-accelerated `ecpairing` precompile call. pub(crate) fn fpvm_bn128_pair( @@ -20,20 +25,21 @@ pub(crate) fn fpvm_bn128_pair( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { - let gas_used = - (input.len() / PAIR_ELEMENT_LEN) as u64 * ISTANBUL_PAIR_PER_POINT + ISTANBUL_PAIR_BASE; + let pairs = (input.len() / PAIR_ELEMENT_LEN) as u64; + let gas_used = pairs * ISTANBUL_PAIR_PER_POINT + ISTANBUL_PAIR_BASE; + let oracle_gas = pairs * GLAMSTERDAM_PAIR_PER_POINT + ISTANBUL_PAIR_BASE; if gas_used > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } if !input.len().is_multiple_of(PAIR_ELEMENT_LEN) { - return Err(PrecompileError::Bn254PairLength); + return Err(PrecompileHalt::Bn254PairLength); } let precompile = pair::ISTANBUL; @@ -41,11 +47,11 @@ where let result_data = kona_proof::block_on(precompile_run! { hint_writer, oracle_reader, - &[precompile.address().as_slice(), &gas_used.to_be_bytes(), input] + &[precompile.address().as_slice(), &oracle_gas.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(gas_used, result_data.into())) + Ok(EthPrecompileOutput::new(gas_used, result_data.into())) } /// Runs the FPVM-accelerated `ecpairing` precompile call, with the input size limited by the @@ -55,13 +61,13 @@ pub(crate) fn fpvm_bn128_pair_granite( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { if input.len() > BN256_MAX_PAIRING_SIZE_GRANITE { - return Err(PrecompileError::Bn254PairLength); + return Err(PrecompileHalt::Bn254PairLength); } fpvm_bn128_pair(input, gas_limit, hint_writer, oracle_reader) @@ -74,13 +80,32 @@ pub(crate) fn fpvm_bn128_pair_jovian( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { if input.len() > BN256_MAX_PAIRING_SIZE_JOVIAN { - return Err(PrecompileError::Bn254PairLength); + return Err(PrecompileHalt::Bn254PairLength); + } + + fpvm_bn128_pair(input, gas_limit, hint_writer, oracle_reader) +} + +/// Runs the FPVM-accelerated `ecpairing` precompile call, with the input size limited by the +/// Karst hardfork. +pub(crate) fn fpvm_bn128_pair_karst( + input: &[u8], + gas_limit: u64, + hint_writer: &H, + oracle_reader: &O, +) -> EthPrecompileResult +where + H: HintWriterClient + Send + Sync, + O: PreimageOracleClient + Send + Sync, +{ + if input.len() > BN256_MAX_PAIRING_SIZE_KARST { + return Err(PrecompileHalt::Bn254PairLength); } fpvm_bn128_pair(input, gas_limit, hint_writer, oracle_reader) @@ -91,6 +116,7 @@ mod test { use super::*; use crate::fpvm_evm::precompiles::test_utils::{ execute_native_precompile, test_accelerated_precompile, + test_accelerated_precompile_capture_hint, }; use alloy_primitives::hex; @@ -140,7 +166,7 @@ mod test { let accelerated_result = fpvm_bn128_pair(&input, 0, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } @@ -152,7 +178,7 @@ mod test { let accelerated_result = fpvm_bn128_pair(&input, u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Bn254PairLength)); + assert!(matches!(accelerated_result, PrecompileHalt::Bn254PairLength)); }) .await; } @@ -168,7 +194,7 @@ mod test { let accelerated_result = fpvm_bn128_pair_granite(&input, u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Bn254PairLength)); + assert!(matches!(accelerated_result, PrecompileHalt::Bn254PairLength)); }) .await; } @@ -200,8 +226,131 @@ mod test { let accelerated_result = fpvm_bn128_pair_jovian(&input, u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::Bn254PairLength)); + assert!(matches!(accelerated_result, PrecompileHalt::Bn254PairLength)); }) .await; } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_bn128_pairing_karst() { + test_accelerated_precompile(|hint_writer, oracle_reader| { + let granite_result = + fpvm_bn128_pair_granite(TEST_INPUT.as_ref(), u64::MAX, hint_writer, oracle_reader) + .unwrap(); + let karst_result = + fpvm_bn128_pair_karst(TEST_INPUT.as_ref(), u64::MAX, hint_writer, oracle_reader) + .unwrap(); + + assert_eq!(granite_result.bytes, karst_result.bytes); + assert_eq!(granite_result.gas_used, karst_result.gas_used); + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_bn128_pairing_bad_input_len_karst() { + test_accelerated_precompile(|hint_writer, oracle_reader| { + // Calculate the next aligned size (multiple of PAIR_ELEMENT_LEN) that exceeds + // BN256_MAX_PAIRING_SIZE_KARST + const INPUT_SIZE: usize = + ((BN256_MAX_PAIRING_SIZE_KARST / PAIR_ELEMENT_LEN) + 1) * PAIR_ELEMENT_LEN; + let input = [0u8; INPUT_SIZE]; + let accelerated_result = + fpvm_bn128_pair_karst(&input, u64::MAX, hint_writer, oracle_reader).unwrap_err(); + + assert!(matches!(accelerated_result, PrecompileHalt::Bn254PairLength)); + }) + .await; + } + + /// Helper: capture the oracle hint and the recorded `gas_used` from a single + /// `fpvm_bn128_pair*` invocation, so the assertion site can compare both halves of the + /// L2-charge / oracle-gas split. + async fn capture_bn128_pair_hint( + run: impl Fn( + &[u8], + &kona_preimage::HintWriter, + &kona_preimage::OracleReader, + ) -> EthPrecompileResult + + Send + + Sync + + 'static, + input: Vec, + ) -> (crate::fpvm_evm::precompiles::test_utils::CapturedHint, u64) { + let recorded_gas_used: std::sync::Arc>> = + std::sync::Arc::new(std::sync::Mutex::new(None)); + let recorded_gas_used_in = recorded_gas_used.clone(); + + let captured = + test_accelerated_precompile_capture_hint(move |hint_writer, oracle_reader| { + let result = run(input.as_slice(), hint_writer, oracle_reader).unwrap(); + *recorded_gas_used_in.lock().unwrap() = Some(result.gas_used); + }) + .await; + + let gas_used = recorded_gas_used.lock().unwrap().unwrap(); + (captured, gas_used) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_bn128_pairing_oracle_gas_carries_l1_cost_two_pairs() { + // 2 pairs (existing TEST_INPUT, 384 bytes). + let (captured, gas_used) = capture_bn128_pair_hint( + |input, hw, or| fpvm_bn128_pair(input, u64::MAX, hw, or), + TEST_INPUT.to_vec(), + ) + .await; + + // Oracle hint at current L1 per-pair rate (EIP-7904): 2 * 34_103 + 45_000 = 113_206 + assert_eq!(captured.oracle_gas(), 2 * GLAMSTERDAM_PAIR_PER_POINT + ISTANBUL_PAIR_BASE); + assert_eq!(captured.oracle_gas(), 113_206); + // L2 charge unchanged at Istanbul rate: 2 * 34_000 + 45_000 = 113_000 + assert_eq!(gas_used, 2 * ISTANBUL_PAIR_PER_POINT + ISTANBUL_PAIR_BASE); + assert_eq!(gas_used, 113_000); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_bn128_pairing_oracle_gas_carries_l1_cost_three_pairs() { + // 3 pairs of zero-byte input (576 bytes). Empty pairs are valid input. + let input = vec![0u8; 3 * PAIR_ELEMENT_LEN]; + let (captured, gas_used) = capture_bn128_pair_hint( + |input, hw, or| fpvm_bn128_pair(input, u64::MAX, hw, or), + input, + ) + .await; + + // Current L1 per-pair rate (EIP-7904): 3 * 34_103 + 45_000 = 147_309 + assert_eq!(captured.oracle_gas(), 3 * GLAMSTERDAM_PAIR_PER_POINT + ISTANBUL_PAIR_BASE); + assert_eq!(captured.oracle_gas(), 147_309); + // 3 * 34_000 + 45_000 = 147_000 + assert_eq!(gas_used, 3 * ISTANBUL_PAIR_PER_POINT + ISTANBUL_PAIR_BASE); + assert_eq!(gas_used, 147_000); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_bn128_pairing_oracle_gas_carries_l1_cost_zero_pairs() { + // Empty input is valid (0 % PAIR_ELEMENT_LEN == 0). Locks the BASE summand. + let (captured, gas_used) = capture_bn128_pair_hint( + |input, hw, or| fpvm_bn128_pair(input, u64::MAX, hw, or), + Vec::new(), + ) + .await; + + assert_eq!(captured.oracle_gas(), ISTANBUL_PAIR_BASE); + assert_eq!(captured.oracle_gas(), 45_000); + assert_eq!(gas_used, ISTANBUL_PAIR_BASE); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_bn128_pairing_granite_oracle_gas_carries_l1_cost() { + // Locks wrapper-inheritance: `fpvm_bn128_pair_granite` must delegate to + // `fpvm_bn128_pair`, so the oracle hint carries the current L1 cost. + let (captured, _) = capture_bn128_pair_hint( + |input, hw, or| fpvm_bn128_pair_granite(input, u64::MAX, hw, or), + TEST_INPUT.to_vec(), + ) + .await; + + assert_eq!(captured.oracle_gas(), 113_206); + } } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/ecrecover.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/ecrecover.rs index 16525744483..77156f30dfc 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/ecrecover.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/ecrecover.rs @@ -4,7 +4,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use alloy_primitives::Address; use kona_preimage::{HintWriterClient, PreimageOracleClient}; -use revm::precompile::{PrecompileError, PrecompileOutput, PrecompileResult}; +use revm::precompile::{EthPrecompileOutput, EthPrecompileResult, PrecompileHalt}; /// Address of the `ecrecover` precompile. pub(crate) const ECRECOVER_ADDR: Address = revm::precompile::u64_to_address(1); @@ -15,7 +15,7 @@ pub(crate) fn fpvm_ec_recover( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, @@ -23,7 +23,7 @@ where const ECRECOVER_BASE: u64 = 3_000; if ECRECOVER_BASE > gas_limit { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } let truncated_input = &input[..input.len().min(128)]; @@ -33,10 +33,10 @@ where oracle_reader, &[ECRECOVER_ADDR.as_slice(), &ECRECOVER_BASE.to_be_bytes(), truncated_input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into())) + .map_err(|e| PrecompileHalt::Other(e.to_string().into())) .unwrap_or_default(); - Ok(PrecompileOutput::new(ECRECOVER_BASE, result_data.into())) + Ok(EthPrecompileOutput::new(ECRECOVER_BASE, result_data.into())) } #[cfg(test)] @@ -74,7 +74,7 @@ mod test { let accelerated_result = fpvm_ec_recover(&[], 0, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/kzg_point_eval.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/kzg_point_eval.rs index 5b98975f131..e5c3220099e 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/kzg_point_eval.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/kzg_point_eval.rs @@ -4,7 +4,7 @@ use crate::fpvm_evm::precompiles::utils::precompile_run; use alloc::string::ToString; use alloy_primitives::Address; use kona_preimage::{HintWriterClient, PreimageOracleClient}; -use revm::precompile::{PrecompileError, PrecompileOutput, PrecompileResult}; +use revm::precompile::{EthPrecompileOutput, EthPrecompileResult, PrecompileHalt}; /// Address of the KZG point evaluation precompile. pub(crate) const KZG_POINT_EVAL_ADDR: Address = revm::precompile::u64_to_address(0x0A); @@ -15,29 +15,31 @@ pub(crate) fn fpvm_kzg_point_eval( gas_limit: u64, hint_writer: &H, oracle_reader: &O, -) -> PrecompileResult +) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { const GAS_COST: u64 = 50_000; + /// Oracle/L1 staticcall gas required by [EIP-7904](https://eips.ethereum.org/EIPS/eip-7904). + const KZG_ORACLE_GAS: u64 = 89_363; if gas_limit < GAS_COST { - return Err(PrecompileError::OutOfGas); + return Err(PrecompileHalt::OutOfGas); } if input.len() != 192 { - return Err(PrecompileError::BlobInvalidInputLength); + return Err(PrecompileHalt::BlobInvalidInputLength); } let result_data = kona_proof::block_on(precompile_run! { hint_writer, oracle_reader, - &[KZG_POINT_EVAL_ADDR.as_slice(), &GAS_COST.to_be_bytes(), input] + &[KZG_POINT_EVAL_ADDR.as_slice(), &KZG_ORACLE_GAS.to_be_bytes(), input] }) - .map_err(|e| PrecompileError::Other(e.to_string().into()))?; + .map_err(|e| PrecompileHalt::Other(e.to_string().into()))?; - Ok(PrecompileOutput::new(GAS_COST, result_data.into())) + Ok(EthPrecompileOutput::new(GAS_COST, result_data.into())) } #[cfg(test)] @@ -45,22 +47,26 @@ mod test { use super::*; use crate::fpvm_evm::precompiles::test_utils::{ execute_native_precompile, test_accelerated_precompile, + test_accelerated_precompile_capture_hint, }; use alloy_eips::eip4844::VERSIONED_HASH_VERSION_KZG; use alloy_primitives::hex; use sha2::{Digest, Sha256}; + fn valid_kzg_input() -> Vec { + let commitment = hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7").to_vec(); + let mut versioned_hash = Sha256::digest(&commitment).to_vec(); + versioned_hash[0] = VERSIONED_HASH_VERSION_KZG; + let z = hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000").to_vec(); + let y = hex!("1522a4a7f34e1ea350ae07c29c96c7e79655aa926122e95fe69fcbd932ca49e9").to_vec(); + let proof = hex!("a62ad71d14c5719385c0686f1871430475bf3a00f0aa3f7b8dd99a9abc2160744faf0070725e00b60ad9a026a15b1a8c").to_vec(); + [versioned_hash, z, y, commitment, proof].concat() + } + #[tokio::test(flavor = "multi_thread")] async fn test_accelerated_kzg_point_eval() { test_accelerated_precompile(|hint_writer, oracle_reader| { - let commitment = hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7").to_vec(); - let mut versioned_hash = Sha256::digest(&commitment).to_vec(); - versioned_hash[0] = VERSIONED_HASH_VERSION_KZG; - let z = hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000").to_vec(); - let y = hex!("1522a4a7f34e1ea350ae07c29c96c7e79655aa926122e95fe69fcbd932ca49e9").to_vec(); - let proof = hex!("a62ad71d14c5719385c0686f1871430475bf3a00f0aa3f7b8dd99a9abc2160744faf0070725e00b60ad9a026a15b1a8c").to_vec(); - - let input = [versioned_hash, z, y, commitment, proof].concat(); + let input = valid_kzg_input(); let expected_result = hex!("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"); @@ -74,12 +80,44 @@ mod test { .await; } + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_kzg_point_eval_oracle_gas_carries_l1_cost() { + let recorded_gas_used: std::sync::Arc>> = + std::sync::Arc::new(std::sync::Mutex::new(None)); + let recorded_gas_used_in = recorded_gas_used.clone(); + + let captured = + test_accelerated_precompile_capture_hint(move |hint_writer, oracle_reader| { + let input = valid_kzg_input(); + let result = + fpvm_kzg_point_eval(&input, u64::MAX, hint_writer, oracle_reader).unwrap(); + *recorded_gas_used_in.lock().unwrap() = Some(result.gas_used); + }) + .await; + + // Oracle hint must carry the current L1 KZG cost (EIP-7904: 89_363). + assert_eq!(captured.oracle_gas(), 89_363); + // L2 charge must remain unchanged (Cancun KZG = 50_000). + assert_eq!(recorded_gas_used.lock().unwrap().unwrap(), 50_000); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_accelerated_kzg_point_eval_at_l2_gas_boundary() { + // OOG guard is `<` not `<=`, so gas_limit == GAS_COST must succeed. + test_accelerated_precompile(|hint_writer, oracle_reader| { + let input = valid_kzg_input(); + let result = fpvm_kzg_point_eval(&input, 50_000, hint_writer, oracle_reader); + assert!(result.is_ok(), "expected Ok at gas_limit == GAS_COST, got {result:?}"); + }) + .await; + } + #[tokio::test(flavor = "multi_thread")] async fn test_accelerated_kzg_point_eval_out_of_gas() { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_kzg_point_eval(&[], 0, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::OutOfGas)); + assert!(matches!(accelerated_result, PrecompileHalt::OutOfGas)); }) .await; } @@ -89,7 +127,7 @@ mod test { test_accelerated_precompile(|hint_writer, oracle_reader| { let accelerated_result = fpvm_kzg_point_eval(&[], u64::MAX, hint_writer, oracle_reader).unwrap_err(); - assert!(matches!(accelerated_result, PrecompileError::BlobInvalidInputLength)); + assert!(matches!(accelerated_result, PrecompileHalt::BlobInvalidInputLength)); }) .await; } diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/provider.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/provider.rs index 8d97fd8ad59..d90757fcd2e 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/provider.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/provider.rs @@ -8,13 +8,15 @@ use alloy_primitives::{Address, Bytes}; use kona_preimage::{HintWriterClient, PreimageOracleClient}; use op_revm::{ OpSpecId, - precompiles::{fjord, granite, isthmus}, + precompiles::{fjord, granite, isthmus, jovian, karst}, }; use revm::{ context::{Cfg, ContextTr}, handler::{EthPrecompiles, PrecompileProvider}, interpreter::{CallInputs, Gas, InstructionResult, InterpreterResult}, - precompile::{PrecompileError, PrecompileResult, Precompiles, bls12_381_const, bn254}, + precompile::{ + EthPrecompileResult, PrecompileError, PrecompileOutput, Precompiles, bls12_381_const, bn254, + }, primitives::{hardfork::SpecId, hash_map::HashMap}, }; @@ -48,7 +50,9 @@ where OpSpecId::ECOTONE) => Precompiles::new(spec.into_eth_spec().into()), OpSpecId::FJORD => fjord(), OpSpecId::GRANITE | OpSpecId::HOLOCENE => granite(), - OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA | OpSpecId::JOVIAN => isthmus(), + OpSpecId::ISTHMUS => isthmus(), + OpSpecId::JOVIAN => jovian(), + OpSpecId::KARST | OpSpecId::INTEROP => karst(), }; let accelerated_precompiles = match spec { @@ -57,10 +61,9 @@ where } OpSpecId::ECOTONE | OpSpecId::FJORD => accelerated_ecotone::(), OpSpecId::GRANITE | OpSpecId::HOLOCENE => accelerated_granite::(), - OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA => { - accelerated_isthmus::() - } + OpSpecId::ISTHMUS => accelerated_isthmus::(), OpSpecId::JOVIAN => accelerated_jovian::(), + OpSpecId::KARST | OpSpecId::INTEROP => accelerated_karst::(), }; Self { @@ -121,28 +124,30 @@ where // 3. If the precompile is not found, return None. let output = if let Some(accelerated) = self.accelerated_precompiles.get(&inputs.bytecode_address) { - (accelerated)(&input, inputs.gas_limit, &self.hint_writer, &self.oracle_reader) + let eth_result = + (accelerated)(&input, inputs.gas_limit, &self.hint_writer, &self.oracle_reader); + PrecompileOutput::from_eth_result(eth_result, inputs.reservoir) } else if let Some(precompile) = self.inner.precompiles.get(&inputs.bytecode_address) { - precompile.execute(&input, inputs.gas_limit) + match precompile.execute(&input, inputs.gas_limit, inputs.reservoir) { + Ok(output) => output, + Err(PrecompileError::Fatal(e)) => return Err(e), + Err(PrecompileError::FatalAny(e)) => return Err(alloc::format!("{e:?}")), + } } else { return Ok(None); }; - match output { - Ok(output) => { - let underflow = result.gas.record_cost(output.gas_used); - assert!(underflow, "Gas underflow is not possible"); - result.result = InstructionResult::Return; - result.output = output.bytes; - } - Err(PrecompileError::Fatal(e)) => return Err(e), - Err(e) => { - result.result = if e.is_oog() { - InstructionResult::PrecompileOOG - } else { - InstructionResult::PrecompileError - }; - } + if output.is_halt() { + result.result = if output.halt_reason().is_some_and(|r| r.is_oog()) { + InstructionResult::PrecompileOOG + } else { + InstructionResult::PrecompileError + }; + } else { + let underflow = result.gas.record_regular_cost(output.gas_used); + assert!(underflow, "Gas underflow is not possible"); + result.result = InstructionResult::Return; + result.output = output.bytes; } Ok(Some(result)) @@ -160,7 +165,7 @@ where } /// A precompile function that can be accelerated by the FPVM. -type AcceleratedPrecompileFn = fn(&[u8], u64, &H, &O) -> PrecompileResult; +type AcceleratedPrecompileFn = fn(&[u8], u64, &H, &O) -> EthPrecompileResult; /// A tuple type for accelerated precompiles with an associated [`Address`]. struct AcceleratedPrecompile { @@ -294,32 +299,59 @@ where base } +/// The accelerated precompiles for the karst spec. +fn accelerated_karst() -> Vec> +where + H: HintWriterClient + Send + Sync, + O: PreimageOracleClient + Send + Sync, +{ + let mut base = accelerated_jovian::(); + + // Replace the bn254 pair precompile with the Karst version (reduced input size limit). + base.retain(|p| p.address != bn254::pair::ADDRESS); + base.push(AcceleratedPrecompile::new( + bn254::pair::ADDRESS, + super::bn128_pair::fpvm_bn128_pair_karst::, + )); + + base +} + #[cfg(test)] mod test { use super::*; + use alloy_op_evm::{OpEvmContext, OpTx}; use kona_preimage::{HintWriterClient, PreimageOracleClient}; - use op_revm::{DefaultOp as _, OpContext, OpSpecId}; - use revm::{Context, database::EmptyDB, handler::PrecompileProvider, interpreter::CallInput}; + use op_revm::{L1BlockInfo, OpSpecId, OpTransaction}; + use revm::{ + Context, MainContext, database::EmptyDB, handler::PrecompileProvider, + interpreter::CallInput, + }; - type TestContext = OpContext; + type TestContext = OpEvmContext; fn create_call_inputs(address: Address, input: Bytes, gas_limit: u64) -> CallInputs { CallInputs { input: CallInput::Bytes(input), + return_memory_offset: 0..0, gas_limit, + reservoir: 0, bytecode_address: address, + known_bytecode: (revm::primitives::KECCAK_EMPTY, revm::bytecode::Bytecode::new()), target_address: Address::ZERO, caller: Address::ZERO, value: revm::interpreter::CallValue::Transfer(alloy_primitives::U256::ZERO), scheme: revm::interpreter::CallScheme::Call, is_static: false, - return_memory_offset: 0..0, - known_bytecode: None, } } fn create_test_context() -> TestContext { - Context::op().with_db(EmptyDB::new()) + Context::mainnet() + .with_tx(OpTx(OpTransaction::builder().build_fill())) + .with_cfg(revm::context::CfgEnv::new_with_spec(OpSpecId::BEDROCK)) + .with_chain(L1BlockInfo::default()) + .with_db(EmptyDB::new()) } /// A mock accelerated precompile function that returns a fixed output. @@ -328,12 +360,12 @@ mod test { gas_limit: u64, _hint_writer: &H, _oracle_reader: &O, - ) -> PrecompileResult + ) -> EthPrecompileResult where H: HintWriterClient + Send + Sync, O: PreimageOracleClient + Send + Sync, { - Ok(revm::precompile::PrecompileOutput::new(gas_limit / 2, Bytes::from_static(b"mock"))) + Ok(revm::precompile::EthPrecompileOutput::new(gas_limit / 2, Bytes::from_static(b"mock"))) } #[test] @@ -438,6 +470,193 @@ mod test { assert_eq!(interpreter_result.result, InstructionResult::PrecompileOOG); } + #[test] + fn test_post_jovian_specs_use_jovian_precompiles() { + let (hint_chan, preimage_chan) = ( + kona_preimage::BidirectionalChannel::new().unwrap(), + kona_preimage::BidirectionalChannel::new().unwrap(), + ); + let hint_writer = kona_preimage::HintWriter::new(hint_chan.client); + let oracle_reader = kona_preimage::OracleReader::new(preimage_chan.client); + + let jovian_provider = OpFpvmPrecompiles::new_with_spec( + OpSpecId::JOVIAN, + hint_writer.clone(), + oracle_reader.clone(), + ); + let interop_provider = OpFpvmPrecompiles::new_with_spec( + OpSpecId::INTEROP, + hint_writer.clone(), + oracle_reader.clone(), + ); + let karst_provider = OpFpvmPrecompiles::new_with_spec( + OpSpecId::KARST, + hint_writer.clone(), + oracle_reader.clone(), + ); + let isthmus_provider = + OpFpvmPrecompiles::new_with_spec(OpSpecId::ISTHMUS, hint_writer, oracle_reader); + + // Each post-Jovian spec accelerates the same set of addresses as the previous fork. + // The dispatched function at a given address may still change (e.g. KARST swaps the + // bn254 pair function at 0x08 for a stricter input-size check). + let jovian_addrs: Vec<_> = { + let mut addrs: Vec<_> = + jovian_provider.accelerated_precompiles.keys().copied().collect(); + addrs.sort(); + addrs + }; + let karst_addrs: Vec<_> = { + let mut addrs: Vec<_> = + karst_provider.accelerated_precompiles.keys().copied().collect(); + addrs.sort(); + addrs + }; + let interop_addrs: Vec<_> = { + let mut addrs: Vec<_> = + interop_provider.accelerated_precompiles.keys().copied().collect(); + addrs.sort(); + addrs + }; + assert_eq!( + jovian_addrs, karst_addrs, + "KARST should accelerate the same addresses as JOVIAN (functions may differ)" + ); + assert_eq!( + karst_addrs, interop_addrs, + "INTEROP should accelerate the same addresses as KARST (functions may differ)" + ); + + // Verify the non-accelerated precompile sets point to the correct static instances. + assert!( + core::ptr::eq(jovian_provider.inner.precompiles, jovian()), + "JOVIAN should use jovian() precompiles" + ); + assert!( + core::ptr::eq(isthmus_provider.inner.precompiles, isthmus()), + "ISTHMUS should use isthmus() precompiles" + ); + assert!( + core::ptr::eq(karst_provider.inner.precompiles, karst()), + "KARST should use karst() precompiles" + ); + assert!( + core::ptr::eq(interop_provider.inner.precompiles, karst()), + "INTEROP should use karst() precompiles" + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_karst_bn128_pair_enforces_karst_limit() { + use crate::fpvm_evm::precompiles::test_utils::test_accelerated_precompile; + + test_accelerated_precompile(|hint_writer, oracle_reader| { + // 301 pairs × 192 bytes = 57_792 — aligned to PAIR_ELEMENT_LEN and one pair + // above BN256_MAX_PAIRING_SIZE_KARST (57_600). + const OVER_KARST_LIMIT: usize = 57_792; + let input = vec![0u8; OVER_KARST_LIMIT]; + + let karst_provider = OpFpvmPrecompiles::new_with_spec( + OpSpecId::KARST, + hint_writer.clone(), + oracle_reader.clone(), + ); + let karst_fn = karst_provider + .accelerated_precompiles + .get(&bn254::pair::ADDRESS) + .copied() + .expect("KARST must have bn254 pair accelerated precompile"); + let karst_res = karst_fn(&input, u64::MAX, hint_writer, oracle_reader); + assert!( + matches!(karst_res, Err(revm::precompile::PrecompileHalt::Bn254PairLength)), + "KARST should reject input > 57_600 bytes with Bn254PairLength" + ); + }) + .await; + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_provider_dispatches_l1_precompile_gas() { + use alloy_eips::eip4844::VERSIONED_HASH_VERSION_KZG; + use alloy_primitives::hex; + use revm::precompile::{ + bls12_381_const::{G1_ADD_INPUT_LENGTH, G2_ADD_INPUT_LENGTH}, + bn254::PAIR_ELEMENT_LEN, + }; + use sha2::{Digest, Sha256}; + + // For each of the four precompiles whose L1 cost diverges from the L2 charge, drive + // `OpFpvmPrecompiles::run` at JOVIAN and assert the hint payload carries the L1 cost. + // This locks the production call site (provider-level dispatch), not just the leaf + // function — a future provider rewrite that swaps in a stale leaf would fail here even + // if the per-leaf tests still pass. Current L1 values reflect EIP-7904. + + // KZG: 0x0a, valid input → expect 89_363 + let kzg_input: Bytes = { + let commitment = hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7").to_vec(); + let mut versioned_hash = Sha256::digest(&commitment).to_vec(); + versioned_hash[0] = VERSIONED_HASH_VERSION_KZG; + let z = + hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000").to_vec(); + let y = + hex!("1522a4a7f34e1ea350ae07c29c96c7e79655aa926122e95fe69fcbd932ca49e9").to_vec(); + let proof = hex!("a62ad71d14c5719385c0686f1871430475bf3a00f0aa3f7b8dd99a9abc2160744faf0070725e00b60ad9a026a15b1a8c").to_vec(); + [versioned_hash, z, y, commitment, proof].concat().into() + }; + run_dispatch_and_assert_oracle_gas( + super::super::kzg_point_eval::KZG_POINT_EVAL_ADDR, + kzg_input, + 89_363, + ) + .await; + + // bn254 pair: 0x08, 2 valid pairs of zero bytes → expect 113_206 + let bn128_input: Bytes = vec![0u8; 2 * PAIR_ELEMENT_LEN].into(); + run_dispatch_and_assert_oracle_gas(bn254::pair::ADDRESS, bn128_input, 113_206).await; + + // BLS12-381 G1Add: 0x0b, 256-byte zero input → expect 643 + let g1_input: Bytes = vec![0u8; G1_ADD_INPUT_LENGTH].into(); + run_dispatch_and_assert_oracle_gas(bls12_381_const::G1_ADD_ADDRESS, g1_input, 643).await; + + // BLS12-381 G2Add: 0x0d, 512-byte zero input → expect 765 + let g2_input: Bytes = vec![0u8; G2_ADD_INPUT_LENGTH].into(); + run_dispatch_and_assert_oracle_gas(bls12_381_const::G2_ADD_ADDRESS, g2_input, 765).await; + } + + /// Helper: drive `OpFpvmPrecompiles::run` at JOVIAN for the given precompile address, + /// capture the L1Precompile hint, and assert its 8-byte gas word equals `expected_gas`. + async fn run_dispatch_and_assert_oracle_gas(address: Address, input: Bytes, expected_gas: u64) { + use crate::fpvm_evm::precompiles::test_utils::test_accelerated_precompile_capture_hint; + + let captured = + test_accelerated_precompile_capture_hint(move |hint_writer, oracle_reader| { + let mut ctx = create_test_context(); + let mut precompiles = OpFpvmPrecompiles::new_with_spec( + OpSpecId::JOVIAN, + hint_writer.clone(), + oracle_reader.clone(), + ); + let call_inputs = create_call_inputs(address, input.clone(), u64::MAX); + let result = precompiles + .run(&mut ctx, &call_inputs) + .expect("provider run errored") + .expect("provider returned None for accelerated address"); + assert_eq!( + result.result, + InstructionResult::Return, + "expected successful precompile result, got {:?}", + result.result + ); + }) + .await; + + assert_eq!( + captured.oracle_gas(), + expected_gas, + "oracle gas mismatch at address {address:?}", + ); + } + #[test] fn test_run_with_shared_buffer_empty() { let (hint_chan, preimage_chan) = ( @@ -456,15 +675,16 @@ mod test { let sha256_addr = revm::precompile::u64_to_address(2); let call_inputs = CallInputs { input: CallInput::SharedBuffer(0..0), + return_memory_offset: 0..0, gas_limit: u64::MAX, + reservoir: 0, bytecode_address: sha256_addr, + known_bytecode: (revm::primitives::KECCAK_EMPTY, revm::bytecode::Bytecode::new()), target_address: Address::ZERO, caller: Address::ZERO, value: revm::interpreter::CallValue::Transfer(alloy_primitives::U256::ZERO), scheme: revm::interpreter::CallScheme::Call, is_static: false, - return_memory_offset: 0..0, - known_bytecode: None, }; let result = precompiles.run(&mut ctx, &call_inputs).unwrap(); diff --git a/rust/kona/bin/client/src/fpvm_evm/precompiles/test_utils.rs b/rust/kona/bin/client/src/fpvm_evm/precompiles/test_utils.rs index bf9d781efcf..663c9c3af1c 100644 --- a/rust/kona/bin/client/src/fpvm_evm/precompiles/test_utils.rs +++ b/rust/kona/bin/client/src/fpvm_evm/precompiles/test_utils.rs @@ -17,6 +17,42 @@ use tokio::sync::{Mutex, RwLock}; /// closure accepts the client's [`HintWriter`] and [`OracleReader`] as arguments. pub(crate) async fn test_accelerated_precompile( f: impl Fn(&HintWriter, &OracleReader) + Send + Sync + 'static, +) { + test_accelerated_precompile_inner(Arc::new(RwLock::new(None)), f).await; +} + +/// The most recent `L1Precompile` hint payload captured by the mock host. +#[derive(Debug, Clone)] +pub(crate) struct CapturedHint { + /// Raw hint payload: `address[20] || gas[8] || input[..]`. + pub(crate) data: Vec, +} + +impl CapturedHint { + /// The 8-byte oracle-gas value the client embedded in the hint. + pub(crate) fn oracle_gas(&self) -> u64 { + u64::from_be_bytes(self.data[20..28].try_into().unwrap()) + } +} + +/// Variant of [`test_accelerated_precompile`] that returns the most recent `L1Precompile` hint +/// payload after the closure completes. Tests use this to assert oracle-gas bytes at offset +/// 20..28 without reading the lock from inside the (sync) closure. +pub(crate) async fn test_accelerated_precompile_capture_hint( + f: impl Fn(&HintWriter, &OracleReader) + Send + Sync + 'static, +) -> CapturedHint { + let last_hint = Arc::new(RwLock::new(None)); + test_accelerated_precompile_inner(last_hint.clone(), f).await; + + let guard = last_hint.read().await; + let raw = guard.as_ref().expect("client did not emit an L1Precompile hint"); + let parsed: Hint = raw.parse().expect("failed to parse captured hint"); + CapturedHint { data: parsed.data.as_ref().to_vec() } +} + +async fn test_accelerated_precompile_inner( + last_hint: Arc>>, + f: impl Fn(&HintWriter, &OracleReader) + Send + Sync + 'static, ) { let (hint_chan, preimage_chan) = (BidirectionalChannel::new().unwrap(), BidirectionalChannel::new().unwrap()); @@ -24,6 +60,7 @@ pub(crate) async fn test_accelerated_precompile( let host = tokio::task::spawn(precompile_host( OracleServer::new(preimage_chan.host), HintReader::new(hint_chan.host), + last_hint, )); let client = tokio::task::spawn(async move { let oracle_reader = OracleReader::new(preimage_chan.client); @@ -43,22 +80,27 @@ pub(crate) fn execute_native_precompile>( input: T, gas: u64, ) -> PrecompileResult { - let precompiles = revm::handler::EthPrecompiles::default(); + let precompiles = + revm::handler::EthPrecompiles::new(revm::primitives::hardfork::SpecId::PRAGUE); let Some(precompile) = precompiles.precompiles.get(&address) else { panic!("Precompile not found"); }; - precompile.execute(&input.into(), gas) + precompile.execute(&input.into(), gas, 0) } /// Starts a mock host thread that serves [`HintType::L1Precompile`] hints and preimages. +/// +/// `last_hint` is shared with the caller so tests using +/// [`test_accelerated_precompile_capture_hint`] can read the most recently routed hint +/// after the client closure completes. async fn precompile_host( oracle_server: OracleServer, hint_reader: HintReader, + last_hint: Arc>>, ) { - let last_hint = Arc::new(RwLock::new(None)); let preimage_fetcher = PrecompilePreimageFetcher { map: Default::default(), last_hint: last_hint.clone() }; - let hint_router = PrecompileHintRouter { last_hint: last_hint.clone() }; + let hint_router = PrecompileHintRouter { last_hint }; let server = tokio::task::spawn(async move { loop { @@ -114,15 +156,15 @@ impl PreimageFetcher for PrecompilePreimageFetcher { let input = parsed_hint.data[28..].to_vec(); let input_hash = keccak256(parsed_hint.data.as_ref()); - let result = execute_native_precompile(address, input, gas).map_or_else( - |_| vec![0u8; 1], - |raw_res| { + let result = match execute_native_precompile(address, input, gas) { + Ok(raw_res) if raw_res.is_success() => { let mut res = Vec::with_capacity(1 + raw_res.bytes.len()); res.push(0x01); res.extend_from_slice(&raw_res.bytes); res - }, - ); + } + _ => vec![0u8; 1], + }; map_lock .insert(PreimageKey::new(*input_hash, PreimageKeyType::Precompile), result.clone()); diff --git a/rust/kona/bin/client/src/fpvm_evm/tx.rs b/rust/kona/bin/client/src/fpvm_evm/tx.rs index e1b281a368c..561063bf38f 100644 --- a/rust/kona/bin/client/src/fpvm_evm/tx.rs +++ b/rust/kona/bin/client/src/fpvm_evm/tx.rs @@ -1,14 +1,14 @@ //! [`FpvmOpTx`] newtype wrapper around [`OpTransaction`]. use alloy_consensus::{ - Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, TxLegacy, + Signed, Transaction, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, TxLegacy, }; use alloy_eips::{Encodable2718, Typed2718, eip7594::Encodable7594}; use alloy_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv}; use alloy_op_evm::block::OpTxEnv; use alloy_primitives::{Address, B256, Bytes, TxKind, U256}; use core::ops::{Deref, DerefMut}; -use op_alloy_consensus::{OpTxEnvelope, TxDeposit}; +use op_alloy_consensus::{OpTxEnvelope, TxDeposit, TxPostExec}; use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; use revm::context::TxEnv; @@ -136,6 +136,7 @@ impl FromTxWithEncoded for FpvmOpTx { OpTxEnvelope::Eip2930(tx) => Self::from_encoded_tx(tx, caller, encoded), OpTxEnvelope::Eip7702(tx) => Self::from_encoded_tx(tx, caller, encoded), OpTxEnvelope::Deposit(tx) => Self::from_encoded_tx(tx.inner(), caller, encoded), + OpTxEnvelope::PostExec(tx) => Self::from_encoded_tx(tx.inner(), caller, encoded), } } } @@ -215,3 +216,17 @@ impl FromTxWithEncoded for FpvmOpTx { Self(OpTransaction { base, enveloped_tx: Some(encoded), deposit }) } } + +impl FromRecoveredTx for FpvmOpTx { + fn from_recovered_tx(tx: &TxPostExec, _sender: Address) -> Self { + let encoded = tx.encoded_2718(); + Self::from_encoded_tx(tx, Address::ZERO, encoded.into()) + } +} + +impl FromTxWithEncoded for FpvmOpTx { + fn from_encoded_tx(tx: &TxPostExec, caller: Address, encoded: Bytes) -> Self { + let base = TxEnv { tx_type: tx.ty(), caller, kind: tx.kind(), ..Default::default() }; + Self(OpTransaction { base, enveloped_tx: Some(encoded), deposit: Default::default() }) + } +} diff --git a/rust/kona/bin/client/src/interop/consolidate.rs b/rust/kona/bin/client/src/interop/consolidate.rs index efaff256edd..f006b3d39ec 100644 --- a/rust/kona/bin/client/src/interop/consolidate.rs +++ b/rust/kona/bin/client/src/interop/consolidate.rs @@ -3,8 +3,11 @@ use super::FaultProofProgramError; use crate::interop::util::fetch_output_block_hash; use alloc::sync::Arc; -use alloy_evm::{EvmFactory, FromRecoveredTx, FromTxWithEncoded}; -use alloy_op_evm::block::OpTxEnv; +use alloy_evm::{EvmFactory, FromRecoveredTx, FromTxWithEncoded, block::BlockExecutorFactory}; +use alloy_op_evm::{ + OpBlockExecutionCtx, OpBlockExecutorFactory, + block::{OpAlloyReceiptBuilder, OpTxEnv}, +}; use core::fmt::Debug; use kona_executor::TrieDBProvider; use kona_preimage::{HintWriterClient, PreimageOracleClient}; @@ -12,8 +15,8 @@ use kona_proof::{CachingOracle, l2::OracleL2ChainProvider}; use kona_proof_interop::{ BootInfo, HintType, OracleInteropProvider, PreState, SuperchainConsolidator, }; -use kona_registry::{HashMap, ROLLUP_CONFIGS}; -use op_alloy_consensus::OpTxEnvelope; +use kona_registry::{HashMap, ROLLUP_CONFIGS, RollupConfig}; +use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope}; use op_revm::OpSpecId; use revm::context::BlockEnv; use tracing::{error, info}; @@ -36,6 +39,12 @@ where Evm: EvmFactory + Send + Sync + Debug + Clone + 'static, ::Tx: FromTxWithEncoded + FromRecoveredTx + OpTxEnv, + OpBlockExecutorFactory: for<'a> BlockExecutorFactory< + EvmFactory = Evm, + ExecutionCtx<'a> = OpBlockExecutionCtx, + Transaction = OpTxEnvelope, + Receipt = OpReceiptEnvelope, + >, { info!(target: "client_interop", "Deriving local-safe headers from prestate"); diff --git a/rust/kona/bin/client/src/interop/mod.rs b/rust/kona/bin/client/src/interop/mod.rs index fa756b08f89..8dfcbf83348 100644 --- a/rust/kona/bin/client/src/interop/mod.rs +++ b/rust/kona/bin/client/src/interop/mod.rs @@ -17,10 +17,11 @@ use tracing::{error, info}; use transition::sub_transition; use crate::fpvm_evm::FpvmOpEvmFactory; +use alloy_op_evm::post_exec::PostExecEvmFactoryAdapter; pub(crate) mod consolidate; pub(crate) mod transition; -pub(crate) mod util; +pub mod util; /// An error that can occur when running the fault proof program. #[derive(Error, Debug)] @@ -76,15 +77,17 @@ where } }; - let evm_factory = FpvmOpEvmFactory::new(hint_client, oracle_client); + let evm_factory = + PostExecEvmFactoryAdapter::new(FpvmOpEvmFactory::new(hint_client, oracle_client)); // Load in the agreed pre-state from the preimage oracle in order to determine the active // sub-problem. match boot.agreed_pre_state { PreState::SuperRoot(ref super_root) => { - // If the claimed L2 block timestamp is less than the super root timestamp, the - // post-state must be the agreed pre-state to accommodate trace extension. - if super_root.timestamp >= boot.claimed_l2_timestamp { + // At the trace-extension boundary, the post-state must equal the agreed pre-state. + // The strict-`>` case is guarded against in `BootInfo::load` (panics there); this + // arm only handles the legitimate `==` boundary. + if super_root.timestamp == boot.claimed_l2_timestamp { if boot.agreed_pre_state_commitment == boot.claimed_post_state { return Ok(()); } else { @@ -99,13 +102,18 @@ where sub_transition(oracle, boot, evm_factory).await } PreState::TransitionState(ref transition_state) => { - // If the claimed L2 block timestamp is less than the prestate timestamp, the - // claim must be invalid. - if transition_state.pre_state.timestamp >= boot.claimed_l2_timestamp { - return Err(FaultProofProgramError::InvalidClaim( - boot.agreed_pre_state_commitment, - boot.claimed_post_state, - )); + // At the trace-extension boundary, the post-state must equal the agreed pre-state. + // The strict-`>` case is guarded against in `BootInfo::load` (panics there); this + // arm only handles the legitimate `==` boundary, mirroring the SuperRoot arm above. + if transition_state.pre_state.timestamp == boot.claimed_l2_timestamp { + if boot.agreed_pre_state_commitment == boot.claimed_post_state { + return Ok(()); + } else { + return Err(FaultProofProgramError::InvalidClaim( + boot.agreed_pre_state_commitment, + boot.claimed_post_state, + )); + } } // If the pre-state is a transition state, the sub-problem is selected based on the diff --git a/rust/kona/bin/client/src/interop/transition.rs b/rust/kona/bin/client/src/interop/transition.rs index 86b6e4ba267..59883fc1a91 100644 --- a/rust/kona/bin/client/src/interop/transition.rs +++ b/rust/kona/bin/client/src/interop/transition.rs @@ -4,8 +4,11 @@ use super::FaultProofProgramError; use crate::interop::util::fetch_l2_safe_head_hash; use alloc::{boxed::Box, sync::Arc}; use alloy_consensus::Sealed; -use alloy_evm::{EvmFactory, FromRecoveredTx, FromTxWithEncoded}; -use alloy_op_evm::block::OpTxEnv; +use alloy_evm::{EvmFactory, FromRecoveredTx, FromTxWithEncoded, block::BlockExecutorFactory}; +use alloy_op_evm::{ + OpBlockExecutionCtx, OpBlockExecutorFactory, + block::{OpAlloyReceiptBuilder, OpTxEnv}, +}; use alloy_primitives::B256; use core::fmt::Debug; use kona_derive::{EthereumDataSource, PipelineError, PipelineErrorKind}; @@ -20,7 +23,8 @@ use kona_proof::{ sync::new_oracle_pipeline_cursor, }; use kona_proof_interop::{BootInfo, INVALID_TRANSITION_HASH, OptimisticBlock, PreState}; -use op_alloy_consensus::OpTxEnvelope; +use kona_registry::RollupConfig; +use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope}; use op_revm::OpSpecId; use revm::context::BlockEnv; use tracing::{error, info, warn}; @@ -38,6 +42,12 @@ where Evm: EvmFactory + Send + Sync + Debug + Clone + 'static, ::Tx: FromTxWithEncoded + FromRecoveredTx + OpTxEnv, + OpBlockExecutorFactory: for<'a> BlockExecutorFactory< + EvmFactory = Evm, + ExecutionCtx<'a> = OpBlockExecutionCtx, + Transaction = OpTxEnvelope, + Receipt = OpReceiptEnvelope, + >, { // Check if we can short-circuit the transition, if we are within padding. if let PreState::TransitionState(ref transition_state) = boot.agreed_pre_state && @@ -97,10 +107,17 @@ where ); } + let agreed_l2_output_root = boot + .agreed_pre_state + .active_l2_output_root() + .ok_or(FaultProofProgramError::StateTransitionFailed)? + .output_root; + // Create a new derivation driver with the given boot information and oracle. let cursor = new_oracle_pipeline_cursor( rollup_config.as_ref(), safe_head, + agreed_l2_output_root, &mut l1_provider, &mut l2_provider, ) @@ -117,6 +134,10 @@ where da_provider, l1_provider.clone(), l2_provider.clone(), + // Interop fault proof: dependency set is part of the boot info. + // This is used to gate the CrossL2Inbox upgrade-tx pair + // (emitted only when `dependencies.len() > 1`). + Some(Arc::new(boot.dependency_set.clone())), ) .await .map_err(Box::new)?; diff --git a/rust/kona/bin/client/src/interop/util.rs b/rust/kona/bin/client/src/interop/util.rs index e36a6630984..60867c37c68 100644 --- a/rust/kona/bin/client/src/interop/util.rs +++ b/rust/kona/bin/client/src/interop/util.rs @@ -24,7 +24,7 @@ where } /// Fetches the block hash that the passed output root commits to. -pub(crate) async fn fetch_output_block_hash( +pub async fn fetch_output_block_hash( caching_oracle: &O, output_root: B256, chain_id: u64, @@ -41,5 +41,18 @@ where .await .map_err(OracleProviderError::Preimage)?; + if output_preimage.len() != 128 { + return Err(OracleProviderError::Preimage(PreimageOracleError::BufferLengthMismatch( + 128, + output_preimage.len(), + ))); + } + + if output_preimage[..32] != [0u8; 32] { + return Err(OracleProviderError::UnknownOutputVersion(B256::from_slice( + &output_preimage[..32], + ))); + } + output_preimage[96..128].try_into().map_err(OracleProviderError::SliceConversion) } diff --git a/rust/kona/bin/client/src/single.rs b/rust/kona/bin/client/src/single.rs index a9dbea8b446..f9f92431b72 100644 --- a/rust/kona/bin/client/src/single.rs +++ b/rust/kona/bin/client/src/single.rs @@ -3,6 +3,7 @@ use crate::fpvm_evm::FpvmOpEvmFactory; use alloc::sync::Arc; use alloy_consensus::Sealed; +use alloy_op_evm::post_exec::PostExecEvmFactoryAdapter; use alloy_primitives::B256; use core::fmt::Debug; use kona_derive::{EthereumDataSource, PipelineErrorKind}; @@ -82,9 +83,26 @@ where )); } - // In the case where the agreed upon L2 output root is the same as the claimed L2 output root, - // trace extension is detected and we can skip the derivation and execution steps. - if boot.agreed_l2_output_root == boot.claimed_l2_output_root { + // If the claim targets the safe head block itself, then no derivation is required. This can + // happen at trace-extension leaves where the trace is capped at the root-claim block number. + // + // In this case, the only valid output root is the agreed output root (a zero-step transition). + if boot.claimed_l2_block_number == safe_head.number { + if boot.claimed_l2_output_root != boot.agreed_l2_output_root { + error!( + target: "client", + claimed = boot.claimed_l2_block_number, + safe = safe_head.number, + expected_output_root = ?boot.agreed_l2_output_root, + claimed_output_root = ?boot.claimed_l2_output_root, + "Claimed output root does not match agreed output root at safe head", + ); + return Err(FaultProofProgramError::InvalidClaim( + boot.agreed_l2_output_root, + boot.claimed_l2_output_root, + )); + } + info!( target: "client", "Trace extension detected. State transition is already agreed upon.", @@ -100,6 +118,7 @@ where let cursor = new_oracle_pipeline_cursor( rollup_config.as_ref(), safe_head, + boot.agreed_l2_output_root, &mut l1_provider, &mut l2_provider, ) @@ -110,7 +129,8 @@ where })?; l2_provider.set_cursor(cursor.clone()); - let evm_factory = FpvmOpEvmFactory::new(hint_client, oracle_client); + let evm_factory = + PostExecEvmFactoryAdapter::new(FpvmOpEvmFactory::new(hint_client, oracle_client)); let da_provider = EthereumDataSource::new_from_parts(l1_provider.clone(), beacon, &rollup_config); let pipeline = OraclePipeline::new( @@ -121,6 +141,10 @@ where da_provider, l1_provider.clone(), l2_provider.clone(), + // Single-chain fault proof: no interop dependency set. If this chain + // ever schedules interop, the StatefulAttributesBuilder constructor + // will panic. + None, ) .await?; @@ -182,5 +206,11 @@ where .get_exact(PreimageKey::new_keccak256(*agreed_l2_output_root), output_preimage.as_mut()) .await?; + if output_preimage[..32] != [0u8; 32] { + return Err(OracleProviderError::UnknownOutputVersion(B256::from_slice( + &output_preimage[..32], + ))); + } + output_preimage[96..128].try_into().map_err(OracleProviderError::SliceConversion) } diff --git a/rust/kona/bin/client/testdata/.gitignore b/rust/kona/bin/client/testdata/.gitignore new file mode 100644 index 00000000000..f92c9fca5bc --- /dev/null +++ b/rust/kona/bin/client/testdata/.gitignore @@ -0,0 +1,4 @@ +# Witness tars are fetched on demand from the chain-test-data release hosting +# (see scripts/fetch-witness-tar.sh) rather than committed. +*.tar.zst +*.tar.zst.etag diff --git a/rust/kona/bin/client/testdata/holocene-op-sepolia-26215604-witness.tar.zst b/rust/kona/bin/client/testdata/holocene-op-sepolia-26215604-witness.tar.zst deleted file mode 100644 index 10ef96c1c54..00000000000 Binary files a/rust/kona/bin/client/testdata/holocene-op-sepolia-26215604-witness.tar.zst and /dev/null differ diff --git a/rust/kona/bin/client/tests/common/mod.rs b/rust/kona/bin/client/tests/common/mod.rs new file mode 100644 index 00000000000..0f74d7cabc5 --- /dev/null +++ b/rust/kona/bin/client/tests/common/mod.rs @@ -0,0 +1,51 @@ +//! Shared helpers for integration tests under `tests/`. + +#![allow(dead_code)] + +use async_trait::async_trait; +use kona_preimage::{ + HintWriterClient, PreimageKey, PreimageOracleClient, + errors::{PreimageOracleError, PreimageOracleResult}, +}; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::Mutex; + +#[derive(Clone, Debug, Default)] +pub struct MockOracle { + preimages: Arc>>>, +} + +impl MockOracle { + pub fn from_preimages(preimages: HashMap>) -> Self { + Self { preimages: Arc::new(Mutex::new(preimages)) } + } + + pub fn single(key: PreimageKey, value: Vec) -> Self { + let mut preimages = HashMap::new(); + preimages.insert(key, value); + Self::from_preimages(preimages) + } +} + +#[async_trait] +impl PreimageOracleClient for MockOracle { + async fn get(&self, key: PreimageKey) -> PreimageOracleResult> { + self.preimages.lock().await.get(&key).cloned().ok_or(PreimageOracleError::KeyNotFound) + } + + async fn get_exact(&self, key: PreimageKey, buf: &mut [u8]) -> PreimageOracleResult<()> { + let data = self.get(key).await?; + if data.len() != buf.len() { + return Err(PreimageOracleError::BufferLengthMismatch(buf.len(), data.len())); + } + buf.copy_from_slice(&data); + Ok(()) + } +} + +#[async_trait] +impl HintWriterClient for MockOracle { + async fn write(&self, _hint: &str) -> PreimageOracleResult<()> { + Ok(()) + } +} diff --git a/rust/kona/bin/client/tests/interop_trace_extension.rs b/rust/kona/bin/client/tests/interop_trace_extension.rs new file mode 100644 index 00000000000..6682bc4aa13 --- /dev/null +++ b/rust/kona/bin/client/tests/interop_trace_extension.rs @@ -0,0 +1,267 @@ +//! Trace-extension boundary tests for the interop `run()` dispatch. +//! +//! Covers both `PreState::SuperRoot` and `PreState::TransitionState` agreed prestates at +//! `prestate.timestamp == claimed_l2_timestamp` (legitimate boundary) and the strict-`>` case +//! (invariant violation, must panic to match op-program; see +//! `op-program/client/interop/interop.go:87-97`). + +use alloy_primitives::B256; +use alloy_rlp::Encodable; +use async_trait::async_trait; +use kona_client::interop::{FaultProofProgramError, run}; +use kona_genesis::{DependencySet, L1ChainConfig, RollupConfig}; +use kona_interop::{OutputRootWithChain, SuperRoot}; +use kona_preimage::{ + HintWriterClient, PreimageKey, PreimageOracleClient, + errors::{PreimageOracleError, PreimageOracleResult}, +}; +use kona_proof_interop::{PreState, TransitionState}; +use std::{ + collections::{BTreeMap, HashMap}, + sync::Arc, +}; +use tokio::sync::Mutex; + +#[derive(Clone, Debug, Default)] +struct MockOracle { + preimages: Arc>>>, +} + +impl MockOracle { + fn from_preimages(preimages: HashMap>) -> Self { + Self { preimages: Arc::new(Mutex::new(preimages)) } + } +} + +#[async_trait] +impl PreimageOracleClient for MockOracle { + async fn get(&self, key: PreimageKey) -> PreimageOracleResult> { + self.preimages.lock().await.get(&key).cloned().ok_or(PreimageOracleError::KeyNotFound) + } + + async fn get_exact(&self, key: PreimageKey, buf: &mut [u8]) -> PreimageOracleResult<()> { + let data = self.get(key).await?; + if data.len() != buf.len() { + return Err(PreimageOracleError::BufferLengthMismatch(buf.len(), data.len())); + } + buf.copy_from_slice(&data); + Ok(()) + } +} + +#[derive(Clone, Debug, Default)] +struct MockHintWriter { + _hints: Arc>>, +} + +#[async_trait] +impl HintWriterClient for MockHintWriter { + async fn write(&self, hint: &str) -> PreimageOracleResult<()> { + self._hints.lock().await.push(hint.to_string()); + Ok(()) + } +} + +fn b256(fill: u8) -> B256 { + B256::from([fill; 32]) +} + +const CHAIN_A: u64 = 9901; +const CHAIN_B: u64 = 9902; +const L1_CHAIN_ID: u64 = 9001; + +fn super_root(timestamp: u64) -> SuperRoot { + SuperRoot::new( + timestamp, + vec![ + OutputRootWithChain::new(CHAIN_A, b256(0xA1)), + OutputRootWithChain::new(CHAIN_B, b256(0xB2)), + ], + ) +} + +fn transition_state_prestate(timestamp: u64) -> PreState { + PreState::TransitionState(TransitionState::new(super_root(timestamp), Vec::new(), 0)) +} + +fn super_root_prestate(timestamp: u64) -> PreState { + PreState::SuperRoot(super_root(timestamp)) +} + +// Synthetic chain IDs absent from the embedded registries, so `BootInfo::load` +// uses the preimage-oracle fallback paths and the fixture stays self-contained. +fn setup_interop_preimages( + pre_state: PreState, + claimed_l2_timestamp: u64, + claimed_post_state: B256, +) -> (HashMap>, B256) { + let mut pre_state_rlp = Vec::with_capacity(pre_state.length()); + pre_state.encode(&mut pre_state_rlp); + let agreed_pre_state_commitment = pre_state.hash(); + + let mut preimages = HashMap::new(); + + preimages.insert(PreimageKey::new_local(1), b256(0x11).as_slice().to_vec()); + preimages.insert(PreimageKey::new_local(2), agreed_pre_state_commitment.as_slice().to_vec()); + preimages.insert(PreimageKey::new_local(3), claimed_post_state.as_slice().to_vec()); + preimages.insert(PreimageKey::new_local(4), claimed_l2_timestamp.to_be_bytes().to_vec()); + + let mut rollup_configs: HashMap = HashMap::new(); + for chain_id in [CHAIN_A, CHAIN_B] { + let cfg = RollupConfig { l1_chain_id: L1_CHAIN_ID, ..RollupConfig::default() }; + rollup_configs.insert(chain_id, cfg); + } + preimages.insert( + PreimageKey::new_local(6), + serde_json::to_vec(&rollup_configs).expect("serialize rollup configs"), + ); + + let l1_config = L1ChainConfig { chain_id: L1_CHAIN_ID, ..L1ChainConfig::default() }; + preimages.insert( + PreimageKey::new_local(7), + serde_json::to_vec(&l1_config).expect("serialize l1 config"), + ); + + let mut dependencies = BTreeMap::new(); + dependencies.insert(CHAIN_A, kona_genesis::ChainDependency {}); + dependencies.insert(CHAIN_B, kona_genesis::ChainDependency {}); + let depset = DependencySet { dependencies, override_message_expiry_window: None }; + preimages + .insert(PreimageKey::new_local(8), serde_json::to_vec(&depset).expect("serialize depset")); + + preimages.insert(PreimageKey::new_keccak256(*agreed_pre_state_commitment), pre_state_rlp); + + (preimages, agreed_pre_state_commitment) +} + +// `prestate.timestamp == claimed_l2_timestamp` and `claim == prestate_commitment`: must accept. +#[tokio::test(flavor = "multi_thread")] +async fn trace_extension_transition_state_at_game_timestamp_accepts_matching_claim() { + let t: u64 = 1000; + let (preimages, agreed_commit) = + setup_interop_preimages(transition_state_prestate(t), t, B256::ZERO); + let mut preimages = preimages; + preimages.insert(PreimageKey::new_local(3), agreed_commit.as_slice().to_vec()); + + let oracle = MockOracle::from_preimages(preimages); + let hints = MockHintWriter::default(); + + let result = run(oracle, hints).await; + match result { + Ok(()) => {} + Err(FaultProofProgramError::InvalidClaim(expected, actual)) => { + panic!("expected Ok(()); got InvalidClaim(expected={expected}, actual={actual})"); + } + Err(other) => panic!("unexpected error variant: {other:?}"), + } +} + +// `prestate.timestamp == claimed_l2_timestamp` but `claim != prestate_commitment`: must reject. +#[tokio::test(flavor = "multi_thread")] +async fn trace_extension_transition_state_at_game_timestamp_rejects_mismatched_claim() { + let t: u64 = 1000; + let mismatched_claim = b256(0xCC); + let (preimages, agreed_commit) = + setup_interop_preimages(transition_state_prestate(t), t, mismatched_claim); + + let oracle = MockOracle::from_preimages(preimages); + let hints = MockHintWriter::default(); + + let err = run(oracle, hints).await.unwrap_err(); + match err { + FaultProofProgramError::InvalidClaim(expected, actual) => { + assert_eq!(expected, agreed_commit); + assert_eq!(actual, mismatched_claim); + assert_ne!(expected, actual); + } + other => panic!("unexpected error variant: {other:?}"), + } +} + +// Mirror of `..._transition_state_at_game_timestamp_accepts_matching_claim` for the +// `PreState::SuperRoot` arm: `prestate.timestamp == claimed_l2_timestamp` and +// `claim == prestate_commitment`: must accept (trace extension, `Ok(())`). +#[tokio::test(flavor = "multi_thread")] +async fn trace_extension_super_root_at_game_timestamp_accepts_matching_claim() { + let t: u64 = 1000; + let (preimages, agreed_commit) = setup_interop_preimages(super_root_prestate(t), t, B256::ZERO); + let mut preimages = preimages; + preimages.insert(PreimageKey::new_local(3), agreed_commit.as_slice().to_vec()); + + let oracle = MockOracle::from_preimages(preimages); + let hints = MockHintWriter::default(); + + let result = run(oracle, hints).await; + match result { + Ok(()) => {} + Err(FaultProofProgramError::InvalidClaim(expected, actual)) => { + panic!("expected Ok(()); got InvalidClaim(expected={expected}, actual={actual})"); + } + Err(other) => panic!("unexpected error variant: {other:?}"), + } +} + +// Mirror of `..._transition_state_at_game_timestamp_rejects_mismatched_claim` for the +// `PreState::SuperRoot` arm: `prestate.timestamp == claimed_l2_timestamp` but +// `claim != prestate_commitment`: must reject with `InvalidClaim`. +#[tokio::test(flavor = "multi_thread")] +async fn trace_extension_super_root_at_game_timestamp_rejects_mismatched_claim() { + let t: u64 = 1000; + let mismatched_claim = b256(0xCC); + let (preimages, agreed_commit) = + setup_interop_preimages(super_root_prestate(t), t, mismatched_claim); + + let oracle = MockOracle::from_preimages(preimages); + let hints = MockHintWriter::default(); + + let err = run(oracle, hints).await.unwrap_err(); + match err { + FaultProofProgramError::InvalidClaim(expected, actual) => { + assert_eq!(expected, agreed_commit); + assert_eq!(actual, mismatched_claim); + assert_ne!(expected, actual); + } + other => panic!("unexpected error variant: {other:?}"), + } +} + +// `prestate.timestamp > claimed_l2_timestamp`: invariant violation, must panic in +// `BootInfo::load`. Matches op-program's defensive panic on the same condition. +#[tokio::test(flavor = "multi_thread")] +#[should_panic(expected = "agreed prestate timestamp")] +async fn rejects_transition_state_with_timestamp_after_game_timestamp() { + let prestate_timestamp: u64 = 1000; + let claimed_l2_timestamp: u64 = prestate_timestamp - 1; + let (preimages, agreed_commit) = setup_interop_preimages( + transition_state_prestate(prestate_timestamp), + claimed_l2_timestamp, + B256::ZERO, + ); + let mut preimages = preimages; + preimages.insert(PreimageKey::new_local(3), agreed_commit.as_slice().to_vec()); + + let oracle = MockOracle::from_preimages(preimages); + let hints = MockHintWriter::default(); + + let _ = run(oracle, hints).await; +} + +// Mirror of the above for the `PreState::SuperRoot` arm: same invariant, same guard, same panic. +#[tokio::test(flavor = "multi_thread")] +#[should_panic(expected = "agreed prestate timestamp")] +async fn rejects_super_root_with_timestamp_after_game_timestamp() { + let prestate_timestamp: u64 = 1000; + let claimed_l2_timestamp: u64 = prestate_timestamp - 1; + let (preimages, agreed_commit) = setup_interop_preimages( + super_root_prestate(prestate_timestamp), + claimed_l2_timestamp, + B256::ZERO, + ); + let mut preimages = preimages; + preimages.insert(PreimageKey::new_local(3), agreed_commit.as_slice().to_vec()); + + let oracle = MockOracle::from_preimages(preimages); + let hints = MockHintWriter::default(); + + let _ = run(oracle, hints).await; +} diff --git a/rust/kona/bin/client/tests/output_root.rs b/rust/kona/bin/client/tests/output_root.rs new file mode 100644 index 00000000000..c53b2530259 --- /dev/null +++ b/rust/kona/bin/client/tests/output_root.rs @@ -0,0 +1,97 @@ +//! Tests for the output-root preimage validation in the helpers that decode +//! an L2 output root into the safe head block hash. +//! +//! Two parallel helpers are covered: +//! * `single::fetch_safe_head_hash` (non-interop fault proof path, uses `get_exact` with a `[u8; +//! 128]` buffer). +//! * `interop::util::fetch_output_block_hash` (interop path, uses `.get()` and validates the +//! length explicitly). +//! +//! For each helper we assert that both the version-word and the preimage- +//! length guard reject malformed inputs, so refactors to either function are +//! caught independently. + +use alloy_primitives::B256; +use kona_client::{interop, single}; +use kona_preimage::{PreimageKey, errors::PreimageOracleError}; +use kona_proof::errors::OracleProviderError; + +mod common; +use common::MockOracle; + +fn b256(fill: u8) -> B256 { + B256::from([fill; 32]) +} + +#[tokio::test(flavor = "multi_thread")] +async fn fetch_safe_head_hash_rejects_unknown_output_version() { + let agreed_root = b256(0xAA); + let mut bad_preimage = [0u8; 128]; + bad_preimage[0] = 0x01; // non-V0 version word; [96..128] stays zero + let oracle = + MockOracle::single(PreimageKey::new_keccak256(*agreed_root), bad_preimage.to_vec()); + + let err = single::fetch_safe_head_hash(&oracle, agreed_root).await.unwrap_err(); + match err { + OracleProviderError::UnknownOutputVersion(version) => { + assert_eq!(version[0], 0x01); + } + other => panic!("unexpected error: {other:?}"), + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn fetch_safe_head_hash_rejects_wrong_preimage_length() { + // `get_exact` with a `[u8; 128]` buffer is what enforces the length here; + // this test guards against a future refactor swapping to `get`. + let agreed_root = b256(0xAA); + let oracle = MockOracle::single(PreimageKey::new_keccak256(*agreed_root), vec![0u8; 127]); + + let err = single::fetch_safe_head_hash(&oracle, agreed_root).await.unwrap_err(); + match err { + OracleProviderError::Preimage(PreimageOracleError::BufferLengthMismatch( + expected, + actual, + )) => { + assert_eq!(expected, 128); + assert_eq!(actual, 127); + } + other => panic!("unexpected error: {other:?}"), + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn fetch_output_block_hash_rejects_unknown_output_version() { + let output_root = b256(0xAA); + let mut bad_preimage = vec![0u8; 128]; + bad_preimage[0] = 0x01; // non-V0 version word + let oracle = MockOracle::single(PreimageKey::new_keccak256(*output_root), bad_preimage); + + let err = interop::util::fetch_output_block_hash(&oracle, output_root, 10).await.unwrap_err(); + match err { + OracleProviderError::UnknownOutputVersion(version) => { + assert_eq!(version[0], 0x01); + } + other => panic!("unexpected error: {other:?}"), + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn fetch_output_block_hash_rejects_wrong_preimage_length() { + // 127-byte preimage with a V0 version word; without the length guard this + // slips past the version check and then panics on the [96..128] slice. + let output_root = b256(0xAA); + let oracle = MockOracle::single(PreimageKey::new_keccak256(*output_root), vec![0u8; 127]); + + let err = interop::util::fetch_output_block_hash(&oracle, output_root, 10).await.unwrap_err(); + match err { + OracleProviderError::Preimage(PreimageOracleError::BufferLengthMismatch( + expected, + actual, + )) => { + assert_eq!(expected, 128); + assert_eq!(actual, 127); + } + other => panic!("unexpected error: {other:?}"), + } +} diff --git a/rust/kona/bin/client/tests/trace_extension.rs b/rust/kona/bin/client/tests/trace_extension.rs new file mode 100644 index 00000000000..a45461f83c5 --- /dev/null +++ b/rust/kona/bin/client/tests/trace_extension.rs @@ -0,0 +1,123 @@ +use alloy_consensus::Header; +use alloy_primitives::B256; +use async_trait::async_trait; +use kona_client::single::{FaultProofProgramError, run}; +use kona_preimage::{HintWriterClient, PreimageKey, errors::PreimageOracleResult}; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::Mutex; + +mod common; +use common::MockOracle; + +#[derive(Clone, Debug, Default)] +struct MockHintWriter { + _hints: Arc>>, +} + +#[async_trait] +impl HintWriterClient for MockHintWriter { + async fn write(&self, hint: &str) -> PreimageOracleResult<()> { + self._hints.lock().await.push(hint.to_string()); + Ok(()) + } +} + +fn b256(fill: u8) -> B256 { + B256::from([fill; 32]) +} + +fn setup_preimages( + agreed_root: B256, + claimed_root: B256, + claimed_block_number: u64, + safe_head_hash: B256, + safe_head_number: u64, +) -> HashMap> { + let mut preimages = HashMap::new(); + + // BootInfo local keys (see `kona_proof::boot`): + // 1: L1 head hash, 2: agreed output root, 3: claimed output root, 4: claimed block number, + // 5: chain id. + preimages.insert(PreimageKey::new_local(1), b256(0x11).as_slice().to_vec()); + preimages.insert(PreimageKey::new_local(2), agreed_root.as_slice().to_vec()); + preimages.insert(PreimageKey::new_local(3), claimed_root.as_slice().to_vec()); + preimages.insert(PreimageKey::new_local(4), claimed_block_number.to_be_bytes().to_vec()); + preimages.insert(PreimageKey::new_local(5), 10u64.to_be_bytes().to_vec()); + + // Output root preimage for `fetch_safe_head_hash(...)`. The safe head hash is read from + // bytes [96..128]. + let mut output_root_preimage = [0u8; 128]; + output_root_preimage[96..128].copy_from_slice(safe_head_hash.as_slice()); + preimages.insert(PreimageKey::new_keccak256(*agreed_root), output_root_preimage.to_vec()); + + // L2 safe head header. + let header = Header { number: safe_head_number, ..Default::default() }; + let header_rlp = alloy_rlp::encode(&header).to_vec(); + preimages.insert(PreimageKey::new_keccak256(*safe_head_hash), header_rlp); + + preimages +} + +#[tokio::test(flavor = "multi_thread")] +async fn trace_extension_leaf_rejects_mismatched_output_root() { + let safe_head_number = 3; + let safe_head_hash = b256(0x22); + let agreed_root = b256(0xAA); + let claimed_root = b256(0xBB); + + let oracle = MockOracle::from_preimages(setup_preimages( + agreed_root, + claimed_root, + safe_head_number, + safe_head_hash, + safe_head_number, + )); + let hints = MockHintWriter::default(); + + let err = run(oracle, hints).await.unwrap_err(); + match err { + FaultProofProgramError::InvalidClaim(expected, actual) => { + assert_eq!(expected, agreed_root); + assert_eq!(actual, claimed_root); + } + other => panic!("unexpected error: {other:?}"), + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn trace_extension_leaf_accepts_matching_output_root() { + let safe_head_number = 3; + let safe_head_hash = b256(0x22); + let agreed_root = b256(0xAA); + + let oracle = MockOracle::from_preimages(setup_preimages( + agreed_root, + agreed_root, + safe_head_number, + safe_head_hash, + safe_head_number, + )); + let hints = MockHintWriter::default(); + + run(oracle, hints).await.unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn does_not_short_circuit_on_root_match_at_different_block() { + let safe_head_number = 3; + let safe_head_hash = b256(0x22); + let agreed_root = b256(0xAA); + + // With the historical bug (checking only `agreed_root == claimed_root`), this would + // incorrectly return `Ok(())` without attempting derivation. + let oracle = MockOracle::from_preimages(setup_preimages( + agreed_root, + agreed_root, + safe_head_number + 1, + safe_head_hash, + safe_head_number, + )); + let hints = MockHintWriter::default(); + + assert!(run(oracle, hints).await.is_err()); +} diff --git a/rust/kona/bin/host/Cargo.toml b/rust/kona/bin/host/Cargo.toml index 1f4a88ef340..a949285add9 100644 --- a/rust/kona/bin/host/Cargo.toml +++ b/rust/kona/bin/host/Cargo.toml @@ -19,11 +19,12 @@ kona-executor.workspace = true kona-std-fpvm.workspace = true kona-proof-interop.workspace = true kona-proof = { workspace = true, features = ["std"] } -kona-preimage = { workspace = true, features = ["std"] } +kona-preimage = { workspace = true, features = ["std", "verify"] } # Protocol kona-driver.workspace = true kona-derive.workspace = true +kona-interop = { workspace = true, features = ["serde"] } kona-registry.workspace = true kona-protocol = { workspace = true, features = ["std", "serde"] } kona-genesis = { workspace = true, features = ["std", "serde"] } @@ -65,6 +66,7 @@ serde = { workspace = true, features = ["derive"] } clap = { workspace = true, features = ["derive", "env"] } tracing-subscriber = { workspace = true, features = ["fmt"] } thiserror.workspace = true +tempfile.workspace = true # KZG ark-ff.workspace = true diff --git a/rust/kona/bin/host/src/eth/precompiles.rs b/rust/kona/bin/host/src/eth/precompiles.rs index 1f890407b3d..f18fed7af53 100644 --- a/rust/kona/bin/host/src/eth/precompiles.rs +++ b/rust/kona/bin/host/src/eth/precompiles.rs @@ -24,8 +24,11 @@ pub(crate) fn execute>(address: Address, input: T, gas: u64) -> R if let Some(precompile) = ACCELERATED_PRECOMPILES.iter().find(|precompile| *precompile.address() == address) { - let output = precompile.precompile()(&input.into(), gas) - .map_err(|e| HostError::PrecompileExecutionFailed(e.to_string()))?; + let output = precompile.execute(&input.into(), gas, 0).map_err( + |e: revm::precompile::PrecompileError| { + HostError::PrecompileExecutionFailed(e.to_string()) + }, + )?; Ok(output.bytes.into()) } else { diff --git a/rust/kona/bin/host/src/interop/cfg.rs b/rust/kona/bin/host/src/interop/cfg.rs index a9692938fab..f30bea0a9cd 100644 --- a/rust/kona/bin/host/src/interop/cfg.rs +++ b/rust/kona/bin/host/src/interop/cfg.rs @@ -2,28 +2,34 @@ use super::{InteropHintHandler, InteropLocalInputs}; use crate::{ - DiskKeyValueStore, MemoryKeyValueStore, OfflineHostBackend, OnlineHostBackend, - OnlineHostBackendCfg, PreimageServer, SharedKeyValueStore, SplitKeyValueStore, - eth::rpc_provider, server::PreimageServerError, + OfflineHostBackend, OnlineHostBackend, OnlineHostBackendCfg, PreimageServer, + SharedKeyValueStore, + eth::rpc_provider, + kv::{DataFormat, create_key_value_store}, + server::PreimageServerError, }; use alloy_primitives::{B256, Bytes}; use alloy_provider::{Provider, RootProvider}; use clap::Parser; use kona_cli::cli_styles; use kona_genesis::{L1ChainConfig, RollupConfig}; +use kona_interop::DependencySet; use kona_preimage::{ BidirectionalChannel, Channel, HintReader, HintWriter, OracleReader, OracleServer, + VerifyingPreimageFetcher, }; use kona_proof_interop::HintType; use kona_providers_alloy::{OnlineBeaconClient, OnlineBlobProvider}; use kona_std_fpvm::{FileChannel, FileDescriptor}; use op_alloy_network::Optimism; use serde::Serialize; -use std::{collections::HashMap, path::PathBuf, str::FromStr, sync::Arc}; -use tokio::{ - sync::RwLock, - task::{self, JoinHandle}, +use std::{ + collections::{BTreeMap, HashMap}, + path::PathBuf, + str::FromStr, + sync::Arc, }; +use tokio::task::{self, JoinHandle}; /// The interop host application. #[derive(Default, Parser, Serialize, Clone, Debug)] @@ -79,6 +85,10 @@ pub struct InteropHost { env )] pub data_dir: Option, + /// The default format for preimage data storage on disk. If the data directory already + /// contains a `kvformat` marker file, that format is used instead of this value. + #[arg(long, default_value = "directory", env)] + pub data_format: DataFormat, /// Run the client program natively. #[arg(long, conflicts_with = "server", required_unless_present = "server")] pub native: bool, @@ -96,10 +106,10 @@ pub struct InteropHost { /// The l1 config should be stored as serde-JSON serialized files. #[arg(long, alias = "l1-cfg")] pub l1_config_path: Option, - /// Optionally enables the use of `debug_executePayload` to collect the execution witness from - /// the execution layer. - #[arg(long, env)] - pub enable_experimental_witness_endpoint: bool, + /// Path to the dependency set config. If provided, the host will use this config for the + /// dependency set. The config should be stored as a serde-JSON serialized file. + #[arg(long, alias = "depset-cfg", env)] + pub dependency_set_path: Option, } /// An error that can occur when handling interop hosts @@ -126,6 +136,18 @@ pub enum InteropHostError { /// An error when no provider found for chain ID. #[error("No provider found for chain ID: {0}")] RootProviderError(u64), + /// Interop is scheduled for a supplied rollup config but no dependency-set file was provided. + #[error( + "Interop is scheduled for chain {chain_id} (interop_time = {interop_time:?}), but \ + --depset-cfg was not provided. Supply the dependency-set JSON file matching op-node's \ + --interop.dependency-set to avoid silent state divergence on interop activation." + )] + InteropWithoutDependencySet { + /// The L2 chain ID whose rollup config has interop scheduled. + chain_id: u64, + /// The `interop_time` from that rollup config. + interop_time: Option, + }, /// Any other error. #[error("Error: {0}")] Other(&'static str), @@ -134,6 +156,8 @@ pub enum InteropHostError { impl InteropHost { /// Starts the [`InteropHost`] application. pub async fn start(self) -> Result<(), InteropHostError> { + self.require_dependency_set_if_interop_scheduled()?; + if self.server { let hint = FileChannel::new(FileDescriptor::HintRead, FileDescriptor::HintWrite); let preimage = @@ -145,6 +169,17 @@ impl InteropHost { } } + /// Refuses to start when any supplied rollup config schedules the Interop hardfork but no + /// `--depset-cfg` was provided. Mirrors the same invariant enforced by `kona-node`, turning a + /// silent state-divergence bug into a startup crash. + /// + /// When `rollup_config_paths` is `None`, the host relies on the superchain registry and this + /// check is skipped; The registry path does not statically know which chains are scheduled. + fn require_dependency_set_if_interop_scheduled(&self) -> Result<(), InteropHostError> { + let Some(configs) = self.read_rollup_configs().transpose()? else { return Ok(()) }; + require_dependency_set_for_configs(&configs, &self.dependency_set_path) + } + /// Starts the preimage server, communicating with the client over the provided channels. async fn start_server( &self, @@ -161,7 +196,7 @@ impl InteropHost { PreimageServer::new( OracleServer::new(preimage), HintReader::new(hint), - Arc::new(OfflineHostBackend::new(kv_store)), + Arc::new(VerifyingPreimageFetcher::new(OfflineHostBackend::new(kv_store))), ) .start() .await @@ -182,7 +217,7 @@ impl InteropHost { PreimageServer::new( OracleServer::new(preimage), HintReader::new(hint), - Arc::new(backend), + Arc::new(VerifyingPreimageFetcher::new(backend)), ) .start() .await @@ -220,13 +255,14 @@ impl InteropHost { } /// Reads the [`RollupConfig`]s from the file system and returns a map of L2 chain ID -> - /// [`RollupConfig`]s. + /// [`RollupConfig`]s. Uses `BTreeMap` to ensure deterministic JSON serialization order when + /// these configs are served as preimages to the cannon VM. pub fn read_rollup_configs( &self, - ) -> Option, InteropHostError>> { + ) -> Option, InteropHostError>> { let rollup_config_paths = self.rollup_config_paths.as_ref()?; - Some(rollup_config_paths.iter().try_fold(HashMap::default(), |mut acc, path| { + Some(rollup_config_paths.iter().try_fold(BTreeMap::default(), |mut acc, path| { // Read the serialized config from the file system. let ser_config = std::fs::read_to_string(path)?; @@ -251,21 +287,24 @@ impl InteropHost { .map_err(|_| InteropHostError::Other("failed to parse L1 config")) } + /// Reads the [`DependencySet`] from the file system. + pub fn read_dependency_set(&self) -> Option> { + let path = self.dependency_set_path.as_ref()?; + + Some((|| { + let ser_config = std::fs::read_to_string(path)?; + let dep_set: DependencySet = serde_json::from_str(&ser_config)?; + Ok(dep_set) + })()) + } + /// Creates the key-value store for the host backend. + /// + /// If the data directory contains a `kvformat` marker file, the recorded format is used to + /// ensure compatibility with existing data. Otherwise, `--data-format` is used as the default. fn create_key_value_store(&self) -> Result { let local_kv_store = InteropLocalInputs::new(self.clone()); - - let kv_store: SharedKeyValueStore = if let Some(ref data_dir) = self.data_dir { - let disk_kv_store = DiskKeyValueStore::new(data_dir.clone()); - let split_kv_store = SplitKeyValueStore::new(local_kv_store, disk_kv_store); - Arc::new(RwLock::new(split_kv_store)) - } else { - let mem_kv_store = MemoryKeyValueStore::new(); - let split_kv_store = SplitKeyValueStore::new(local_kv_store, mem_kv_store); - Arc::new(RwLock::new(split_kv_store)) - }; - - Ok(kv_store) + Ok(create_key_value_store(local_kv_store, self.data_dir.as_deref(), self.data_format)) } /// Creates the providers required for the preimage server backend. @@ -321,10 +360,70 @@ impl InteropProviders { } } +/// Returns `Err` when any config in `configs` schedules the Interop hardfork but +/// `dependency_set_path` is `None`. +fn require_dependency_set_for_configs( + configs: &BTreeMap, + dependency_set_path: &Option, +) -> Result<(), InteropHostError> { + if dependency_set_path.is_some() { + return Ok(()); + } + for (chain_id, cfg) in configs { + if cfg.hardforks.interop_time.is_some() { + return Err(InteropHostError::InteropWithoutDependencySet { + chain_id: *chain_id, + interop_time: cfg.hardforks.interop_time, + }); + } + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; use alloy_primitives::b256; + use kona_genesis::HardForkConfig; + + fn rollup_config_with_interop_time(interop_time: Option) -> RollupConfig { + RollupConfig { + hardforks: HardForkConfig { interop_time, ..Default::default() }, + ..Default::default() + } + } + + #[test] + fn test_require_dependency_set_interop_scheduled_without_depset() { + let configs: BTreeMap = + BTreeMap::from([(10u64, rollup_config_with_interop_time(Some(42)))]); + + let err = require_dependency_set_for_configs(&configs, &None).unwrap_err(); + match err { + InteropHostError::InteropWithoutDependencySet { chain_id, interop_time } => { + assert_eq!(chain_id, 10); + assert_eq!(interop_time, Some(42)); + } + other => panic!("expected InteropWithoutDependencySet, got {other:?}"), + } + } + + #[test] + fn test_require_dependency_set_interop_scheduled_with_depset() { + let configs: BTreeMap = + BTreeMap::from([(10u64, rollup_config_with_interop_time(Some(42)))]); + let depset = Some(PathBuf::from("/tmp/depset.json")); + + assert!(require_dependency_set_for_configs(&configs, &depset).is_ok()); + } + + #[test] + fn test_require_dependency_set_no_interop_no_depset() { + let configs: BTreeMap = + BTreeMap::from([(10u64, rollup_config_with_interop_time(None))]); + + assert!(require_dependency_set_for_configs(&configs, &None).is_ok()); + } #[test] fn test_parse_interop_host_cli() { diff --git a/rust/kona/bin/host/src/interop/handler.rs b/rust/kona/bin/host/src/interop/handler.rs index a23a1895ec3..b25498d0297 100644 --- a/rust/kona/bin/host/src/interop/handler.rs +++ b/rust/kona/bin/host/src/interop/handler.rs @@ -21,7 +21,7 @@ use kona_driver::Driver; use kona_executor::TrieDBProvider; use kona_preimage::{ BidirectionalChannel, HintReader, HintWriter, OracleReader, OracleServer, PreimageKey, - PreimageKeyType, + PreimageKeyType, VerifyingPreimageFetcher, }; use kona_proof::{ CachingOracle, Hint, @@ -35,11 +35,9 @@ use kona_protocol::{BlockInfo, OutputRoot, Predeploys}; use kona_providers_alloy::BlobWithCommitmentAndProof; use kona_registry::{L1_CONFIGS, ROLLUP_CONFIGS}; use op_alloy_rpc_types_engine::OpPayloadAttributes; -use op_revm::OpTransaction; -use revm::context::TxEnv; use std::sync::Arc; use tokio::task; -use tracing::{Instrument, debug, info, info_span, warn}; +use tracing::{Instrument, debug, info, info_span}; /// Parses the binary framing of a [`HintType::L2PayloadWitness`] hint. /// @@ -371,16 +369,33 @@ impl HintHandler for InteropHintHandler { kv_write_lock.set(PreimageKey::new_keccak256(*hash).into(), preimage.into())?; } HintType::L2AccountProof => { - ensure!(hint.data.len() == 8 + 20 + 8, "Invalid hint data length"); - - let block_number = u64::from_be_bytes(hint.data.as_ref()[..8].try_into()?); - let address = Address::from_slice(&hint.data.as_ref()[8..28]); - let chain_id = u64::from_be_bytes(hint.data[28..].try_into()?); + // Backwards compatibility: old prestates send an 8-byte block number; new + // prestates send a 32-byte block hash. A single kona-host version serves all + // games. + const BLOCK_NUMBER_HINT_LEN: usize = 8 + 20 + 8; + const BLOCK_HASH_HINT_LEN: usize = 32 + 20 + 8; + let (block_id, address, chain_id) = match hint.data.len() { + BLOCK_NUMBER_HINT_LEN => { + let block_number = u64::from_be_bytes(hint.data.as_ref()[..8].try_into()?); + let address = Address::from_slice(&hint.data.as_ref()[8..28]); + let chain_id = u64::from_be_bytes(hint.data.as_ref()[28..36].try_into()?); + (block_number.into(), address, chain_id) + } + BLOCK_HASH_HINT_LEN => { + let block_hash = B256::from_slice(&hint.data.as_ref()[..32]); + let address = Address::from_slice(&hint.data.as_ref()[32..52]); + let chain_id = u64::from_be_bytes(hint.data.as_ref()[52..60].try_into()?); + (block_hash.into(), address, chain_id) + } + other => anyhow::bail!( + "Invalid L2AccountProof hint length: expected {BLOCK_NUMBER_HINT_LEN} or {BLOCK_HASH_HINT_LEN}, got {other}" + ), + }; let proof_response = providers .l2(&chain_id)? .get_proof(address, Default::default()) - .block_id(block_number.into()) + .block_id(block_id) .await?; // Write the account proof nodes to the key-value store. @@ -393,17 +408,35 @@ impl HintHandler for InteropHintHandler { })?; } HintType::L2AccountStorageProof => { - ensure!(hint.data.len() == 8 + 20 + 32 + 8, "Invalid hint data length"); - - let block_number = u64::from_be_bytes(hint.data.as_ref()[..8].try_into()?); - let address = Address::from_slice(&hint.data.as_ref()[8..28]); - let slot = B256::from_slice(&hint.data.as_ref()[28..60]); - let chain_id = u64::from_be_bytes(hint.data[60..].try_into()?); + // Backwards compatibility: old prestates send an 8-byte block number; new + // prestates send a 32-byte block hash. A single kona-host version serves all + // games. + const BLOCK_NUMBER_HINT_LEN: usize = 8 + 20 + 32 + 8; + const BLOCK_HASH_HINT_LEN: usize = 32 + 20 + 32 + 8; + let (block_id, address, slot, chain_id) = match hint.data.len() { + BLOCK_NUMBER_HINT_LEN => { + let block_number = u64::from_be_bytes(hint.data.as_ref()[..8].try_into()?); + let address = Address::from_slice(&hint.data.as_ref()[8..28]); + let slot = B256::from_slice(&hint.data.as_ref()[28..60]); + let chain_id = u64::from_be_bytes(hint.data.as_ref()[60..68].try_into()?); + (block_number.into(), address, slot, chain_id) + } + BLOCK_HASH_HINT_LEN => { + let block_hash = B256::from_slice(&hint.data.as_ref()[..32]); + let address = Address::from_slice(&hint.data.as_ref()[32..52]); + let slot = B256::from_slice(&hint.data.as_ref()[52..84]); + let chain_id = u64::from_be_bytes(hint.data.as_ref()[84..92].try_into()?); + (block_hash.into(), address, slot, chain_id) + } + other => anyhow::bail!( + "Invalid L2AccountStorageProof hint length: expected {BLOCK_NUMBER_HINT_LEN} or {BLOCK_HASH_HINT_LEN}, got {other}" + ), + }; let mut proof_response = providers .l2(&chain_id)? .get_proof(address, vec![slot]) - .block_id(block_number.into()) + .block_id(block_id) .await?; let mut kv_lock = kv.write().await; @@ -507,12 +540,23 @@ impl HintHandler for InteropHintHandler { PreimageServer::new( OracleServer::new(preimage.host), HintReader::new(hint.host), - Arc::new(backend), + Arc::new(VerifyingPreimageFetcher::new(backend)), ) .start(), ); + // The host re-execution path mirrors the client's + // `sub_transition` fault-proof pipeline, so we must pass the same + // dependency set that the client will derive from BootInfo. Parse + // it from the same path the KV store uses (`--interop-dep-set-path`) + // here so the owned value can move into the spawned task. + let dependency_set = cfg + .read_dependency_set() + .transpose() + .map_err(|e| anyhow!("failed to read interop dep-set: {e}"))? + .map(Arc::new); let client_task = task::spawn({ let l1_head = cfg.l1_head; + let dependency_set = dependency_set.clone(); async move { let oracle = Arc::new(CachingOracle::new( @@ -536,9 +580,12 @@ impl HintHandler for InteropHintHandler { .map(|header| Sealed::new_unchecked(header, agreed_block_hash))?; let target_block = safe_head.number + 1; + // The output root is unused in the host re-execution context, + // which only collects preimages for witness generation. let cursor = new_oracle_pipeline_cursor( rollup_config.as_ref(), safe_head, + B256::ZERO, &mut l1_provider, &mut l2_provider, ) @@ -558,13 +605,14 @@ impl HintHandler for InteropHintHandler { da_provider, l1_provider, l2_provider.clone(), + dependency_set, ) .await?; let executor = KonaExecutor::new( rollup_config.as_ref(), l2_provider.clone(), l2_provider, - OpEvmFactory::>::default(), + OpEvmFactory::::default(), None, ); let mut driver = Driver::new(cursor, executor, pipeline); @@ -620,25 +668,16 @@ impl HintHandler for InteropHintHandler { ); } HintType::L2PayloadWitness => { - // 1. Check feature flag - if !cfg.enable_experimental_witness_endpoint { - warn!( - target: "interop_hint_handler", - "L2PayloadWitness hint was sent, but payload witness is disabled. Skipping hint." - ); - return Ok(()); - } - - // 2. Parse hint data + // 1. Parse hint data let (parent_block_hash, payload_attributes_bytes, chain_id) = parse_l2_payload_witness_hint(&hint.data)?; let payload_attributes: OpPayloadAttributes = serde_json::from_slice(payload_attributes_bytes)?; - // 3. Route to correct L2 provider + // 2. Route to correct L2 provider let l2_provider = providers.l2(&chain_id)?; - // 4. Call debug_executePayload RPC + // 3. Call debug_executePayload RPC let execute_payload_response = match l2_provider .client() .request::<(B256, OpPayloadAttributes), ExecutionWitness>( @@ -660,7 +699,7 @@ impl HintHandler for InteropHintHandler { } }; - // 5. Store preimages in KV store + // 4. Store preimages in KV store let preimages = execute_payload_response .state .into_iter() diff --git a/rust/kona/bin/host/src/interop/local_kv.rs b/rust/kona/bin/host/src/interop/local_kv.rs index fd33d2854cf..8478b5387e6 100644 --- a/rust/kona/bin/host/src/interop/local_kv.rs +++ b/rust/kona/bin/host/src/interop/local_kv.rs @@ -4,10 +4,11 @@ use super::InteropHost; use crate::{KeyValueStore, Result}; use alloy_primitives::{B256, keccak256}; +use kona_interop::DependencySet; use kona_preimage::PreimageKey; use kona_proof_interop::boot::{ - L1_CONFIG_KEY, L1_HEAD_KEY, L2_AGREED_PRE_STATE_KEY, L2_CLAIMED_POST_STATE_KEY, - L2_CLAIMED_TIMESTAMP_KEY, L2_ROLLUP_CONFIG_KEY, + DEPENDENCY_SET_KEY, L1_CONFIG_KEY, L1_HEAD_KEY, L2_AGREED_PRE_STATE_KEY, + L2_CLAIMED_POST_STATE_KEY, L2_CLAIMED_TIMESTAMP_KEY, L2_ROLLUP_CONFIG_KEY, }; /// A simple, synchronous key-value store that returns data from a [`InteropHost`] config. @@ -41,6 +42,16 @@ impl KeyValueStore for InteropLocalInputs { let l1_config = self.cfg.read_l1_config().ok()?; serde_json::to_vec(&l1_config).ok() } + DEPENDENCY_SET_KEY => { + let dependency_set = + self.cfg.read_dependency_set().and_then(|r| r.ok()).unwrap_or_else(|| { + DependencySet { + dependencies: Default::default(), + override_message_expiry_window: None, + } + }); + serde_json::to_vec(&dependency_set).ok() + } _ => None, } } diff --git a/rust/kona/bin/host/src/kv/directory.rs b/rust/kona/bin/host/src/kv/directory.rs new file mode 100644 index 00000000000..0160067f86f --- /dev/null +++ b/rust/kona/bin/host/src/kv/directory.rs @@ -0,0 +1,142 @@ +//! Contains a concrete implementation of the [`KeyValueStore`] trait that stores data on disk +//! using a directory-based layout compatible with op-challenger's `DataFormatDirectory`. + +use super::{DataFormat, FORMAT_FILENAME, KeyValueStore}; +use crate::{HostError, Result}; +use alloy_primitives::{B256, hex}; +use std::{ + fs, + io::Write, + path::{Path, PathBuf}, +}; + +/// A key-value store that writes preimages as hex-encoded files in subdirectories. +/// +/// Layout is compatible with op-challenger's `directoryKV`: +/// - Key `0x0123456789...abc` maps to `/0123/456789...abc.txt` +/// - Values are hex-encoded on disk +/// - A `kvformat` marker file containing `"directory"` is written for op-challenger compatibility +#[derive(Debug)] +pub struct DirectoryKeyValueStore { + data_directory: PathBuf, +} + +impl DirectoryKeyValueStore { + /// Create a new [`DirectoryKeyValueStore`] with the given data directory. + pub fn new(data_directory: &Path) -> Self { + fs::create_dir_all(data_directory) + .unwrap_or_else(|e| panic!("Failed to create directory {data_directory:?}: {e}")); + + let format_path = data_directory.join(FORMAT_FILENAME); + if !format_path.exists() { + fs::write(&format_path, DataFormat::Directory.as_str()) + .unwrap_or_else(|e| panic!("Failed to write kvformat marker: {e}")); + } + + Self { data_directory: data_directory.to_path_buf() } + } + + /// Returns the file path for the given key. + /// + /// The hex key (without `0x` prefix) is split into a 4-char directory prefix and the + /// remainder as the filename with `.txt` extension. This matches op-challenger's layout. + fn key_path(&self, key: B256) -> PathBuf { + let hex_key = format!("{key:x}"); + let (dir_part, file_part) = hex_key.split_at(4); + self.data_directory.join(dir_part).join(format!("{file_part}.txt")) + } +} + +impl KeyValueStore for DirectoryKeyValueStore { + fn get(&self, key: B256) -> Option> { + let path = self.key_path(key); + let data = fs::read_to_string(&path).ok()?; + match hex::decode(&data) { + Ok(value) => Some(value), + Err(e) => { + tracing::warn!(key = %key, path = %path.display(), error = %e, "Corrupt preimage file, ignoring"); + None + } + } + } + + fn set(&mut self, key: B256, value: Vec) -> Result<()> { + let path = self.key_path(key); + let parent = path.parent().ok_or_else(|| { + HostError::KeyValueSetFailed(format!("no parent directory for {path:?}")) + })?; + fs::create_dir_all(parent).map_err(|e| { + HostError::KeyValueSetFailed(format!("failed to create directory {parent:?}: {e}")) + })?; + + // Write to a temp file and rename for atomicity — a crash during fs::write could leave + // a partially written (corrupt) file, but rename is atomic on POSIX. + let mut tmp = tempfile::NamedTempFile::new_in(parent).map_err(|e| { + HostError::KeyValueSetFailed(format!("failed to create temp file in {parent:?}: {e}")) + })?; + tmp.write_all(hex::encode(&value).as_bytes()) + .map_err(|e| HostError::KeyValueSetFailed(format!("failed to write temp file: {e}")))?; + tmp.persist(&path).map_err(|e| { + HostError::KeyValueSetFailed(format!("failed to rename temp file to {path:?}: {e}")) + })?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::DirectoryKeyValueStore; + use crate::kv::KeyValueStore; + use alloy_primitives::B256; + use proptest::{ + arbitrary::any, + collection::{hash_map, vec}, + proptest, + test_runner::Config, + }; + + proptest! { + #![proptest_config(Config::with_cases(16))] + + #[test] + fn directory_kv_roundtrip(k_v in hash_map(any::<[u8; 32]>(), vec(any::(), 0..128), 1..128)) { + let tempdir = tempfile::TempDir::new().unwrap(); + let mut kv = DirectoryKeyValueStore::new(tempdir.path()); + + for (k, v) in &k_v { + kv.set((*k).into(), v.clone()).unwrap(); + } + + for (k, v) in &k_v { + let key: B256 = (*k).into(); + assert_eq!(kv.get(key).unwrap(), *v); + } + } + } + + #[test] + fn writes_kvformat_marker() { + let tempdir = tempfile::TempDir::new().unwrap(); + let _kv = DirectoryKeyValueStore::new(tempdir.path()); + + let marker = std::fs::read_to_string(tempdir.path().join("kvformat")).unwrap(); + assert_eq!(marker, "directory"); + } + + #[test] + fn key_path_layout() { + let tempdir = tempfile::TempDir::new().unwrap(); + let kv = DirectoryKeyValueStore::new(tempdir.path()); + + let key = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + .parse::() + .unwrap(); + let path = kv.key_path(key); + + let expected = tempdir + .path() + .join("0123") + .join("456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef.txt"); + assert_eq!(path, expected); + } +} diff --git a/rust/kona/bin/host/src/kv/disk.rs b/rust/kona/bin/host/src/kv/disk.rs index 00d4bb96fd5..88d8448b393 100644 --- a/rust/kona/bin/host/src/kv/disk.rs +++ b/rust/kona/bin/host/src/kv/disk.rs @@ -1,7 +1,7 @@ //! Contains a concrete implementation of the [`KeyValueStore`] trait that stores data on disk //! using [rocksdb]. -use super::{KeyValueStore, MemoryKeyValueStore}; +use super::{DataFormat, FORMAT_FILENAME, KeyValueStore, MemoryKeyValueStore}; use crate::{HostError, Result}; use alloy_primitives::B256; use rocksdb::{DB, Options}; @@ -20,6 +20,13 @@ impl DiskKeyValueStore { let db = DB::open(&Self::get_db_options(), data_directory.as_path()) .unwrap_or_else(|e| panic!("Failed to open database at {data_directory:?}: {e}")); + // Write the kvformat marker file for op-challenger compatibility. + let format_path = data_directory.join(FORMAT_FILENAME); + if !format_path.exists() { + std::fs::write(&format_path, DataFormat::Rocksdb.as_str()) + .unwrap_or_else(|e| panic!("Failed to write kvformat marker: {e}")); + } + Self { data_directory, db } } @@ -77,16 +84,14 @@ mod test { proptest, test_runner::Config, }; - use std::env::temp_dir; - proptest! { #![proptest_config(Config::with_cases(16))] /// Test that converting from a [DiskKeyValueStore] to a [MemoryKeyValueStore] is lossless. #[test] fn convert_disk_kv_to_mem_kv(k_v in hash_map(any::<[u8; 32]>(), vec(any::(), 0..128), 1..128)) { - let tempdir = temp_dir(); - let mut disk_kv = DiskKeyValueStore::new(tempdir); + let tempdir = tempfile::TempDir::new().unwrap(); + let mut disk_kv = DiskKeyValueStore::new(tempdir.path().to_path_buf()); for (k, v) in &k_v { disk_kv.set(k.into(), v.clone()).unwrap(); } diff --git a/rust/kona/bin/host/src/kv/mod.rs b/rust/kona/bin/host/src/kv/mod.rs index 45468e79c8e..3dc67acf97b 100644 --- a/rust/kona/bin/host/src/kv/mod.rs +++ b/rust/kona/bin/host/src/kv/mod.rs @@ -2,7 +2,7 @@ use crate::Result; use alloy_primitives::B256; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; use tokio::sync::RwLock; mod mem; @@ -11,12 +11,90 @@ pub use mem::MemoryKeyValueStore; mod disk; pub use disk::DiskKeyValueStore; +mod directory; +pub use directory::DirectoryKeyValueStore; + mod split; pub use split::SplitKeyValueStore; +/// The filename used to record the storage format, for compatibility with op-challenger. +const FORMAT_FILENAME: &str = "kvformat"; + +/// The storage format for on-disk preimage data. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::ValueEnum, serde::Serialize)] +pub enum DataFormat { + /// Files stored in subdirectories with hex-encoded values. + /// Compatible with op-challenger's `DataFormatDirectory`. + #[default] + Directory, + /// RocksDB-backed storage. + Rocksdb, +} + +impl DataFormat { + /// Returns the string identifier written to the `kvformat` marker file. + const fn as_str(self) -> &'static str { + match self { + Self::Directory => "directory", + Self::Rocksdb => "rocksdb", + } + } +} + +/// Reads the `kvformat` marker file from the given directory. If the marker exists and contains +/// a supported format, returns that format. Otherwise, returns `default_format`. The marker file +/// is written by the individual store implementations (`DirectoryKeyValueStore`, +/// `DiskKeyValueStore`) when they initialize. +pub(crate) fn detect_data_format(data_dir: &Path, default_format: DataFormat) -> DataFormat { + let format_path = data_dir.join(FORMAT_FILENAME); + std::fs::read_to_string(&format_path).map_or(default_format, |contents| { + match contents.as_str() { + "directory" => DataFormat::Directory, + "rocksdb" => DataFormat::Rocksdb, + other => { + tracing::warn!(format = other, "Unknown kvformat marker, using CLI default"); + default_format + } + } + }) +} + /// A type alias for a shared key-value store. pub type SharedKeyValueStore = Arc>; +/// Creates a [`SharedKeyValueStore`] backed by disk or memory. +/// +/// If `data_dir` is provided, the format is auto-detected from any existing `kvformat` marker file, +/// falling back to `default_format`. Otherwise a [`MemoryKeyValueStore`] is used. +pub(crate) fn create_key_value_store( + local_kv_store: L, + data_dir: Option<&Path>, + default_format: DataFormat, +) -> SharedKeyValueStore +where + L: KeyValueStore + Send + Sync + 'static, +{ + match data_dir { + Some(data_dir) => { + let format = detect_data_format(data_dir, default_format); + match format { + DataFormat::Directory => { + let dir_kv_store = DirectoryKeyValueStore::new(data_dir); + Arc::new(RwLock::new(SplitKeyValueStore::new(local_kv_store, dir_kv_store))) + } + DataFormat::Rocksdb => { + let disk_kv_store = DiskKeyValueStore::new(data_dir.to_path_buf()); + Arc::new(RwLock::new(SplitKeyValueStore::new(local_kv_store, disk_kv_store))) + } + } + } + None => { + let mem_kv_store = MemoryKeyValueStore::new(); + Arc::new(RwLock::new(SplitKeyValueStore::new(local_kv_store, mem_kv_store))) + } + } +} + /// Describes the interface of a simple, synchronous key-value store. pub trait KeyValueStore { /// Get the value associated with the given key. @@ -25,3 +103,44 @@ pub trait KeyValueStore { /// Set the value associated with the given key. fn set(&mut self, key: B256, value: Vec) -> Result<()>; } + +#[cfg(test)] +mod test { + use super::*; + use std::fs; + + #[test] + fn detect_reads_existing_directory_marker() { + let dir = tempfile::TempDir::new().unwrap(); + fs::write(dir.path().join("kvformat"), "directory").unwrap(); + + let format = detect_data_format(dir.path(), DataFormat::Rocksdb); + assert_eq!(format, DataFormat::Directory); + } + + #[test] + fn detect_reads_existing_rocksdb_marker() { + let dir = tempfile::TempDir::new().unwrap(); + fs::write(dir.path().join("kvformat"), "rocksdb").unwrap(); + + let format = detect_data_format(dir.path(), DataFormat::Directory); + assert_eq!(format, DataFormat::Rocksdb); + } + + #[test] + fn detect_falls_back_to_default_when_no_marker() { + let dir = tempfile::TempDir::new().unwrap(); + + assert_eq!(detect_data_format(dir.path(), DataFormat::Directory), DataFormat::Directory); + assert_eq!(detect_data_format(dir.path(), DataFormat::Rocksdb), DataFormat::Rocksdb); + } + + #[test] + fn detect_falls_back_to_default_for_unknown_format() { + let dir = tempfile::TempDir::new().unwrap(); + fs::write(dir.path().join("kvformat"), "pebble").unwrap(); + + let format = detect_data_format(dir.path(), DataFormat::Directory); + assert_eq!(format, DataFormat::Directory); + } +} diff --git a/rust/kona/bin/host/src/lib.rs b/rust/kona/bin/host/src/lib.rs index 623ba04ac6a..79fbfd7ed53 100644 --- a/rust/kona/bin/host/src/lib.rs +++ b/rust/kona/bin/host/src/lib.rs @@ -7,9 +7,10 @@ pub use error::{HostError, Result}; mod server; pub use server::{PreimageServer, PreimageServerError}; -mod kv; +pub(crate) mod kv; pub use kv::{ - DiskKeyValueStore, KeyValueStore, MemoryKeyValueStore, SharedKeyValueStore, SplitKeyValueStore, + DataFormat, DirectoryKeyValueStore, DiskKeyValueStore, KeyValueStore, MemoryKeyValueStore, + SharedKeyValueStore, SplitKeyValueStore, }; mod backend; diff --git a/rust/kona/bin/host/src/single/cfg.rs b/rust/kona/bin/host/src/single/cfg.rs index d14057194ee..4e5f2edaeaa 100644 --- a/rust/kona/bin/host/src/single/cfg.rs +++ b/rust/kona/bin/host/src/single/cfg.rs @@ -2,9 +2,11 @@ use super::{SingleChainHintHandler, SingleChainLocalInputs}; use crate::{ - DiskKeyValueStore, MemoryKeyValueStore, OfflineHostBackend, OnlineHostBackend, - OnlineHostBackendCfg, PreimageServer, SharedKeyValueStore, SplitKeyValueStore, - eth::rpc_provider, server::PreimageServerError, + OfflineHostBackend, OnlineHostBackend, OnlineHostBackendCfg, PreimageServer, + SharedKeyValueStore, + eth::rpc_provider, + kv::{DataFormat, create_key_value_store}, + server::PreimageServerError, }; use alloy_primitives::B256; use alloy_provider::RootProvider; @@ -13,6 +15,7 @@ use kona_cli::cli_styles; use kona_genesis::{L1ChainConfig, RollupConfig}; use kona_preimage::{ BidirectionalChannel, Channel, HintReader, HintWriter, OracleReader, OracleServer, + VerifyingPreimageFetcher, }; use kona_proof::HintType; use kona_providers_alloy::{OnlineBeaconClient, OnlineBlobProvider}; @@ -20,10 +23,7 @@ use kona_std_fpvm::{FileChannel, FileDescriptor}; use op_alloy_network::Optimism; use serde::Serialize; use std::{path::PathBuf, sync::Arc}; -use tokio::{ - sync::RwLock, - task::{self, JoinHandle}, -}; +use tokio::task::{self, JoinHandle}; /// The host binary CLI application arguments. #[derive(Default, Parser, Serialize, Clone, Debug)] @@ -80,6 +80,10 @@ pub struct SingleChainHost { env )] pub data_dir: Option, + /// The default format for preimage data storage on disk. If the data directory already + /// contains a `kvformat` marker file, that format is used instead of this value. + #[arg(long, default_value = "directory", env)] + pub data_format: DataFormat, /// Run the client program natively. #[arg(long, conflicts_with = "server", required_unless_present = "server")] pub native: bool, @@ -110,10 +114,6 @@ pub struct SingleChainHost { /// look up the config in the known l1 configs. #[arg(long, alias = "l1-cfg", env)] pub l1_config_path: Option, - /// Optionally enables the use of `debug_executePayload` to collect the execution witness from - /// the execution layer. - #[arg(long, env)] - pub enable_experimental_witness_endpoint: bool, } /// An error that can occur when handling single chain hosts @@ -172,7 +172,7 @@ impl SingleChainHost { PreimageServer::new( OracleServer::new(preimage), HintReader::new(hint), - Arc::new(OfflineHostBackend::new(kv_store)), + Arc::new(VerifyingPreimageFetcher::new(OfflineHostBackend::new(kv_store))), ) .start() .await @@ -192,7 +192,7 @@ impl SingleChainHost { PreimageServer::new( OracleServer::new(preimage), HintReader::new(hint), - Arc::new(backend), + Arc::new(VerifyingPreimageFetcher::new(backend)), ) .start() .await @@ -253,20 +253,12 @@ impl SingleChainHost { } /// Creates the key-value store for the host backend. + /// + /// If the data directory contains a `kvformat` marker file, the recorded format is used to + /// ensure compatibility with existing data. Otherwise, `--data-format` is used as the default. pub fn create_key_value_store(&self) -> Result { let local_kv_store = SingleChainLocalInputs::new(self.clone()); - - let kv_store: SharedKeyValueStore = if let Some(ref data_dir) = self.data_dir { - let disk_kv_store = DiskKeyValueStore::new(data_dir.clone()); - let split_kv_store = SplitKeyValueStore::new(local_kv_store, disk_kv_store); - Arc::new(RwLock::new(split_kv_store)) - } else { - let mem_kv_store = MemoryKeyValueStore::new(); - let split_kv_store = SplitKeyValueStore::new(local_kv_store, mem_kv_store); - Arc::new(RwLock::new(split_kv_store)) - }; - - Ok(kv_store) + Ok(create_key_value_store(local_kv_store, self.data_dir.as_deref(), self.data_format)) } /// Creates the providers required for the host backend. @@ -354,18 +346,6 @@ mod test { .as_slice(), true, ), - ( - [ - "--server", - "--l2-chain-id", - "0", - "--data-dir", - "dummy", - "--enable-experimental-witness-endpoint", - ] - .as_slice(), - true, - ), // invalid (["--server", "--native", "--l2-chain-id", "0"].as_slice(), false), (["--l2-chain-id", "0", "--rollup-config-path", "dummy", "--server"].as_slice(), false), diff --git a/rust/kona/bin/host/src/single/handler.rs b/rust/kona/bin/host/src/single/handler.rs index e221d35b25e..ac3ca6dcb58 100644 --- a/rust/kona/bin/host/src/single/handler.rs +++ b/rust/kona/bin/host/src/single/handler.rs @@ -316,15 +316,31 @@ impl HintHandler for SingleChainHintHandler { kv_write_lock.set(PreimageKey::new_keccak256(*hash).into(), preimage.into())?; } HintType::L2AccountProof => { - ensure!(hint.data.len() == 8 + 20, "Invalid hint data length"); - - let block_number = u64::from_be_bytes(hint.data.as_ref()[..8].try_into()?); - let address = Address::from_slice(&hint.data.as_ref()[8..28]); + // Backwards compatibility: old prestates send an 8-byte block number; new + // prestates send a 32-byte block hash. A single kona-host version serves all + // games. + const BLOCK_NUMBER_HINT_LEN: usize = 8 + 20; + const BLOCK_HASH_HINT_LEN: usize = 32 + 20; + let block_id = match hint.data.len() { + BLOCK_NUMBER_HINT_LEN => { + let block_number = u64::from_be_bytes(hint.data.as_ref()[..8].try_into()?); + let address = Address::from_slice(&hint.data.as_ref()[8..28]); + (block_number.into(), address) + } + BLOCK_HASH_HINT_LEN => { + let block_hash = B256::from_slice(&hint.data.as_ref()[..32]); + let address = Address::from_slice(&hint.data.as_ref()[32..52]); + (block_hash.into(), address) + } + other => anyhow::bail!( + "Invalid L2AccountProof hint length: expected {BLOCK_NUMBER_HINT_LEN} or {BLOCK_HASH_HINT_LEN}, got {other}" + ), + }; let proof_response = providers .l2 - .get_proof(address, Default::default()) - .block_id(block_number.into()) + .get_proof(block_id.1, Default::default()) + .block_id(block_id.0) .await?; // Write the account proof nodes to the key-value store. @@ -337,17 +353,31 @@ impl HintHandler for SingleChainHintHandler { })?; } HintType::L2AccountStorageProof => { - ensure!(hint.data.len() == 8 + 20 + 32, "Invalid hint data length"); - - let block_number = u64::from_be_bytes(hint.data.as_ref()[..8].try_into()?); - let address = Address::from_slice(&hint.data.as_ref()[8..28]); - let slot = B256::from_slice(&hint.data.as_ref()[28..]); + // Backwards compatibility: old prestates send an 8-byte block number; new + // prestates send a 32-byte block hash. A single kona-host version serves all + // games. + const BLOCK_NUMBER_HINT_LEN: usize = 8 + 20 + 32; + const BLOCK_HASH_HINT_LEN: usize = 32 + 20 + 32; + let (block_id, address, slot) = match hint.data.len() { + BLOCK_NUMBER_HINT_LEN => { + let block_number = u64::from_be_bytes(hint.data.as_ref()[..8].try_into()?); + let address = Address::from_slice(&hint.data.as_ref()[8..28]); + let slot = B256::from_slice(&hint.data.as_ref()[28..60]); + (block_number.into(), address, slot) + } + BLOCK_HASH_HINT_LEN => { + let block_hash = B256::from_slice(&hint.data.as_ref()[..32]); + let address = Address::from_slice(&hint.data.as_ref()[32..52]); + let slot = B256::from_slice(&hint.data.as_ref()[52..84]); + (block_hash.into(), address, slot) + } + other => anyhow::bail!( + "Invalid L2AccountStorageProof hint length: expected {BLOCK_NUMBER_HINT_LEN} or {BLOCK_HASH_HINT_LEN}, got {other}" + ), + }; - let mut proof_response = providers - .l2 - .get_proof(address, vec![slot]) - .block_id(block_number.into()) - .await?; + let mut proof_response = + providers.l2.get_proof(address, vec![slot]).block_id(block_id).await?; let mut kv_lock = kv.write().await; @@ -369,14 +399,6 @@ impl HintHandler for SingleChainHintHandler { })?; } HintType::L2PayloadWitness => { - if !cfg.enable_experimental_witness_endpoint { - warn!( - target: "single_hint_handler", - "L2PayloadWitness hint was sent, but payload witness is disabled. Skipping hint." - ); - return Ok(()); - } - ensure!(hint.data.len() >= 32, "Invalid hint data length"); let parent_block_hash = B256::from_slice(&hint.data.as_ref()[..32]); diff --git a/rust/kona/bin/node/Cargo.toml b/rust/kona/bin/node/Cargo.toml index 9feedad43a8..41a5e807b3f 100644 --- a/rust/kona/bin/node/Cargo.toml +++ b/rust/kona/bin/node/Cargo.toml @@ -19,6 +19,7 @@ workspace = true kona-rpc.workspace = true kona-peers.workspace = true kona-genesis = { workspace = true, features = ["tabled"] } +kona-interop = { workspace = true, features = ["serde"] } kona-protocol.workspace = true kona-cli = { workspace = true, features = ["secrets"] } @@ -49,7 +50,6 @@ op-alloy-rpc-types-engine = { workspace = true, features = ["serde"] } # general url.workspace = true -http.workspace = true dirs.workspace = true strum.workspace = true discv5.workspace = true @@ -71,9 +71,6 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } backon = { workspace = true, features = ["std", "tokio", "tokio-sleep"] } tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } -# rollup boost -rollup-boost.workspace = true - [dev-dependencies] rstest.workspace = true tempfile.workspace = true diff --git a/rust/kona/bin/node/src/commands/net.rs b/rust/kona/bin/node/src/commands/net.rs index 4a8478ee1f1..a0238c65e45 100644 --- a/rust/kona/bin/node/src/commands/net.rs +++ b/rust/kona/bin/node/src/commands/net.rs @@ -7,14 +7,12 @@ use jsonrpsee::{RpcModule, core::async_trait, server::Server}; use kona_cli::LogConfig; use kona_gossip::P2pRpcRequest; use kona_node_service::{ - EngineClientResult, NetworkActor, NetworkBuilder, NetworkEngineClient, NetworkInboundData, - NodeActor, + EngineClientResult, NetworkActor, NetworkBuilder, NetworkEngineClient, NodeActor, }; use kona_registry::scr_rollup_config_by_alloy_ident; use kona_rpc::{OpP2PApiServer, P2pRpc, RpcBuilder}; use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; use tokio::sync::mpsc; -use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; use url::Url; @@ -74,13 +72,34 @@ impl NetCommand { let p2p_config = self.p2p.config(rollup_config, args, self.l1_eth_rpc).await?; let (block_tx, mut block_rx) = mpsc::channel(1024); - let (NetworkInboundData { p2p_rpc: rpc, .. }, network) = NetworkActor::new( + let (signer_tx, signer_rx) = mpsc::channel(16); + let (rpc, p2p_rpc_rx) = mpsc::channel(1024); + let (admin_rpc_tx, admin_rpc_rx) = mpsc::channel(1024); + let (gossip_payload_tx, gossip_payload_rx) = mpsc::channel(256); + // signer_tx, admin_rpc_tx, gossip_payload_tx are not used by this single-purpose binary — + // they exist solely to satisfy NetworkActor::new and are held to keep the channels open. + let _unused_senders = (signer_tx, admin_rpc_tx, gossip_payload_tx); + + let handler = NetworkBuilder::from(p2p_config).build()?.start().await?; + + let mut network = NetworkActor::new( ForwardingNetworkEngineClient { block_tx }, - CancellationToken::new(), - NetworkBuilder::from(p2p_config), + handler, + signer_rx, + p2p_rpc_rx, + admin_rpc_rx, + gossip_payload_rx, ); - network.start(()).await?; + // Spawn the actor; the loop below polls the p2p RPC interface on an interval. + tokio::spawn(async move { + loop { + if let Err(e) = network.step().await { + error!(target: "net", "Network actor error: {e:?}"); + return; + } + } + }); info!(target: "net", "Network started, receiving blocks."); diff --git a/rust/kona/bin/node/src/commands/node.rs b/rust/kona/bin/node/src/commands/node.rs index 093e7ee6fd4..e8b11bf7986 100644 --- a/rust/kona/bin/node/src/commands/node.rs +++ b/rust/kona/bin/node/src/commands/node.rs @@ -2,8 +2,8 @@ use crate::{ flags::{ - BuilderClientArgs, DerivationDelegateArgs, GlobalArgs, L1ClientArgs, L2ClientArgs, P2PArgs, - RollupBoostFlags, RpcArgs, SequencerArgs, + DerivationDelegateArgs, GlobalArgs, L1ClientArgs, L2ClientArgs, P2PArgs, RpcArgs, + SequencerArgs, }, metrics::{CliMetrics, init_rollup_config_metrics}, }; @@ -16,12 +16,13 @@ use clap::Parser; use kona_cli::{LogConfig, MetricsArgs}; use kona_engine::{HyperAuthClient, OpEngineClient}; use kona_genesis::{L1ChainConfig, RollupConfig}; +use kona_interop::DependencySet; use kona_node_service::{EngineConfig, L1ConfigBuilder, NodeMode, RollupNodeBuilder}; use kona_registry::{L1Config, scr_rollup_config_by_alloy_ident}; use op_alloy_network::Optimism; use op_alloy_provider::ext::engine::OpEngineApi; use serde_json::from_reader; -use std::{fs::File, io::Write, path::PathBuf, sync::Arc, time::Duration}; +use std::{fs::File, io::Write, path::PathBuf, sync::Arc}; use strum::IntoEnumIterator; use tracing::{debug, error, info}; @@ -94,10 +95,6 @@ pub struct NodeCommand { #[clap(flatten)] pub l2_client_args: L2ClientArgs, - /// Optional block builder client. - #[clap(flatten)] - pub builder_client_args: BuilderClientArgs, - /// Optional derivation delegation client. #[clap(flatten)] pub derivation_delegate_args: DerivationDelegateArgs, @@ -110,6 +107,13 @@ pub struct NodeCommand { /// (overrides the default rollup configuration from the registry) #[arg(long, visible_alias = "rollup-l1-cfg", env = "KONA_NODE_L1_CHAIN_CONFIG")] pub l1_config_file: Option, + /// Path to a JSON file describing the interop dependency set for this + /// chain. Mirrors op-node's `--interop.dependency-set`. Required when the + /// rollup config schedules the Interop hardfork; the inner + /// `StatefulAttributesBuilder` constructor panics otherwise; turning a + /// silent state-divergence bug into a startup crash. + #[arg(long = "interop.dependency-set", env = "KONA_NODE_INTEROP_DEPENDENCY_SET")] + pub interop_dependency_set: Option, /// P2P CLI arguments. #[command(flatten)] pub p2p_flags: P2PArgs, @@ -119,10 +123,6 @@ pub struct NodeCommand { /// SEQUENCER CLI arguments. #[command(flatten)] pub sequencer_flags: SequencerArgs, - - /// Rollup boost CLI arguments - contains the builder and l2 engine arguments. - #[command(flatten)] - pub rollup_boost_flags: RollupBoostFlags, } impl Default for NodeCommand { @@ -130,15 +130,14 @@ impl Default for NodeCommand { Self { l1_rpc_args: L1ClientArgs::default(), l2_client_args: L2ClientArgs::default(), - builder_client_args: BuilderClientArgs::default(), derivation_delegate_args: DerivationDelegateArgs::default(), l2_config_file: None, l1_config_file: None, + interop_dependency_set: None, node_mode: NodeMode::Validator, p2p_flags: P2PArgs::default(), rpc_flags: RpcArgs::default(), sequencer_flags: SequencerArgs::default(), - rollup_boost_flags: RollupBoostFlags::default(), } } } @@ -308,17 +307,14 @@ impl NodeCommand { let engine_config = EngineConfig { config: Arc::new(cfg.clone()), - builder_url: self.builder_client_args.l2_builder_rpc.clone(), - builder_jwt_secret: self.builder_jwt_secret()?, - builder_timeout: Duration::from_millis(self.builder_client_args.builder_timeout), l2_url: self.l2_client_args.l2_engine_rpc.clone(), l2_jwt_secret: jwt_secret, - l2_timeout: Duration::from_millis(self.l2_client_args.l2_engine_timeout), l1_url: self.l1_rpc_args.l1_eth_rpc.clone(), mode: self.node_mode, - rollup_boost: self.rollup_boost_flags.as_rollup_boost_args(), }; + let dependency_set = self.load_dependency_set(&cfg)?; + RollupNodeBuilder::new( cfg, l1_config, @@ -329,6 +325,7 @@ impl NodeCommand { ) .with_sequencer_config(self.sequencer_flags.config()) .with_derivation_delegate_config(self.derivation_delegate_args.config()) + .with_dependency_set(dependency_set) .build() .start() .await @@ -340,6 +337,33 @@ impl NodeCommand { Ok(()) } + /// Loads the interop [`DependencySet`] from `--interop.dependency-set`. + /// + /// Enforces the invariant that when the rollup config schedules the + /// Interop hardfork, the operator must supply a dependency-set JSON file. + /// Errors rather than panicking so the operator sees a clear message. + fn load_dependency_set(&self, cfg: &RollupConfig) -> Result>> { + match &self.interop_dependency_set { + Some(path) => { + let file = File::open(path).map_err(|e| { + anyhow::anyhow!("Failed to open interop dependency-set file {path:?}: {e}") + })?; + let dep_set: DependencySet = from_reader(file).map_err(|e| { + anyhow::anyhow!("Failed to parse interop dependency-set {path:?}: {e}") + })?; + Ok(Some(Arc::new(dep_set))) + } + None if cfg.hardforks.interop_time.is_some() => bail!( + "Interop is scheduled for this chain (interop_time = {:?}), but \ + --interop.dependency-set was not provided. Supply the dependency-set \ + JSON file matching op-node's --interop.dependency-set to avoid silent \ + state divergence on interop activation.", + cfg.hardforks.interop_time, + ), + None => Ok(None), + } + } + /// Get the L1 config, either from a file or the known chains. pub fn get_l1_config(&self, l1_chain_id: u64) -> Result { match &self.l1_config_file { @@ -396,24 +420,6 @@ impl NodeCommand { Self::default_jwt_secret("l2_jwt.hex") } - /// Returns the builder JWT secret for the engine API - /// using the provided [`PathBuf`]. If the file is not found, - /// it will return the default JWT secret. - pub fn builder_jwt_secret(&self) -> anyhow::Result { - if let Some(path) = &self.builder_client_args.builder_jwt_path && - let Ok(secret) = std::fs::read_to_string(path) - { - return JwtSecret::from_hex(secret) - .map_err(|e| anyhow::anyhow!("Failed to parse JWT secret: {e}")); - } - - if let Some(secret) = &self.builder_client_args.builder_jwt_secret { - return Ok(*secret); - } - - Self::default_jwt_secret("builder_jwt.hex") - } - /// Uses the current directory to attempt to read /// the JWT secret from a file named `file_name`. /// If the file is not found, it will create a new random JWT secret and write it to the file. diff --git a/rust/kona/bin/node/src/flags/engine/flashblocks.rs b/rust/kona/bin/node/src/flags/engine/flashblocks.rs deleted file mode 100644 index dbbb8d3b4d9..00000000000 --- a/rust/kona/bin/node/src/flags/engine/flashblocks.rs +++ /dev/null @@ -1,161 +0,0 @@ -use clap::Parser; -use reqwest::Url; - -const DEFAULT_FLASHBLOCKS_BUILDER_URL: &str = "ws://localhost:1111"; -const DEFAULT_FLASHBLOCKS_HOST: &str = "localhost"; -const DEFAULT_FLASHBLOCKS_PORT: u16 = 1112; - -const DEFAULT_FLASHBLOCKS_BUILDER_WS_INITIAL_RECONNECT_MS: u64 = 10; -const DEFAULT_FLASHBLOCKS_BUILDER_WS_MAX_RECONNECT_MS: u64 = 5000; -const DEFAULT_FLASHBLOCKS_BUILDER_WS_CONNECT_TIMEOUT_MS: u64 = 5000; -const DEFAULT_FLASHBLOCKS_BUILDER_WS_PING_INTERVAL_MS: u64 = 500; -const DEFAULT_FLASHBLOCKS_BUILDER_WS_PONG_TIMEOUT_MS: u64 = 1500; - -/// Flashblocks flags. -#[derive(Clone, Debug, clap::Args)] -pub struct FlashblocksFlags { - /// Enable Flashblocks client - #[arg( - long, - visible_alias = "rollup-boost.flashblocks", - env = "KONA_NODE_FLASHBLOCKS", - default_value = "false" - )] - pub flashblocks: bool, - - /// Flashblocks Builder `WebSocket` URL - #[arg( - long, - visible_alias = "rollup-boost.flashblocks-builder-url", - env = "KONA_NODE_FLASHBLOCKS_BUILDER_URL", - default_value = DEFAULT_FLASHBLOCKS_BUILDER_URL - )] - pub flashblocks_builder_url: Url, - - /// Flashblocks `WebSocket` host for outbound connections - #[arg( - long, - visible_alias = "rollup-boost.flashblocks-host", - env = "KONA_NODE_FLASHBLOCKS_HOST", - default_value = DEFAULT_FLASHBLOCKS_HOST - )] - pub flashblocks_host: String, - - /// Flashblocks `WebSocket` port for outbound connections - #[arg( - long, - visible_alias = "rollup-boost.flashblocks-port", - env = "KONA_NODE_FLASHBLOCKS_PORT", - default_value_t = DEFAULT_FLASHBLOCKS_PORT - )] - pub flashblocks_port: u16, - - /// Websocket connection configuration - #[command(flatten)] - pub flashblocks_ws_config: FlashblocksWebsocketFlags, -} - -impl Default for FlashblocksFlags { - fn default() -> Self { - Self { - flashblocks: false, - flashblocks_builder_url: Url::parse(DEFAULT_FLASHBLOCKS_BUILDER_URL).unwrap(), - flashblocks_host: DEFAULT_FLASHBLOCKS_HOST.to_string(), - flashblocks_port: DEFAULT_FLASHBLOCKS_PORT, - flashblocks_ws_config: FlashblocksWebsocketFlags::default(), - } - } -} - -/// Configuration for the Flashblocks `WebSocket` connection. -#[derive(Parser, Debug, Clone, Copy)] -pub struct FlashblocksWebsocketFlags { - /// Minimum time for exponential backoff for timeout if builder disconnected - #[arg( - long, - visible_alias = "rollup-boost.flashblocks-initial-reconnect-ms", - env = "KONA_NODE_FLASHBLOCKS_BUILDER_WS_INITIAL_RECONNECT_MS", - default_value_t = DEFAULT_FLASHBLOCKS_BUILDER_WS_INITIAL_RECONNECT_MS - )] - pub flashblock_builder_ws_initial_reconnect_ms: u64, - - /// Maximum time for exponential backoff for timeout if builder disconnected - #[arg( - long, - visible_alias = "rollup-boost.flashblocks-max-reconnect-ms", - env = "KONA_NODE_FLASHBLOCKS_BUILDER_WS_MAX_RECONNECT_MS", - default_value_t = DEFAULT_FLASHBLOCKS_BUILDER_WS_MAX_RECONNECT_MS - )] - pub flashblock_builder_ws_max_reconnect_ms: u64, - - /// Timeout for connection attempt - #[arg( - long, - visible_alias = "rollup-boost.flashblocks-connect-timeout-ms", - env = "KONA_NODE_FLASHBLOCKS_BUILDER_WS_CONNECT_TIMEOUT_MS", - default_value_t = DEFAULT_FLASHBLOCKS_BUILDER_WS_CONNECT_TIMEOUT_MS - )] - pub flashblock_builder_ws_connect_timeout_ms: u64, - - /// Interval in milliseconds between ping messages sent to upstream servers to detect - /// unresponsive connections - #[arg( - long, - visible_alias = "rollup-boost.flashblocks-ping-interval-ms", - env = "KONA_NODE_FLASHBLOCKS_BUILDER_WS_PING_INTERVAL_MS", - default_value_t = DEFAULT_FLASHBLOCKS_BUILDER_WS_PING_INTERVAL_MS - )] - pub flashblock_builder_ws_ping_interval_ms: u64, - - /// Timeout in milliseconds to wait for pong responses from upstream servers before considering - /// the connection dead - #[arg( - long, - visible_alias = "rollup-boost.flashblocks-pong-timeout-ms", - env = "KONA_NODE_FLASHBLOCKS_BUILDER_WS_PONG_TIMEOUT_MS", - default_value_t = DEFAULT_FLASHBLOCKS_BUILDER_WS_PONG_TIMEOUT_MS - )] - pub flashblock_builder_ws_pong_timeout_ms: u64, -} - -impl FlashblocksFlags { - /// Converts the flashblocks cli arguments to the flashblocks arguments used by the rollup-boost - /// server. - pub fn as_rollup_boost_args(self) -> rollup_boost::FlashblocksWsArgs { - rollup_boost::FlashblocksWsArgs { - flashblocks_ws: self.flashblocks, - flashblocks_builder_url: self.flashblocks_builder_url, - flashblocks_host: self.flashblocks_host, - flashblocks_port: self.flashblocks_port, - flashblocks_ws_config: rollup_boost::FlashblocksWebsocketConfig { - flashblock_builder_ws_initial_reconnect_ms: self - .flashblocks_ws_config - .flashblock_builder_ws_initial_reconnect_ms, - flashblock_builder_ws_max_reconnect_ms: self - .flashblocks_ws_config - .flashblock_builder_ws_max_reconnect_ms, - flashblock_builder_ws_connect_timeout_ms: self - .flashblocks_ws_config - .flashblock_builder_ws_connect_timeout_ms, - flashblock_builder_ws_ping_interval_ms: self - .flashblocks_ws_config - .flashblock_builder_ws_ping_interval_ms, - flashblock_builder_ws_pong_timeout_ms: self - .flashblocks_ws_config - .flashblock_builder_ws_pong_timeout_ms, - }, - } - } -} - -impl Default for FlashblocksWebsocketFlags { - fn default() -> Self { - Self { - flashblock_builder_ws_initial_reconnect_ms: 10, - flashblock_builder_ws_max_reconnect_ms: 5000, - flashblock_builder_ws_connect_timeout_ms: 5000, - flashblock_builder_ws_ping_interval_ms: 500, - flashblock_builder_ws_pong_timeout_ms: 1500, - } - } -} diff --git a/rust/kona/bin/node/src/flags/engine/mod.rs b/rust/kona/bin/node/src/flags/engine/mod.rs index 68a5d65551f..8819894b7f6 100644 --- a/rust/kona/bin/node/src/flags/engine/mod.rs +++ b/rust/kona/bin/node/src/flags/engine/mod.rs @@ -1,8 +1,2 @@ -mod flashblocks; -pub use flashblocks::{FlashblocksFlags, FlashblocksWebsocketFlags}; - mod providers; -pub use providers::{BuilderClientArgs, DerivationDelegateArgs, L1ClientArgs, L2ClientArgs}; - -mod rollup_boost; -pub use rollup_boost::RollupBoostFlags; +pub use providers::{DerivationDelegateArgs, L1ClientArgs, L2ClientArgs}; diff --git a/rust/kona/bin/node/src/flags/engine/providers.rs b/rust/kona/bin/node/src/flags/engine/providers.rs index 340125176be..039635d09d8 100644 --- a/rust/kona/bin/node/src/flags/engine/providers.rs +++ b/rust/kona/bin/node/src/flags/engine/providers.rs @@ -3,50 +3,11 @@ use kona_node_service::DerivationDelegateConfig; use std::path::PathBuf; use url::Url; -const DEFAULT_BUILDER_TIMEOUT: u64 = 30; const DEFAULT_L2_ENGINE_TIMEOUT: u64 = 30_000; const DEFAULT_L2_TRUST_RPC: bool = true; const DEFAULT_L1_TRUST_RPC: bool = true; -/// Rollup-boost builder client arguments. -#[derive(Clone, Debug, clap::Args)] -pub struct BuilderClientArgs { - /// URL of the builder RPC API. - #[arg( - long, - visible_alias = "builder", - env = "KONA_NODE_BUILDER_RPC", - default_value = "http://localhost:8552" - )] - pub l2_builder_rpc: Url, - /// Hex encoded JWT secret to use for the authenticated builder RPC server. - #[arg(long, visible_alias = "builder.auth", env = "KONA_NODE_BUILDER_AUTH")] - pub builder_jwt_secret: Option, - /// Path to a JWT secret to use for the authenticated builder RPC server. - #[arg(long, visible_alias = "builder.jwt-path", env = "KONA_NODE_BUILDER_JWT_PATH")] - pub builder_jwt_path: Option, - /// Timeout for http calls in milliseconds. - #[arg( - long, - visible_alias = "builder.timeout", - env = "KONA_NODE_BUILDER_TIMEOUT", - default_value_t = DEFAULT_BUILDER_TIMEOUT - )] - pub builder_timeout: u64, -} - -impl Default for BuilderClientArgs { - fn default() -> Self { - Self { - l2_builder_rpc: Url::parse("http://localhost:8552").unwrap(), - builder_jwt_secret: None, - builder_jwt_path: None, - builder_timeout: DEFAULT_BUILDER_TIMEOUT, - } - } -} - /// L1 client arguments. #[derive(Clone, Debug, clap::Args)] pub struct L1ClientArgs { diff --git a/rust/kona/bin/node/src/flags/engine/rollup_boost.rs b/rust/kona/bin/node/src/flags/engine/rollup_boost.rs deleted file mode 100644 index bb1c6ee147b..00000000000 --- a/rust/kona/bin/node/src/flags/engine/rollup_boost.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::flags::engine::flashblocks::FlashblocksFlags; -use rollup_boost::{BlockSelectionPolicy, ExecutionMode}; - -/// Custom block builder flags. -#[derive(Clone, Debug, clap::Args)] -pub struct RollupBoostFlags { - /// Execution mode to start rollup boost with - #[arg( - long, - visible_alias = "rollup-boost.execution-mode", - env = "KONA_NODE_ROLLUP_BOOST_EXECUTION_MODE", - default_value = "disabled" - )] - pub execution_mode: ExecutionMode, - - /// Block selection policy to use for the rollup boost server. - #[arg( - long, - visible_alias = "rollup-boost.block-selection-policy", - env = "KONA_NODE_ROLLUP_BOOST_BLOCK_SELECTION_POLICY" - )] - pub block_selection_policy: Option, - - /// Should we use the l2 client for computing state root - #[arg( - long, - visible_alias = "rollup-boost.external-state-root", - env = "KONA_NODE_ROLLUP_BOOST_EXTERNAL_STATE_ROOT", - default_value = "false" - )] - pub external_state_root: bool, - - /// Allow all engine API calls to builder even when marked as unhealthy - /// This is default true assuming no builder CL set up - #[arg( - long, - visible_alias = "rollup-boost.ignore-unhealthy-builders", - env = "KONA_NODE_ROLLUP_BOOST_IGNORE_UNHEALTHY_BUILDERS", - default_value = "false" - )] - pub ignore_unhealthy_builders: bool, - - /// Duration in seconds between async health checks on the builder - #[arg( - long, - visible_alias = "rollup-boost.health-check-interval", - env = "KONA_NODE_ROLLUP_BOOST_HEALTH_CHECK_INTERVAL", - default_value = "60" - )] - pub health_check_interval: u64, - - /// Max duration in seconds between the unsafe head block of the builder and the current time - #[arg( - long, - visible_alias = "rollup-boost.max-unsafe-interval", - env = "KONA_NODE_ROLLUP_BOOST_MAX_UNSAFE_INTERVAL", - default_value = "10" - )] - pub max_unsafe_interval: u64, - - /// Flashblocks configuration - #[clap(flatten)] - pub flashblocks: FlashblocksFlags, -} - -impl Default for RollupBoostFlags { - fn default() -> Self { - Self { - execution_mode: ExecutionMode::Disabled, - block_selection_policy: None, - external_state_root: false, - ignore_unhealthy_builders: false, - flashblocks: FlashblocksFlags::default(), - health_check_interval: 60, - max_unsafe_interval: 10, - } - } -} - -impl RollupBoostFlags { - /// Converts the rollup boost cli arguments to the rollup boost arguments used by the engine. - pub fn as_rollup_boost_args(self) -> kona_engine::RollupBoostServerArgs { - kona_engine::RollupBoostServerArgs { - initial_execution_mode: self.execution_mode, - block_selection_policy: self.block_selection_policy, - external_state_root: self.external_state_root, - ignore_unhealthy_builders: self.ignore_unhealthy_builders, - flashblocks: self.flashblocks.flashblocks.then_some( - kona_engine::FlashblocksClientArgs { - flashblocks_builder_url: self.flashblocks.flashblocks_builder_url, - flashblocks_host: self.flashblocks.flashblocks_host, - flashblocks_port: self.flashblocks.flashblocks_port, - flashblocks_ws_config: kona_engine::FlashblocksWebsocketConfig { - flashblock_builder_ws_initial_reconnect_ms: self - .flashblocks - .flashblocks_ws_config - .flashblock_builder_ws_initial_reconnect_ms, - flashblock_builder_ws_max_reconnect_ms: self - .flashblocks - .flashblocks_ws_config - .flashblock_builder_ws_max_reconnect_ms, - flashblock_builder_ws_connect_timeout_ms: self - .flashblocks - .flashblocks_ws_config - .flashblock_builder_ws_connect_timeout_ms, - flashblock_builder_ws_ping_interval_ms: self - .flashblocks - .flashblocks_ws_config - .flashblock_builder_ws_ping_interval_ms, - flashblock_builder_ws_pong_timeout_ms: self - .flashblocks - .flashblocks_ws_config - .flashblock_builder_ws_pong_timeout_ms, - }, - }, - ), - } - } -} diff --git a/rust/kona/bin/node/src/flags/mod.rs b/rust/kona/bin/node/src/flags/mod.rs index 3d4ea40b6e2..b63526a8bd2 100644 --- a/rust/kona/bin/node/src/flags/mod.rs +++ b/rust/kona/bin/node/src/flags/mod.rs @@ -22,7 +22,4 @@ mod signer; pub use signer::{SignerArgs, SignerArgsParseError}; mod engine; -pub use engine::{ - BuilderClientArgs, DerivationDelegateArgs, FlashblocksFlags, FlashblocksWebsocketFlags, - L1ClientArgs, L2ClientArgs, RollupBoostFlags, -}; +pub use engine::{DerivationDelegateArgs, L1ClientArgs, L2ClientArgs}; diff --git a/rust/kona/bin/node/src/metrics/cli_opts.rs b/rust/kona/bin/node/src/metrics/cli_opts.rs index 8b593d93271..ff48c2bec7d 100644 --- a/rust/kona/bin/node/src/metrics/cli_opts.rs +++ b/rust/kona/bin/node/src/metrics/cli_opts.rs @@ -92,7 +92,6 @@ pub fn init_rollup_config_metrics(config: &RollupConfig) { ("batch_inbox_address", config.batch_inbox_address.to_string()), ("deposit_contract_address", config.deposit_contract_address.to_string()), ("l1_system_config_address", config.l1_system_config_address.to_string()), - ("protocol_versions_address", config.protocol_versions_address.to_string()), ] ) .set(1); diff --git a/rust/kona/crates/node/disc/src/driver.rs b/rust/kona/crates/node/disc/src/driver.rs index 094ce8fa4e2..6fcbb2c3ea0 100644 --- a/rust/kona/crates/node/disc/src/driver.rs +++ b/rust/kona/crates/node/disc/src/driver.rs @@ -108,7 +108,7 @@ impl Discv5Driver { let initial_store_length = store.len(); - for bn in bootnodes.0.into_iter().chain(BootNodes::from_chain_id(chain_id).0.into_iter()) { + for bn in bootnodes.0.into_iter().chain(BootNodes::from_chain_id(chain_id).0) { let res = match bn { BootNode::Enr(enr) => Ok(enr.clone()), BootNode::Enode(enode) => disc.request_enr(enode.clone()).await, @@ -275,45 +275,38 @@ impl Discv5Driver { continue; }; match event { - discv5::Event::Discovered(enr) => { - if EnrValidation::validate(&enr, chain_id).is_valid() { - debug!(target: "discovery", "Valid ENR discovered, forwarding to swarm: {:?}", enr); - kona_macros::inc!(gauge, crate::Metrics::DISCOVERY_EVENT, "type" => "discovered"); - store.add_enr(enr.clone()); - let sender = enr_sender.clone(); - tokio::spawn(async move { - if let Err(e) = sender.send(enr).await { - debug!(target: "discovery", "Failed to send enr: {:?}", e); - } - }); - } + discv5::Event::Discovered(enr) if EnrValidation::validate(&enr, chain_id).is_valid() => { + debug!(target: "discovery", "Valid ENR discovered, forwarding to swarm: {:?}", enr); + kona_macros::inc!(gauge, crate::Metrics::DISCOVERY_EVENT, "type" => "discovered"); + store.add_enr(enr.clone()); + let sender = enr_sender.clone(); + tokio::spawn(async move { + if let Err(e) = sender.send(enr).await { + debug!(target: "discovery", "Failed to send enr: {:?}", e); + } + }); } - discv5::Event::SessionEstablished(enr, addr) => { - if EnrValidation::validate(&enr, chain_id).is_valid() { - debug!(target: "discovery", "Session established with valid ENR, forwarding to swarm. Address: {:?}, ENR: {:?}", addr, enr); - kona_macros::inc!(gauge, crate::Metrics::DISCOVERY_EVENT, "type" => "session_established"); - store.add_enr(enr.clone()); - let sender = enr_sender.clone(); - tokio::spawn(async move { - if let Err(e) = sender.send(enr).await { - debug!(target: "discovery", "Failed to send enr: {:?}", e); - } - }); - } + discv5::Event::SessionEstablished(enr, addr) if EnrValidation::validate(&enr, chain_id).is_valid() => { + debug!(target: "discovery", "Session established with valid ENR, forwarding to swarm. Address: {:?}, ENR: {:?}", addr, enr); + kona_macros::inc!(gauge, crate::Metrics::DISCOVERY_EVENT, "type" => "session_established"); + store.add_enr(enr.clone()); + let sender = enr_sender.clone(); + tokio::spawn(async move { + if let Err(e) = sender.send(enr).await { + debug!(target: "discovery", "Failed to send enr: {:?}", e); + } + }); } - discv5::Event::UnverifiableEnr { enr, .. } => { - if EnrValidation::validate(&enr, chain_id).is_valid() { - debug!(target: "discovery", "Valid ENR discovered, forwarding to swarm: {:?}", enr); - kona_macros::inc!(gauge, crate::Metrics::DISCOVERY_EVENT, "type" => "unverifiable_enr"); - store.add_enr(enr.clone()); - let sender = enr_sender.clone(); - tokio::spawn(async move { - if let Err(e) = sender.send(enr).await { - debug!(target: "discovery", "Failed to send enr: {:?}", e); - } - }); - } - + discv5::Event::UnverifiableEnr { enr, .. } if EnrValidation::validate(&enr, chain_id).is_valid() => { + debug!(target: "discovery", "Valid ENR discovered, forwarding to swarm: {:?}", enr); + kona_macros::inc!(gauge, crate::Metrics::DISCOVERY_EVENT, "type" => "unverifiable_enr"); + store.add_enr(enr.clone()); + let sender = enr_sender.clone(); + tokio::spawn(async move { + if let Err(e) = sender.send(enr).await { + debug!(target: "discovery", "Failed to send enr: {:?}", e); + } + }); } _ => {} } diff --git a/rust/kona/crates/node/engine/Cargo.toml b/rust/kona/crates/node/engine/Cargo.toml index 3545fcaf9f9..643ae9d47aa 100644 --- a/rust/kona/crates/node/engine/Cargo.toml +++ b/rust/kona/crates/node/engine/Cargo.toml @@ -20,7 +20,6 @@ kona-protocol = {workspace = true, features = ["serde", "std"]} # alloy alloy-eips.workspace = true alloy-consensus.workspace = true -alloy-json-rpc.workspace = true alloy-network.workspace = true alloy-transport.workspace = true alloy-primitives.workspace = true @@ -48,17 +47,10 @@ tower.workspace = true http-body-util.workspace = true derive_more = { workspace = true, features = ["display", "deref", "from_str", "constructor"] } serde_json.workspace = true -jsonrpsee-types.workspace = true # metrics metrics = { workspace = true, optional = true } -# rollup boost -rollup-boost.workspace = true -rollup-boost-types.workspace = true -http.workspace = true -parking_lot.workspace = true - [dev-dependencies] kona-registry.workspace = true rand = {workspace = true, features = ["thread_rng"]} @@ -70,5 +62,6 @@ rstest.workspace = true [features] metrics = [ "dep:metrics" ] test-utils = [ - "kona-protocol/test-utils" + "kona-protocol/test-utils", + "alloy-primitives/rand", ] diff --git a/rust/kona/crates/node/engine/src/client.rs b/rust/kona/crates/node/engine/src/client.rs index 4e73ece6919..23be1059c4e 100644 --- a/rust/kona/crates/node/engine/src/client.rs +++ b/rust/kona/crates/node/engine/src/client.rs @@ -1,6 +1,6 @@ //! An Engine API Client. -use crate::{Metrics, RollupBoostServerArgs, RollupBoostServerError}; +use crate::Metrics; use alloy_eips::{BlockId, eip1898::BlockNumberOrTag}; use alloy_network::{Ethereum, Network}; use alloy_primitives::{Address, B256, BlockHash, Bytes, StorageKey}; @@ -21,7 +21,6 @@ use alloy_transport_http::{ }, }; use async_trait::async_trait; -use http::uri::InvalidUri; use http_body_util::Full; use kona_genesis::RollupConfig; use kona_protocol::{FromBlockError, L2BlockInfo}; @@ -30,21 +29,9 @@ use op_alloy_provider::ext::engine::OpEngineApi; use op_alloy_rpc_types::Transaction; use op_alloy_rpc_types_engine::{ OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4, - OpPayloadAttributes, ProtocolVersion, -}; -use parking_lot::Mutex; -use rollup_boost::{ - EngineApiServer, Flashblocks, FlashblocksWebsocketConfig, Probes, RollupBoostServer, - RpcClientError, -}; -use rollup_boost_types::payload::PayloadSource; -use std::{ - future::Future, - net::{AddrParseError, IpAddr, SocketAddr}, - str::FromStr, - sync::Arc, - time::{Duration, Instant}, + OpPayloadAttributes, }; +use std::{future::Future, sync::Arc, time::Instant}; use thiserror::Error; use tower::ServiceBuilder; use url::Url; @@ -63,7 +50,7 @@ pub enum EngineClientError { /// A Hyper HTTP client with a JWT authentication layer. pub type HyperAuthClient> = HyperClient>>; -/// Engine API client used to communicate with L1/L2 ELs and optional rollup-boost. +/// Engine API client used to communicate with L1/L2 ELs. /// `EngineClient` trait that is very coupled to its only implementation. /// The main reason this exists is for mocking/unit testing. #[async_trait] @@ -101,13 +88,54 @@ pub trait EngineClient: OpEngineApi> + Send + Sy ) -> Result, EngineClientError>; } +/// Read-only subset of [`EngineClient`] used by the engine RPC actor. +/// +/// Exposes only the methods required to serve [`crate::EngineQueries`] — fetching an L2 block by +/// label, and reading the L2-to-L1 message-passer storage hash. The engine RPC actor handles +/// queries only and must not have any way to call state-mutating Engine API methods; constraining +/// it to this trait prevents that at the type system level. +#[async_trait] +pub trait EngineRpcClient: Send + Sync { + /// Fetches the [`Block`] for the given [`BlockNumberOrTag`]. + async fn l2_block_by_label( + &self, + numtag: BlockNumberOrTag, + ) -> Result>, EngineClientError>; + + /// Returns the storage hash of `address` at the given block, used to compute the L2-to-L1 + /// message-passer storage root pre-Isthmus. This is a narrower projection of `get_proof`'s + /// `storage_hash` field; callers needing the full account proof should not be using this + /// trait. + async fn get_storage_hash( + &self, + address: Address, + block: BlockId, + ) -> Result>; +} + +#[async_trait] +impl EngineRpcClient for T { + async fn l2_block_by_label( + &self, + numtag: BlockNumberOrTag, + ) -> Result>, EngineClientError> { + EngineClient::l2_block_by_label(self, numtag).await + } + + async fn get_storage_hash( + &self, + address: Address, + block: BlockId, + ) -> Result> { + Ok(self.get_proof(address, Default::default()).block_id(block).await?.storage_hash) + } +} + /// An Engine API client that provides authenticated HTTP communication with an execution layer. /// /// The [`OpEngineClient`] handles JWT authentication and manages connections to both L1 and L2 /// execution layers. It automatically selects the appropriate Engine API version based on the /// rollup configuration and block timestamps. -/// -/// Engine API client used to communicate with L1/L2 ELs and optional rollup-boost. #[derive(Clone, Debug)] pub struct OpEngineClient where @@ -120,8 +148,6 @@ where l1_provider: L1Provider, /// The [`RollupConfig`] for determining Engine API versions based on hardfork activations. cfg: Arc, - /// The rollup boost server - pub rollup_boost: Arc, } impl OpEngineClient @@ -144,124 +170,22 @@ where /// The builder for the [`OpEngineClient`]. #[derive(Debug, Clone)] pub struct EngineClientBuilder { - /// The builder URL. - pub builder: Url, - /// The builder JWT secret. - pub builder_jwt: JwtSecret, - /// The builder timeout. - pub builder_timeout: Duration, /// The L2 Engine API endpoint URL. pub l2: Url, /// The L2 JWT secret. pub l2_jwt: JwtSecret, - /// The L2 timeout. - pub l2_timeout: Duration, /// The L1 RPC URL. pub l1_rpc: Url, /// The [`RollupConfig`] for determining Engine API versions based on hardfork activations. pub cfg: Arc, - /// The rollup boost arguments. - pub rollup_boost: RollupBoostServerArgs, -} - -/// An error that occurred in the [`EngineClientBuilder`]. -#[derive(Error, Debug)] -pub enum EngineClientBuilderError { - /// An error occurred while parsing the URL - #[error("An error occurred while parsing the URL: {0}")] - UrlParseError(#[from] InvalidUri), - /// An error occurred while parsing the IP address - #[error("An error occurred while parsing the IP address: {0}")] - IpAddrParseError(#[from] AddrParseError), - /// An error occurred while creating the RPC client - #[error("An error occurred while creating the RPC client: {0}")] - RpcClientError(#[from] RpcClientError), - /// An error occurred while creating the Flashblocks service - #[error("An error occurred while creating the Flashblocks service: {0}")] - FlashblocksError(String), } impl EngineClientBuilder { /// Creates a new [`OpEngineClient`] with authenticated HTTP connections. /// - /// Sets up JWT-authenticated connections to the Engine API endpoint through the rollup-boost - /// server along with an unauthenticated connection to the L1 chain. - /// - /// # FIXME(@theochap, ``, ``): - /// This method can be simplified/improved in a few ways: - /// - Unify kona's and rollup-boost's RPC client creation - /// - Removed the `dyn RollupBoostServerLike` type erasure. - pub fn build( - self, - ) -> Result>, EngineClientBuilderError> - { - let probes = Arc::new(Probes::default()); - let l2_client = rollup_boost::RpcClient::new( - http::Uri::from_str(self.l2.to_string().as_str())?, - self.l2_jwt, - self.l2_timeout.as_millis() as u64, - PayloadSource::L2, - )?; - let builder_client = rollup_boost::RpcClient::new( - http::Uri::from_str(self.builder.to_string().as_str())?, - self.builder_jwt, - self.builder_timeout.as_millis() as u64, - PayloadSource::Builder, - )?; - - let rollup_boost_server = match self.rollup_boost.flashblocks { - Some(flashblocks) => { - let inbound_url = flashblocks.flashblocks_builder_url; - let outbound_addr = SocketAddr::new( - IpAddr::from_str(&flashblocks.flashblocks_host)?, - flashblocks.flashblocks_port, - ); - - let ws_config = flashblocks.flashblocks_ws_config; - - let builder_client = Arc::new( - Flashblocks::run( - builder_client, - inbound_url, - outbound_addr, - FlashblocksWebsocketConfig { - flashblock_builder_ws_initial_reconnect_ms: ws_config - .flashblock_builder_ws_initial_reconnect_ms, - flashblock_builder_ws_max_reconnect_ms: ws_config - .flashblock_builder_ws_max_reconnect_ms, - flashblock_builder_ws_connect_timeout_ms: ws_config - .flashblock_builder_ws_connect_timeout_ms, - flashblock_builder_ws_ping_interval_ms: ws_config - .flashblock_builder_ws_ping_interval_ms, - flashblock_builder_ws_pong_timeout_ms: ws_config - .flashblock_builder_ws_pong_timeout_ms, - }, - ) - .map_err(|e| EngineClientBuilderError::FlashblocksError(e.to_string()))?, - ); - Arc::new(rollup_boost::RollupBoostServer::new( - l2_client, - builder_client, - Arc::new(Mutex::new(self.rollup_boost.initial_execution_mode)), - self.rollup_boost.block_selection_policy, - probes, - self.rollup_boost.external_state_root, - self.rollup_boost.ignore_unhealthy_builders, - )) - } - None => Arc::new(rollup_boost::RollupBoostServer::new( - l2_client, - Arc::new(builder_client), - Arc::new(Mutex::new(self.rollup_boost.initial_execution_mode)), - self.rollup_boost.block_selection_policy, - probes, - self.rollup_boost.external_state_root, - self.rollup_boost.ignore_unhealthy_builders, - )), - }; - - // TODO(ethereum-optimism/optimism#18656): remove this client, upstream the remaining - // EngineApiExt methods to the RollupBoostServer + /// Sets up a JWT-authenticated connection to the L2 Engine API endpoint + /// along with an unauthenticated connection to the L1 chain. + pub fn build(self) -> OpEngineClient> { let engine = OpEngineClient::>::rpc_client::( self.l2, self.l2_jwt, @@ -269,7 +193,7 @@ impl EngineClientBuilder { let l1_provider = RootProvider::new_http(self.l1_rpc); - Ok(OpEngineClient { engine, l1_provider, cfg: self.cfg, rollup_boost: rollup_boost_server }) + OpEngineClient { engine, l1_provider, cfg: self.cfg } } } @@ -346,11 +270,13 @@ where payload: ExecutionPayloadV3, parent_beacon_block_root: B256, ) -> TransportResult { - let call = self.rollup_boost.new_payload_v3(payload, vec![], parent_beacon_block_root); + let call = >>::new_payload_v3( + &self.engine, + payload, + parent_beacon_block_root, + ); - record_call_time(call, Metrics::NEW_PAYLOAD_METHOD) - .await - .map_err(|err| RollupBoostServerError::from(err).into()) + record_call_time(call, Metrics::NEW_PAYLOAD_METHOD).await } async fn new_payload_v4( @@ -358,16 +284,13 @@ where payload: OpExecutionPayloadV4, parent_beacon_block_root: B256, ) -> TransportResult { - let call = self.rollup_boost.new_payload_v4( - payload.clone(), - vec![], + let call = >>::new_payload_v4( + &self.engine, + payload, parent_beacon_block_root, - vec![], ); - record_call_time(call, Metrics::NEW_PAYLOAD_METHOD) - .await - .map_err(|err| RollupBoostServerError::from(err).into()) + record_call_time(call, Metrics::NEW_PAYLOAD_METHOD).await } async fn fork_choice_updated_v2( @@ -390,11 +313,14 @@ where fork_choice_state: ForkchoiceState, payload_attributes: Option, ) -> TransportResult { - let call = self.rollup_boost.fork_choice_updated_v3(fork_choice_state, payload_attributes); + let call = + >>::fork_choice_updated_v3( + &self.engine, + fork_choice_state, + payload_attributes, + ); - record_call_time(call, Metrics::FORKCHOICE_UPDATE_METHOD) - .await - .map_err(|err| RollupBoostServerError::from(err).into()) + record_call_time(call, Metrics::FORKCHOICE_UPDATE_METHOD).await } async fn get_payload_v2( @@ -413,22 +339,24 @@ where &self, payload_id: PayloadId, ) -> TransportResult { - let call = self.rollup_boost.get_payload_v3(payload_id); + let call = >>::get_payload_v3( + &self.engine, + payload_id, + ); - record_call_time(call, Metrics::GET_PAYLOAD_METHOD) - .await - .map_err(|err| RollupBoostServerError::from(err).into()) + record_call_time(call, Metrics::GET_PAYLOAD_METHOD).await } async fn get_payload_v4( &self, payload_id: PayloadId, ) -> TransportResult { - let call = self.rollup_boost.get_payload_v4(payload_id); + let call = >>::get_payload_v4( + &self.engine, + payload_id, + ); - record_call_time(call, Metrics::GET_PAYLOAD_METHOD) - .await - .map_err(|err| RollupBoostServerError::from(err).into()) + record_call_time(call, Metrics::GET_PAYLOAD_METHOD).await } async fn get_payload_bodies_by_hash_v1( @@ -464,19 +392,6 @@ where .await } - async fn signal_superchain_v1( - &self, - recommended: ProtocolVersion, - required: ProtocolVersion, - ) -> TransportResult { - >>::signal_superchain_v1( - &self.engine, - recommended, - required, - ) - .await - } - async fn exchange_capabilities( &self, capabilities: Vec, diff --git a/rust/kona/crates/node/engine/src/lib.rs b/rust/kona/crates/node/engine/src/lib.rs index 22b647bb468..f61a4b84051 100644 --- a/rust/kona/crates/node/engine/src/lib.rs +++ b/rust/kona/crates/node/engine/src/lib.rs @@ -42,8 +42,8 @@ mod task_queue; pub use task_queue::{ BuildTask, BuildTaskError, ConsolidateInput, ConsolidateTask, ConsolidateTaskError, Engine, EngineBuildError, EngineResetError, EngineTask, EngineTaskError, EngineTaskErrorSeverity, - EngineTaskErrors, EngineTaskExt, FinalizeTask, FinalizeTaskError, InsertTask, InsertTaskError, - SealTask, SealTaskError, SynchronizeTask, SynchronizeTaskError, + EngineTaskErrors, EngineTaskExt, FinalizeBlockId, FinalizeTask, FinalizeTaskError, InsertTask, + InsertTaskError, SealTask, SealTaskError, SynchronizeTask, SynchronizeTaskError, }; mod attributes; @@ -51,14 +51,8 @@ pub use attributes::{AttributesMatch, AttributesMismatch}; mod client; pub use client::{ - EngineClient, EngineClientBuilder, EngineClientBuilderError, EngineClientError, - HyperAuthClient, OpEngineClient, -}; - -mod rollup_boost; -pub use rollup_boost::{ - FlashblocksClientArgs, FlashblocksWebsocketConfig, RollupBoostServer, RollupBoostServerArgs, - RollupBoostServerError, + EngineClient, EngineClientBuilder, EngineClientError, EngineRpcClient, HyperAuthClient, + OpEngineClient, }; mod versions; diff --git a/rust/kona/crates/node/engine/src/query.rs b/rust/kona/crates/node/engine/src/query.rs index aa879d4373c..a255895c491 100644 --- a/rust/kona/crates/node/engine/src/query.rs +++ b/rust/kona/crates/node/engine/src/query.rs @@ -12,7 +12,7 @@ use kona_genesis::RollupConfig; use kona_protocol::{L2BlockInfo, OutputRoot, Predeploys}; use tokio::sync::oneshot::Sender; -use crate::{EngineClient, EngineClientError, EngineState}; +use crate::{EngineClientError, EngineRpcClient, EngineState}; /// Channel sender for submitting [`EngineQueries`] to the engine. pub type EngineQuerySender = tokio::sync::mpsc::Sender; @@ -67,11 +67,11 @@ pub enum EngineQueriesError { impl EngineQueries { /// Handles the engine query request. - pub async fn handle( + pub async fn handle( self, state_recv: &tokio::sync::watch::Receiver, queue_length_recv: &tokio::sync::watch::Receiver, - client: &Arc, + client: &Arc, rollup_config: &Arc, ) -> Result<(), EngineQueriesError> { let state = *state_recv.borrow(); @@ -107,12 +107,9 @@ impl EngineQueries { .ok_or(EngineQueriesError::NoWithdrawalsRoot)? } else { // Fetch the storage root for the L2 head block. - let l2_to_l1_message_passer = client - .get_proof(Predeploys::L2_TO_L1_MESSAGE_PASSER, Default::default()) - .block_id(block.into()) - .await?; - - l2_to_l1_message_passer.storage_hash + client + .get_storage_hash(Predeploys::L2_TO_L1_MESSAGE_PASSER, block.into()) + .await? }; let output_response_v0 = OutputRoot::from_parts( diff --git a/rust/kona/crates/node/engine/src/rollup_boost.rs b/rust/kona/crates/node/engine/src/rollup_boost.rs deleted file mode 100644 index 9dc21079839..00000000000 --- a/rust/kona/crates/node/engine/src/rollup_boost.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Rollup-boost abstraction used by the engine client. - -use alloy_json_rpc::{ErrorPayload, RpcError}; -use alloy_transport::TransportErrorKind; -use rollup_boost::ExecutionMode; -use std::fmt::Debug; - -use rollup_boost::BlockSelectionPolicy; -pub use rollup_boost::RollupBoostServer; -use url::Url; - -/// Configuration for the rollup-boost server. -#[derive(Clone, Debug)] -pub struct RollupBoostServerArgs { - /// The initial execution mode of the rollup-boost server. - pub initial_execution_mode: ExecutionMode, - /// The block selection policy of the rollup-boost server. - pub block_selection_policy: Option, - /// Whether to use the l2 client for computing state root. - pub external_state_root: bool, - /// Allow all engine API calls to builder even when marked as unhealthy - /// This is default true assuming no builder CL set up - pub ignore_unhealthy_builders: bool, - /// Flashblocks configuration - pub flashblocks: Option, -} - -/// Configuration for the Flashblocks client. -#[derive(Clone, Debug)] -pub struct FlashblocksClientArgs { - /// Flashblocks Builder `WebSocket` URL - pub flashblocks_builder_url: Url, - - /// Flashblocks `WebSocket` host for outbound connections - pub flashblocks_host: String, - - /// Flashblocks `WebSocket` port for outbound connections - pub flashblocks_port: u16, - - /// Websocket connection configuration - pub flashblocks_ws_config: FlashblocksWebsocketConfig, -} - -/// Configuration for the Flashblocks `WebSocket` connection. -#[derive(Debug, Clone, Copy)] -pub struct FlashblocksWebsocketConfig { - /// Minimum time for exponential backoff for timeout if builder disconnected - pub flashblock_builder_ws_initial_reconnect_ms: u64, - - /// Maximum time for exponential backoff for timeout if builder disconnected - pub flashblock_builder_ws_max_reconnect_ms: u64, - - /// Timeout for connection attempt - pub flashblock_builder_ws_connect_timeout_ms: u64, - - /// Interval in milliseconds between ping messages sent to upstream servers to detect - /// unresponsive connections - pub flashblock_builder_ws_ping_interval_ms: u64, - - /// Timeout in milliseconds to wait for pong responses from upstream servers before considering - /// the connection dead - pub flashblock_builder_ws_pong_timeout_ms: u64, -} - -/// An error that occurred in the rollup-boost server. -#[derive(Debug, thiserror::Error)] -pub enum RollupBoostServerError { - /// JSON-RPC error. - #[error("Rollup boost server error: {0}")] - Jsonrpsee(#[from] jsonrpsee_types::ErrorObjectOwned), -} - -impl From for RpcError { - fn from(error: RollupBoostServerError) -> Self { - match error { - RollupBoostServerError::Jsonrpsee(error) => Self::ErrorResp(ErrorPayload { - code: error.code().into(), - message: error.message().to_string().into(), - data: None, - }), - } - } -} diff --git a/rust/kona/crates/node/engine/src/task_queue/core.rs b/rust/kona/crates/node/engine/src/task_queue/core.rs index 9ebad4f9cb9..07467815a15 100644 --- a/rust/kona/crates/node/engine/src/task_queue/core.rs +++ b/rust/kona/crates/node/engine/src/task_queue/core.rs @@ -6,10 +6,8 @@ use crate::{ EngineTaskErrorSeverity, Metrics, SyncStartError, SynchronizeTask, SynchronizeTaskError, find_starting_forkchoice, task_queue::EngineTaskErrors, }; -use alloy_rpc_types_eth::Transaction; -use kona_genesis::{RollupConfig, SystemConfig}; -use kona_protocol::{BlockInfo, L2BlockInfo, OpBlockConversionError, to_system_config}; -use op_alloy_consensus::OpTxEnvelope; +use kona_genesis::RollupConfig; +use kona_protocol::L2BlockInfo; use std::{collections::BinaryHeap, sync::Arc}; use thiserror::Error; use tokio::sync::watch::Sender; @@ -78,7 +76,7 @@ impl Engine { &mut self, client: Arc, config: Arc, - ) -> Result<(L2BlockInfo, BlockInfo, SystemConfig), EngineResetError> { + ) -> Result { // Clear any outstanding tasks to prepare for the reset. self.clear(); @@ -112,32 +110,9 @@ impl Engine { } } - // Find the new safe head's L1 origin and SystemConfig. - let origin_block = start - .safe - .l1_origin - .number - .saturating_sub(config.channel_timeout(start.safe.block_info.timestamp)); - let l1_origin_info: BlockInfo = client - .get_l1_block(origin_block.into()) - .await - .map_err(SyncStartError::RpcError)? - .ok_or(SyncStartError::BlockNotFound(origin_block.into()))? - .into_consensus() - .into(); - let l2_safe_block = client - .get_l2_block(start.safe.block_info.hash.into()) - .full() - .await - .map_err(SyncStartError::RpcError)? - .ok_or(SyncStartError::BlockNotFound(origin_block.into()))? - .into_consensus() - .map_transactions(|t| as Clone>::clone(&t).into_inner()); - let system_config = to_system_config(&l2_safe_block, &config)?; - kona_macros::inc!(counter, Metrics::ENGINE_RESET_COUNT); - Ok((start.safe, l1_origin_info, system_config)) + Ok(start.safe) } /// Clears the task queue. @@ -176,7 +151,4 @@ pub enum EngineResetError { /// An error occurred while traversing the L1 for the sync starting point. #[error(transparent)] SyncStart(#[from] SyncStartError), - /// An error occurred while constructing the `SystemConfig` for the new safe head. - #[error(transparent)] - SystemConfigConversion(#[from] OpBlockConversionError), } diff --git a/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/id.rs b/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/id.rs new file mode 100644 index 00000000000..ab5a92e4ddb --- /dev/null +++ b/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/id.rs @@ -0,0 +1,36 @@ +//! Identifier for the L2 block that a [`crate::FinalizeTask`] should finalize. + +use alloy_eips::BlockNumHash; + +/// Identifies the L2 block to finalize. +/// +/// Two semantic regimes drive finalization in kona-node: +/// +/// - [`FinalizeBlockId::ByNumber`] is used when the engine's own canonical chain is the +/// authoritative source for the block at a given height (e.g. local L1-finality driven by +/// `L2Finalizer`). The number is sufficient because there is only one chain to consult. +/// - [`FinalizeBlockId::ByHash`] is used when an upstream supplies `(number, hash)` and the engine +/// must finalize the specific block identified by hash. If the engine does not have that hash, +/// the task must error rather than silently finalize the engine's canonical block at the same +/// height. +/// +/// EL finalization is irreversible, so finalizing the wrong block is unrecoverable. Forcing every +/// caller to pick a variant prevents accidental hash-loss at the boundary. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FinalizeBlockId { + /// Finalize whatever block the engine has at the given L2 block number. + ByNumber(u64), + /// Finalize the block matching the given `(number, hash)` pair. The task fails if the engine + /// does not have that hash. + ByHash(BlockNumHash), +} + +impl FinalizeBlockId { + /// Returns the L2 block number identified by this id. + pub const fn number(&self) -> u64 { + match self { + Self::ByNumber(n) => *n, + Self::ByHash(id) => id.number, + } + } +} diff --git a/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/mod.rs b/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/mod.rs index dccec062025..863ef77933a 100644 --- a/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/mod.rs +++ b/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/mod.rs @@ -5,3 +5,9 @@ pub use task::FinalizeTask; mod error; pub use error::FinalizeTaskError; + +mod id; +pub use id::FinalizeBlockId; + +#[cfg(test)] +mod task_test; diff --git a/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/task.rs b/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/task.rs index 4b26aeb7b1f..2be521761c7 100644 --- a/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/task.rs +++ b/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/task.rs @@ -1,25 +1,26 @@ //! A task for finalizing an L2 block. use crate::{ - EngineClient, EngineState, EngineTaskExt, FinalizeTaskError, SynchronizeTask, + EngineClient, EngineState, EngineTaskExt, FinalizeBlockId, FinalizeTaskError, SynchronizeTask, state::EngineSyncStateUpdate, }; +use alloy_eips::BlockId; use async_trait::async_trait; use derive_more::Constructor; use kona_genesis::RollupConfig; use kona_protocol::L2BlockInfo; use std::{sync::Arc, time::Instant}; -/// The [`FinalizeTask`] fetches the [`L2BlockInfo`] at `block_number`, updates the [`EngineState`], -/// and dispatches a forkchoice update to finalize the block. +/// The [`FinalizeTask`] fetches the [`L2BlockInfo`] identified by `block_id`, updates the +/// [`EngineState`], and dispatches a forkchoice update to finalize the block. #[derive(Debug, Clone, Constructor)] pub struct FinalizeTask { /// The engine client. pub client: Arc, /// The rollup config. pub cfg: Arc, - /// The number of the L2 block to finalize. - pub block_number: u64, + /// Identifier of the L2 block to finalize. + pub block_id: FinalizeBlockId, } #[async_trait] @@ -29,22 +30,44 @@ impl EngineTaskExt for FinalizeTask type Error = FinalizeTaskError; async fn execute(&self, state: &mut EngineState) -> Result<(), FinalizeTaskError> { + let block_number = self.block_id.number(); + // Sanity check that the block that is being finalized is at least safe. - if state.sync_state.safe_head().block_info.number < self.block_number { + if state.sync_state.safe_head().block_info.number < block_number { return Err(FinalizeTaskError::BlockNotSafe); } + // Look up by hash when the caller pinned a specific hash (delegated polling supplies + // `(number, hash)`); otherwise look up by number. Finalization is irreversible, so a stale + // hash must produce a hard error, not silently finalize whatever the engine has at the + // same height. + let lookup: BlockId = match self.block_id { + FinalizeBlockId::ByHash(id) => id.hash.into(), + FinalizeBlockId::ByNumber(n) => n.into(), + }; + let block_fetch_start = Instant::now(); let block = self .client - .get_l2_block(self.block_number.into()) + .get_l2_block(lookup) .full() .await .map_err(FinalizeTaskError::TransportError)? - .ok_or(FinalizeTaskError::BlockNotFound(self.block_number))? + .ok_or(FinalizeTaskError::BlockNotFound(block_number))? .into_consensus(); let block_info = L2BlockInfo::from_block_and_genesis(&block, &self.client.cfg().genesis) .map_err(FinalizeTaskError::FromBlock)?; + + // For ByHash, also assert the returned block's height matches the caller's claim. The + // engine should never serve a block at a different height for a given hash, but a height + // mismatch indicates either an upstream protocol bug or a misuse of the API and must + // never lead to silent finalization. + if let FinalizeBlockId::ByHash(id) = self.block_id && + block_info.block_info.number != id.number + { + return Err(FinalizeTaskError::BlockNotFound(id.number)); + } + let block_fetch_duration = block_fetch_start.elapsed(); // Dispatch a forkchoice update. diff --git a/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/task_test.rs b/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/task_test.rs new file mode 100644 index 00000000000..266f102fa2a --- /dev/null +++ b/rust/kona/crates/node/engine/src/task_queue/tasks/finalize/task_test.rs @@ -0,0 +1,87 @@ +//! Tests for [`FinalizeTask::execute`]. + +use crate::{ + EngineTaskExt, FinalizeBlockId, FinalizeTask, FinalizeTaskError, + test_utils::{TestEngineStateBuilder, test_engine_client_builder}, +}; +use alloy_eips::{BlockId, BlockNumHash}; +use alloy_primitives::b256; +use kona_genesis::RollupConfig; +use kona_protocol::{BlockInfo, L2BlockInfo}; +use std::sync::Arc; + +/// When the engine receives a `ByHash` finalize request for a block hash it doesn't have, +/// [`FinalizeTask`] must fail with [`FinalizeTaskError::BlockNotFound`] rather than silently +/// finalize whatever it happens to have at the same height. +/// +/// Reproduces the bug at [`crate::FinalizeTask`]: with the old `block_number: u64` field, the task +/// looked up the block by number, so an upstream-driven `(N, H_b)` request would silently finalize +/// `H_a` if the engine's canonical chain disagrees. EL finalization is irreversible, so this is +/// unrecoverable. +/// +/// Baseline (Phase 1, by-number lookup still in place): the task finds the engine's stale block at +/// number `N` and returns a different error (or `Ok`), never [`FinalizeTaskError::BlockNotFound`]. +/// Post-fix (Phase 2, by-hash lookup): the lookup of `H_b` returns `None` and the task fails +/// loudly. +#[tokio::test] +async fn finalize_task_by_hash_errors_when_engine_lacks_hash() { + const N: u64 = 10; + let hash_a = b256!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + let hash_b = b256!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + + // The engine has H_a at height N. Register the block under its number key so a by-number + // lookup finds it; do *not* register it under any hash key so a by-hash lookup of H_b returns + // None. + let block: alloy_rpc_types_eth::Block = + alloy_rpc_types_eth::Block { + header: alloy_rpc_types_eth::Header { + hash: hash_a, + inner: alloy_consensus::Header { + number: N, + parent_hash: b256!( + "0202020202020202020202020202020202020202020202020202020202020202" + ), + timestamp: N * 2, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }; + + let cfg = Arc::new(RollupConfig::default()); + let engine_client = test_engine_client_builder() + .with_config(cfg.clone()) + .with_l2_block(BlockId::Number(N.into()), block) + .build(); + + // Place the safe head at N so the sanity check (`safe_head.number >= block_id.number`) passes + // and `execute()` reaches the lookup we care about. + let safe_head = L2BlockInfo { + block_info: BlockInfo { + number: N, + hash: hash_a, + parent_hash: Default::default(), + timestamp: N * 2, + }, + l1_origin: BlockNumHash::default(), + seq_num: 0, + }; + let mut state = + TestEngineStateBuilder::new().with_unsafe_head(safe_head).with_safe_head(safe_head).build(); + + let task = FinalizeTask::new( + Arc::new(engine_client), + cfg, + FinalizeBlockId::ByHash(BlockNumHash { number: N, hash: hash_b }), + ); + + let result = task.execute(&mut state).await; + + assert!( + matches!(result, Err(FinalizeTaskError::BlockNotFound(n)) if n == N), + "expected BlockNotFound({N}) — got {result:?}. The by-hash lookup must fail loudly when \ + the engine lacks the requested hash; instead, the task either succeeded (finalizing the \ + wrong block) or surfaced a different error." + ); +} diff --git a/rust/kona/crates/node/engine/src/task_queue/tasks/mod.rs b/rust/kona/crates/node/engine/src/task_queue/tasks/mod.rs index 07a9972d7c0..535d06f0565 100644 --- a/rust/kona/crates/node/engine/src/task_queue/tasks/mod.rs +++ b/rust/kona/crates/node/engine/src/task_queue/tasks/mod.rs @@ -21,7 +21,7 @@ mod consolidate; pub use consolidate::{ConsolidateInput, ConsolidateTask, ConsolidateTaskError}; mod finalize; -pub use finalize::{FinalizeTask, FinalizeTaskError}; +pub use finalize::{FinalizeBlockId, FinalizeTask, FinalizeTaskError}; mod util; pub(super) use util::{BuildAndSealError, build_and_seal}; diff --git a/rust/kona/crates/node/engine/src/task_queue/tasks/seal/task.rs b/rust/kona/crates/node/engine/src/task_queue/tasks/seal/task.rs index 4fe640779b2..8708f039caf 100644 --- a/rust/kona/crates/node/engine/src/task_queue/tasks/seal/task.rs +++ b/rust/kona/crates/node/engine/src/task_queue/tasks/seal/task.rs @@ -238,8 +238,8 @@ impl SealTask { // and this task will always succeed. If not, task failure will be relayed to the caller. if let Some(tx) = &self.result_tx { tx.send(res).await.map_err(|e| SealTaskError::MpscSend(Box::new(e)))?; - } else if let Err(x) = res { - return Err(x); + } else { + res?; } Ok(()) diff --git a/rust/kona/crates/node/engine/src/test_utils/attributes.rs b/rust/kona/crates/node/engine/src/test_utils/attributes.rs index fae84e4fd59..2bb90094886 100644 --- a/rust/kona/crates/node/engine/src/test_utils/attributes.rs +++ b/rust/kona/crates/node/engine/src/test_utils/attributes.rs @@ -87,6 +87,7 @@ impl TestAttributesBuilder { suggested_fee_recipient: self.suggested_fee_recipient, withdrawals: self.withdrawals, parent_beacon_block_root: self.parent_beacon_block_root, + slot_number: Default::default(), }, transactions: self.transactions, no_tx_pool: self.no_tx_pool, diff --git a/rust/kona/crates/node/engine/src/test_utils/engine_client.rs b/rust/kona/crates/node/engine/src/test_utils/engine_client.rs index c2f1a823d40..f46f74f763b 100644 --- a/rust/kona/crates/node/engine/src/test_utils/engine_client.rs +++ b/rust/kona/crates/node/engine/src/test_utils/engine_client.rs @@ -21,7 +21,7 @@ use op_alloy_provider::ext::engine::OpEngineApi; use op_alloy_rpc_types::Transaction as OpTransaction; use op_alloy_rpc_types_engine::{ OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4, - OpPayloadAttributes, ProtocolVersion, + OpPayloadAttributes, }; use std::{collections::HashMap, sync::Arc}; use tokio::sync::RwLock; @@ -77,8 +77,6 @@ pub struct MockEngineStorage { // Non-versioned responses /// Storage for client version responses. pub client_versions: Option>, - /// Storage for protocol version responses. - pub protocol_version: Option, /// Storage for capabilities responses. pub capabilities: Option>, @@ -225,12 +223,6 @@ impl MockEngineClientBuilder { self } - /// Sets the protocol version response. - pub const fn with_protocol_version(mut self, version: ProtocolVersion) -> Self { - self.storage.protocol_version = Some(version); - self - } - /// Sets the capabilities response. pub fn with_capabilities(mut self, capabilities: Vec) -> Self { self.storage.capabilities = Some(capabilities); @@ -380,11 +372,6 @@ impl MockEngineClient { self.storage.write().await.client_versions = Some(versions); } - /// Sets the protocol version response. - pub async fn set_protocol_version(&self, version: ProtocolVersion) { - self.storage.write().await.protocol_version = Some(version); - } - /// Sets the capabilities response. pub async fn set_capabilities(&self, capabilities: Vec) { self.storage.write().await.capabilities = Some(capabilities); @@ -653,17 +640,6 @@ impl OpEngineApi> for MockEngineClient { }) } - async fn signal_superchain_v1( - &self, - _recommended: ProtocolVersion, - _required: ProtocolVersion, - ) -> TransportResult { - let storage = self.storage.read().await; - storage.protocol_version.ok_or_else(|| { - TransportError::from(TransportErrorKind::custom_str("No protocol version set in mock")) - }) - } - async fn exchange_capabilities( &self, _capabilities: Vec, diff --git a/rust/kona/crates/node/peers/src/nodes.rs b/rust/kona/crates/node/peers/src/nodes.rs index d2c62b6dabc..0ca27321096 100644 --- a/rust/kona/crates/node/peers/src/nodes.rs +++ b/rust/kona/crates/node/peers/src/nodes.rs @@ -139,7 +139,7 @@ mod tests { let mainnet = BootNodes::from_chain_id(OP_MAINNET_CHAIN_ID); assert_eq!(mainnet.len(), 24); - let mainnet = BootNodes::from_chain_id(BASE_MAINNET_CHAIN_ID); + let mainnet = BootNodes::from_chain_id(57073 /* Ink */); assert_eq!(mainnet.len(), 24); let mainnet = BootNodes::from_chain_id(130 /* Unichain Mainnet */); @@ -153,6 +153,11 @@ mod tests { let unknown = BootNodes::from_chain_id(0); assert!(unknown.is_empty()); + + // Base was removed from the superchain registry, so its chain id no + // longer resolves to a known parent and yields no bootnodes. + let base = BootNodes::from_chain_id(BASE_MAINNET_CHAIN_ID); + assert!(base.is_empty()); } #[test] diff --git a/rust/kona/crates/node/rpc/Cargo.toml b/rust/kona/crates/node/rpc/Cargo.toml index dadfbeb43c2..3b4a26e08c6 100644 --- a/rust/kona/crates/node/rpc/Cargo.toml +++ b/rust/kona/crates/node/rpc/Cargo.toml @@ -61,9 +61,6 @@ alloy-rpc-client = { workspace = true, features = ["reqwest"], optional = true } # `metrics` feature metrics = { workspace = true, optional = true } -# `rollup-boost` feature -rollup-boost.workspace = true - [dev-dependencies] serde_json.workspace = true diff --git a/rust/kona/crates/node/rpc/src/admin.rs b/rust/kona/crates/node/rpc/src/admin.rs index 4c13c01d7d0..e7714791eab 100644 --- a/rust/kona/crates/node/rpc/src/admin.rs +++ b/rust/kona/crates/node/rpc/src/admin.rs @@ -1,6 +1,6 @@ //! Admin RPC Module -use crate::{AdminApiServer, RollupBoostAdminClient, SequencerAdminAPIClient}; +use crate::{AdminApiServer, SequencerAdminAPIClient}; use alloy_primitives::B256; use async_trait::async_trait; use core::fmt::Debug; @@ -9,10 +9,6 @@ use jsonrpsee::{ types::{ErrorCode, ErrorObject}, }; use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; -use rollup_boost::{ - ExecutionMode, GetExecutionModeResponse, SetExecutionModeRequest, SetExecutionModeResponse, -}; -use tokio::sync::oneshot; /// The query types to the network actor for the admin api. #[derive(Debug)] @@ -24,71 +20,43 @@ pub enum NetworkAdminQuery { }, } -/// The query types to the rollup boost component of the engine actor. -/// Only set when rollup boost is enabled. -#[derive(Debug)] -pub enum RollupBoostAdminQuery { - /// An admin rpc request to set the execution mode. - SetExecutionMode { - /// The execution mode to set. - execution_mode: ExecutionMode, - /// The sender to send the response confirming the execution mode was updated. - sender: oneshot::Sender<()>, - }, - /// An admin rpc request to get the execution mode. - GetExecutionMode { - /// The sender to send the execution mode to. - sender: oneshot::Sender, - }, -} - type NetworkAdminQuerySender = tokio::sync::mpsc::Sender; /// The admin rpc server. #[derive(Debug)] -pub struct AdminRpc { +pub struct AdminRpc { /// The sequencer admin API client. pub sequencer_admin_client: Option, /// The sender to the network actor. pub network_sender: NetworkAdminQuerySender, - /// The sender to the rollup boost component of the engine actor. - /// Only set when rollup boost is enabled. - pub rollup_boost_client: Option, } -impl - AdminRpc +impl AdminRpc where - RollupBoostAdminClient_: RollupBoostAdminClient, SequencerAdminAPIClient_: SequencerAdminAPIClient, { - /// Constructs a new [`AdminRpc`] given the sequencer sender, network sender, and execution - /// mode. + /// Constructs a new [`AdminRpc`] given the sequencer sender and network sender. /// /// # Parameters /// /// - `sequencer_sender`: The [`SequencerAdminAPIClient`] used to fulfill sequencer admin /// queries. /// - `network_sender`: The sender to the network actor. - /// - `rollup_boost_sender`: The [`RollupBoostAdminClient`] used to fulfill rollup boost admin - /// queries. + /// /// # Returns /// /// A new [`AdminRpc`] instance. pub const fn new( sequencer_admin_client: Option, network_sender: NetworkAdminQuerySender, - rollup_boost_client: Option, ) -> Self { - Self { sequencer_admin_client, network_sender, rollup_boost_client } + Self { sequencer_admin_client, network_sender } } } #[async_trait] -impl AdminApiServer - for AdminRpc +impl AdminApiServer for AdminRpc where - RollupBoostAdminClient_: RollupBoostAdminClient + 'static + Send + Sync, SequencerAdminAPIClient_: SequencerAdminAPIClient + 'static + Send + Sync, { async fn admin_post_unsafe_payload( @@ -186,25 +154,6 @@ where .map_err(|_| ErrorObject::from(ErrorCode::InternalError)) } - async fn set_execution_mode( - &self, - request: SetExecutionModeRequest, - ) -> RpcResult { - let Some(ref client) = self.rollup_boost_client else { - return Err(ErrorObject::from(ErrorCode::MethodNotFound)); - }; - - client.set_execution_mode(request).await - } - - async fn get_execution_mode(&self) -> RpcResult { - let Some(ref client) = self.rollup_boost_client else { - return Err(ErrorObject::from(ErrorCode::MethodNotFound)); - }; - - client.get_execution_mode().await - } - async fn admin_reset_derivation_pipeline(&self) -> RpcResult<()> { // If the sequencer is not enabled (mode runs in validator mode), return an error. let Some(ref sequencer_client) = self.sequencer_admin_client else { diff --git a/rust/kona/crates/node/rpc/src/client.rs b/rust/kona/crates/node/rpc/src/client.rs index 1505e48f70f..7833b0636f0 100644 --- a/rust/kona/crates/node/rpc/src/client.rs +++ b/rust/kona/crates/node/rpc/src/client.rs @@ -5,7 +5,6 @@ use jsonrpsee::core::RpcResult; use kona_engine::EngineState; use kona_genesis::RollupConfig; use kona_protocol::{L2BlockInfo, OutputRoot}; -use rollup_boost::{GetExecutionModeResponse, SetExecutionModeRequest, SetExecutionModeResponse}; use std::fmt::Debug; use thiserror::Error; use tokio::sync::watch; @@ -35,19 +34,6 @@ pub trait EngineRpcClient: Debug + Send + Sync + Clone { async fn dev_subscribe_to_engine_state(&self) -> RpcResult>; } -/// Client trait wrapping RPC implementation for the rollup boost admin endpoints. -#[async_trait] -pub trait RollupBoostAdminClient: Send + Sync + Debug { - /// Sets the execution mode for the rollup boost server. - async fn set_execution_mode( - &self, - request: SetExecutionModeRequest, - ) -> RpcResult; - - /// Gets the current execution mode from the rollup boost server. - async fn get_execution_mode(&self) -> RpcResult; -} - /// Client trait wrapping RPC implementation for the Sequencer admin endpoints. #[async_trait] pub trait SequencerAdminAPIClient: Send + Sync + Debug { diff --git a/rust/kona/crates/node/rpc/src/health.rs b/rust/kona/crates/node/rpc/src/health.rs index 777c5c781e6..1a831bcc7c3 100644 --- a/rust/kona/crates/node/rpc/src/health.rs +++ b/rust/kona/crates/node/rpc/src/health.rs @@ -1,52 +1,8 @@ use async_trait::async_trait; use jsonrpsee::core::RpcResult; -use rollup_boost::Health; -use tokio::sync::oneshot; use crate::jsonrpsee::HealthzApiServer; -/// Key for the rollup boost health status. -/// +----------------+-------------------------------+--------------------------------------+-------------------------------+ -/// | Execution Mode | Healthy | `PartialContent` | Service Unavailable | -/// +----------------+-------------------------------+--------------------------------------+-------------------------------+ -/// | Enabled | - Request-path: L2 succeeds | - Request-path: builder fails/stale | - Request-path: L2 fails | -/// | | (get/new payload) → 200 | while L2 succeeds → 206 | (error from L2) → 503 | -/// | | - Background: builder | - Background: builder fetch fails or | - Background: never sets 503 | -/// | | latest-unsafe is fresh → | latest-unsafe is stale → 206 | | -/// | | 200 | | | -/// +----------------+-------------------------------+--------------------------------------+-------------------------------+ -/// | `DryRun` | - Request-path: L2 succeeds | - Never set in `DryRun` | - Request-path: L2 fails | -/// | | (always returns L2) → 200 | (degrade only in Enabled) | (error from L2) → 503 | -/// | | - Background: builder stale | | - Background: never sets 503 | -/// | | ignored (remains 200) | | | -/// +----------------+-------------------------------+--------------------------------------+-------------------------------+ -/// | Disabled | - Request-path: L2 succeeds | - Never set in Disabled | - Request-path: L2 fails | -/// | | (builder skipped) → 200 | (degrade only in Enabled) | (error from L2) → 503 | -/// | | - Background: N/A | | - Background: never sets 503 | -/// +----------------+-------------------------------+--------------------------------------+-------------------------------+ -/// -/// This type is the same as [`Health`], but it implements `serde::Serialize` -/// and `serde::Deserialize`. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] -pub enum RollupBoostHealth { - /// Rollup boost is healthy. - Healthy, - /// Rollup boost is partially healthy. - PartialContent, - /// Rollup boost service is unavailable. - ServiceUnavailable, -} - -impl From for RollupBoostHealth { - fn from(health: Health) -> Self { - match health { - Health::Healthy => Self::Healthy, - Health::PartialContent => Self::PartialContent, - Health::ServiceUnavailable => Self::ServiceUnavailable, - } - } -} - /// A healthcheck response for the RPC server. #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] pub struct HealthzResponse { @@ -54,20 +10,6 @@ pub struct HealthzResponse { pub version: String, } -/// A healthcheck response for the rollup boost health. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] -pub struct RollupBoostHealthzResponse { - /// The rollup boost health. - pub rollup_boost_health: RollupBoostHealth, -} - -/// A query to get the health of the rollup boost server. -#[derive(Debug)] -pub struct RollupBoostHealthQuery { - /// The sender to send the rollup boost health to. - pub sender: oneshot::Sender, -} - /// The healthz rpc server. #[derive(Debug, Clone)] pub struct HealthzRpc {} diff --git a/rust/kona/crates/node/rpc/src/jsonrpsee.rs b/rust/kona/crates/node/rpc/src/jsonrpsee.rs index c09ca3e4bea..c79acccdc5c 100644 --- a/rust/kona/crates/node/rpc/src/jsonrpsee.rs +++ b/rust/kona/crates/node/rpc/src/jsonrpsee.rs @@ -1,9 +1,6 @@ //! The Optimism RPC API using `jsonrpsee` -use crate::{ - OutputResponse, SafeHeadResponse, - health::{HealthzResponse, RollupBoostHealthzResponse}, -}; +use crate::{OutputResponse, SafeHeadResponse, health::HealthzResponse}; use alloy_eips::BlockNumberOrTag; use alloy_primitives::B256; use core::net::IpAddr; @@ -16,7 +13,6 @@ use kona_genesis::RollupConfig; use kona_gossip::{PeerCount, PeerDump, PeerInfo, PeerStats}; use kona_protocol::SyncStatus; use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; -use rollup_boost::{GetExecutionModeResponse, SetExecutionModeRequest, SetExecutionModeResponse}; #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), allow(unused_imports))] use getrandom as _; // required for compiling wasm32-unknown-unknown @@ -206,17 +202,6 @@ pub trait AdminApi { /// Resets the derivation pipeline. #[method(name = "resetDerivationPipeline")] async fn admin_reset_derivation_pipeline(&self) -> RpcResult<()>; - - /// Sets the rollup boost execution mode. - #[method(name = "setExecutionMode")] - async fn set_execution_mode( - &self, - request: SetExecutionModeRequest, - ) -> RpcResult; - - /// Gets the rollup boost execution mode. - #[method(name = "getExecutionMode")] - async fn get_execution_mode(&self) -> RpcResult; } /// The admin namespace for the consensus node. @@ -227,12 +212,3 @@ pub trait HealthzApi { #[method(name = "healthz")] async fn healthz(&self) -> RpcResult; } - -/// The rollup boost health namespace. -#[cfg_attr(not(feature = "client"), rpc(server, namespace = "kona-rollup-boost"))] -#[cfg_attr(feature = "client", rpc(server, client, namespace = "kona-rollup-boost"))] -pub trait RollupBoostHealthzApi { - /// Gets the rollup boost health. - #[method(name = "healthz")] - async fn rollup_boost_healthz(&self) -> RpcResult; -} diff --git a/rust/kona/crates/node/rpc/src/lib.rs b/rust/kona/crates/node/rpc/src/lib.rs index 28d8559986b..37b4dbe11a7 100644 --- a/rust/kona/crates/node/rpc/src/lib.rs +++ b/rust/kona/crates/node/rpc/src/lib.rs @@ -10,12 +10,10 @@ extern crate tracing; mod admin; -pub use admin::{AdminRpc, NetworkAdminQuery, RollupBoostAdminQuery}; +pub use admin::{AdminRpc, NetworkAdminQuery}; mod client; -pub use client::{ - EngineRpcClient, RollupBoostAdminClient, SequencerAdminAPIClient, SequencerAdminAPIError, -}; +pub use client::{EngineRpcClient, SequencerAdminAPIClient, SequencerAdminAPIError}; mod config; pub use config::RpcBuilder; @@ -37,7 +35,7 @@ pub use dev::DevEngineRpc; mod jsonrpsee; pub use jsonrpsee::{ AdminApiServer, DevEngineApiServer, HealthzApiServer, MinerApiExtServer, OpAdminApiServer, - OpP2PApiServer, RollupBoostHealthzApiServer, RollupNodeApiServer, WsServer, + OpP2PApiServer, RollupNodeApiServer, WsServer, }; #[cfg(feature = "client")] @@ -53,7 +51,4 @@ mod ws; pub use ws::WsRPC; mod health; -pub use health::{ - HealthzResponse, HealthzRpc, RollupBoostHealth, RollupBoostHealthQuery, - RollupBoostHealthzResponse, -}; +pub use health::{HealthzResponse, HealthzRpc}; diff --git a/rust/kona/crates/node/service/Cargo.toml b/rust/kona/crates/node/service/Cargo.toml index 9014f105fca..4f0cd02c806 100644 --- a/rust/kona/crates/node/service/Cargo.toml +++ b/rust/kona/crates/node/service/Cargo.toml @@ -19,15 +19,13 @@ kona-engine.workspace = true kona-sources.workspace = true kona-genesis.workspace = true kona-derive.workspace = true +kona-interop.workspace = true kona-protocol.workspace = true kona-providers-alloy.workspace = true kona-rpc = { workspace = true, features = ["client"] } kona-peers.workspace = true kona-macros.workspace = true -# rollup-boost -rollup-boost.workspace = true - # alloy alloy-chains.workspace = true alloy-signer.workspace = true @@ -75,7 +73,6 @@ arbitrary.workspace = true rand.workspace = true anyhow.workspace = true backon.workspace = true -http.workspace = true mockall.workspace = true alloy-primitives = { workspace = true, features = ["k256"] } alloy-rpc-types-engine = { workspace = true, features = ["arbitrary"] } diff --git a/rust/kona/crates/node/service/src/actors/derivation/actor.rs b/rust/kona/crates/node/service/src/actors/derivation/actor.rs index 1df73b78145..f1c78ed89ea 100644 --- a/rust/kona/crates/node/service/src/actors/derivation/actor.rs +++ b/rust/kona/crates/node/service/src/actors/derivation/actor.rs @@ -1,19 +1,19 @@ //! [`NodeActor`] implementation for the derivation sub-routine. use crate::{ - CancellableContext, DerivationActorRequest, DerivationEngineClient, DerivationState, - DerivationStateMachine, DerivationStateTransitionError, DerivationStateUpdate, Metrics, - NodeActor, actors::derivation::L2Finalizer, + DerivationActorRequest, DerivationEngineClient, DerivationState, DerivationStateMachine, + DerivationStateTransitionError, DerivationStateUpdate, Metrics, NodeActor, + actors::derivation::L2Finalizer, }; use async_trait::async_trait; use kona_derive::{ - ActivationSignal, Pipeline, PipelineError, PipelineErrorKind, ResetError, ResetSignal, Signal, + ActivationSignal, Pipeline, PipelineError, PipelineErrorKind, ResetError, Signal, SignalReceiver, StepResult, }; +use kona_engine::FinalizeBlockId; use kona_protocol::OpAttributesWithParent; use thiserror::Error; -use tokio::{select, sync::mpsc}; -use tokio_util::sync::{CancellationToken, WaitForCancellationFuture}; +use tokio::sync::mpsc; /// The [`NodeActor`] for the derivation sub-routine. /// @@ -26,8 +26,6 @@ where DerivationEngineClient_: DerivationEngineClient, PipelineSignalReceiver: Pipeline + SignalReceiver, { - /// The cancellation token, shared between all tasks. - cancellation_token: CancellationToken, /// The channel on which all inbound requests are received by the [`DerivationActor`]. inbound_request_rx: mpsc::Receiver, /// The Engine client used to interact with the engine. @@ -41,17 +39,6 @@ where pub(crate) finalizer: L2Finalizer, } -impl CancellableContext - for DerivationActor -where - DerivationEngineClient_: DerivationEngineClient, - PipelineSignalReceiver: Pipeline + SignalReceiver + Send + Sync, -{ - fn cancelled(&self) -> WaitForCancellationFuture<'_> { - self.cancellation_token.cancelled() - } -} - impl DerivationActor where @@ -61,12 +48,10 @@ where /// Creates a new instance of the [`DerivationActor`]. pub fn new( engine_client: DerivationEngineClient_, - cancellation_token: CancellationToken, inbound_request_rx: mpsc::Receiver, pipeline: PipelineSignalReceiver, ) -> Self { Self { - cancellation_token, pipeline, inbound_request_rx, engine_client, @@ -77,8 +62,7 @@ where /// Handles a [`Signal`] received over the derivation signal receiver channel. async fn signal(&mut self, signal: Signal) { - if let Signal::Reset(ResetSignal { l1_origin, .. }) = signal { - kona_macros::set!(counter, Metrics::DERIVATION_L1_ORIGIN, l1_origin.number); + if matches!(signal, Signal::Reset(_)) { // Clear the finalization queue on reset. self.finalizer.clear(); } @@ -126,33 +110,13 @@ where PipelineErrorKind::Reset(e) => { warn!(target: "derivation", "Derivation pipeline is being reset: {e}"); - let system_config = self - .pipeline - .system_config_by_number( - self.derivation_state_machine - .last_confirmed_safe_head() - .block_info - .number, - ) - .await?; - if matches!(e, ResetError::HoloceneActivation) { - let l1_origin = self - .pipeline - .origin() - .ok_or(PipelineError::MissingOrigin.crit())?; - self.pipeline - .signal( - ActivationSignal { - l2_safe_head: self - .derivation_state_machine - .last_confirmed_safe_head(), - l1_origin, - system_config: Some(system_config), - } - .signal(), - ) + .signal(Signal::Activation(ActivationSignal { + l2_safe_head: self + .derivation_state_machine + .last_confirmed_safe_head(), + })) .await?; } else { if let ResetError::ReorgDetected(expected, new) = e { @@ -210,8 +174,10 @@ where // Attempt to finalize the block. If successful, notify engine. if let Some(l2_block_number) = self.finalizer.try_finalize_next(*finalized_l1_block) { + // Local L1-finality: the engine's own canonical chain is the authoritative + // source at this height, so finalize by number. self.engine_client - .send_finalized_l2_block(l2_block_number) + .send_finalized_l2_block(FinalizeBlockId::ByNumber(l2_block_number)) .await .map_err(|e| DerivationError::Sender(Box::new(e)))?; } @@ -291,32 +257,16 @@ where PipelineSignalReceiver: Pipeline + SignalReceiver + Send + Sync + 'static, { type Error = DerivationError; - type StartData = (); - async fn start(mut self, _: Self::StartData) -> Result<(), Self::Error> { - info!(target: "derivation", "Starting derivation"); - loop { - select! { - biased; - - _ = self.cancellation_token.cancelled() => { - info!( - target: "derivation", - "Received shutdown signal. Exiting derivation task." - ); - return Ok(()); - } - req = self.inbound_request_rx.recv() => { - let Some(request_type) = req else { - error!(target: "derivation", "DerivationActor inbound request receiver closed unexpectedly"); - self.cancellation_token.cancel(); - return Err(DerivationError::RequestReceiveFailed); - }; - - self.handle_derivation_actor_request(request_type).await?; - } - } - } + async fn step(&mut self) -> Result<(), Self::Error> { + let request = self.inbound_request_rx.recv().await.ok_or_else(|| { + error!( + target: "derivation", + "DerivationActor inbound request receiver closed unexpectedly", + ); + DerivationError::RequestReceiveFailed + })?; + self.handle_derivation_actor_request(request).await } } diff --git a/rust/kona/crates/node/service/src/actors/derivation/delegated/actor.rs b/rust/kona/crates/node/service/src/actors/derivation/delegated/actor.rs index bbe03d37052..051480f78f5 100644 --- a/rust/kona/crates/node/service/src/actors/derivation/delegated/actor.rs +++ b/rust/kona/crates/node/service/src/actors/derivation/delegated/actor.rs @@ -1,14 +1,14 @@ use crate::{ - CancellableContext, DerivationActorRequest, DerivationEngineClient, NodeActor, - actors::derivation::{DerivationDelegateClient, DerivationError}, + DerivationActorRequest, DerivationEngineClient, NodeActor, + actors::derivation::{DerivationDelegateProvider, DerivationError}, }; use alloy_primitives::BlockHash; use async_trait::async_trait; +use kona_derive::ChainProvider; +use kona_engine::FinalizeBlockId; use kona_protocol::{L2BlockInfo, SyncStatus}; -use kona_providers_alloy::AlloyChainProvider; use thiserror::Error; use tokio::{select, sync::mpsc, time}; -use tokio_util::sync::{CancellationToken, WaitForCancellationFuture}; /// The [`NodeActor`] for the delegate derivation sub-routine. /// @@ -20,78 +20,97 @@ use tokio_util::sync::{CancellationToken, WaitForCancellationFuture}; /// Once validated, the actor sends the derived safe and finalized L2 info /// to the [`NodeActor`] responsible for the execution sub-routine. #[derive(Debug)] -pub struct DelegateDerivationActor +pub struct DelegateDerivationActor where DerivationEngineClient_: DerivationEngineClient, + DelegateProvider: DerivationDelegateProvider, + L1Provider: ChainProvider, { - /// The cancellation token, shared between all tasks. - cancellation_token: CancellationToken, /// The channel on which all inbound requests are received by the [`DelegateDerivationActor`]. inbound_request_rx: mpsc::Receiver, /// The Engine client used to interact with the engine. engine_client: DerivationEngineClient_, /// Derivation delegate provider. - derivation_delegate_provider: DerivationDelegateClient, + derivation_delegate_provider: DelegateProvider, /// L1 provider for validating L1 info for derivation delegation. - l1_provider: AlloyChainProvider, + l1_provider: L1Provider, /// The engine's L2 safe head, according to updates from the Engine. engine_l2_safe_head: L2BlockInfo, /// Whether the engine sync has completed. This will only ever go from false -> true. has_engine_sync_completed: bool, + /// Ticker driving periodic polls of the derivation delegate provider. + delegated_derivation_ticker: time::Interval, } -impl CancellableContext - for DelegateDerivationActor +impl + DelegateDerivationActor where - DerivationEngineClient_: DerivationEngineClient, -{ - fn cancelled(&self) -> WaitForCancellationFuture<'_> { - self.cancellation_token.cancelled() - } -} - -impl DelegateDerivationActor -where - DerivationEngineClient_: DerivationEngineClient, + DerivationEngineClient_: DerivationEngineClient + 'static, + DelegateProvider: DerivationDelegateProvider + 'static, + L1Provider: ChainProvider + 'static, { /// Creates a new instance of the [`DelegateDerivationActor`]. pub fn new( engine_client: DerivationEngineClient_, - cancellation_token: CancellationToken, inbound_request_rx: mpsc::Receiver, - derivation_delegate_provider: DerivationDelegateClient, - l1_provider: AlloyChainProvider, + derivation_delegate_provider: DelegateProvider, + l1_provider: L1Provider, ) -> Self { + let mut delegated_derivation_ticker = + time::interval(Self::DERIVATION_DELEGATE_POLL_INTERVAL); + delegated_derivation_ticker.set_missed_tick_behavior(time::MissedTickBehavior::Skip); Self { - cancellation_token, inbound_request_rx, engine_client, derivation_delegate_provider, l1_provider, engine_l2_safe_head: L2BlockInfo::default(), has_engine_sync_completed: false, + delegated_derivation_ticker, } } } #[async_trait] -impl NodeActor for DelegateDerivationActor +impl NodeActor + for DelegateDerivationActor where DerivationEngineClient_: DerivationEngineClient + 'static, + DelegateProvider: DerivationDelegateProvider + 'static, + L1Provider: ChainProvider + Send + 'static, { type Error = DerivationError; - type StartData = (); - async fn start(mut self, _: Self::StartData) -> Result<(), Self::Error> { - self.start_delegate_derivation().await + async fn step(&mut self) -> Result<(), Self::Error> { + select! { + biased; + + req = self.inbound_request_rx.recv() => { + let request = req.ok_or_else(|| { + error!( + target: "derivation", + "DerivationActor inbound request receiver closed unexpectedly", + ); + DerivationError::RequestReceiveFailed + })?; + self.handle_derivation_delegation_actor_request(request).await + } + _ = self.delegated_derivation_ticker.tick(), + if self.has_engine_sync_completed => { + self.fetch_and_apply_delegate_safe_head().await + } + } } } -impl DelegateDerivationActor +impl + DelegateDerivationActor where DerivationEngineClient_: DerivationEngineClient + 'static, + DelegateProvider: DerivationDelegateProvider + 'static, + L1Provider: ChainProvider + 'static, { /// Hardcoded poll interval for Derivation Delegation const DERIVATION_DELEGATE_POLL_INTERVAL: std::time::Duration = @@ -104,8 +123,6 @@ where l1_block_number: u64, expected_hash: BlockHash, ) -> Result<(), DerivationDelegationError> { - use kona_derive::ChainProvider; - let block = self .l1_provider .block_info_by_number(l1_block_number) @@ -172,8 +189,13 @@ where .await .map_err(|e| DerivationError::Sender(Box::new(e)))?; + // Delegated polling supplies `(number, hash)`. Carry the hash through so the engine + // finalizes the specific block we were asked to, not whatever it happens to have at the + // same height. self.engine_client - .send_finalized_l2_block(sync_status.finalized_l2.block_info.number) + .send_finalized_l2_block(FinalizeBlockId::ByHash( + sync_status.finalized_l2.block_info.id(), + )) .await .map_err(|e| DerivationError::Sender(Box::new(e)))?; @@ -187,39 +209,6 @@ where Ok(()) } - async fn start_delegate_derivation(mut self) -> Result<(), DerivationError> { - info!(target: "derivation", "Starting derivation with delegation"); - let mut delegated_derivation_ticker = - time::interval(Self::DERIVATION_DELEGATE_POLL_INTERVAL); - delegated_derivation_ticker.set_missed_tick_behavior(time::MissedTickBehavior::Skip); - loop { - select! { - biased; - - _ = self.cancellation_token.cancelled() => { - info!( - target: "derivation", - "Received shutdown signal. Exiting derivation task." - ); - return Ok(()); - } - req = self.inbound_request_rx.recv() => { - let Some(request_type) = req else { - error!(target: "derivation", "DerivationActor inbound request receiver closed unexpectedly"); - self.cancellation_token.cancel(); - return Err(DerivationError::RequestReceiveFailed); - }; - - self.handle_derivation_delegation_actor_request(request_type).await?; - } - _ = delegated_derivation_ticker.tick(), - if self.has_engine_sync_completed => { - self.fetch_and_apply_delegate_safe_head().await?; - } - } - } - } - async fn handle_derivation_delegation_actor_request( &mut self, request_type: DerivationActorRequest, diff --git a/rust/kona/crates/node/service/src/actors/derivation/delegated/client.rs b/rust/kona/crates/node/service/src/actors/derivation/delegated/client.rs index f2b5d9f7b8f..76ebe3fc4a4 100644 --- a/rust/kona/crates/node/service/src/actors/derivation/delegated/client.rs +++ b/rust/kona/crates/node/service/src/actors/derivation/delegated/client.rs @@ -1,5 +1,6 @@ //! Client for polling Derivation Delegate sync status. +use async_trait::async_trait; use jsonrpsee::{ core::ClientError, http_client::{HttpClient, HttpClientBuilder}, @@ -29,6 +30,16 @@ pub enum DerivationDelegateClientError { HttpClientBuild(String), } +/// Polls sync status from an external OP Stack CL node acting as the derivation delegate. +/// +/// Abstracted as a trait so [`DelegateDerivationActor`](super::DelegateDerivationActor) can be +/// constructed with a mock in tests instead of a live HTTP client. +#[async_trait] +pub trait DerivationDelegateProvider: Send + Sync { + /// Fetches the current sync status from the derivation delegate. + async fn fetch_sync_status(&self) -> Result; +} + /// Client for fetching sync status from an external OP Stack CL node. #[derive(Debug, Clone)] pub struct DerivationDelegateClient { @@ -46,11 +57,12 @@ impl DerivationDelegateClient { Ok(Self { derivation_client }) } +} - /// Fetches the current sync status from the Derivation Delegate. - /// +#[async_trait] +impl DerivationDelegateProvider for DerivationDelegateClient { /// Calls `optimism_syncStatus` RPC method. - pub async fn fetch_sync_status(&self) -> Result { + async fn fetch_sync_status(&self) -> Result { Ok(self.derivation_client.op_sync_status().await?) } } diff --git a/rust/kona/crates/node/service/src/actors/derivation/delegated/mod.rs b/rust/kona/crates/node/service/src/actors/derivation/delegated/mod.rs index 70635837cf7..e5460eceb11 100644 --- a/rust/kona/crates/node/service/src/actors/derivation/delegated/mod.rs +++ b/rust/kona/crates/node/service/src/actors/derivation/delegated/mod.rs @@ -2,4 +2,6 @@ mod actor; pub use actor::DelegateDerivationActor; mod client; -pub use client::{DerivationDelegateClient, DerivationDelegateClientError}; +pub use client::{ + DerivationDelegateClient, DerivationDelegateClientError, DerivationDelegateProvider, +}; diff --git a/rust/kona/crates/node/service/src/actors/derivation/engine_client.rs b/rust/kona/crates/node/service/src/actors/derivation/engine_client.rs index b1b36500b42..734a7ba54a3 100644 --- a/rust/kona/crates/node/service/src/actors/derivation/engine_client.rs +++ b/rust/kona/crates/node/service/src/actors/derivation/engine_client.rs @@ -1,7 +1,7 @@ use crate::{EngineActorRequest, EngineClientError, EngineClientResult, ResetRequest}; use async_trait::async_trait; use derive_more::Constructor; -use kona_engine::ConsolidateInput; +use kona_engine::{ConsolidateInput, FinalizeBlockId}; use std::fmt::Debug; use tokio::sync::mpsc; @@ -12,9 +12,9 @@ pub trait DerivationEngineClient: Debug + Send + Sync { /// Resets the engine's forkchoice. async fn reset_engine_forkchoice(&self) -> EngineClientResult<()>; - /// Sends a request to finalize the L2 block at the provided block number. + /// Sends a request to finalize the L2 block identified by the provided [`FinalizeBlockId`]. /// Note: This does not wait for the engine to process it. - async fn send_finalized_l2_block(&self, block_number: u64) -> EngineClientResult<()>; + async fn send_finalized_l2_block(&self, block_id: FinalizeBlockId) -> EngineClientResult<()>; /// Sends a consolidation signal to the engine. /// @@ -40,7 +40,7 @@ impl DerivationEngineClient for QueuedDerivationEngineClient { info!(target: "derivation", "Sending reset request to engine."); self.engine_actor_request_tx - .send(EngineActorRequest::ResetRequest(Box::new(ResetRequest { result_tx }))) + .send(EngineActorRequest::Reset(Box::new(ResetRequest { result_tx }))) .await .map_err(|_| EngineClientError::RequestError("request channel closed.".to_string()))?; @@ -54,10 +54,10 @@ impl DerivationEngineClient for QueuedDerivationEngineClient { })? } - async fn send_finalized_l2_block(&self, block_number: u64) -> EngineClientResult<()> { - trace!(target: "derivation", block_number, "Sending finalized L2 block number to engine."); + async fn send_finalized_l2_block(&self, block_id: FinalizeBlockId) -> EngineClientResult<()> { + trace!(target: "derivation", ?block_id, "Sending finalized L2 block id to engine."); self.engine_actor_request_tx - .send(EngineActorRequest::ProcessFinalizedL2BlockNumberRequest(Box::new(block_number))) + .send(EngineActorRequest::ProcessFinalizedL2Block(Box::new(block_id))) .await .map_err(|_| EngineClientError::RequestError("request channel closed.".to_string()))?; @@ -67,7 +67,7 @@ impl DerivationEngineClient for QueuedDerivationEngineClient { async fn send_safe_l2_signal(&self, signal: ConsolidateInput) -> EngineClientResult<()> { trace!(target: "derivation", ?signal, "Sending safe L2 signal info to engine."); self.engine_actor_request_tx - .send(EngineActorRequest::ProcessSafeL2SignalRequest(signal)) + .send(EngineActorRequest::ProcessSafeL2Signal(signal)) .await .map_err(|_| EngineClientError::RequestError("request channel closed.".to_string()))?; diff --git a/rust/kona/crates/node/service/src/actors/derivation/mod.rs b/rust/kona/crates/node/service/src/actors/derivation/mod.rs index 11aa1c6efc6..af3263147e0 100644 --- a/rust/kona/crates/node/service/src/actors/derivation/mod.rs +++ b/rust/kona/crates/node/service/src/actors/derivation/mod.rs @@ -4,6 +4,7 @@ pub use actor::{DerivationActor, DerivationError}; mod delegated; pub use delegated::{ DelegateDerivationActor, DerivationDelegateClient, DerivationDelegateClientError, + DerivationDelegateProvider, }; mod engine_client; diff --git a/rust/kona/crates/node/service/src/actors/engine/actor.rs b/rust/kona/crates/node/service/src/actors/engine/actor.rs index 0a1439c190f..01f7de51b72 100644 --- a/rust/kona/crates/node/service/src/actors/engine/actor.rs +++ b/rust/kona/crates/node/service/src/actors/engine/actor.rs @@ -1,163 +1,304 @@ -//! The [`EngineActor`]. - use crate::{ - EngineActorRequest, EngineError, EngineProcessingRequest, EngineRequestReceiver, - EngineRpcRequestReceiver, NodeActor, actors::CancellableContext, + BuildRequest, EngineClientError, EngineDerivationClient, EngineError, NodeActor, ResetRequest, + SealRequest, }; use async_trait::async_trait; -use derive_more::Constructor; -use futures::FutureExt; -use tokio::sync::mpsc; -use tokio_util::{ - future::FutureExt as _, - sync::{CancellationToken, WaitForCancellationFuture}, +use kona_derive::{ResetSignal, Signal}; +use kona_engine::{ + BuildTask, ConsolidateInput, ConsolidateTask, Engine, EngineClient, EngineTask, + EngineTaskError, EngineTaskErrorSeverity, FinalizeBlockId, FinalizeTask, InsertTask, SealTask, }; +use kona_genesis::RollupConfig; +use kona_protocol::L2BlockInfo; +use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; +use std::sync::Arc; +use tokio::sync::{mpsc, watch}; + +/// A request handled by the [`EngineActor`]. +#[derive(Debug)] +pub enum EngineActorRequest { + /// Request to start building a block. + Build(Box), + /// Request to process a Safe signal, which can be derived attributes or delegated block info. + ProcessSafeL2Signal(ConsolidateInput), + /// Request to process the finalized L2 block identified by the provided [`FinalizeBlockId`]. + ProcessFinalizedL2Block(Box), + /// Request to process a received unsafe L2 block. + ProcessUnsafeL2Block(Box), + /// Request to reset the forkchoice. + Reset(Box), + /// Request to seal a block. + Seal(Box), +} -/// The [`EngineActor`] is an intermediary that receives [`EngineActorRequest`] and delegates: -/// - Engine RPC queries requests to the configured [`EngineRpcRequestReceiver`] -/// - Node engine requests to the configured [`EngineRequestReceiver`]. -#[derive(Constructor, Debug)] -pub struct EngineActor +/// Responsible for managing the operations sent to the execution layer's Engine API. To accomplish +/// this, it uses the [`Engine`] task queue to order Engine API interactions based off of +/// the [`Ord`] implementation of [`EngineTask`]. +#[derive(Debug)] +pub struct EngineActor where - EngineRequestReceiver_: EngineRequestReceiver, - RpcRequestReceiver: EngineRpcRequestReceiver, + EngineClient_: EngineClient, + DerivationClient: EngineDerivationClient, { - /// The cancellation token shared by all tasks. - cancellation_token: CancellationToken, + /// The client used to send messages to the [`crate::DerivationActor`]. + derivation_client: DerivationClient, + /// Whether the EL sync is complete. This should only ever go from false to true. + el_sync_complete: bool, + /// The last safe head update sent. + last_safe_head_sent: L2BlockInfo, + /// A channel to use to relay the current unsafe head. + /// ## Note + /// This is `Some` when the node is in sequencer mode, and `None` when the node is in validator + /// mode. + unsafe_head_tx: Option>, + + /// The [`RollupConfig`] used to build tasks. + rollup: Arc, + /// An [`EngineClient`] used for creating engine tasks. + client: Arc, + /// The [`Engine`] task queue. + engine: Engine, /// The inbound request channel. inbound_request_rx: mpsc::Receiver, - /// The processor for engine requests - engine_receiver: EngineRequestReceiver_, - /// The processor for engine RPC requests - rpc_receiver: RpcRequestReceiver, } -impl CancellableContext - for EngineActor +impl EngineActor where - EngineRequestReceiver_: EngineRequestReceiver, - RpcRequestReceiver: EngineRpcRequestReceiver, + EngineClient_: EngineClient + 'static, + DerivationClient: EngineDerivationClient + 'static, { - fn cancelled(&self) -> WaitForCancellationFuture<'_> { - self.cancellation_token.cancelled() + /// Constructs a new [`EngineActor`] from the params. + pub fn new( + client: Arc, + config: Arc, + derivation_client: DerivationClient, + engine: Engine, + unsafe_head_tx: Option>, + inbound_request_rx: mpsc::Receiver, + ) -> Self { + Self { + client, + derivation_client, + el_sync_complete: false, + engine, + last_safe_head_sent: L2BlockInfo::default(), + rollup: config, + unsafe_head_tx, + inbound_request_rx, + } } -} -#[async_trait] -impl NodeActor - for EngineActor -where - EngineRequestReceiver_: EngineRequestReceiver + 'static, - RpcRequestReceiver: EngineRpcRequestReceiver + 'static, -{ - type Error = EngineError; - type StartData = (); - - async fn start(mut self, _: Self::StartData) -> Result<(), Self::Error> { - let (rpc_tx, rpc_rx) = mpsc::channel(1024); - let (engine_processing_tx, engine_processing_rx) = mpsc::channel(1024); - - // Helper to DRY task completion handling for RPC & Processing tasks. - let handle_task_result = |task_name: &'static str, cancel_token: CancellationToken| { - move |result: Option, tokio::task::JoinError>>| async move { - cancel_token.cancel(); - - let Some(result) = result else { - warn!(target: "engine", "{task_name} task cancelled"); - return Ok(()); - }; - - let Ok(result) = result else { - error!(target: "engine", ?result, "{task_name} task panicked"); - return Err(EngineError::ChannelClosed); - }; - - match result { - Ok(()) => { - info!(target: "engine", "{task_name} task completed successfully"); - Ok(()) + /// Resets the inner [`Engine`] and propagates the reset to the derivation actor. + async fn reset(&mut self) -> Result<(), EngineError> { + // Reset the engine. + let l2_safe_head = self.engine.reset(self.client.clone(), self.rollup.clone()).await?; + + // Signal the derivation actor to reset. + let signal = Signal::Reset(ResetSignal { l2_safe_head }); + match self.derivation_client.send_signal(signal).await { + Ok(_) => info!(target: "engine", "Sent reset signal to derivation actor"), + Err(err) => { + error!(target: "engine", ?err, "Failed to send reset signal to the derivation actor"); + return Err(EngineError::ChannelClosed); + } + } + + self.send_derivation_actor_safe_head_if_updated().await?; + + Ok(()) + } + + /// Drains the inner [`Engine`] task queue and attempts to update the safe head. + async fn drain(&mut self) -> Result<(), EngineError> { + match self.engine.drain().await { + Ok(_) => { + trace!(target: "engine", "[ENGINE] tasks drained"); + } + Err(err) => { + match err.severity() { + EngineTaskErrorSeverity::Critical => { + error!(target: "engine", ?err, "Critical error draining engine tasks"); + return Err(err.into()); + } + EngineTaskErrorSeverity::Reset => { + warn!(target: "engine", ?err, "Received reset request"); + self.reset().await?; + } + EngineTaskErrorSeverity::Flush => { + // This error is encountered when the payload is marked INVALID + // by the engine api. Post-holocene, the payload is replaced by + // a "deposits-only" block and re-executed. At the same time, + // the channel and any remaining buffered batches are flushed. + warn!(target: "engine", ?err, "Invalid payload, Flushing derivation pipeline."); + match self.derivation_client.send_signal(Signal::FlushChannel).await { + Ok(_) => { + debug!(target: "engine", "Sent flush signal to derivation actor") + } + Err(err) => { + error!(target: "engine", ?err, "Failed to send flush signal to the derivation actor."); + return Err(EngineError::ChannelClosed); + } + } } - Err(err) => { - error!(target: "engine", ?err, "{task_name} task failed"); - Err(err) + EngineTaskErrorSeverity::Temporary => { + trace!(target: "engine", ?err, "Temporary error draining engine tasks"); } } } - }; - - let rpc_cancellation = self.cancellation_token.clone(); - // Start the engine query server in a separate task to avoid blocking the main task. - let rpc_handle = self - .rpc_receiver - .start(rpc_rx) - .with_cancellation_token(&rpc_cancellation) - .then(handle_task_result("Engine query", rpc_cancellation.clone())); - - let processing_cancellation = self.cancellation_token.clone(); - // Start the engine processing task. - let processing_handle = self - .engine_receiver - .start(engine_processing_rx) - .with_cancellation_token(&processing_cancellation) - .then(handle_task_result("Engine processing", processing_cancellation.clone())); - - // Helper to send processing requests with error handling. - let send_engine_processing_request = |req: EngineProcessingRequest| async { - engine_processing_tx.send(req).await.map_err(|_| { - error!(target: "engine", "Engine processing channel closed unexpectedly"); - self.cancellation_token.clone().cancel(); + } + + self.send_derivation_actor_safe_head_if_updated().await?; + + if !self.el_sync_complete && self.engine.state().el_sync_finished { + self.mark_el_sync_complete_and_notify_derivation_actor().await?; + } + + Ok(()) + } + + async fn mark_el_sync_complete_and_notify_derivation_actor( + &mut self, + ) -> Result<(), EngineError> { + self.el_sync_complete = true; + + // Reset the engine if the sync state does not already know about a finalized block. + if self.engine.state().sync_state.finalized_head() == L2BlockInfo::default() { + // If the sync status is finished, we can reset the engine and start derivation. + info!(target: "engine", "Performing initial engine reset"); + self.reset().await?; + } else { + info!(target: "engine", "finalized head is not default, so not resetting"); + } + + self.derivation_client + .notify_sync_completed(self.engine.state().sync_state.safe_head()) + .await + .map(|_| Ok(())) + .map_err(|e| { + error!(target: "engine", ?e, "Failed to notify sync completed"); EngineError::ChannelClosed - }) - }; + })? + } - loop { - tokio::select! { - _ = self.cancellation_token.cancelled() => { - warn!(target: "engine", "EngineActor received shutdown signal. Awaiting task completion."); + /// Attempts to send the [`crate::DerivationActor`] the safe head if updated. + async fn send_derivation_actor_safe_head_if_updated(&mut self) -> Result<(), EngineError> { + let engine_safe_head = self.engine.state().sync_state.safe_head(); + if engine_safe_head == self.last_safe_head_sent { + info!(target: "engine", safe_head = ?engine_safe_head, "Safe head unchanged"); + // This was already sent, so do not send it. + return Ok(()); + } - rpc_handle.await?; - processing_handle.await?; + self.derivation_client.send_new_engine_safe_head(engine_safe_head).await.map_err(|e| { + error!(target: "engine", ?e, "Failed to send new engine safe head"); + EngineError::ChannelClosed + })?; - return Ok(()); - } + info!(target: "engine", safe_head = ?engine_safe_head, "Attempted L2 Safe Head Update"); + self.last_safe_head_sent = engine_safe_head; - req = self.inbound_request_rx.recv() => { - let Some(request) = req else { - error!(target: "engine", "Engine inbound request receiver closed unexpectedly"); - self.cancellation_token.cancel(); - return Err(EngineError::ChannelClosed); - }; - - // Route the request to the appropriate channel. - match request { - EngineActorRequest::RpcRequest(rpc_req) => { - rpc_tx.send(*rpc_req).await.map_err(|_| { - error!(target: "engine", "Engine RPC request handler channel closed unexpectedly"); - self.cancellation_token.cancel(); - EngineError::ChannelClosed - })?; - } - EngineActorRequest::BuildRequest(build_req) => { - send_engine_processing_request(EngineProcessingRequest::Build(build_req)).await?; - } - EngineActorRequest::ProcessSafeL2SignalRequest(signal) => { - send_engine_processing_request(EngineProcessingRequest::ProcessSafeL2Signal(signal)).await?; - } - EngineActorRequest::ProcessFinalizedL2BlockNumberRequest(block_number) => { - send_engine_processing_request(EngineProcessingRequest::ProcessFinalizedL2BlockNumber(block_number)).await?; - } - EngineActorRequest::ProcessUnsafeL2BlockRequest(envelope) => { - send_engine_processing_request(EngineProcessingRequest::ProcessUnsafeL2Block(envelope)).await?; - } - EngineActorRequest::ResetRequest(reset_req) => { - send_engine_processing_request(EngineProcessingRequest::Reset(reset_req)).await?; - } - EngineActorRequest::SealRequest(seal_req) => { - send_engine_processing_request(EngineProcessingRequest::Seal(seal_req)).await?; - } - } + Ok(()) + } +} + +#[async_trait] +impl NodeActor for EngineActor +where + EngineClient_: EngineClient + 'static, + DerivationClient: EngineDerivationClient + 'static, +{ + type Error = EngineError; + + async fn step(&mut self) -> Result<(), Self::Error> { + // Attempt to drain all outstanding tasks from the engine queue before adding new ones. + self.drain() + .await + .inspect_err(|err| error!(target: "engine", ?err, "Failed to drain engine tasks"))?; + + // If the unsafe head has updated, propagate it to the outbound channels. + if let Some(unsafe_head_tx) = self.unsafe_head_tx.as_ref() { + unsafe_head_tx.send_if_modified(|val| { + let new_head = self.engine.state().sync_state.unsafe_head(); + (*val != new_head).then(|| *val = new_head).is_some() + }); + } + + // Wait for the next processing request. + let request = self.inbound_request_rx.recv().await.ok_or_else(|| { + error!(target: "engine", "Engine processing request receiver closed unexpectedly"); + EngineError::ChannelClosed + })?; + + match request { + EngineActorRequest::Build(build_request) => { + let BuildRequest { attributes, result_tx } = *build_request; + let task = EngineTask::Build(Box::new(BuildTask::new( + self.client.clone(), + self.rollup.clone(), + attributes, + Some(result_tx), + ))); + self.engine.enqueue(task); + } + EngineActorRequest::ProcessSafeL2Signal(safe_signal) => { + let task = EngineTask::Consolidate(Box::new(ConsolidateTask::new( + self.client.clone(), + self.rollup.clone(), + safe_signal, + ))); + self.engine.enqueue(task); + } + EngineActorRequest::ProcessFinalizedL2Block(finalized_l2_block_id) => { + // Finalize the L2 block identified by the provided [`FinalizeBlockId`]. + let task = EngineTask::Finalize(Box::new(FinalizeTask::new( + self.client.clone(), + self.rollup.clone(), + *finalized_l2_block_id, + ))); + self.engine.enqueue(task); + } + EngineActorRequest::ProcessUnsafeL2Block(envelope) => { + let task = EngineTask::Insert(Box::new(InsertTask::new( + self.client.clone(), + self.rollup.clone(), + *envelope, + false, /* The payload is not derived in this case. This is an unsafe + * block. */ + ))); + self.engine.enqueue(task); + } + EngineActorRequest::Reset(reset_request) => { + warn!(target: "engine", "Received reset request"); + + let reset_res = self.reset().await; + + // Send the result. + let response_payload = reset_res + .as_ref() + .map(|_| ()) + .map_err(|e| EngineClientError::ResetForkchoiceError(e.to_string())); + if reset_request.result_tx.send(response_payload).await.is_err() { + warn!(target: "engine", "Sending reset response failed"); + // If there was an error and we couldn't notify the caller to handle it, + // return the error. + reset_res?; } } + EngineActorRequest::Seal(seal_request) => { + let SealRequest { payload_id, attributes, result_tx } = *seal_request; + let task = EngineTask::Seal(Box::new(SealTask::new( + self.client.clone(), + self.rollup.clone(), + payload_id, + attributes, + // The payload is not derived in this case. + false, + Some(result_tx), + ))); + self.engine.enqueue(task); + } } + + Ok(()) } } diff --git a/rust/kona/crates/node/service/src/actors/engine/config.rs b/rust/kona/crates/node/service/src/actors/engine/config.rs index be4bd3b83fc..fc1e6c4b809 100644 --- a/rust/kona/crates/node/service/src/actors/engine/config.rs +++ b/rust/kona/crates/node/service/src/actors/engine/config.rs @@ -1,12 +1,10 @@ use crate::NodeMode; use alloy_provider::RootProvider; use alloy_rpc_types_engine::JwtSecret; -use kona_engine::{ - EngineClientBuilder, EngineClientBuilderError, OpEngineClient, RollupBoostServerArgs, -}; +use kona_engine::{EngineClientBuilder, OpEngineClient}; use kona_genesis::RollupConfig; use op_alloy_network::Optimism; -use std::{sync::Arc, time::Duration}; +use std::sync::Arc; use url::Url; /// Configuration for the Engine Actor. @@ -15,19 +13,10 @@ pub struct EngineConfig { /// The [`RollupConfig`]. pub config: Arc, - /// Builder url. - pub builder_url: Url, - /// Builder jwt secret. - pub builder_jwt_secret: JwtSecret, - /// Builder timeout. - pub builder_timeout: Duration, - /// The engine rpc url. pub l2_url: Url, /// The engine jwt secret. pub l2_jwt_secret: JwtSecret, - /// The l2 timeout. - pub l2_timeout: Duration, /// The L1 rpc url. pub l1_url: Url, @@ -36,27 +25,16 @@ pub struct EngineConfig { /// When the node is in sequencer mode, the engine actor will receive requests to build blocks /// from the sequencer actor. pub mode: NodeMode, - - /// The rollup boost arguments. - pub rollup_boost: RollupBoostServerArgs, } impl EngineConfig { /// Builds and returns the [`OpEngineClient`]. - pub fn build_engine_client( - self, - ) -> Result>, EngineClientBuilderError> - { + pub fn build_engine_client(self) -> OpEngineClient> { EngineClientBuilder { - builder: self.builder_url.clone(), - builder_jwt: self.builder_jwt_secret, - builder_timeout: self.builder_timeout, - l2: self.l2_url.clone(), + l2: self.l2_url, l2_jwt: self.l2_jwt_secret, - l2_timeout: self.l2_timeout, - l1_rpc: self.l1_url.clone(), - cfg: self.config.clone(), - rollup_boost: self.rollup_boost, + l1_rpc: self.l1_url, + cfg: self.config, } .build() } diff --git a/rust/kona/crates/node/service/src/actors/engine/engine_request_processor.rs b/rust/kona/crates/node/service/src/actors/engine/engine_request_processor.rs deleted file mode 100644 index 85b7c88a07b..00000000000 --- a/rust/kona/crates/node/service/src/actors/engine/engine_request_processor.rs +++ /dev/null @@ -1,320 +0,0 @@ -use crate::{ - BuildRequest, EngineClientError, EngineDerivationClient, EngineError, ResetRequest, SealRequest, -}; -use kona_derive::{ResetSignal, Signal}; -use kona_engine::{ - BuildTask, ConsolidateInput, ConsolidateTask, Engine, EngineClient, EngineTask, - EngineTaskError, EngineTaskErrorSeverity, FinalizeTask, InsertTask, SealTask, -}; -use kona_genesis::RollupConfig; -use kona_protocol::L2BlockInfo; -use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; -use std::sync::Arc; -use tokio::{ - sync::{mpsc, watch}, - task::JoinHandle, -}; - -/// Requires that the implementor handles [`EngineProcessingRequest`]s via the provided channel. -/// Note: this exists to facilitate unit testing rather than consolidate multiple implementations -/// under a well-thought-out interface. -pub trait EngineRequestReceiver: Send + Sync { - /// Starts a task to handle engine processing requests. - fn start( - self, - request_channel: mpsc::Receiver, - ) -> JoinHandle>; -} - -/// A request to process engine tasks. -#[derive(Debug)] -pub enum EngineProcessingRequest { - /// Request to start building a block. - Build(Box), - /// Request to process a Safe signal, which can be derived attributes or delegated block info. - ProcessSafeL2Signal(ConsolidateInput), - /// Request to process the finalized L2 block with the provided block number. - ProcessFinalizedL2BlockNumber(Box), - /// Request to process a received unsafe L2 block. - ProcessUnsafeL2Block(Box), - /// Request to reset the forkchoice. - Reset(Box), - /// Request to seal a block. - Seal(Box), -} - -/// Responsible for managing the operations sent to the execution layer's Engine API. To accomplish -/// this, it uses the [`Engine`] task queue to order Engine API interactions based off of -/// the [`Ord`] implementation of [`EngineTask`]. -#[derive(Debug)] -pub struct EngineProcessor -where - EngineClient_: EngineClient, - DerivationClient: EngineDerivationClient, -{ - /// The client used to send messages to the [`crate::DerivationActor`]. - derivation_client: DerivationClient, - /// Whether the EL sync is complete. This should only ever go from false to true. - el_sync_complete: bool, - /// The last safe head update sent. - last_safe_head_sent: L2BlockInfo, - /// The [`RollupConfig`] . - /// A channel to use to relay the current unsafe head. - /// ## Note - /// This is `Some` when the node is in sequencer mode, and `None` when the node is in validator - /// mode. - unsafe_head_tx: Option>, - - /// The [`RollupConfig`] used to build tasks. - rollup: Arc, - /// An [`EngineClient`] used for creating engine tasks. - client: Arc, - /// The [`Engine`] task queue. - engine: Engine, -} - -impl EngineProcessor -where - EngineClient_: EngineClient + 'static, - DerivationClient: EngineDerivationClient + 'static, -{ - /// Constructs a new [`EngineProcessor`] from the params. - pub fn new( - client: Arc, - config: Arc, - derivation_client: DerivationClient, - engine: Engine, - unsafe_head_tx: Option>, - ) -> Self { - Self { - client, - derivation_client, - el_sync_complete: false, - engine, - last_safe_head_sent: L2BlockInfo::default(), - rollup: config, - unsafe_head_tx, - } - } - - /// Resets the inner [`Engine`] and propagates the reset to the derivation actor. - async fn reset(&mut self) -> Result<(), EngineError> { - // Reset the engine. - let (l2_safe_head, l1_origin, system_config) = - self.engine.reset(self.client.clone(), self.rollup.clone()).await?; - - // Signal the derivation actor to reset. - let signal = ResetSignal { l2_safe_head, l1_origin, system_config: Some(system_config) }; - match self.derivation_client.send_signal(signal.signal()).await { - Ok(_) => info!(target: "engine", "Sent reset signal to derivation actor"), - Err(err) => { - error!(target: "engine", ?err, "Failed to send reset signal to the derivation actor"); - return Err(EngineError::ChannelClosed); - } - } - - self.send_derivation_actor_safe_head_if_updated().await?; - - Ok(()) - } - - /// Drains the inner [`Engine`] task queue and attempts to update the safe head. - async fn drain(&mut self) -> Result<(), EngineError> { - match self.engine.drain().await { - Ok(_) => { - trace!(target: "engine", "[ENGINE] tasks drained"); - } - Err(err) => { - match err.severity() { - EngineTaskErrorSeverity::Critical => { - error!(target: "engine", ?err, "Critical error draining engine tasks"); - return Err(err.into()); - } - EngineTaskErrorSeverity::Reset => { - warn!(target: "engine", ?err, "Received reset request"); - self.reset().await?; - } - EngineTaskErrorSeverity::Flush => { - // This error is encountered when the payload is marked INVALID - // by the engine api. Post-holocene, the payload is replaced by - // a "deposits-only" block and re-executed. At the same time, - // the channel and any remaining buffered batches are flushed. - warn!(target: "engine", ?err, "Invalid payload, Flushing derivation pipeline."); - match self.derivation_client.send_signal(Signal::FlushChannel).await { - Ok(_) => { - debug!(target: "engine", "Sent flush signal to derivation actor") - } - Err(err) => { - error!(target: "engine", ?err, "Failed to send flush signal to the derivation actor."); - return Err(EngineError::ChannelClosed); - } - } - } - EngineTaskErrorSeverity::Temporary => { - trace!(target: "engine", ?err, "Temporary error draining engine tasks"); - } - } - } - } - - self.send_derivation_actor_safe_head_if_updated().await?; - - if !self.el_sync_complete && self.engine.state().el_sync_finished { - self.mark_el_sync_complete_and_notify_derivation_actor().await?; - } - - Ok(()) - } - - async fn mark_el_sync_complete_and_notify_derivation_actor( - &mut self, - ) -> Result<(), EngineError> { - self.el_sync_complete = true; - - // Reset the engine if the sync state does not already know about a finalized block. - if self.engine.state().sync_state.finalized_head() == L2BlockInfo::default() { - // If the sync status is finished, we can reset the engine and start derivation. - info!(target: "engine", "Performing initial engine reset"); - self.reset().await?; - } else { - info!(target: "engine", "finalized head is not default, so not resetting"); - } - - self.derivation_client - .notify_sync_completed(self.engine.state().sync_state.safe_head()) - .await - .map(|_| Ok(())) - .map_err(|e| { - error!(target: "engine", ?e, "Failed to notify sync completed"); - EngineError::ChannelClosed - })? - } - - /// Attempts to send the [`crate::DerivationActor`] the safe head if updated. - async fn send_derivation_actor_safe_head_if_updated(&mut self) -> Result<(), EngineError> { - let engine_safe_head = self.engine.state().sync_state.safe_head(); - if engine_safe_head == self.last_safe_head_sent { - info!(target: "engine", safe_head = ?engine_safe_head, "Safe head unchanged"); - // This was already sent, so do not send it. - return Ok(()); - } - - self.derivation_client.send_new_engine_safe_head(engine_safe_head).await.map_err(|e| { - error!(target: "engine", ?e, "Failed to send new engine safe head"); - EngineError::ChannelClosed - })?; - - info!(target: "engine", safe_head = ?engine_safe_head, "Attempted L2 Safe Head Update"); - self.last_safe_head_sent = engine_safe_head; - - Ok(()) - } -} - -impl EngineRequestReceiver - for EngineProcessor -where - EngineClient_: EngineClient + 'static, - DerivationClient: EngineDerivationClient + 'static, -{ - fn start( - mut self, - mut request_channel: mpsc::Receiver, - ) -> JoinHandle> { - tokio::spawn(async move { - loop { - // Attempt to drain all outstanding tasks from the engine queue before adding new - // ones. - self.drain().await.inspect_err( - |err| error!(target: "engine", ?err, "Failed to drain engine tasks"), - )?; - - // If the unsafe head has updated, propagate it to the outbound channels. - if let Some(unsafe_head_tx) = self.unsafe_head_tx.as_ref() { - unsafe_head_tx.send_if_modified(|val| { - let new_head = self.engine.state().sync_state.unsafe_head(); - (*val != new_head).then(|| *val = new_head).is_some() - }); - } - - // Wait for the next processing request. - let Some(request) = request_channel.recv().await else { - error!(target: "engine", "Engine processing request receiver closed unexpectedly"); - return Err(EngineError::ChannelClosed); - }; - - match request { - EngineProcessingRequest::Build(build_request) => { - let BuildRequest { attributes, result_tx } = *build_request; - let task = EngineTask::Build(Box::new(BuildTask::new( - self.client.clone(), - self.rollup.clone(), - attributes, - Some(result_tx), - ))); - self.engine.enqueue(task); - } - EngineProcessingRequest::ProcessSafeL2Signal(safe_signal) => { - let task = EngineTask::Consolidate(Box::new(ConsolidateTask::new( - self.client.clone(), - self.rollup.clone(), - safe_signal, - ))); - self.engine.enqueue(task); - } - EngineProcessingRequest::ProcessFinalizedL2BlockNumber( - finalized_l2_block_number, - ) => { - // Finalize the L2 block at the provided block number. - let task = EngineTask::Finalize(Box::new(FinalizeTask::new( - self.client.clone(), - self.rollup.clone(), - *finalized_l2_block_number, - ))); - self.engine.enqueue(task); - } - EngineProcessingRequest::ProcessUnsafeL2Block(envelope) => { - let task = EngineTask::Insert(Box::new(InsertTask::new( - self.client.clone(), - self.rollup.clone(), - *envelope, - false, /* The payload is not derived in this case. This is an unsafe - * block. */ - ))); - self.engine.enqueue(task); - } - EngineProcessingRequest::Reset(reset_request) => { - warn!(target: "engine", "Received reset request"); - - let reset_res = self.reset().await; - - // Send the result. - let response_payload = reset_res - .as_ref() - .map(|_| ()) - .map_err(|e| EngineClientError::ResetForkchoiceError(e.to_string())); - if reset_request.result_tx.send(response_payload).await.is_err() { - warn!(target: "engine", "Sending reset response failed"); - // If there was an error and we couldn't notify the caller to handle it, - // return the error. - reset_res?; - } - } - EngineProcessingRequest::Seal(seal_request) => { - let SealRequest { payload_id, attributes, result_tx } = *seal_request; - let task = EngineTask::Seal(Box::new(SealTask::new( - self.client.clone(), - self.rollup.clone(), - payload_id, - attributes, - // The payload is not derived in this case. - false, - Some(result_tx), - ))); - self.engine.enqueue(task); - } - } - } - }) - } -} diff --git a/rust/kona/crates/node/service/src/actors/engine/error.rs b/rust/kona/crates/node/service/src/actors/engine/error.rs index 1a8055c35ec..64fe428d00e 100644 --- a/rust/kona/crates/node/service/src/actors/engine/error.rs +++ b/rust/kona/crates/node/service/src/actors/engine/error.rs @@ -2,7 +2,7 @@ //! //! [`EngineActor`]: super::EngineActor -use kona_engine::{EngineClientBuilderError, EngineResetError, EngineTaskErrors}; +use kona_engine::{EngineResetError, EngineTaskErrors}; /// An error from the [`EngineActor`]. /// @@ -15,9 +15,6 @@ pub enum EngineError { /// Engine reset error. #[error(transparent)] EngineReset(#[from] EngineResetError), - /// Engine client builder error. - #[error(transparent)] - EngineClientBuilder(#[from] EngineClientBuilderError), /// Engine task error. #[error(transparent)] EngineTask(#[from] EngineTaskErrors), diff --git a/rust/kona/crates/node/service/src/actors/engine/mod.rs b/rust/kona/crates/node/service/src/actors/engine/mod.rs index 51acbe6e16d..aa646ef1f43 100644 --- a/rust/kona/crates/node/service/src/actors/engine/mod.rs +++ b/rust/kona/crates/node/service/src/actors/engine/mod.rs @@ -1,7 +1,7 @@ -//! The [`EngineActor`] and its components. +//! The [`EngineActor`], [`EngineRpcActor`], and their components. mod actor; -pub use actor::EngineActor; +pub use actor::{EngineActor, EngineActorRequest}; mod client; pub use client::{EngineDerivationClient, QueuedEngineDerivationClient}; @@ -14,14 +14,9 @@ pub use error::EngineError; mod request; pub use request::{ - BuildRequest, EngineActorRequest, EngineClientError, EngineClientResult, EngineRpcRequest, - ResetRequest, SealRequest, + BuildRequest, EngineClientError, EngineClientResult, EngineRpcRequest, ResetRequest, + SealRequest, }; -mod engine_request_processor; -pub use engine_request_processor::{ - EngineProcessingRequest, EngineProcessor, EngineRequestReceiver, -}; - -mod rpc_request_processor; -pub use rpc_request_processor::{EngineRpcProcessor, EngineRpcRequestReceiver}; +mod rpc_actor; +pub use rpc_actor::EngineRpcActor; diff --git a/rust/kona/crates/node/service/src/actors/engine/request.rs b/rust/kona/crates/node/service/src/actors/engine/request.rs index 5b058d8bfed..df5c868ab88 100644 --- a/rust/kona/crates/node/service/src/actors/engine/request.rs +++ b/rust/kona/crates/node/service/src/actors/engine/request.rs @@ -1,7 +1,6 @@ use alloy_rpc_types_engine::PayloadId; -use kona_engine::{BuildTaskError, ConsolidateInput, EngineQueries, SealTaskError}; +use kona_engine::{BuildTaskError, EngineQueries, SealTaskError}; use kona_protocol::OpAttributesWithParent; -use kona_rpc::{RollupBoostAdminQuery, RollupBoostHealthQuery}; use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; use thiserror::Error; use tokio::sync::mpsc; @@ -34,36 +33,9 @@ pub enum EngineClientError { ResetForkchoiceError(String), } -/// Inbound requests that the [`crate::EngineActor`] can process. -#[derive(Debug)] -pub enum EngineActorRequest { - /// Request to build. - BuildRequest(Box), - /// Request to consolidate using a safe L2 signal from attributes or delegated safe-block - /// derivation - ProcessSafeL2SignalRequest(ConsolidateInput), - /// Request to finalize the L2 block at the provided block number. - ProcessFinalizedL2BlockNumberRequest(Box), - /// Request to insert the provided unsafe block. - ProcessUnsafeL2BlockRequest(Box), - /// Request to reset engine forkchoice. - ResetRequest(Box), - /// Request for the engine to process the provided RPC request. - RpcRequest(Box), - /// Request to seal the block with the provided details. - SealRequest(Box), -} - /// RPC Request for the engine to handle. #[derive(Debug)] -pub enum EngineRpcRequest { - /// Engine RPC query. - EngineQuery(Box), - /// Rollup boost admin request. - RollupBoostAdminRequest(Box), - /// Rollup boost health request. - RollupBoostHealthRequest(Box), -} +pub struct EngineRpcRequest(pub Box); /// A request to build a payload. /// Contains the attributes to build and a channel to send back the resulting `PayloadId`. diff --git a/rust/kona/crates/node/service/src/actors/engine/rollup_boost.rs b/rust/kona/crates/node/service/src/actors/engine/rollup_boost.rs deleted file mode 100644 index 8b137891791..00000000000 --- a/rust/kona/crates/node/service/src/actors/engine/rollup_boost.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/rust/kona/crates/node/service/src/actors/engine/rpc_actor.rs b/rust/kona/crates/node/service/src/actors/engine/rpc_actor.rs new file mode 100644 index 00000000000..272d5ba6cb9 --- /dev/null +++ b/rust/kona/crates/node/service/src/actors/engine/rpc_actor.rs @@ -0,0 +1,80 @@ +use crate::{EngineError, EngineRpcRequest, NodeActor}; +use async_trait::async_trait; +use kona_engine::{EngineRpcClient, EngineState}; +use kona_genesis::RollupConfig; +use std::sync::Arc; +use tokio::sync::{mpsc, watch}; + +/// Handles [`EngineRpcRequest`]s by reading engine state and queue length via watches and +/// dispatching the request through an [`EngineRpcClient`]. +/// +/// The client type is constrained to [`EngineRpcClient`] — a read-only subset of +/// [`kona_engine::EngineClient`] — so this actor cannot reach into Engine API mutation methods +/// even by accident. +#[derive(Debug)] +pub struct EngineRpcActor { + /// An [`EngineRpcClient`] used for handling engine queries. + engine_rpc_client: Arc, + /// The [`RollupConfig`] used to handle queries. + rollup_config: Arc, + /// Receiver for [`EngineState`] updates. + engine_state_receiver: watch::Receiver, + /// Receiver for engine queue length updates. + engine_queue_length_receiver: watch::Receiver, + /// The inbound request channel. + inbound_request_rx: mpsc::Receiver, +} + +impl EngineRpcActor { + /// Constructs a new [`EngineRpcActor`]. + pub const fn new( + engine_rpc_client: Arc, + rollup_config: Arc, + engine_state_receiver: watch::Receiver, + engine_queue_length_receiver: watch::Receiver, + inbound_request_rx: mpsc::Receiver, + ) -> Self { + Self { + engine_rpc_client, + rollup_config, + engine_state_receiver, + engine_queue_length_receiver, + inbound_request_rx, + } + } + + async fn handle_rpc_request(&self, request: EngineRpcRequest) -> Result<(), EngineError> { + let EngineRpcRequest(req) = request; + trace!(target: "engine", ?req, "Received engine query."); + + if let Err(e) = req + .handle( + &self.engine_state_receiver, + &self.engine_queue_length_receiver, + &self.engine_rpc_client, + &self.rollup_config, + ) + .await + { + warn!(target: "engine", err = ?e, "Failed to handle engine query."); + } + + Ok(()) + } +} + +#[async_trait] +impl NodeActor for EngineRpcActor +where + EngineRpcClient_: EngineRpcClient + 'static, +{ + type Error = EngineError; + + async fn step(&mut self) -> Result<(), Self::Error> { + let query = self.inbound_request_rx.recv().await.ok_or_else(|| { + error!(target: "engine", "Engine rpc request receiver closed unexpectedly"); + EngineError::ChannelClosed + })?; + self.handle_rpc_request(query).await + } +} diff --git a/rust/kona/crates/node/service/src/actors/engine/rpc_request_processor.rs b/rust/kona/crates/node/service/src/actors/engine/rpc_request_processor.rs deleted file mode 100644 index d622d93e019..00000000000 --- a/rust/kona/crates/node/service/src/actors/engine/rpc_request_processor.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::{EngineError, EngineRpcRequest}; -use derive_more::Constructor; -use kona_engine::{EngineClient, EngineState, RollupBoostServer}; -use kona_genesis::RollupConfig; -use kona_rpc::RollupBoostAdminQuery; -use std::sync::Arc; -use tokio::{ - sync::{mpsc, watch}, - task::JoinHandle, -}; - -/// Requires that the implementor handles [`EngineRpcRequest`]s via the provided channel. -/// Note: this exists to facilitate unit testing rather than consolidate multiple implementations -/// under a well-thought-out interface. -pub trait EngineRpcRequestReceiver: Send + Sync { - /// Starts a task to handle engine queries. - fn start( - self, - request_channel: mpsc::Receiver, - ) -> JoinHandle>; -} - -/// Processor for [`EngineRpcRequest`] requests. -#[derive(Constructor, Debug)] -pub struct EngineRpcProcessor { - /// An [`EngineClient`] used for creating engine tasks. - engine_client: Arc, - // RollupBoost server handle - rollup_boost_server: Arc, - /// The [`RollupConfig`] used to build tasks. - rollup_config: Arc, - /// Receiver for [`EngineState`] updates. - engine_state_receiver: watch::Receiver, - /// Receiver for engine queue length updates. - engine_queue_length_receiver: watch::Receiver, -} - -impl EngineRpcProcessor -where - EngineClient_: EngineClient + 'static, -{ - async fn handle_rpc_request(&self, request: EngineRpcRequest) -> Result<(), EngineError> { - match request { - EngineRpcRequest::EngineQuery(req) => { - trace!(target: "engine", ?req, "Received engine query."); - - if let Err(e) = req - .handle( - &self.engine_state_receiver, - &self.engine_queue_length_receiver, - &self.engine_client, - &self.rollup_config, - ) - .await - { - warn!(target: "engine", err = ?e, "Failed to handle engine query."); - } - } - EngineRpcRequest::RollupBoostAdminRequest(admin_query) => { - trace!(target: "engine", ?admin_query, "Received rollup boost admin query."); - - self.handle_rollup_boost_admin_query(*admin_query); - } - EngineRpcRequest::RollupBoostHealthRequest(health_query) => { - trace!(target: "engine", ?health_query, "Received rollup boost health query."); - - let health = self.rollup_boost_server.probes().health(); - health_query.sender.send(health.into()).unwrap(); - } - } - - Ok(()) - } - - fn handle_rollup_boost_admin_query(&self, admin_query: RollupBoostAdminQuery) { - match admin_query { - RollupBoostAdminQuery::SetExecutionMode { execution_mode, sender } => { - self.rollup_boost_server.set_execution_mode(execution_mode); - let _ = sender.send(()).map_err(|_| { - warn!(target: "engine", "set execution mode response channel closed when trying to send"); - }); - } - RollupBoostAdminQuery::GetExecutionMode { sender } => { - let execution_mode = self.rollup_boost_server.get_execution_mode(); - let _ = sender.send(execution_mode).map_err(|_| { - warn!(target: "engine", "get execution mode response channel closed when trying to send"); - }); - } - } - } -} - -impl EngineRpcRequestReceiver for EngineRpcProcessor -where - EngineClient_: EngineClient + 'static, -{ - fn start( - self, - mut request_channel: mpsc::Receiver, - ) -> JoinHandle> { - tokio::spawn(async move { - loop { - tokio::select! { - query = request_channel.recv(), if !request_channel.is_closed() => { - let Some(query) = query else { - error!(target: "engine", "Engine rpc request receiver closed unexpectedly"); - return Err(EngineError::ChannelClosed); - }; - self.handle_rpc_request(query).await?; - } - } - } - }) - } -} diff --git a/rust/kona/crates/node/service/src/actors/l1_watcher/actor.rs b/rust/kona/crates/node/service/src/actors/l1_watcher/actor.rs index 0065711f65f..2b5acd83f35 100644 --- a/rust/kona/crates/node/service/src/actors/l1_watcher/actor.rs +++ b/rust/kona/crates/node/service/src/actors/l1_watcher/actor.rs @@ -1,10 +1,7 @@ //! [`NodeActor`] implementation for an L1 chain watcher that polls for L1 block updates over HTTP //! RPC. -use crate::{ - NodeActor, - actors::{CancellableContext, l1_watcher::error::L1WatcherActorError}, -}; +use crate::{NodeActor, actors::l1_watcher::error::L1WatcherActorError}; use alloy_eips::BlockId; use alloy_primitives::Address; use alloy_provider::Provider; @@ -21,7 +18,6 @@ use tokio::{ watch, }, }; -use tokio_util::sync::{CancellationToken, WaitForCancellationFuture}; use super::L1WatcherDerivationClient; @@ -46,8 +42,6 @@ where derivation_client: L1WatcherDerivationClient_, /// The block signer sender. block_signer_sender: mpsc::Sender
, - /// The cancellation token, shared between all tasks. - cancellation: CancellationToken, /// A stream over the latest head. head_stream: BlockStream, /// A stream over the finalized block accepted as canonical. @@ -69,7 +63,6 @@ where l1_head_updates_tx: watch::Sender>, derivation_client: L1WatcherDerivationClient_, signer: mpsc::Sender
, - cancellation: CancellationToken, head_stream: BlockStream, finalized_stream: BlockStream, ) -> Self { @@ -80,7 +73,6 @@ where latest_head: l1_head_updates_tx, derivation_client, block_signer_sender: signer, - cancellation, head_stream, finalized_stream, } @@ -96,72 +88,59 @@ where L1WatcherDerivationClient_: L1WatcherDerivationClient + 'static, { type Error = L1WatcherActorError; - type StartData = (); - - /// Start the main processing loop. - async fn start(mut self, _: Self::StartData) -> Result<(), Self::Error> { - let cancel = self.cancellation.clone(); - let latest_head = self.latest_head.subscribe(); - - loop { - select! { - _ = cancel.cancelled() => { - // Exit the task on cancellation. - info!( - target: "l1_watcher", - "Received shutdown signal. Exiting L1 watcher task." - ); - return Ok(()); - }, - new_head = self.head_stream.next() => match new_head { - None => { - return Err(L1WatcherActorError::StreamEnded); - } - Some(head_block_info) => { - // Send the head update event to all consumers. - self.latest_head.send_replace(Some(head_block_info)); - self.derivation_client.send_new_l1_head(head_block_info).await.map_err(|e| { - warn!(target: "l1_watcher", "Error sending l1 head update to derivation actor: {e}"); - L1WatcherActorError::DerivationClientError(e) - })?; + async fn step(&mut self) -> Result<(), Self::Error> { + select! { + new_head = self.head_stream.next() => match new_head { + None => { + Err(L1WatcherActorError::StreamEnded) + } + Some(head_block_info) => { + // Send the head update event to all consumers. + self.latest_head.send_replace(Some(head_block_info)); + self.derivation_client.send_new_l1_head(head_block_info).await.map_err(|e| { + warn!(target: "l1_watcher", "Error sending l1 head update to derivation actor: {e}"); + L1WatcherActorError::DerivationClientError(e) + })?; - // For each log, attempt to construct a [`SystemConfigLog`]. - // Build the [`SystemConfigUpdate`] from the log. - // If the update is an Unsafe block signer update, send the address - // to the block signer sender. - let filter_address = self.rollup_config.l1_system_config_address; - let logs = self.l1_provider .get_logs(&alloy_rpc_types_eth::Filter::new().address(filter_address).select(head_block_info.hash)).await?; - let ecotone_active = self.rollup_config.is_ecotone_active(head_block_info.timestamp); - for log in logs { - let sys_cfg_log = SystemConfigLog::new(log.into(), ecotone_active); - if let Ok(SystemConfigUpdate::UnsafeBlockSigner(UnsafeBlockSignerUpdate { unsafe_block_signer })) = sys_cfg_log.build() { - info!( + // For each log, attempt to construct a [`SystemConfigLog`]. + // Build the [`SystemConfigUpdate`] from the log. + // If the update is an Unsafe block signer update, send the address + // to the block signer sender. + let filter_address = self.rollup_config.l1_system_config_address; + let logs = self.l1_provider .get_logs(&alloy_rpc_types_eth::Filter::new().address(filter_address).select(head_block_info.hash)).await?; + let ecotone_active = self.rollup_config.is_ecotone_active(head_block_info.timestamp); + for log in logs { + let sys_cfg_log = SystemConfigLog::new(log.into(), ecotone_active); + if let Ok(SystemConfigUpdate::UnsafeBlockSigner(UnsafeBlockSignerUpdate { unsafe_block_signer })) = sys_cfg_log.build() { + info!( + target: "l1_watcher", + "Unsafe block signer update: {unsafe_block_signer}" + ); + if let Err(e) = self.block_signer_sender.send(unsafe_block_signer).await { + error!( target: "l1_watcher", - "Unsafe block signer update: {unsafe_block_signer}" + "Error sending unsafe block signer update: {e}" ); - if let Err(e) = self.block_signer_sender.send(unsafe_block_signer).await { - error!( - target: "l1_watcher", - "Error sending unsafe block signer update: {e}" - ); - } } } - }, - }, - new_finalized = self.finalized_stream.next() => match new_finalized { - None => { - return Err(L1WatcherActorError::StreamEnded); - } - Some(finalized_block_info) => { - self.derivation_client.send_finalized_l1_block(finalized_block_info).await.map_err(|e| { - warn!(target: "l1_watcher", "Error sending finalized l1 block update to derivation actor: {e}"); - L1WatcherActorError::DerivationClientError(e) - })?; } + Ok(()) }, - inbound_query = self.inbound_queries.recv() => match inbound_query { + }, + new_finalized = self.finalized_stream.next() => match new_finalized { + None => { + Err(L1WatcherActorError::StreamEnded) + } + Some(finalized_block_info) => { + self.derivation_client.send_finalized_l1_block(finalized_block_info).await.map_err(|e| { + warn!(target: "l1_watcher", "Error sending finalized l1 block update to derivation actor: {e}"); + L1WatcherActorError::DerivationClientError(e) + })?; + Ok(()) + } + }, + inbound_query = self.inbound_queries.recv() => match inbound_query { Some(query) => { match query { L1WatcherQueries::Config(sender) => { @@ -170,7 +149,7 @@ where } } L1WatcherQueries::L1State(sender) => { - let current_l1 = *latest_head.borrow(); + let current_l1 = *self.latest_head.borrow(); let head_l1 = match self.l1_provider.get_block(BlockId::latest()).await { Ok(block) => block, @@ -204,25 +183,13 @@ where } } } + Ok(()) }, None => { error!(target: "l1_watcher", "L1 watcher query channel closed unexpectedly, exiting query processor task."); - return Err(L1WatcherActorError::StreamEnded) + Err(L1WatcherActorError::StreamEnded) } } - } } } } - -impl CancellableContext - for L1WatcherActor -where - BlockStream: Stream + Unpin + Send + 'static, - L1Provider: Provider, - L1WatcherDerivationClient_: L1WatcherDerivationClient + 'static, -{ - fn cancelled(&self) -> WaitForCancellationFuture<'_> { - self.cancellation.cancelled() - } -} diff --git a/rust/kona/crates/node/service/src/actors/mod.rs b/rust/kona/crates/node/service/src/actors/mod.rs index 5002b554cf4..ddf4cee4897 100644 --- a/rust/kona/crates/node/service/src/actors/mod.rs +++ b/rust/kona/crates/node/service/src/actors/mod.rs @@ -3,28 +3,28 @@ //! [NodeActor]: super::NodeActor mod traits; -pub use traits::{CancellableContext, NodeActor}; +pub use traits::NodeActor; mod engine; pub use engine::{ BuildRequest, EngineActor, EngineActorRequest, EngineClientError, EngineClientResult, - EngineConfig, EngineDerivationClient, EngineError, EngineProcessingRequest, EngineProcessor, - EngineRequestReceiver, EngineRpcProcessor, EngineRpcRequest, EngineRpcRequestReceiver, + EngineConfig, EngineDerivationClient, EngineError, EngineRpcActor, EngineRpcRequest, QueuedEngineDerivationClient, ResetRequest, SealRequest, }; -mod rpc; +pub(crate) mod rpc; pub use rpc::{ - QueuedEngineRpcClient, QueuedSequencerAdminAPIClient, RollupBoostAdminApiClient, - RollupBoostHealthRpcClient, RpcActor, RpcActorError, RpcContext, + JsonrpseeServerLauncher, QueuedEngineRpcClient, QueuedSequencerAdminAPIClient, RpcActor, + RpcActorError, RpcServerHandle, RpcServerLauncher, }; mod derivation; pub use derivation::{ DelegateDerivationActor, DerivationActor, DerivationActorRequest, DerivationClientError, DerivationClientResult, DerivationDelegateClient, DerivationDelegateClientError, - DerivationEngineClient, DerivationError, DerivationState, DerivationStateMachine, - DerivationStateTransitionError, DerivationStateUpdate, QueuedDerivationEngineClient, + DerivationDelegateProvider, DerivationEngineClient, DerivationError, DerivationState, + DerivationStateMachine, DerivationStateTransitionError, DerivationStateUpdate, + QueuedDerivationEngineClient, }; mod l1_watcher; @@ -36,7 +36,7 @@ pub use l1_watcher::{ mod network; pub use network::{ NetworkActor, NetworkActorError, NetworkBuilder, NetworkBuilderError, NetworkConfig, - NetworkDriver, NetworkDriverError, NetworkEngineClient, NetworkHandler, NetworkInboundData, + NetworkDriver, NetworkDriverError, NetworkEngineClient, NetworkHandler, QueuedNetworkEngineClient, QueuedUnsafePayloadGossipClient, UnsafePayloadGossipClient, UnsafePayloadGossipClientError, }; diff --git a/rust/kona/crates/node/service/src/actors/network/actor.rs b/rust/kona/crates/node/service/src/actors/network/actor.rs index c4b51caa73e..e4d14cecb6e 100644 --- a/rust/kona/crates/node/service/src/actors/network/actor.rs +++ b/rust/kona/crates/node/service/src/actors/network/actor.rs @@ -6,109 +6,68 @@ use kona_sources::BlockSignerError; use libp2p::TransportError; use op_alloy_rpc_types_engine::{OpExecutionPayloadEnvelope, OpNetworkPayloadEnvelope}; use thiserror::Error; -use tokio::{self, select, sync::mpsc}; -use tokio_util::sync::{CancellationToken, WaitForCancellationFuture}; +use tokio::{ + self, select, + sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, +}; use crate::{ - CancellableContext, NetworkEngineClient, NodeActor, + NetworkEngineClient, NodeActor, actors::network::{ - builder::NetworkBuilder, driver::NetworkDriverError, error::NetworkBuilderError, + driver::NetworkDriverError, error::NetworkBuilderError, handler::NetworkHandler, }, }; /// The network actor handles two core networking components of the rollup node: /// - *discovery*: Peer discovery over UDP using discv5. /// - *gossip*: Block gossip over TCP using libp2p. -/// -/// The network actor itself is a light wrapper around the [`NetworkBuilder`]. -/// -/// ## Example -/// -/// ```rust,ignore -/// use kona_gossip::NetworkDriver; -/// use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -/// -/// let chain_id = 10; -/// let signer = Address::random(); -/// let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9099); -/// -/// // Construct the `Network` using the builder. -/// // let mut driver = Network::builder() -/// // .with_unsafe_block_signer(signer) -/// // .with_chain_id(chain_id) -/// // .with_gossip_addr(socket) -/// // .build() -/// // .unwrap(); -/// -/// // Construct the `NetworkActor` with the [`Network`]. -/// // let actor = NetworkActor::new(driver); -/// ``` #[derive(Debug)] pub struct NetworkActor { - /// Network driver - pub(super) builder: NetworkBuilder, - /// The cancellation token, shared between all tasks. - pub(super) cancellation_token: CancellationToken, + /// The live libp2p [`NetworkHandler`]. + handler: NetworkHandler, /// A channel to receive the unsafe block signer address. - pub(super) signer: mpsc::Receiver
, - /// Handler for p2p RPC Requests. - pub(super) p2p_rpc: mpsc::Receiver, - /// A channel to receive admin rpc requests. - pub(super) admin_rpc: mpsc::Receiver, + unsafe_block_signer_rx: mpsc::Receiver
, + /// A channel to receive p2p RPC requests. + p2p_rpc_rx: mpsc::Receiver, + /// A channel to receive admin RPC queries. + admin_query_rx: mpsc::Receiver, /// A channel to receive unsafe blocks and send them through the gossip layer. - pub(super) publish_rx: mpsc::Receiver, - /// A channel to use to interact with the engine actor. - pub(super) engine_client: NetworkEngineClient_, -} - -/// The inbound data for the network actor. -#[derive(Debug)] -pub struct NetworkInboundData { - /// A channel to send the unsafe block signer address to the network actor. - pub signer: mpsc::Sender
, - /// Handler for p2p RPC Requests sent to the network actor. - pub p2p_rpc: mpsc::Sender, - /// Handler for admin RPC Requests. - pub admin_rpc: mpsc::Sender, - /// A channel to send unsafe blocks to the network actor. - /// This channel should only be used by the sequencer actor/admin RPC api to forward their - /// newly produced unsafe blocks to the network actor. - pub gossip_payload_tx: mpsc::Sender, + publish_rx: mpsc::Receiver, + /// A client to use to interact with the engine actor. + engine_client: NetworkEngineClient_, + // Purely-internal channel: loops gossip-swarm events back into this actor's own select. It + // never crosses an actor boundary, so it lives here rather than being injected. + unsafe_block_tx: UnboundedSender, + unsafe_block_rx: UnboundedReceiver, } impl NetworkActor { - /// Constructs a new [`NetworkActor`] given the [`NetworkBuilder`] + /// Constructs a new [`NetworkActor`]. + /// + /// `handler` must already be live — i.e. the libp2p swarm it wraps must already have been + /// built and started — before being passed in. Passing an unstarted handler will cause + /// `step()` to hang or fail on its first poll of the gossip swarm. Keeping the constructor + /// sync and treating the "is this live?" invariant as the caller's responsibility is the + /// deliberate trade-off over an `init()`-style trait method. pub fn new( engine_client: NetworkEngineClient_, - cancellation_token: CancellationToken, - driver: NetworkBuilder, - ) -> (NetworkInboundData, Self) { - let (signer_tx, signer_rx) = mpsc::channel(16); - let (rpc_tx, rpc_rx) = mpsc::channel(1024); - let (admin_rpc_tx, admin_rpc_rx) = mpsc::channel(1024); - let (publish_tx, publish_rx) = tokio::sync::mpsc::channel(256); - let actor = Self { - builder: driver, - cancellation_token, - signer: signer_rx, - p2p_rpc: rpc_rx, - admin_rpc: admin_rpc_rx, + handler: NetworkHandler, + unsafe_block_signer_rx: mpsc::Receiver
, + p2p_rpc_rx: mpsc::Receiver, + admin_query_rx: mpsc::Receiver, + publish_rx: mpsc::Receiver, + ) -> Self { + let (unsafe_block_tx, unsafe_block_rx) = mpsc::unbounded_channel(); + Self { + handler, + unsafe_block_signer_rx, + p2p_rpc_rx, + admin_query_rx, publish_rx, engine_client, - }; - let outbound_data = NetworkInboundData { - signer: signer_tx, - p2p_rpc: rpc_tx, - admin_rpc: admin_rpc_tx, - gossip_payload_tx: publish_tx, - }; - (outbound_data, actor) - } -} - -impl CancellableContext for NetworkActor { - fn cancelled(&self) -> WaitForCancellationFuture<'_> { - self.cancellation_token.cancelled() + unsafe_block_tx, + unsafe_block_rx, + } } } @@ -143,109 +102,102 @@ impl NodeActor for NetworkActor { type Error = NetworkActorError; - type StartData = (); - - async fn start(mut self, _: Self::StartData) -> Result<(), Self::Error> { - let mut handler = self.builder.build()?.start().await?; - - // New unsafe block channel. - let (unsafe_block_tx, mut unsafe_block_rx) = tokio::sync::mpsc::unbounded_channel(); - loop { - select! { - _ = self.cancellation_token.cancelled() => { - info!( + async fn step(&mut self) -> Result<(), Self::Error> { + select! { + block = self.unsafe_block_rx.recv() => { + let Some(block) = block else { + error!(target: "node::p2p", "The unsafe block receiver channel has closed"); + return Err(NetworkActorError::ChannelClosed); + }; + + if self.engine_client.send_unsafe_block(block).await.is_err() { + warn!(target: "network", "Failed to forward unsafe block to engine"); + return Err(NetworkActorError::ChannelClosed); + } + Ok(()) + } + unsafe_block_signer = self.unsafe_block_signer_rx.recv() => { + let Some(unsafe_block_signer) = unsafe_block_signer else { + warn!( target: "network", - "Received shutdown signal. Exiting network task." + "Found no unsafe block signer on receive" + ); + return Err(NetworkActorError::ChannelClosed); + }; + if self.handler.unsafe_block_signer_sender.send(unsafe_block_signer).is_err() { + warn!( + target: "network", + "Failed to send unsafe block signer to network handler", ); - return Ok(()); - } - block = unsafe_block_rx.recv() => { - let Some(block) = block else { - error!(target: "node::p2p", "The unsafe block receiver channel has closed"); - return Err(NetworkActorError::ChannelClosed); - }; - - if self.engine_client.send_unsafe_block(block).await.is_err() { - warn!(target: "network", "Failed to forward unsafe block to engine"); - return Err(NetworkActorError::ChannelClosed); - } - } - signer = self.signer.recv() => { - let Some(signer) = signer else { - warn!( - target: "network", - "Found no unsafe block signer on receive" - ); - return Err(NetworkActorError::ChannelClosed); - }; - if handler.unsafe_block_signer_sender.send(signer).is_err() { - warn!( - target: "network", - "Failed to send unsafe block signer to network handler", - ); - } } - Some(block) = self.publish_rx.recv(), if !self.publish_rx.is_closed() => { - let timestamp = block.execution_payload.timestamp(); - let selector = |handler: &kona_gossip::BlockHandler| { - handler.topic(timestamp) - }; - let Some(signer) = handler.signer.as_ref() else { - warn!(target: "net", "No local signer available to sign the payload"); - continue; - }; + Ok(()) + } + Some(block) = self.publish_rx.recv(), if !self.publish_rx.is_closed() => { + let timestamp = block.execution_payload.timestamp(); + let selector = |handler: &kona_gossip::BlockHandler| { + handler.topic(timestamp) + }; + let Some(signer) = self.handler.signer.as_ref() else { + warn!(target: "net", "No local signer available to sign the payload"); + return Ok(()); + }; - let chain_id = handler.discovery.chain_id; + let chain_id = self.handler.discovery.chain_id; - let sender_address = *handler.unsafe_block_signer_sender.borrow(); + let sender_address = *self.handler.unsafe_block_signer_sender.borrow(); - let payload_hash = block.payload_hash(); - let signature = signer.sign_block(payload_hash, chain_id, sender_address).await?; + let payload_hash = block.payload_hash(); + let signature = signer.sign_block(payload_hash, chain_id, sender_address).await?; - let payload = OpNetworkPayloadEnvelope { - payload: block.execution_payload, - parent_beacon_block_root: block.parent_beacon_block_root, - signature, - payload_hash, - }; + let payload = OpNetworkPayloadEnvelope { + payload: block.execution_payload, + parent_beacon_block_root: block.parent_beacon_block_root, + signature, + payload_hash, + }; - match handler.gossip.publish(selector, Some(payload)) { - Ok(id) => info!("Published unsafe payload | {:?}", id), - Err(e) => warn!("Failed to publish unsafe payload: {:?}", e), - } + match self.handler.gossip.publish(selector, Some(payload)) { + Ok(id) => info!("Published unsafe payload | {:?}", id), + Err(e) => warn!("Failed to publish unsafe payload: {:?}", e), } - event = handler.gossip.next() => { - let Some(event) = event else { - error!(target: "node::p2p", "The gossip swarm stream has ended"); - return Err(NetworkActorError::ChannelClosed); - }; - - if let Some(payload) = handler.gossip.handle_event(event) - && unsafe_block_tx.send(payload.into()).is_err() - { - warn!(target: "node::p2p", "Failed to send unsafe block to network handler"); - } - }, - enr = handler.enr_receiver.recv() => { - let Some(enr) = enr else { - error!(target: "node::p2p", "The enr receiver channel has closed"); - return Err(NetworkActorError::ChannelClosed); - }; - handler.gossip.dial(enr); - }, - _ = handler.peer_score_inspector.tick(), if handler.gossip.peer_monitoring.as_ref().is_some() => { - handler.handle_peer_monitoring().await; - }, - Some(NetworkAdminQuery::PostUnsafePayload { payload }) = self.admin_rpc.recv(), if !self.admin_rpc.is_closed() => { - debug!(target: "node::p2p", "Broadcasting unsafe payload from admin api"); - if unsafe_block_tx.send(payload).is_err() { - warn!(target: "node::p2p", "Failed to send unsafe block to network handler"); - } - }, - Some(req) = self.p2p_rpc.recv(), if !self.p2p_rpc.is_closed() => { - req.handle(&mut handler.gossip, &handler.discovery); - }, + Ok(()) + } + event = self.handler.gossip.next() => { + let Some(event) = event else { + error!(target: "node::p2p", "The gossip swarm stream has ended"); + return Err(NetworkActorError::ChannelClosed); + }; + + if let Some(payload) = self.handler.gossip.handle_event(event) + && self.unsafe_block_tx.send(payload.into()).is_err() + { + warn!(target: "node::p2p", "Failed to send unsafe block to network handler"); + } + Ok(()) + } + enr = self.handler.enr_receiver.recv() => { + let Some(enr) = enr else { + error!(target: "node::p2p", "The enr receiver channel has closed"); + return Err(NetworkActorError::ChannelClosed); + }; + self.handler.gossip.dial(enr); + Ok(()) + } + _ = self.handler.peer_score_inspector.tick(), if self.handler.gossip.peer_monitoring.as_ref().is_some() => { + self.handler.handle_peer_monitoring().await; + Ok(()) + } + Some(NetworkAdminQuery::PostUnsafePayload { payload }) = self.admin_query_rx.recv(), if !self.admin_query_rx.is_closed() => { + debug!(target: "node::p2p", "Broadcasting unsafe payload from admin api"); + if self.unsafe_block_tx.send(payload).is_err() { + warn!(target: "node::p2p", "Failed to send unsafe block to network handler"); + } + Ok(()) + } + Some(req) = self.p2p_rpc_rx.recv(), if !self.p2p_rpc_rx.is_closed() => { + req.handle(&mut self.handler.gossip, &self.handler.discovery); + Ok(()) } } } diff --git a/rust/kona/crates/node/service/src/actors/network/engine_client.rs b/rust/kona/crates/node/service/src/actors/network/engine_client.rs index b677e8a511f..9ee633a5767 100644 --- a/rust/kona/crates/node/service/src/actors/network/engine_client.rs +++ b/rust/kona/crates/node/service/src/actors/network/engine_client.rs @@ -26,7 +26,7 @@ impl NetworkEngineClient for QueuedNetworkEngineClient { trace!(target: "network", ?block, "Sending unsafe block to engine."); Ok(self .engine_actor_request_tx - .send(EngineActorRequest::ProcessUnsafeL2BlockRequest(Box::new(block))) + .send(EngineActorRequest::ProcessUnsafeL2Block(Box::new(block))) .await .map_err(|_| EngineClientError::RequestError("request channel closed.".to_string()))?) } diff --git a/rust/kona/crates/node/service/src/actors/network/mod.rs b/rust/kona/crates/node/service/src/actors/network/mod.rs index 932f627aa1e..c446fb518e9 100644 --- a/rust/kona/crates/node/service/src/actors/network/mod.rs +++ b/rust/kona/crates/node/service/src/actors/network/mod.rs @@ -1,7 +1,7 @@ //! Network Actor mod actor; -pub use actor::{NetworkActor, NetworkActorError, NetworkInboundData}; +pub use actor::{NetworkActor, NetworkActorError}; mod builder; pub use builder::NetworkBuilder; diff --git a/rust/kona/crates/node/service/src/actors/rpc/actor.rs b/rust/kona/crates/node/service/src/actors/rpc/actor.rs index d8b25a8d17e..7da9cdcb118 100644 --- a/rust/kona/crates/node/service/src/actors/rpc/actor.rs +++ b/rust/kona/crates/node/service/src/actors/rpc/actor.rs @@ -1,223 +1,252 @@ //! RPC Server Actor -use crate::{NodeActor, RpcActorError, actors::CancellableContext}; -use async_trait::async_trait; -use derive_more::Constructor; -use jsonrpsee::{ - RpcModule, - server::{Server, ServerHandle, middleware::http::ProxyGetRequestLayer}, -}; -use kona_gossip::P2pRpcRequest; -use kona_rpc::{ - AdminApiServer, AdminRpc, DevEngineApiServer, DevEngineRpc, EngineRpcClient, HealthzApiServer, - HealthzRpc, L1WatcherQueries, NetworkAdminQuery, OpP2PApiServer, P2pRpc, - RollupBoostAdminClient, RollupBoostHealthzApiServer, RollupNodeApiServer, RollupRpc, - RpcBuilder, SequencerAdminAPIClient, WsRPC, WsServer, +use crate::{ + NodeActor, RpcActorError, + actors::rpc::launcher::{RpcServerHandle, RpcServerLauncher}, }; -use std::time::Duration; -use tokio::sync::mpsc; -use tokio_util::sync::{CancellationToken, WaitForCancellationFuture}; - -/// An actor that handles the RPC server for the rollup node. -#[derive(Constructor, Debug)] -pub struct RpcActor< - EngineRpcClient_, - RollupBoostAdminClient_, - RollupBoostHealth, - SequencerAdminApiClient_, -> where - EngineRpcClient_: EngineRpcClient, - RollupBoostAdminClient_: RollupBoostAdminClient, - RollupBoostHealth: RollupBoostHealthzApiServer, - SequencerAdminApiClient_: SequencerAdminAPIClient, -{ - /// A launcher for the rpc. - config: RpcBuilder, - - engine_rpc_client: EngineRpcClient_, - rollup_boost_admin_rpc_client: RollupBoostAdminClient_, - rollup_boost_health_rpc_client: RollupBoostHealth, - sequencer_admin_rpc_client: Option, -} +use async_trait::async_trait; +use jsonrpsee::RpcModule; -/// The communication context used by the RPC actor. +/// An actor that runs the JSON-RPC server for the rollup node. +/// +/// The first launch happens upstream of this actor; restarts (up to `restart_count` provided +/// at construction) are handled inside [`Self::step`]. #[derive(Debug)] -pub struct RpcContext { - /// The network p2p rpc sender. - pub p2p_network: mpsc::Sender, - /// The network admin rpc sender. - pub network_admin: mpsc::Sender, - /// The l1 watcher queries sender. - pub l1_watcher_queries: mpsc::Sender, - /// The cancellation token, shared between all tasks. - pub cancellation: CancellationToken, +pub struct RpcActor { + /// Launcher used to relaunch the server if it stops. + launcher: Launcher, + /// Module set used to relaunch the server if it stops. + modules: RpcModule<()>, + /// The currently-running server handle. Replaced on each successful relaunch. + handle: Option, + /// Remaining relaunches allowed before [`Self::step`] returns + /// [`RpcActorError::ServerStopped`]. + restarts_remaining: u32, } -impl CancellableContext for RpcContext { - fn cancelled(&self) -> WaitForCancellationFuture<'_> { - self.cancellation.cancelled() +impl RpcActor { + /// Constructs a new [`RpcActor`]. + /// + /// `handle` is the live server returned by the caller's initial launch; this actor takes + /// ownership of the running server and handles up to `restarts_remaining` subsequent + /// relaunches via `launcher`. + pub const fn new( + launcher: Launcher, + modules: RpcModule<()>, + handle: Launcher::Handle, + restarts_remaining: u32, + ) -> Self { + Self { launcher, modules, handle: Some(handle), restarts_remaining } } } -/// Launches the jsonrpsee [`Server`]. -/// -/// If the RPC server is disabled, this will return `Ok(None)`. -/// -/// ## Errors -/// -/// - [`std::io::Error`] if the server fails to start. -async fn launch( - config: &RpcBuilder, - module: RpcModule<()>, -) -> Result { - let middleware = tower::ServiceBuilder::new() - .layer( - ProxyGetRequestLayer::new([ - ("/healthz", "healthz"), - ("/kona-rollup-boost/healthz", "kona-rollup-boost_healthz"), - ]) - .expect("Critical: Failed to build GET method proxy"), - ) - .timeout(Duration::from_secs(2)); - let server = Server::builder().set_http_middleware(middleware).build(config.socket).await?; - - if let Ok(addr) = server.local_addr() { - info!(target: "rpc", addr = ?addr, "RPC server bound to address"); - } else { - error!(target: "rpc", "Failed to get local address for RPC server"); - } - - Ok(server.start(module)) +impl Drop for RpcActor { + fn drop(&mut self) { + // jsonrpsee's ServerHandle is Arc>; dropping is enough to close the + // watch and stop the server, but calling `stop()` explicitly is clearer about intent. + // Errors here mean the server is already stopped. + if let Some(handle) = self.handle.take() { + handle.stop(); + } + } } #[async_trait] -impl - NodeActor - for RpcActor< - EngineRpcClient_, - RollupBoostAdminClient_, - RollupBoostHealth, - SequencerAdminApiClient_, - > -where - EngineRpcClient_: EngineRpcClient + 'static, - RollupBoostAdminClient_: RollupBoostAdminClient + 'static, - RollupBoostHealth: RollupBoostHealthzApiServer + 'static, - SequencerAdminApiClient_: SequencerAdminAPIClient + 'static, -{ +impl NodeActor for RpcActor { type Error = RpcActorError; - type StartData = RpcContext; - - async fn start( - mut self, - RpcContext { - cancellation, - p2p_network, - l1_watcher_queries, - network_admin, - }: Self::StartData, - ) -> Result<(), Self::Error> { - let mut modules = RpcModule::new(()); - - modules.merge(HealthzApiServer::into_rpc(HealthzRpc {}))?; - modules - .merge(RollupBoostHealthzApiServer::into_rpc(self.rollup_boost_health_rpc_client))?; - - // Build the p2p rpc module. - modules.merge(P2pRpc::new(p2p_network).into_rpc())?; - - // Build the admin rpc module. - modules.merge( - AdminRpc::new( - self.sequencer_admin_rpc_client, - network_admin, - Some(self.rollup_boost_admin_rpc_client), - ) - .into_rpc(), - )?; - - // Create context for communication between actors. - let rollup_rpc = RollupRpc::new(self.engine_rpc_client.clone(), l1_watcher_queries); - modules.merge(rollup_rpc.into_rpc())?; - - // Add development RPC module for engine state introspection if enabled - if self.config.dev_enabled() { - let dev_rpc = DevEngineRpc::new(self.engine_rpc_client.clone()); - modules.merge(dev_rpc.into_rpc())?; - } - if self.config.ws_enabled() { - modules.merge(WsRPC::new(self.engine_rpc_client.clone()).into_rpc())?; + async fn step(&mut self) -> Result<(), Self::Error> { + let handle = self.handle.as_ref().ok_or(RpcActorError::ServerStopped)?; + handle.stopped().await; + + if self.restarts_remaining == 0 { + return Err(RpcActorError::ServerStopped); } + self.restarts_remaining = self.restarts_remaining.saturating_sub(1); - let restarts = self.config.restart_count(); - - let mut handle = launch(&self.config, modules.clone()).await?; - - for _ in 0..=restarts { - tokio::select! { - _ = handle.clone().stopped() => { - match launch(&self.config, modules.clone()).await { - Ok(h) => handle = h, - Err(err) => { - error!(target: "rpc", ?err, "Failed to launch rpc server"); - cancellation.cancel(); - return Err(RpcActorError::ServerStopped); - } - } - } - _ = cancellation.cancelled() => { - // The cancellation token has been triggered, so we should stop the server. - handle.stop().map_err(|_| RpcActorError::StopFailed)?; - // Since the RPC Server didn't originate the error, we should return Ok. - return Ok(()); - } + match self.launcher.launch(self.modules.clone()).await { + Ok(new_handle) => { + self.handle = Some(new_handle); + Ok(()) + } + Err(err) => { + error!(target: "rpc", ?err, "Failed to launch rpc server"); + Err(RpcActorError::ServerStopped) } } - - // Stop the node if there has already been 3 rpc restarts. - cancellation.cancel(); - return Err(RpcActorError::ServerStopped); } } #[cfg(test)] mod tests { - use std::net::SocketAddr; - use super::*; + use std::sync::{ + Arc, + atomic::{AtomicU32, Ordering}, + }; + use tokio::sync::watch; + + /// Mock handle backed by a [`watch::channel`] of `bool`. + /// + /// The handle starts with the value `false` ("running") and `stop()` sets it to `true` + /// ("stopped"). [`Self::stopped`] resolves when the value becomes `true`, including + /// retroactively if `stop()` was called before any awaiter started waiting — which matches + /// the behavior of `jsonrpsee::ServerHandle` (and avoids the lost-wakeup hazard of `Notify`). + /// + /// Holds an idle [`watch::Receiver`] so that `Sender::send` from `stop()` always succeeds + /// (a watch sender errors when every receiver has been dropped). + #[derive(Debug, Clone)] + struct MockHandle { + stopped_tx: watch::Sender, + _keepalive: Arc>, + } + + impl MockHandle { + fn new() -> Self { + let (stopped_tx, keepalive) = watch::channel(false); + Self { stopped_tx, _keepalive: Arc::new(keepalive) } + } + } + + #[async_trait] + impl RpcServerHandle for MockHandle { + async fn stopped(&self) { + let mut rx = self.stopped_tx.subscribe(); + // If the value is already `true`, this returns immediately; otherwise it waits for + // the next change. + if *rx.borrow() { + return; + } + let _ = rx.changed().await; + } + + fn stop(&self) { + let _ = self.stopped_tx.send(true); + } + } + + /// Mock launcher that hands out `MockHandle`s and counts launch invocations. + /// + /// When `fail_after` is `Some(n)`, the n-th launch (0-indexed) returns an error instead. + #[derive(Debug)] + struct MockLauncher { + launch_count: Arc, + fail_after: Option, + handles: std::sync::Mutex>, + } + + impl MockLauncher { + fn new(fail_after: Option) -> Self { + Self { + launch_count: Arc::new(AtomicU32::new(0)), + fail_after, + handles: std::sync::Mutex::new(Vec::new()), + } + } + + fn launch_count(&self) -> u32 { + self.launch_count.load(Ordering::SeqCst) + } + + /// Returns a clone of the handle from the n-th successful launch (0-indexed). + fn handle(&self, n: usize) -> MockHandle { + self.handles.lock().unwrap()[n].clone() + } + } + + #[async_trait] + impl RpcServerLauncher for MockLauncher { + type Handle = MockHandle; + + async fn launch(&self, _modules: RpcModule<()>) -> Result { + let call = self.launch_count.fetch_add(1, Ordering::SeqCst); + if Some(call) == self.fail_after { + return Err(std::io::Error::other("simulated launch failure")); + } + let handle = MockHandle::new(); + self.handles.lock().unwrap().push(handle.clone()); + Ok(handle) + } + } + + // Allow `Arc` to be used directly as the actor's launcher generic, so tests can + // both hold a reference for assertions and pass ownership to the actor. + #[async_trait] + impl RpcServerLauncher for Arc { + type Handle = MockHandle; + + async fn launch(&self, modules: RpcModule<()>) -> Result { + (**self).launch(modules).await + } + } + + async fn make_actor_with_initial_handle( + launcher: Arc, + restarts_remaining: u32, + ) -> RpcActor> { + let initial_handle = launcher.launch(RpcModule::new(())).await.expect("initial launch"); + RpcActor::new(launcher, RpcModule::new(()), initial_handle, restarts_remaining) + } + + #[tokio::test] + async fn step_relaunches_on_first_stop() { + let launcher = Arc::new(MockLauncher::new(None)); + let mut actor = make_actor_with_initial_handle(launcher.clone(), 2).await; + + // Signal the first handle to stop, then drive one step. + launcher.handle(0).stop(); + actor.step().await.expect("first relaunch should succeed"); + + assert_eq!(launcher.launch_count(), 2, "expected one relaunch after the initial launch"); + } #[tokio::test] - async fn test_launch_no_modules() { - let launcher = RpcBuilder { - socket: SocketAddr::from(([127, 0, 0, 1], 8080)), - no_restart: false, - enable_admin: false, - admin_persistence: None, - ws_enabled: false, - dev_enabled: false, - }; - let result = launch(&launcher, RpcModule::new(())).await; - assert!(result.is_ok()); + async fn step_exhausts_restarts_then_errors() { + let launcher = Arc::new(MockLauncher::new(None)); + let mut actor = make_actor_with_initial_handle(launcher.clone(), 1).await; + + // First stop: should relaunch. + launcher.handle(0).stop(); + actor.step().await.expect("first relaunch should succeed"); + + // Second stop: restart budget is exhausted; step should error. + launcher.handle(1).stop(); + let err = actor.step().await.expect_err("second stop should exhaust restarts"); + assert!(matches!(err, RpcActorError::ServerStopped)); } #[tokio::test] - async fn test_launch_with_modules() { - let launcher = RpcBuilder { - socket: SocketAddr::from(([127, 0, 0, 1], 8081)), - no_restart: false, - enable_admin: false, - admin_persistence: None, - ws_enabled: false, - dev_enabled: false, - }; - let mut modules = RpcModule::new(()); - - modules.merge(RpcModule::new(())).expect("module merge"); - modules.merge(RpcModule::new(())).expect("module merge"); - modules.merge(RpcModule::new(())).expect("module merge"); - - let result = launch(&launcher, modules).await; - assert!(result.is_ok()); + async fn step_errors_with_zero_restarts_budget() { + let launcher = Arc::new(MockLauncher::new(None)); + let mut actor = make_actor_with_initial_handle(launcher.clone(), 0).await; + + launcher.handle(0).stop(); + let err = actor.step().await.expect_err("zero-restart budget should error on first stop"); + assert!(matches!(err, RpcActorError::ServerStopped)); + assert_eq!(launcher.launch_count(), 1, "relaunch should not have been attempted"); + } + + #[tokio::test] + async fn step_errors_when_relaunch_fails() { + // `fail_after = 1` means the second launch (the first relaunch) fails. + let launcher = Arc::new(MockLauncher::new(Some(1))); + let mut actor = make_actor_with_initial_handle(launcher.clone(), 3).await; + + launcher.handle(0).stop(); + let err = actor.step().await.expect_err("failed relaunch should surface as ServerStopped"); + assert!(matches!(err, RpcActorError::ServerStopped)); + } + + #[tokio::test] + async fn drop_calls_stop_on_live_handle() { + let launcher = Arc::new(MockLauncher::new(None)); + let actor = make_actor_with_initial_handle(launcher.clone(), 0).await; + + let handle_copy = launcher.handle(0); + assert!(!*handle_copy.stopped_tx.borrow(), "handle should start running"); + + drop(actor); + + assert!(*handle_copy.stopped_tx.borrow(), "drop should have stopped the handle"); } } diff --git a/rust/kona/crates/node/service/src/actors/rpc/engine_rpc_client.rs b/rust/kona/crates/node/service/src/actors/rpc/engine_rpc_client.rs index bbf02f130ef..2f1bc0eaf1e 100644 --- a/rust/kona/crates/node/service/src/actors/rpc/engine_rpc_client.rs +++ b/rust/kona/crates/node/service/src/actors/rpc/engine_rpc_client.rs @@ -1,4 +1,4 @@ -use crate::{EngineActorRequest, EngineRpcRequest}; +use crate::EngineRpcRequest; use alloy_eips::BlockNumberOrTag; use async_trait::async_trait; use derive_more::Constructor; @@ -14,12 +14,11 @@ use std::fmt::Debug; use tokio::sync::{mpsc, oneshot, watch}; /// Queue-based implementation of the [`EngineRpcClient`] trait. This handles all channel-based -/// operations, providing a nice facade for callers. This also exposes only a subset of the -/// supported [`EngineActorRequest`] operations to limit the power of callers to RPC-type requests. +/// operations, providing a nice facade for callers. #[derive(Clone, Constructor, Debug)] pub struct QueuedEngineRpcClient { - /// A channel to use to send the `EngineActor` requests. - pub engine_actor_request_tx: mpsc::Sender, + /// A channel to use to send [`EngineRpcRequest`]s to the [`crate::EngineRpcActor`]. + pub engine_rpc_request_tx: mpsc::Sender, } #[async_trait] @@ -27,10 +26,8 @@ impl EngineRpcClient for QueuedEngineRpcClient { async fn get_config(&self) -> RpcResult { let (config_tx, config_rx) = oneshot::channel(); - self.engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new(EngineRpcRequest::EngineQuery( - Box::new(EngineQueries::Config(config_tx)), - )))) + self.engine_rpc_request_tx + .send(EngineRpcRequest(Box::new(EngineQueries::Config(config_tx)))) .await .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; @@ -43,10 +40,8 @@ impl EngineRpcClient for QueuedEngineRpcClient { async fn get_state(&self) -> RpcResult { let (state_tx, state_rx) = oneshot::channel(); - self.engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new(EngineRpcRequest::EngineQuery( - Box::new(EngineQueries::State(state_tx)), - )))) + self.engine_rpc_request_tx + .send(EngineRpcRequest(Box::new(EngineQueries::State(state_tx)))) .await .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; @@ -62,10 +57,11 @@ impl EngineRpcClient for QueuedEngineRpcClient { ) -> RpcResult<(L2BlockInfo, OutputRoot, EngineState)> { let (output_tx, output_rx) = oneshot::channel(); - self.engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new(EngineRpcRequest::EngineQuery( - Box::new(EngineQueries::OutputAtBlock { block, sender: output_tx }), - )))) + self.engine_rpc_request_tx + .send(EngineRpcRequest(Box::new(EngineQueries::OutputAtBlock { + block, + sender: output_tx, + }))) .await .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; @@ -78,10 +74,8 @@ impl EngineRpcClient for QueuedEngineRpcClient { async fn dev_get_task_queue_length(&self) -> RpcResult { let (length_tx, length_rx) = oneshot::channel(); - self.engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new(EngineRpcRequest::EngineQuery( - Box::new(EngineQueries::TaskQueueLength(length_tx)), - )))) + self.engine_rpc_request_tx + .send(EngineRpcRequest(Box::new(EngineQueries::TaskQueueLength(length_tx)))) .await .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; @@ -94,10 +88,8 @@ impl EngineRpcClient for QueuedEngineRpcClient { async fn dev_subscribe_to_engine_queue_length(&self) -> RpcResult> { let (sub_tx, sub_rx) = oneshot::channel(); - self.engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new(EngineRpcRequest::EngineQuery( - Box::new(EngineQueries::QueueLengthReceiver(sub_tx)), - )))) + self.engine_rpc_request_tx + .send(EngineRpcRequest(Box::new(EngineQueries::QueueLengthReceiver(sub_tx)))) .await .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; @@ -109,10 +101,8 @@ impl EngineRpcClient for QueuedEngineRpcClient { async fn dev_subscribe_to_engine_state(&self) -> RpcResult> { let (sub_tx, sub_rx) = oneshot::channel(); - self.engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new(EngineRpcRequest::EngineQuery( - Box::new(EngineQueries::StateReceiver(sub_tx)), - )))) + self.engine_rpc_request_tx + .send(EngineRpcRequest(Box::new(EngineQueries::StateReceiver(sub_tx)))) .await .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; diff --git a/rust/kona/crates/node/service/src/actors/rpc/launcher.rs b/rust/kona/crates/node/service/src/actors/rpc/launcher.rs new file mode 100644 index 00000000000..5f1723880f2 --- /dev/null +++ b/rust/kona/crates/node/service/src/actors/rpc/launcher.rs @@ -0,0 +1,102 @@ +//! Server launcher and handle traits used by [`crate::RpcActor`]. +//! +//! These traits decouple [`crate::RpcActor`] from the concrete [`jsonrpsee::server::Server`] so +//! the actor's restart logic can be unit-tested with a controllable mock. The production +//! [`JsonrpseeServerLauncher`] implementation is a thin pass-through over jsonrpsee. + +use async_trait::async_trait; +use jsonrpsee::{ + RpcModule, + server::{Server, ServerHandle, middleware::http::ProxyGetRequestLayer}, +}; +use kona_rpc::RpcBuilder; +use std::time::Duration; + +/// A handle to a running RPC server. +/// +/// The actor awaits [`Self::stopped`] to detect that the server has terminated, and calls +/// [`Self::stop`] from its `Drop` impl to request graceful termination on shutdown. +#[async_trait] +pub trait RpcServerHandle: Send + Sync + 'static { + /// Resolves when the server has stopped. + async fn stopped(&self); + + /// Requests that the server stop. May be a no-op if already stopped. + fn stop(&self); +} + +/// Launches an RPC server bound to the configuration carried by the implementor. +#[async_trait] +pub trait RpcServerLauncher: Send + Sync + 'static { + /// The handle type produced by a successful [`Self::launch`]. + type Handle: RpcServerHandle; + + /// Launches a new server instance bound to `modules`. + async fn launch(&self, modules: RpcModule<()>) -> Result; +} + +#[async_trait] +impl RpcServerHandle for ServerHandle { + async fn stopped(&self) { + self.clone().stopped().await; + } + + fn stop(&self) { + // jsonrpsee returns `Err` only when the server is already stopped; for the actor's + // purposes that is indistinguishable from success. + // + // UFCS is required here to disambiguate: `self.stop()` and `Self::stop(self)` would both + // resolve to this trait method (infinite recursion). We want the inherent + // `ServerHandle::stop` from jsonrpsee, which clippy's `use_self` lint can't model. + #[allow(clippy::use_self)] + let _ = ServerHandle::stop(self); + } +} + +/// Production [`RpcServerLauncher`] backed by [`jsonrpsee::server::Server`]. +#[derive(Debug, Clone)] +pub struct JsonrpseeServerLauncher { + config: RpcBuilder, +} + +impl JsonrpseeServerLauncher { + /// Wraps an [`RpcBuilder`] for use as a launcher. + pub const fn new(config: RpcBuilder) -> Self { + Self { config } + } +} + +#[async_trait] +impl RpcServerLauncher for JsonrpseeServerLauncher { + type Handle = ServerHandle; + + async fn launch(&self, modules: RpcModule<()>) -> Result { + launch(&self.config, modules).await + } +} + +/// Launches the jsonrpsee [`Server`]. +/// +/// ## Errors +/// +/// - [`std::io::Error`] if the server fails to start. +async fn launch( + config: &RpcBuilder, + module: RpcModule<()>, +) -> Result { + let middleware = tower::ServiceBuilder::new() + .layer( + ProxyGetRequestLayer::new([("/healthz", "healthz")]) + .expect("Critical: Failed to build GET method proxy"), + ) + .timeout(Duration::from_secs(2)); + let server = Server::builder().set_http_middleware(middleware).build(config.socket).await?; + + if let Ok(addr) = server.local_addr() { + info!(target: "rpc", addr = ?addr, "RPC server bound to address"); + } else { + error!(target: "rpc", "Failed to get local address for RPC server"); + } + + Ok(server.start(module)) +} diff --git a/rust/kona/crates/node/service/src/actors/rpc/mod.rs b/rust/kona/crates/node/service/src/actors/rpc/mod.rs index 8838bd37687..81792d982be 100644 --- a/rust/kona/crates/node/service/src/actors/rpc/mod.rs +++ b/rust/kona/crates/node/service/src/actors/rpc/mod.rs @@ -1,5 +1,8 @@ mod actor; -pub use actor::{RpcActor, RpcContext}; +pub use actor::RpcActor; + +mod launcher; +pub use launcher::{JsonrpseeServerLauncher, RpcServerHandle, RpcServerLauncher}; mod engine_rpc_client; pub use engine_rpc_client::QueuedEngineRpcClient; @@ -7,8 +10,5 @@ pub use engine_rpc_client::QueuedEngineRpcClient; mod error; pub use error::RpcActorError; -mod rollup_boost_rpc_client; -pub use rollup_boost_rpc_client::{RollupBoostAdminApiClient, RollupBoostHealthRpcClient}; - mod sequencer_rpc_client; pub use sequencer_rpc_client::QueuedSequencerAdminAPIClient; diff --git a/rust/kona/crates/node/service/src/actors/rpc/rollup_boost_rpc_client.rs b/rust/kona/crates/node/service/src/actors/rpc/rollup_boost_rpc_client.rs deleted file mode 100644 index dee64408b92..00000000000 --- a/rust/kona/crates/node/service/src/actors/rpc/rollup_boost_rpc_client.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::{EngineActorRequest, EngineRpcRequest}; -use async_trait::async_trait; -use jsonrpsee::{ - core::RpcResult, - types::{ErrorCode, ErrorObject}, -}; -use kona_rpc::{RollupBoostAdminClient, RollupBoostHealthzApiServer, RollupBoostHealthzResponse}; -use rollup_boost::{GetExecutionModeResponse, SetExecutionModeRequest, SetExecutionModeResponse}; -use std::fmt::Debug; -use tokio::sync::{mpsc, oneshot}; - -/// [`RollupBoostHealthzApiServer`] implementation to send the request to `EngineActor`'s request -/// channel. -#[derive(Debug)] -pub struct RollupBoostHealthRpcClient { - /// A channel to use to send the `EngineActor` requests. - pub engine_actor_request_tx: mpsc::Sender, -} - -#[async_trait] -impl RollupBoostHealthzApiServer for RollupBoostHealthRpcClient { - async fn rollup_boost_healthz(&self) -> RpcResult { - let (health_tx, health_rx) = oneshot::channel(); - - self.engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new( - EngineRpcRequest::RollupBoostHealthRequest(Box::new( - kona_rpc::RollupBoostHealthQuery { sender: health_tx }, - )), - ))) - .await - .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; - - health_rx.await.map_err(|_| { - error!(target: "block_engine", "Failed to receive rollup boost health from engine rpc"); - ErrorObject::from(ErrorCode::InternalError) - }).map(|resp| RollupBoostHealthzResponse{rollup_boost_health: resp}) - } -} - -/// [`RollupBoostAdminClient`] implementation to send the request to `EngineActor`'s request -/// channel. -#[derive(Debug)] -pub struct RollupBoostAdminApiClient { - /// A channel to use to send the `EngineActor` requests. - pub engine_actor_request_tx: mpsc::Sender, -} - -#[async_trait] -impl RollupBoostAdminClient for RollupBoostAdminApiClient { - async fn set_execution_mode( - &self, - request: SetExecutionModeRequest, - ) -> RpcResult { - let engine_actor_request_tx = self.engine_actor_request_tx.clone(); - let (mode_tx, mode_rx) = oneshot::channel(); - - engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new( - EngineRpcRequest::RollupBoostAdminRequest(Box::new( - kona_rpc::RollupBoostAdminQuery::SetExecutionMode { - execution_mode: request.execution_mode, - sender: mode_tx, - }, - )), - ))) - .await - .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; - - mode_rx - .await - .map_err(|_| ErrorObject::from(ErrorCode::InternalError)) - .map(|_| SetExecutionModeResponse { execution_mode: request.execution_mode }) - } - - async fn get_execution_mode(&self) -> RpcResult { - let engine_actor_request_tx = self.engine_actor_request_tx.clone(); - let (mode_tx, mode_rx) = oneshot::channel(); - - engine_actor_request_tx - .send(EngineActorRequest::RpcRequest(Box::new( - EngineRpcRequest::RollupBoostAdminRequest(Box::new( - kona_rpc::RollupBoostAdminQuery::GetExecutionMode { sender: mode_tx }, - )), - ))) - .await - .map_err(|_| ErrorObject::from(ErrorCode::InternalError))?; - - mode_rx - .await - .map_err(|_| ErrorObject::from(ErrorCode::InternalError)) - .map(|execution_mode| GetExecutionModeResponse { execution_mode }) - } -} diff --git a/rust/kona/crates/node/service/src/actors/sequencer/actor.rs b/rust/kona/crates/node/service/src/actors/sequencer/actor.rs index 0722c031683..c3e58b29cff 100644 --- a/rust/kona/crates/node/service/src/actors/sequencer/actor.rs +++ b/rust/kona/crates/node/service/src/actors/sequencer/actor.rs @@ -1,7 +1,7 @@ //! The [`SequencerActor`]. use crate::{ - CancellableContext, NodeActor, SequencerAdminQuery, UnsafePayloadGossipClient, + NodeActor, SequencerAdminQuery, UnsafePayloadGossipClient, actors::{ SequencerEngineClient, engine::EngineClientError, @@ -13,7 +13,7 @@ use crate::{ update_conductor_commitment_duration_metrics, update_seal_duration_metrics, update_total_transactions_sequenced, }, - origin_selector::OriginSelector, + origin_selector::{L1OriginSelectorError, OriginSelector}, }, }, }; @@ -28,8 +28,7 @@ use std::{ sync::Arc, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; -use tokio::{select, sync::mpsc}; -use tokio_util::sync::{CancellationToken, WaitForCancellationFuture}; +use tokio::{select, sync::mpsc, time::Interval}; /// The handle to a block that has been started but not sealed. #[derive(Debug)] @@ -71,8 +70,6 @@ pub struct SequencerActor< pub admin_api_rx: mpsc::Receiver, /// The attributes builder used for block building. pub attributes_builder: AttributesBuilder_, - /// The cancellation token, shared between all tasks. - pub cancellation_token: CancellationToken, /// The optional conductor RPC client. pub conductor: Option, /// The struct used to interact with the engine. @@ -87,6 +84,15 @@ pub struct SequencerActor< pub rollup_config: Arc, /// A client to asynchronously sign and gossip built payloads to the network actor. pub unsafe_payload_gossip_client: UnsafePayloadGossipClient_, + + /// Ticker that paces block-building attempts. + build_ticker: Interval, + /// The handle for the payload built on the previous tick that is waiting to be sealed. + next_payload_to_seal: Option, + /// Duration of the most recent seal operation, used to back-pressure the build ticker. + last_seal_duration: Duration, + /// Whether the one-shot startup work (metrics + initial engine reset) has run. + started: bool, } impl< @@ -110,6 +116,37 @@ where SequencerEngineClient_: SequencerEngineClient, UnsafePayloadGossipClient_: UnsafePayloadGossipClient, { + /// Instantiate a new [`SequencerActor`]. + #[allow(clippy::too_many_arguments)] + pub fn new( + admin_api_rx: mpsc::Receiver, + attributes_builder: AttributesBuilder_, + conductor: Option, + engine_client: SequencerEngineClient_, + is_active: bool, + in_recovery_mode: bool, + origin_selector: OriginSelector_, + rollup_config: Arc, + unsafe_payload_gossip_client: UnsafePayloadGossipClient_, + ) -> Self { + let build_ticker = tokio::time::interval(Duration::from_secs(rollup_config.block_time)); + Self { + admin_api_rx, + attributes_builder, + conductor, + engine_client, + is_active, + in_recovery_mode, + origin_selector, + rollup_config, + unsafe_payload_gossip_client, + build_ticker, + next_payload_to_seal: None, + last_seal_duration: Duration::from_secs(0), + started: false, + } + } + /// Seals and commits the last pending block, if one exists and starts the build job for the /// next L2 block, on top of the current unsafe head. /// @@ -225,6 +262,15 @@ where .await { Ok(l1_origin) => l1_origin, + Err(L1OriginSelectorError::OriginNotFound(hash)) => { + warn!( + target: "sequencer", + %hash, + "L1 origin block not found, resetting engine" + ); + self.engine_client.reset_engine_forkchoice().await?; + return Ok(None); + } Err(err) => { warn!( target: "sequencer", @@ -398,107 +444,70 @@ where UnsafePayloadGossipClient_: UnsafePayloadGossipClient + Sync + 'static, { type Error = SequencerActorError; - type StartData = (); - - async fn start(mut self, _: Self::StartData) -> Result<(), Self::Error> { - let mut build_ticker = - tokio::time::interval(Duration::from_secs(self.rollup_config.block_time)); - - self.update_metrics(); - - // Reset the engine state prior to beginning block building. - self.schedule_initial_reset().await?; - - let mut next_payload_to_seal: Option = None; - let mut last_seal_duration = Duration::from_secs(0); - loop { - select! { - // We are using a biased select here to ensure that the admin queries are given priority over the block building task. - // This is important to limit the occurrence of race conditions where a stopped query is received when a sequencer is building a new block. - biased; - _ = self.cancellation_token.cancelled() => { - info!( - target: "sequencer", - "Received shutdown signal. Exiting sequencer task." - ); - return Ok(()); - } - Some(query) = self.admin_api_rx.recv() => { - let active_before = self.is_active; - self.handle_admin_query(query).await; + async fn step(&mut self) -> Result<(), Self::Error> { + if !self.started { + self.update_metrics(); + // Reset the engine state prior to beginning block building. + self.schedule_initial_reset().await?; + self.started = true; + } - // immediately attempt to build a block if the sequencer was just started - if !active_before && self.is_active { - build_ticker.reset_immediately(); - } + select! { + // We are using a biased select here to ensure that the admin queries are given priority over the block building task. + // This is important to limit the occurrence of race conditions where a stopped query is received when a sequencer is building a new block. + biased; + Some(query) = self.admin_api_rx.recv() => { + let active_before = self.is_active; + + self.handle_admin_query(query).await; + + // immediately attempt to build a block if the sequencer was just started + if !active_before && self.is_active { + self.build_ticker.reset_immediately(); } - // The sequencer must be active to build new blocks. - _ = build_ticker.tick(), if self.is_active => { - - match self.seal_last_and_start_next(next_payload_to_seal.as_ref()).await { - Ok(res) => { - next_payload_to_seal = res.unsealed_payload_handle; - last_seal_duration = res.seal_duration; - }, - Err(SequencerActorError::EngineError(EngineClientError::SealError(err))) => { - if is_seal_task_err_fatal(&err) { - error!(target: "sequencer", err=?err, "Critical seal task error occurred"); - self.cancellation_token.cancel(); - return Err(SequencerActorError::EngineError(EngineClientError::SealError(err))); - } - next_payload_to_seal = None; - }, - Err(other_err) => { - error!(target: "sequencer", err = ?other_err, "Unexpected error building or sealing payload"); - self.cancellation_token.cancel(); - return Err(other_err); + Ok(()) + } + // The sequencer must be active to build new blocks. + _ = self.build_ticker.tick(), if self.is_active => { + // Move the pending payload out of self so the &mut self call below doesn't conflict + // with the &self read of self.next_payload_to_seal. + let pending = self.next_payload_to_seal.take(); + match self.seal_last_and_start_next(pending.as_ref()).await { + Ok(res) => { + self.next_payload_to_seal = res.unsealed_payload_handle; + self.last_seal_duration = res.seal_duration; + } + Err(SequencerActorError::EngineError(EngineClientError::SealError(err))) => { + if is_seal_task_err_fatal(&err) { + error!(target: "sequencer", err=?err, "Critical seal task error occurred"); + return Err(SequencerActorError::EngineError(EngineClientError::SealError(err))); } + self.next_payload_to_seal = None; } - - if let Some(ref payload) = next_payload_to_seal { - let next_block_seconds = payload.attributes_with_parent.parent().block_info.timestamp.saturating_add(self.rollup_config.block_time); - // next block time is last + block_time - time it takes to seal. - let next_block_time = UNIX_EPOCH + Duration::from_secs(next_block_seconds) - last_seal_duration; - match next_block_time.duration_since(SystemTime::now()) { - Ok(duration) => build_ticker.reset_after(duration), - Err(_) => build_ticker.reset_immediately(), - }; - } else { - build_ticker.reset_immediately(); + Err(other_err) => { + error!(target: "sequencer", err = ?other_err, "Unexpected error building or sealing payload"); + return Err(other_err); } } + + if let Some(payload) = self.next_payload_to_seal.as_ref() { + let next_block_seconds = payload.attributes_with_parent.parent().block_info.timestamp.saturating_add(self.rollup_config.block_time); + // next block time is last + block_time - time it takes to seal. + let next_block_time = UNIX_EPOCH + Duration::from_secs(next_block_seconds) - self.last_seal_duration; + match next_block_time.duration_since(SystemTime::now()) { + Ok(duration) => self.build_ticker.reset_after(duration), + Err(_) => self.build_ticker.reset_immediately(), + }; + } else { + self.build_ticker.reset_immediately(); + } + Ok(()) } } } } -impl< - AttributesBuilder_, - Conductor_, - OriginSelector_, - SequencerEngineClient_, - UnsafePayloadGossipClient_, -> CancellableContext - for SequencerActor< - AttributesBuilder_, - Conductor_, - OriginSelector_, - SequencerEngineClient_, - UnsafePayloadGossipClient_, - > -where - AttributesBuilder_: AttributesBuilder, - Conductor_: Conductor, - OriginSelector_: OriginSelector, - SequencerEngineClient_: SequencerEngineClient, - UnsafePayloadGossipClient_: UnsafePayloadGossipClient, -{ - fn cancelled(&self) -> WaitForCancellationFuture<'_> { - self.cancellation_token.cancelled() - } -} - // Determines whether the provided [`SealTaskError`] is fatal for the sequencer. // // NB: We could use `err.severity()`, but that gives EngineActor control over this classification. diff --git a/rust/kona/crates/node/service/src/actors/sequencer/engine_client.rs b/rust/kona/crates/node/service/src/actors/sequencer/engine_client.rs index 638f0603a31..78fb5bdc15c 100644 --- a/rust/kona/crates/node/service/src/actors/sequencer/engine_client.rs +++ b/rust/kona/crates/node/service/src/actors/sequencer/engine_client.rs @@ -62,7 +62,7 @@ impl SequencerEngineClient for QueuedSequencerEngineClient { info!(target: "sequencer", "Sending reset request to engine."); self.engine_actor_request_tx - .send(EngineActorRequest::ResetRequest(Box::new(ResetRequest { result_tx }))) + .send(EngineActorRequest::Reset(Box::new(ResetRequest { result_tx }))) .await .map_err(|_| EngineClientError::RequestError("request channel closed.".to_string()))?; @@ -85,7 +85,7 @@ impl SequencerEngineClient for QueuedSequencerEngineClient { trace!(target: "sequencer", "Sending start build request to engine."); if self .engine_actor_request_tx - .send(EngineActorRequest::BuildRequest(Box::new(BuildRequest { + .send(EngineActorRequest::Build(Box::new(BuildRequest { attributes, result_tx: payload_id_tx, }))) @@ -113,7 +113,7 @@ impl SequencerEngineClient for QueuedSequencerEngineClient { trace!(target: "sequencer", ?attributes, "Sending seal request to engine."); self.engine_actor_request_tx - .send(EngineActorRequest::SealRequest(Box::new(SealRequest { + .send(EngineActorRequest::Seal(Box::new(SealRequest { payload_id, attributes, result_tx, diff --git a/rust/kona/crates/node/service/src/actors/sequencer/origin_selector.rs b/rust/kona/crates/node/service/src/actors/sequencer/origin_selector.rs index 5ff8cdd8631..097e4eb0b37 100644 --- a/rust/kona/crates/node/service/src/actors/sequencer/origin_selector.rs +++ b/rust/kona/crates/node/service/src/actors/sequencer/origin_selector.rs @@ -70,7 +70,7 @@ impl OriginSelector for L1OriginSelec } let Some(current) = self.current else { - unreachable!("Current L1 origin should always be set by `select_origins`"); + return Err(L1OriginSelectorError::OriginNotFound(unsafe_head.l1_origin.hash)); }; let max_seq_drift = self.cfg.max_sequencer_drift(current.timestamp); @@ -128,7 +128,12 @@ impl L1OriginSelector

{ in_recovery_mode: bool, ) -> Result<(), L1OriginSelectorError> { if in_recovery_mode { - self.current = self.l1.get_block_by_hash(unsafe_head.l1_origin.hash).await?; + self.current = Some( + self.l1 + .get_block_by_hash(unsafe_head.l1_origin.hash) + .await? + .ok_or(L1OriginSelectorError::OriginNotFound(unsafe_head.l1_origin.hash))?, + ); self.next = self.l1.get_block_by_number(unsafe_head.l1_origin.number + 1).await?; return Ok(()); } @@ -141,9 +146,12 @@ impl L1OriginSelector

{ self.next = None; } else { // Find the current origin block, as it is missing. - let current = self.l1.get_block_by_hash(unsafe_head.l1_origin.hash).await?; - - self.current = current; + self.current = Some( + self.l1 + .get_block_by_hash(unsafe_head.l1_origin.hash) + .await? + .ok_or(L1OriginSelectorError::OriginNotFound(unsafe_head.l1_origin.hash))?, + ); self.next = None; } @@ -185,6 +193,9 @@ pub enum L1OriginSelectorError { "Waiting for more L1 data to be available to select the next L1 origin block. Current L1 origin: {0:?}" )] NotEnoughData(BlockInfo), + /// The L1 origin block could not be found by its hash. + #[error("L1 origin block not found for hash: {0}")] + OriginNotFound(B256), } /// L1 [`BlockInfo`] provider interface for the [`L1OriginSelector`]. @@ -493,4 +504,129 @@ mod test { assert!(matches!(next_err, L1OriginSelectorError::NotEnoughData(_))); } } + + #[tokio::test] + async fn test_next_l1_origin_recovery_mode_found() { + const L2_BLOCK_TIME: u64 = 2; + + let cfg = Arc::new(RollupConfig { + block_time: L2_BLOCK_TIME, + max_sequencer_drift: 600, + ..Default::default() + }); + + let mut provider = MockOriginSelectorProvider::default(); + provider.with_block(BlockInfo { + parent_hash: B256::ZERO, + hash: B256::with_last_byte(1), + number: 1, + timestamp: 12, + }); + provider.with_block(BlockInfo { + parent_hash: B256::with_last_byte(1), + hash: B256::with_last_byte(2), + number: 2, + timestamp: 24, + }); + + let mut selector = L1OriginSelector::new(cfg, provider); + + let unsafe_head = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: 5, + timestamp: 10, + ..Default::default() + }, + l1_origin: NumHash { number: 1, hash: B256::with_last_byte(1) }, + seq_num: 0, + }; + + let origin = selector.next_l1_origin(unsafe_head, true).await.unwrap(); + assert_eq!(origin.number, 1); + assert_eq!(origin.hash, B256::with_last_byte(1)); + } + + #[tokio::test] + async fn test_next_l1_origin_recovery_mode_not_found() { + const L2_BLOCK_TIME: u64 = 2; + + let cfg = Arc::new(RollupConfig { + block_time: L2_BLOCK_TIME, + max_sequencer_drift: 600, + ..Default::default() + }); + + let provider = MockOriginSelectorProvider::default(); + let mut selector = L1OriginSelector::new(cfg, provider); + + let unsafe_head = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: 5, + timestamp: 10, + ..Default::default() + }, + l1_origin: NumHash { number: 1, hash: B256::with_last_byte(1) }, + seq_num: 0, + }; + + let result = selector.next_l1_origin(unsafe_head, true).await; + assert!(matches!( + result, + Err(L1OriginSelectorError::OriginNotFound(hash)) if hash == B256::with_last_byte(1) + )); + } + + #[tokio::test] + async fn test_next_l1_origin_normal_mode_origin_not_found() { + const L2_BLOCK_TIME: u64 = 2; + + let cfg = Arc::new(RollupConfig { + block_time: L2_BLOCK_TIME, + max_sequencer_drift: 600, + ..Default::default() + }); + + let mut provider = MockOriginSelectorProvider::default(); + provider.with_block(BlockInfo { + parent_hash: B256::ZERO, + hash: B256::ZERO, + number: 0, + timestamp: 0, + }); + + let mut selector = L1OriginSelector::new(cfg, provider); + + // First call: set current to block 0. + let unsafe_head_epoch0 = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: 0, + timestamp: 0, + ..Default::default() + }, + l1_origin: NumHash { number: 0, hash: B256::ZERO }, + seq_num: 0, + }; + let _ = selector.next_l1_origin(unsafe_head_epoch0, false).await.unwrap(); + + // Second call: reference a non-existent L1 origin hash, triggering the else branch. + let unsafe_head_missing = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: 1, + timestamp: L2_BLOCK_TIME, + ..Default::default() + }, + l1_origin: NumHash { number: 99, hash: B256::with_last_byte(0xFF) }, + seq_num: 0, + }; + + let result = selector.next_l1_origin(unsafe_head_missing, false).await; + assert!(matches!( + result, + Err(L1OriginSelectorError::OriginNotFound(hash)) if hash == B256::with_last_byte(0xFF) + )); + } } diff --git a/rust/kona/crates/node/service/src/actors/sequencer/tests/test_util.rs b/rust/kona/crates/node/service/src/actors/sequencer/tests/test_util.rs index 3a6a08b4e50..a7e4f3c97b7 100644 --- a/rust/kona/crates/node/service/src/actors/sequencer/tests/test_util.rs +++ b/rust/kona/crates/node/service/src/actors/sequencer/tests/test_util.rs @@ -8,7 +8,6 @@ use kona_derive::test_utils::TestAttributesBuilder; use kona_genesis::RollupConfig; use std::sync::Arc; use tokio::sync::mpsc; -use tokio_util::sync::CancellationToken; // Returns a test SequencerActor with mocks that can be used or overridden. pub(crate) fn test_actor() -> SequencerActor< @@ -21,16 +20,16 @@ pub(crate) fn test_actor() -> SequencerActor< // The sender is intentionally dropped, so the channel starts closed. // If future tests need to send messages, keep the sender instead of dropping it. let (_admin_api_tx, admin_api_rx) = mpsc::channel(20); - SequencerActor { + SequencerActor::new( admin_api_rx, - attributes_builder: TestAttributesBuilder { attributes: vec![] }, - cancellation_token: CancellationToken::new(), - conductor: None, - engine_client: MockSequencerEngineClient::new(), - is_active: true, - in_recovery_mode: false, - origin_selector: MockOriginSelector::new(), - rollup_config: Arc::new(RollupConfig::default()), - unsafe_payload_gossip_client: MockUnsafePayloadGossipClient::new(), - } + TestAttributesBuilder { attributes: vec![] }, + None, + MockSequencerEngineClient::new(), + true, + false, + MockOriginSelector::new(), + // tokio::time::interval requires a non-zero period; the default block_time is 0. + Arc::new(RollupConfig { block_time: 2, ..Default::default() }), + MockUnsafePayloadGossipClient::new(), + ) } diff --git a/rust/kona/crates/node/service/src/actors/traits.rs b/rust/kona/crates/node/service/src/actors/traits.rs index d68a5b12066..0d9bee6214b 100644 --- a/rust/kona/crates/node/service/src/actors/traits.rs +++ b/rust/kona/crates/node/service/src/actors/traits.rs @@ -1,28 +1,20 @@ //! [`NodeActor`] trait. use async_trait::async_trait; -use tokio_util::sync::WaitForCancellationFuture; - -/// The communication context used by the actor. -pub trait CancellableContext: Send { - /// Returns a future that resolves when the actor is cancelled. - fn cancelled(&self) -> WaitForCancellationFuture<'_>; -} /// The [`NodeActor`] is an actor-like service for the node. /// -/// Actors may: -/// - Handle incoming messages. -/// - Perform background tasks. -/// - Emit new events for other actors to process. +/// Callers may call [`Self::step`] to execute a single inbound request, +/// event, or tick. #[async_trait] pub trait NodeActor: Send + 'static { /// The error type for the actor. type Error: std::fmt::Debug; - /// The type necessary to pass to the start function. - /// This is the result of - type StartData: Sized; - /// Starts the actor. - async fn start(self, start_context: Self::StartData) -> Result<(), Self::Error>; + /// Handle the next inbound request, event, or scheduled tick. + /// + /// Returning `Ok(())` indicates the actor is ready to be stepped again. + /// Returning `Err(_)` indicates the actor has encountered a fatal + /// condition and should not be stepped further. + async fn step(&mut self) -> Result<(), Self::Error>; } diff --git a/rust/kona/crates/node/service/src/lib.rs b/rust/kona/crates/node/service/src/lib.rs index deeab0fb47e..8e96a2d437c 100644 --- a/rust/kona/crates/node/service/src/lib.rs +++ b/rust/kona/crates/node/service/src/lib.rs @@ -17,24 +17,24 @@ pub use service::{ mod actors; pub use actors::{ - BlockStream, BuildRequest, CancellableContext, Conductor, ConductorClient, ConductorError, + BlockStream, BuildRequest, Conductor, ConductorClient, ConductorError, DelayedL1OriginSelectorProvider, DelegateDerivationActor, DerivationActor, DerivationActorRequest, DerivationClientError, DerivationClientResult, - DerivationDelegateClient, DerivationDelegateClientError, DerivationEngineClient, - DerivationError, DerivationState, DerivationStateMachine, DerivationStateTransitionError, - DerivationStateUpdate, EngineActor, EngineActorRequest, EngineClientError, EngineClientResult, - EngineConfig, EngineDerivationClient, EngineError, EngineProcessingRequest, EngineProcessor, - EngineRequestReceiver, EngineRpcProcessor, EngineRpcRequest, EngineRpcRequestReceiver, - L1OriginSelector, L1OriginSelectorError, L1OriginSelectorProvider, L1WatcherActor, - L1WatcherActorError, L1WatcherDerivationClient, NetworkActor, NetworkActorError, - NetworkBuilder, NetworkBuilderError, NetworkConfig, NetworkDriver, NetworkDriverError, - NetworkEngineClient, NetworkHandler, NetworkInboundData, NodeActor, OriginSelector, - QueuedDerivationEngineClient, QueuedEngineDerivationClient, QueuedEngineRpcClient, - QueuedL1WatcherDerivationClient, QueuedNetworkEngineClient, QueuedSequencerAdminAPIClient, - QueuedSequencerEngineClient, QueuedUnsafePayloadGossipClient, ResetRequest, - RollupBoostAdminApiClient, RollupBoostHealthRpcClient, RpcActor, RpcActorError, RpcContext, - SealRequest, SequencerActor, SequencerActorError, SequencerAdminQuery, SequencerConfig, - SequencerEngineClient, UnsafePayloadGossipClient, UnsafePayloadGossipClientError, + DerivationDelegateClient, DerivationDelegateClientError, DerivationDelegateProvider, + DerivationEngineClient, DerivationError, DerivationState, DerivationStateMachine, + DerivationStateTransitionError, DerivationStateUpdate, EngineActor, EngineActorRequest, + EngineClientError, EngineClientResult, EngineConfig, EngineDerivationClient, EngineError, + EngineRpcActor, EngineRpcRequest, JsonrpseeServerLauncher, L1OriginSelector, + L1OriginSelectorError, L1OriginSelectorProvider, L1WatcherActor, L1WatcherActorError, + L1WatcherDerivationClient, NetworkActor, NetworkActorError, NetworkBuilder, + NetworkBuilderError, NetworkConfig, NetworkDriver, NetworkDriverError, NetworkEngineClient, + NetworkHandler, NodeActor, OriginSelector, QueuedDerivationEngineClient, + QueuedEngineDerivationClient, QueuedEngineRpcClient, QueuedL1WatcherDerivationClient, + QueuedNetworkEngineClient, QueuedSequencerAdminAPIClient, QueuedSequencerEngineClient, + QueuedUnsafePayloadGossipClient, ResetRequest, RpcActor, RpcActorError, RpcServerHandle, + RpcServerLauncher, SealRequest, SequencerActor, SequencerActorError, SequencerAdminQuery, + SequencerConfig, SequencerEngineClient, UnsafePayloadGossipClient, + UnsafePayloadGossipClientError, }; mod metrics; diff --git a/rust/kona/crates/node/service/src/service/builder.rs b/rust/kona/crates/node/service/src/service/builder.rs index 2c229167963..18ab5e85e38 100644 --- a/rust/kona/crates/node/service/src/service/builder.rs +++ b/rust/kona/crates/node/service/src/service/builder.rs @@ -18,6 +18,7 @@ use tower::ServiceBuilder; use url::Url; use kona_genesis::{L1ChainConfig, RollupConfig}; +use kona_interop::DependencySet; use kona_providers_alloy::OnlineBeaconClient; use kona_rpc::RpcBuilder; @@ -73,6 +74,8 @@ pub struct RollupNodeBuilder { /// Optional configuration for Derivation Delegate mode. /// When present, the node does not run derivation, instead trusting the configured delegate. pub derivation_delegate_config: Option, + /// The interop dependency set for this chain. + pub dependency_set: Option>, } impl RollupNodeBuilder { @@ -95,9 +98,19 @@ impl RollupNodeBuilder { interop_mode: InteropMode::default(), sequencer_config: None, derivation_delegate_config: None, + dependency_set: None, } } + /// Sets the interop [`DependencySet`] on the [`RollupNodeBuilder`]. + /// + /// Must be called when the rollup config schedules the Interop hardfork. + /// When not set, the underlying [`kona_derive::StatefulAttributesBuilder`] + /// constructor panics on an interop-scheduled chain. + pub fn with_dependency_set(self, dependency_set: Option>) -> Self { + Self { dependency_set, ..self } + } + /// Sets the [`EngineConfig`] on the [`RollupNodeBuilder`]. pub fn with_engine_config(self, engine_config: EngineConfig) -> Self { Self { engine_config, ..self } @@ -133,7 +146,6 @@ impl RollupNodeBuilder { /// - The L2 engine URL is not set. /// - The jwt secret is not set. /// - The P2P config is not set. - /// - The rollup boost args are not set. pub fn build(self) -> RollupNode { let mut l1_beacon = OnlineBeaconClient::new_http(self.l1_config_builder.beacon.to_string()); if let Some(l1_slot_duration) = self.l1_config_builder.slot_duration_override { @@ -180,6 +192,7 @@ impl RollupNodeBuilder { p2p_config, sequencer_config, derivation_delegate_provider, + dependency_set: self.dependency_set, } } } diff --git a/rust/kona/crates/node/service/src/service/node.rs b/rust/kona/crates/node/service/src/service/node.rs index 7c130b21220..54739714453 100644 --- a/rust/kona/crates/node/service/src/service/node.rs +++ b/rust/kona/crates/node/service/src/service/node.rs @@ -1,27 +1,36 @@ //! Contains the [`RollupNode`] implementation. use crate::{ ConductorClient, DelayedL1OriginSelectorProvider, DelegateDerivationActor, DerivationActor, - DerivationDelegateClient, DerivationError, EngineActor, EngineActorRequest, EngineConfig, - EngineProcessor, EngineRpcProcessor, InteropMode, L1OriginSelector, L1WatcherActor, - NetworkActor, NetworkBuilder, NetworkConfig, NodeActor, NodeMode, QueuedDerivationEngineClient, + DerivationActorRequest, DerivationDelegateClient, DerivationError, EngineActor, + EngineActorRequest, EngineConfig, EngineRpcActor, EngineRpcRequest, InteropMode, + JsonrpseeServerLauncher, L1OriginSelector, L1WatcherActor, NetworkActor, NetworkBuilder, + NetworkConfig, NetworkHandler, NodeActor, NodeMode, QueuedDerivationEngineClient, QueuedEngineDerivationClient, QueuedEngineRpcClient, QueuedL1WatcherDerivationClient, QueuedNetworkEngineClient, QueuedSequencerAdminAPIClient, QueuedSequencerEngineClient, - RollupBoostAdminApiClient, RollupBoostHealthRpcClient, RpcActor, RpcContext, SequencerActor, - SequencerConfig, - actors::{BlockStream, NetworkInboundData, QueuedUnsafePayloadGossipClient}, + RpcActor, RpcServerLauncher, SequencerActor, SequencerConfig, + actors::{BlockStream, QueuedUnsafePayloadGossipClient}, }; use alloy_eips::BlockNumberOrTag; +use alloy_primitives::Address; use alloy_provider::RootProvider; +use jsonrpsee::RpcModule; use kona_derive::StatefulAttributesBuilder; use kona_engine::{Engine, EngineState, OpEngineClient}; use kona_genesis::{L1ChainConfig, RollupConfig}; -use kona_protocol::L2BlockInfo; +use kona_gossip::P2pRpcRequest; +use kona_interop::DependencySet; +use kona_protocol::{BlockInfo, L2BlockInfo}; use kona_providers_alloy::{ AlloyChainProvider, AlloyL2ChainProvider, OnlineBeaconClient, OnlineBlobProvider, OnlinePipeline, }; -use kona_rpc::RpcBuilder; +use kona_rpc::{ + AdminApiServer, AdminRpc, DevEngineApiServer, DevEngineRpc, HealthzApiServer, HealthzRpc, + L1WatcherQueries, NetworkAdminQuery, OpP2PApiServer, P2pRpc, RollupNodeApiServer, RollupRpc, + RpcBuilder, WsRPC, WsServer, +}; use op_alloy_network::Optimism; +use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; use std::{ops::Not as _, sync::Arc, time::Duration}; use tokio::sync::{mpsc, watch}; use tokio_util::sync::CancellationToken; @@ -67,6 +76,10 @@ pub struct RollupNode { pub(crate) sequencer_config: SequencerConfig, /// Optional derivation delegate provider. pub(crate) derivation_delegate_provider: Option, + /// The interop dependency set for this chain. + /// Mirrors op-node's `--interop.dependency-set`. + /// [`StatefulAttributesBuilder`] constructor panics otherwise. + pub(crate) dependency_set: Option>, } /// A RollupNode-level derivation actor wrapper. @@ -77,29 +90,59 @@ pub struct RollupNode { /// It is not intended to be generic or reusable outside the /// `RollupNode` wiring logic. enum ConfiguredDerivationActor { - Delegate(Box>), + Delegate( + Box< + DelegateDerivationActor< + QueuedDerivationEngineClient, + DerivationDelegateClient, + AlloyChainProvider, + >, + >, + ), Normal(Box>), } #[async_trait::async_trait] impl NodeActor for ConfiguredDerivationActor where - DelegateDerivationActor: - NodeActor, + DelegateDerivationActor< + QueuedDerivationEngineClient, + DerivationDelegateClient, + AlloyChainProvider, + >: NodeActor, DerivationActor: - NodeActor, + NodeActor, { - type StartData = (); type Error = DerivationError; - async fn start(self, ctx: ()) -> Result<(), Self::Error> { + async fn step(&mut self) -> Result<(), Self::Error> { match self { - Self::Delegate(a) => a.start(ctx).await, - Self::Normal(a) => a.start(ctx).await, + Self::Delegate(a) => a.step().await, + Self::Normal(a) => a.step().await, } } } +/// Concrete type of the engine actor used by `RollupNode`. +type ConfiguredEngineActor = + EngineActor>, QueuedEngineDerivationClient>; + +/// Concrete type of the engine rpc actor used by `RollupNode`. +type ConfiguredEngineRpcActor = + EngineRpcActor>>; + +/// Concrete type of the sequencer actor used by `RollupNode`. +type ConfiguredSequencerActor = SequencerActor< + StatefulAttributesBuilder, + ConductorClient, + L1OriginSelector, + QueuedSequencerEngineClient, + QueuedUnsafePayloadGossipClient, +>; + +/// Concrete type of the rpc actor used by `RollupNode`. +type ConfiguredRpcActor = RpcActor; + impl RollupNode { /// The mode of operation for the node. const fn mode(&self) -> NodeMode { @@ -111,11 +154,6 @@ impl RollupNode { NetworkBuilder::from(self.p2p_config.clone()) } - /// Returns an engine builder for the node. - fn engine_config(&self) -> EngineConfig { - self.engine_config.clone() - } - /// Returns an rpc builder for the node. fn rpc_builder(&self) -> Option { self.rpc_builder.clone() @@ -142,6 +180,7 @@ impl RollupNode { self.l1_config.chain_config.clone(), l2_derivation_provider, l1_derivation_provider, + self.dependency_set.clone(), ) } @@ -166,6 +205,7 @@ impl RollupNode { OnlineBlobProvider::init(self.l1_config.beacon_client.clone()).await, l1_derivation_provider, l2_derivation_provider, + self.dependency_set.clone(), ), InteropMode::Indexed => OnlinePipeline::new_indexed( self.config.clone(), @@ -173,164 +213,96 @@ impl RollupNode { OnlineBlobProvider::init(self.l1_config.beacon_client.clone()).await, l1_derivation_provider, l2_derivation_provider, + self.dependency_set.clone(), ), } } - /// Helper function to assemble the [`EngineActor`] since there are many structs created that - /// are not relevant to other actors or logic. - /// Note: ignoring complex type warning. This type only pertains to this function, so it is - /// better to have the full type here than have to piece it together from multiple type defs. - #[allow(clippy::type_complexity)] - fn create_engine_actor( + /// Builds both engine actors. They share a single [`kona_engine::EngineClient`] and a watch + /// over the engine queue length / state, but otherwise run as independent peers. + /// + /// The non-rpc actor handles state-mutating requests (build, reset, seal, safe-signal + /// consolidation, etc); the rpc actor handles read-only queries. + fn build_engine_actors( &self, - cancellation_token: CancellationToken, engine_request_rx: mpsc::Receiver, - derivation_client: QueuedEngineDerivationClient, + engine_rpc_request_rx: mpsc::Receiver, + derivation_actor_request_tx: mpsc::Sender, unsafe_head_tx: watch::Sender, - ) -> Result< - EngineActor< - EngineProcessor< - OpEngineClient>, - QueuedEngineDerivationClient, - >, - EngineRpcProcessor>>, - >, - String, - > { + ) -> (ConfiguredEngineActor, ConfiguredEngineRpcActor) { + // Engine-internal watches; not visible outside this helper. let engine_state = EngineState::default(); let (engine_state_tx, engine_state_rx) = watch::channel(engine_state); let (engine_queue_length_tx, engine_queue_length_rx) = watch::channel(0); let engine = Engine::new(engine_state, engine_state_tx, engine_queue_length_tx); - let engine_client = Arc::new(self.engine_config().build_engine_client().map_err(|e| { - error!(target: "service", error = ?e, "engine client build failed"); - format!("Engine client build failed: {e:?}") - })?); + let engine_client = Arc::new(self.engine_config.clone().build_engine_client()); - let engine_processor = EngineProcessor::new( + // unsafe_head_tx is only meaningful in sequencer mode; validators ignore it. + let unsafe_head_tx_opt = self.mode().is_sequencer().then_some(unsafe_head_tx); + + let actor = EngineActor::new( engine_client.clone(), self.config.clone(), - derivation_client, + QueuedEngineDerivationClient::new(derivation_actor_request_tx), engine, - self.mode().is_sequencer().then_some(unsafe_head_tx), + unsafe_head_tx_opt, + engine_request_rx, ); - let engine_rpc_processor = EngineRpcProcessor::new( - engine_client.clone(), - engine_client.rollup_boost.clone(), + let rpc_actor = EngineRpcActor::new( + engine_client, self.config.clone(), engine_state_rx, engine_queue_length_rx, + engine_rpc_request_rx, ); - Ok(EngineActor::new( - cancellation_token, - engine_request_rx, - engine_processor, - engine_rpc_processor, - )) + (actor, rpc_actor) } - /// Starts the rollup node service. - /// - /// The rollup node, in validator mode, listens to two sources of information to sync the L2 - /// chain: - /// - /// 1. The data availability layer, with a watcher that listens for new updates. L2 inputs (L2 - /// transaction batches + deposits) are then derived from the DA layer. - /// 2. The L2 sequencer, which produces unsafe L2 blocks and sends them to the network over p2p - /// gossip. - /// - /// From these two sources, the node imports `unsafe` blocks from the L2 sequencer, `safe` - /// blocks from the L2 derivation pipeline into the L2 execution layer via the Engine API, - /// and finalizes `safe` blocks that it has derived when L1 finalized block updates are - /// received. - /// - /// In sequencer mode, the node is responsible for producing unsafe L2 blocks and sending them - /// to the network over p2p gossip. The node also listens for L1 finalized block updates and - /// finalizes `safe` blocks that it has derived when L1 finalized block updates are - /// received. - pub async fn start(&self) -> Result<(), String> { - // Create a global cancellation token for graceful shutdown of tasks. - let cancellation = CancellationToken::new(); - - let (derivation_actor_request_tx, derivation_actor_request_rx) = mpsc::channel(1024); - - let (engine_actor_request_tx, engine_actor_request_rx) = mpsc::channel(1024); - let (unsafe_head_tx, unsafe_head_rx) = watch::channel(L2BlockInfo::default()); - - let engine_actor = self.create_engine_actor( - cancellation.clone(), - engine_actor_request_rx, - QueuedEngineDerivationClient::new(derivation_actor_request_tx.clone()), - unsafe_head_tx, - )?; - - // Select the concrete derivation actor implementation based on - // RollupNode configuration. - let derivation: ConfiguredDerivationActor = if let Some(provider) = - self.derivation_delegate_provider.clone() - { + /// Selects between the standard and delegate derivation actor implementations and constructs + /// the chosen one. + async fn build_derivation_actor( + &self, + engine_actor_request_tx: mpsc::Sender, + derivation_actor_request_rx: mpsc::Receiver, + ) -> ConfiguredDerivationActor { + if let Some(provider) = self.derivation_delegate_provider.clone() { // L1 Provider for sanity checking Derivation Delegation let l1_provider = AlloyChainProvider::new( self.l1_config.engine_provider.clone(), DERIVATION_PROVIDER_CACHE_SIZE, ); - ConfiguredDerivationActor::Delegate(Box::new(DelegateDerivationActor::<_>::new( - QueuedDerivationEngineClient { - engine_actor_request_tx: engine_actor_request_tx.clone(), - }, - cancellation.clone(), + ConfiguredDerivationActor::Delegate(Box::new(DelegateDerivationActor::new( + QueuedDerivationEngineClient { engine_actor_request_tx }, derivation_actor_request_rx, provider, l1_provider, ))) } else { ConfiguredDerivationActor::Normal(Box::new(DerivationActor::<_, OnlinePipeline>::new( - QueuedDerivationEngineClient { - engine_actor_request_tx: engine_actor_request_tx.clone(), - }, - cancellation.clone(), + QueuedDerivationEngineClient { engine_actor_request_tx }, derivation_actor_request_rx, self.create_pipeline().await, ))) - }; - - // Create the p2p actor. - let ( - NetworkInboundData { - signer, - p2p_rpc: network_rpc, - gossip_payload_tx, - admin_rpc: net_admin_rpc, - }, - network, - ) = NetworkActor::new( - QueuedNetworkEngineClient { engine_actor_request_tx: engine_actor_request_tx.clone() }, - cancellation.clone(), - self.network_builder(), - ); - - let (l1_head_updates_tx, l1_head_updates_rx) = watch::channel(None); - let delayed_l1_provider = DelayedL1OriginSelectorProvider::new( - self.l1_config.engine_provider.clone(), - l1_head_updates_rx, - self.sequencer_config.l1_conf_delay, - ); - - let delayed_origin_selector = - L1OriginSelector::new(self.config.clone(), delayed_l1_provider); - - // Conditionally add conductor if configured - let conductor = - self.sequencer_config.conductor_rpc_url.clone().map(ConductorClient::new_http); - - // Create the L1 Watcher actor - - // A channel to send queries about the state of L1. - let (l1_query_tx, l1_query_rx) = mpsc::channel(1024); + } + } + /// Builds the L1 watcher actor along with its head and finalized block streams. + /// + /// Unlike the other `build_*` helpers, this one returns `impl NodeActor` rather than a named + /// type alias: the block-stream type produced by [`BlockStream::new_as_stream`] is + /// `impl Stream`, so the resulting `L1WatcherActor` generic parameter cannot be written down. + /// Using `impl Trait` here is intentional; the macro consumer only requires `NodeActor`. + fn build_l1_watcher( + &self, + derivation_actor_request_tx: mpsc::Sender, + signer_tx: mpsc::Sender

, + l1_query_rx: mpsc::Receiver, + l1_head_updates_tx: watch::Sender>, + ) -> Result> + 'static, String> + { let head_stream = BlockStream::new_as_stream( self.l1_config.engine_provider.clone(), BlockNumberOrTag::Latest, @@ -342,82 +314,229 @@ impl RollupNode { Duration::from_secs(FINALIZED_STREAM_POLL_INTERVAL), )?; - // Create the [`L1WatcherActor`]. Previously known as the DA watcher actor. - let l1_watcher = L1WatcherActor::new( + Ok(L1WatcherActor::new( self.config.clone(), self.l1_config.engine_provider.clone(), l1_query_rx, - l1_head_updates_tx.clone(), + l1_head_updates_tx, QueuedL1WatcherDerivationClient { derivation_actor_request_tx }, - signer, - cancellation.clone(), + signer_tx, head_stream, finalized_stream, + )) + } + + /// Builds the sequencer actor when the node is in sequencer mode; otherwise returns `None`. + fn build_sequencer( + &self, + engine_actor_request_tx: mpsc::Sender, + gossip_payload_tx: mpsc::Sender, + unsafe_head_rx: watch::Receiver, + l1_head_updates_rx: watch::Receiver>, + sequencer_admin_api_rx: mpsc::Receiver, + ) -> Option { + if !self.mode().is_sequencer() { + return None; + } + + let delayed_l1_provider = DelayedL1OriginSelectorProvider::new( + self.l1_config.engine_provider.clone(), + l1_head_updates_rx, + self.sequencer_config.l1_conf_delay, ); + let delayed_origin_selector = + L1OriginSelector::new(self.config.clone(), delayed_l1_provider); - // Create the sequencer if needed - let (sequencer_actor, sequencer_admin_client) = if self.mode().is_sequencer() { - let sequencer_engine_client = QueuedSequencerEngineClient { - engine_actor_request_tx: engine_actor_request_tx.clone(), - unsafe_head_rx, - }; - - // Create the admin API channel - let (sequencer_admin_api_tx, sequencer_admin_api_rx) = mpsc::channel(1024); - let queued_gossip_client = - QueuedUnsafePayloadGossipClient::new(gossip_payload_tx.clone()); - - ( - Some(SequencerActor { - admin_api_rx: sequencer_admin_api_rx, - attributes_builder: self.create_attributes_builder(), - cancellation_token: cancellation.clone(), - conductor, - engine_client: sequencer_engine_client, - is_active: self.sequencer_config.sequencer_stopped.not(), - in_recovery_mode: self.sequencer_config.sequencer_recovery_mode, - origin_selector: delayed_origin_selector, - rollup_config: self.config.clone(), - unsafe_payload_gossip_client: queued_gossip_client, - }), - Some(QueuedSequencerAdminAPIClient::new(sequencer_admin_api_tx)), - ) - } else { - (None, None) + let conductor = + self.sequencer_config.conductor_rpc_url.clone().map(ConductorClient::new_http); + + let sequencer_engine_client = + QueuedSequencerEngineClient { engine_actor_request_tx, unsafe_head_rx }; + + let queued_gossip_client = QueuedUnsafePayloadGossipClient::new(gossip_payload_tx); + + Some(SequencerActor::new( + sequencer_admin_api_rx, + self.create_attributes_builder(), + conductor, + sequencer_engine_client, + self.sequencer_config.sequencer_stopped.not(), + self.sequencer_config.sequencer_recovery_mode, + delayed_origin_selector, + self.config.clone(), + queued_gossip_client, + )) + } + + /// Assembles the JSON-RPC module set, performs the initial server launch, and returns the + /// configured [`RpcActor`]. Returns `Ok(None)` when no [`RpcBuilder`] is configured. + async fn build_rpc_actor( + &self, + engine_rpc_request_tx: mpsc::Sender, + sequencer_admin_client: Option, + p2p_rpc_tx: mpsc::Sender, + network_admin_tx: mpsc::Sender, + l1_watcher_queries_tx: mpsc::Sender, + ) -> Result, String> { + let Some(config) = self.rpc_builder() else { + return Ok(None); }; - // Create the RPC server actor. - let rpc = self.rpc_builder().map(|b| { - RpcActor::new( - b, - QueuedEngineRpcClient::new(engine_actor_request_tx.clone()), - RollupBoostAdminApiClient { - engine_actor_request_tx: engine_actor_request_tx.clone(), - }, - RollupBoostHealthRpcClient { - engine_actor_request_tx: engine_actor_request_tx.clone(), - }, + let engine_rpc_client = QueuedEngineRpcClient::new(engine_rpc_request_tx); + + let mut modules = RpcModule::new(()); + modules + .merge(HealthzApiServer::into_rpc(HealthzRpc {})) + .map_err(|e| format!("Failed to register healthz module: {e:?}"))?; + modules + .merge(P2pRpc::new(p2p_rpc_tx).into_rpc()) + .map_err(|e| format!("Failed to register p2p module: {e:?}"))?; + modules + .merge(AdminRpc::new(sequencer_admin_client, network_admin_tx).into_rpc()) + .map_err(|e| format!("Failed to register admin module: {e:?}"))?; + modules + .merge(RollupRpc::new(engine_rpc_client.clone(), l1_watcher_queries_tx).into_rpc()) + .map_err(|e| format!("Failed to register rollup module: {e:?}"))?; + if config.dev_enabled() { + modules + .merge(DevEngineRpc::new(engine_rpc_client.clone()).into_rpc()) + .map_err(|e| format!("Failed to register dev engine module: {e:?}"))?; + } + if config.ws_enabled() { + modules + .merge(WsRPC::new(engine_rpc_client.clone()).into_rpc()) + .map_err(|e| format!("Failed to register ws module: {e:?}"))?; + } + + let restarts_remaining = config.restart_count(); + let launcher = JsonrpseeServerLauncher::new(config); + let handle = launcher + .launch(modules.clone()) + .await + .map_err(|e: std::io::Error| format!("Failed to launch rpc server: {e:?}"))?; + + Ok(Some(RpcActor::new(launcher, modules, handle, restarts_remaining))) + } + + /// Starts the rollup node service. + /// + /// The rollup node, in validator mode, listens to two sources of information to sync the L2 + /// chain: + /// + /// 1. The data availability layer, with a watcher that listens for new updates. L2 inputs (L2 + /// transaction batches + deposits) are then derived from the DA layer. + /// 2. The L2 sequencer, which produces unsafe L2 blocks and sends them to the network over p2p + /// gossip. + /// + /// From these two sources, the node imports `unsafe` blocks from the L2 sequencer, `safe` + /// blocks from the L2 derivation pipeline into the L2 execution layer via the Engine API, + /// and finalizes `safe` blocks that it has derived when L1 finalized block updates are + /// received. + /// + /// In sequencer mode, the node is responsible for producing unsafe L2 blocks and sending them + /// to the network over p2p gossip. The node also listens for L1 finalized block updates and + /// finalizes `safe` blocks that it has derived when L1 finalized block updates are + /// received. + /// + /// ## Shutdown + /// + /// Shutdown is unordered: when any actor exits (success, error, or panic) or an OS signal is + /// received, the umbrella cancellation token fires and all peer actors observe it on their + /// next `select!`. Actors may log channel-closed errors while peers are torn down + /// concurrently; this is expected and not a sign of an unclean exit. + pub async fn start(&self) -> Result<(), String> { + // Single umbrella cancellation token owned by the spawn_and_wait! macro. + let cancellation = CancellationToken::new(); + + // ─── cross-actor channels ─────────────────────────────────────────────────────────── + // actor request channels + let (derivation_actor_request_tx, derivation_actor_request_rx) = + mpsc::channel::(1024); + let (engine_actor_request_tx, engine_actor_request_rx) = + mpsc::channel::(1024); + let (engine_rpc_request_tx, engine_rpc_request_rx) = + mpsc::channel::(1024); + let (l1_query_tx, l1_query_rx) = mpsc::channel::(1024); + let (sequencer_admin_api_tx, sequencer_admin_api_rx) = mpsc::channel(1024); + // Network actor inbound channels + let (signer_tx, signer_rx) = mpsc::channel::
(16); + let (p2p_rpc_tx, p2p_rpc_rx) = mpsc::channel::(1024); + let (network_admin_tx, network_admin_rx) = mpsc::channel::(1024); + let (gossip_payload_tx, gossip_payload_rx) = + mpsc::channel::(256); + // watch channels + let (unsafe_head_tx, unsafe_head_rx) = watch::channel(L2BlockInfo::default()); + let (l1_head_updates_tx, l1_head_updates_rx) = watch::channel::>(None); + + // ─── actor construction ───────────────────────────────────────────────────────────── + let (engine_actor, engine_rpc_actor) = self.build_engine_actors( + engine_actor_request_rx, + engine_rpc_request_rx, + derivation_actor_request_tx.clone(), + unsafe_head_tx, + ); + + let derivation = self + .build_derivation_actor(engine_actor_request_tx.clone(), derivation_actor_request_rx) + .await; + + // Build and start the libp2p swarm upstream of `NetworkActor::new` so the constructor + // stays sync. + let handler: NetworkHandler = self + .network_builder() + .build() + .map_err(|e| format!("Failed to build network: {e:?}"))? + .start() + .await + .map_err(|e| format!("Failed to start network: {e:?}"))?; + + let network = NetworkActor::new( + QueuedNetworkEngineClient { engine_actor_request_tx: engine_actor_request_tx.clone() }, + handler, + signer_rx, + p2p_rpc_rx, + network_admin_rx, + gossip_payload_rx, + ); + + let l1_watcher = self.build_l1_watcher( + derivation_actor_request_tx, + signer_tx, + l1_query_rx, + l1_head_updates_tx, + )?; + + let sequencer_actor = self.build_sequencer( + engine_actor_request_tx, + gossip_payload_tx, + unsafe_head_rx, + l1_head_updates_rx, + sequencer_admin_api_rx, + ); + let sequencer_admin_client = sequencer_actor + .is_some() + .then(|| QueuedSequencerAdminAPIClient::new(sequencer_admin_api_tx)); + + let rpc = self + .build_rpc_actor( + engine_rpc_request_tx, sequencer_admin_client, + p2p_rpc_tx, + network_admin_tx, + l1_query_tx, ) - }); + .await?; crate::service::spawn_and_wait!( cancellation, actors = [ - rpc.map(|r| ( - r, - RpcContext { - cancellation: cancellation.clone(), - p2p_network: network_rpc, - network_admin: net_admin_rpc, - l1_watcher_queries: l1_query_tx, - } - )), - sequencer_actor.map(|s| (s, ())), - Some((network, ())), - Some((l1_watcher, ())), - Some((derivation, ())), - Some((engine_actor, ())), + rpc, + sequencer_actor, + Some(network), + Some(l1_watcher), + Some(derivation), + Some(engine_actor), + Some(engine_rpc_actor), ] ); Ok(()) diff --git a/rust/kona/crates/node/service/src/service/util.rs b/rust/kona/crates/node/service/src/service/util.rs index 3a6ab08064d..ec33967ee47 100644 --- a/rust/kona/crates/node/service/src/service/util.rs +++ b/rust/kona/crates/node/service/src/service/util.rs @@ -4,31 +4,37 @@ /// if any of them fail. The type of the error in the [`NodeActor`](crate::NodeActor)s is erased to /// avoid having to specify a common error type between actors. /// -/// Actors are passed in as optional arguments, in case a given actor is not needed. +/// Actors are passed in as `Option`. Each actor's [`step`](crate::NodeActor::step) method is +/// called in a loop, with external cancellation via the provided +/// [`CancellationToken`](tokio_util::sync::CancellationToken). /// /// This macro also handles OS shutdown signals (SIGTERM, SIGINT) and triggers graceful shutdown /// when received. macro_rules! spawn_and_wait { - ($cancellation:expr, actors = [$($actor:expr$(,)?)*]) => { + ($cancellation:expr, actors = [$($actor:expr),* $(,)?]) => { let mut task_handles = tokio::task::JoinSet::new(); - // Check if the actor is present, and spawn it if it is. $( - if let Some((actor, context)) = $actor { + if let Some(mut actor) = $actor { let cancellation = $cancellation.clone(); task_handles.spawn(async move { - // This guard ensures that the cancellation token is cancelled when the actor is - // dropped. This ensures that the actor is properly shut down. - // Note the underscore prefix: this is to signal that we don't use the guard anywhere, but - // *the compiler shouldn't optimize it away*. - // Note that using a simple `_` would not work here because it gets optimized away in - // release mode. - let _guard = cancellation.drop_guard(); - - if let Err(e) = actor.start(context).await { - return Err(format!("{e:?}")); + // This guard ensures that the cancellation token is cancelled when the actor + // task exits for any reason. This ensures peer actors observe shutdown on + // their next macro-level `select!`. + // Note the underscore prefix: this is to signal that we don't use the guard + // anywhere, but *the compiler shouldn't optimize it away*. Note that using a + // simple `_` would not work here because it gets optimized away in release + // mode. + let _guard = cancellation.clone().drop_guard(); + loop { + tokio::select! { + biased; + _ = cancellation.cancelled() => return Ok(()), + result = actor.step() => { + result.map_err(|e| format!("{e:?}"))?; + } + } } - Ok(()) }); } )* diff --git a/rust/kona/crates/node/service/tests/actors/network/mocks/builder.rs b/rust/kona/crates/node/service/tests/actors/network/mocks/builder.rs index 55e5b84a30f..af21022e97d 100644 --- a/rust/kona/crates/node/service/tests/actors/network/mocks/builder.rs +++ b/rust/kona/crates/node/service/tests/actors/network/mocks/builder.rs @@ -18,7 +18,6 @@ use libp2p::{Multiaddr, identity::Keypair, multiaddr::Protocol}; use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; use rand::RngCore; use tokio::sync::mpsc; -use tokio_util::sync::CancellationToken; use tracing::error; pub(crate) struct TestNetworkBuilder { @@ -59,7 +58,7 @@ impl TestNetworkBuilder { /// Minimal network configuration. /// Only allows loopback addresses in the discovery table. - pub(crate) fn build(&mut self, bootnodes: Vec) -> TestNetwork { + pub(crate) async fn build(&mut self, bootnodes: Vec) -> TestNetwork { let keypair = self.custom_keypair.take().unwrap_or_else(Keypair::generate_secp256k1); let secp256k1_key = keypair.clone().try_into_secp256k1() @@ -97,16 +96,33 @@ impl TestNetworkBuilder { ) .with_bootnodes(bootnodes.into_iter().map(Into::into).collect::>().into()); + let handler = builder.build().expect("build network").start().await.expect("start network"); + let (blocks_tx, blocks_rx) = mpsc::channel(1024); - let (inbound_data, actor) = NetworkActor::new( + let (signer_tx, signer_rx) = mpsc::channel(16); + let (p2p_rpc_tx, p2p_rpc_rx) = mpsc::channel(1024); + let (admin_rpc_tx, admin_rpc_rx) = mpsc::channel(1024); + let (gossip_payload_tx, gossip_payload_rx) = mpsc::channel(256); + + let mut actor = NetworkActor::new( ForwardingNetworkEngineClient { blocks_tx }, - CancellationToken::new(), - builder, + handler, + signer_rx, + p2p_rpc_rx, + admin_rpc_rx, + gossip_payload_rx, ); - let handle = tokio::spawn(async move { actor.start(()).await }); + let handle = tokio::spawn(async move { + loop { + actor.step().await?; + } + // Unreachable: the loop above only exits via Err. + #[allow(unreachable_code)] + Ok(()) + }); - TestNetwork { inbound_data, blocks_rx, handle } + TestNetwork { signer_tx, p2p_rpc_tx, admin_rpc_tx, gossip_payload_tx, blocks_rx, handle } } } diff --git a/rust/kona/crates/node/service/tests/actors/network/mocks/mod.rs b/rust/kona/crates/node/service/tests/actors/network/mocks/mod.rs index 9394205f9ea..a6be9ef846d 100644 --- a/rust/kona/crates/node/service/tests/actors/network/mocks/mod.rs +++ b/rust/kona/crates/node/service/tests/actors/network/mocks/mod.rs @@ -2,10 +2,12 @@ use std::{str::FromStr, time::Duration}; +use alloy_primitives::Address; use backon::{ExponentialBuilder, Retryable}; use discv5::Enr; use kona_gossip::{P2pRpcRequest, PeerDump, PeerInfo}; -use kona_node_service::{NetworkActorError, NetworkInboundData}; +use kona_node_service::NetworkActorError; +use kona_rpc::NetworkAdminQuery; use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelope; use tokio::{ sync::{mpsc, oneshot}, @@ -15,7 +17,12 @@ use tokio::{ pub(crate) mod builder; pub(crate) struct TestNetwork { - pub(super) inbound_data: NetworkInboundData, + #[allow(dead_code)] + pub(super) signer_tx: mpsc::Sender
, + pub(super) p2p_rpc_tx: mpsc::Sender, + #[allow(dead_code)] + pub(super) admin_rpc_tx: mpsc::Sender, + pub(super) gossip_payload_tx: mpsc::Sender, pub(super) blocks_rx: mpsc::Receiver, #[allow(dead_code)] handle: JoinHandle>, @@ -40,8 +47,7 @@ impl TestNetwork { // Try to get the peer info. Send a peer info request to the network actor. let (peer_info_tx, peer_info_rx) = oneshot::channel(); let peer_info_request = P2pRpcRequest::PeerInfo(peer_info_tx); - self.inbound_data - .p2p_rpc + self.p2p_rpc_tx .send(peer_info_request) .await .map_err(|_| TestNetworkError::P2pReceiverClosed)?; @@ -54,8 +60,7 @@ impl TestNetwork { pub(super) async fn peers(&self) -> Result { let (peers_tx, peers_rx) = oneshot::channel(); let peers_request = P2pRpcRequest::Peers { out: peers_tx, connected: true }; - self.inbound_data - .p2p_rpc + self.p2p_rpc_tx .send(peers_request) .await .map_err(|_| TestNetworkError::P2pReceiverClosed)?; diff --git a/rust/kona/crates/node/service/tests/actors/network/p2p.rs b/rust/kona/crates/node/service/tests/actors/network/p2p.rs index 768bdb3d5ad..4fbe8ff008c 100644 --- a/rust/kona/crates/node/service/tests/actors/network/p2p.rs +++ b/rust/kona/crates/node/service/tests/actors/network/p2p.rs @@ -3,10 +3,10 @@ use crate::actors::network::mocks::builder::TestNetworkBuilder; #[tokio::test(flavor = "multi_thread")] async fn test_p2p_network_conn() -> anyhow::Result<()> { let mut builder = TestNetworkBuilder::new(); - let network_1 = builder.build(vec![]); + let network_1 = builder.build(vec![]).await; let enr_1 = network_1.peer_enr().await?; - let network_2 = builder.build(vec![enr_1]); + let network_2 = builder.build(vec![enr_1]).await; network_2.is_connected_to_with_retries(&network_1).await?; @@ -24,7 +24,7 @@ async fn test_large_network_conn() -> anyhow::Result<()> { let (mut networks, mut bootnodes) = (vec![], vec![]); for _ in 0..NETWORKS { - let network = builder.build(bootnodes.clone()); + let network = builder.build(bootnodes.clone()).await; let enr = network.peer_enr().await?; networks.push(network); bootnodes.push(enr); diff --git a/rust/kona/crates/node/service/tests/actors/network/sequencer.rs b/rust/kona/crates/node/service/tests/actors/network/sequencer.rs index 7e868eac45c..7b1b0245e99 100644 --- a/rust/kona/crates/node/service/tests/actors/network/sequencer.rs +++ b/rust/kona/crates/node/service/tests/actors/network/sequencer.rs @@ -8,10 +8,10 @@ use crate::actors::{ async fn test_sequencer_network_conn() -> anyhow::Result<()> { let mut builder = TestNetworkBuilder::new().set_sequencer(); - let sequencer_network = builder.build(vec![]); + let sequencer_network = builder.build(vec![]).await; let enr_1 = sequencer_network.peer_enr().await?; - let mut validator_network = builder.build(vec![enr_1]); + let mut validator_network = builder.build(vec![enr_1]).await; sequencer_network.is_connected_to_with_retries(&validator_network).await?; @@ -21,7 +21,7 @@ async fn test_sequencer_network_conn() -> anyhow::Result<()> { let envelope = seed_generator.random_valid_payload(PayloadVersion::V1)?; - sequencer_network.inbound_data.gossip_payload_tx.send(envelope.clone()).await?; + sequencer_network.gossip_payload_tx.send(envelope.clone()).await?; let block = validator_network .blocks_rx @@ -45,13 +45,13 @@ async fn test_sequencer_network_propagation() -> anyhow::Result<()> { let mut builder = TestNetworkBuilder::new().set_sequencer(); - let sequencer_network = builder.build(vec![]); + let sequencer_network = builder.build(vec![]).await; let mut previous_enrs = vec![sequencer_network.peer_enr().await?]; let mut validator_networks = Vec::new(); for _ in 0..NETWORKS { - let network = builder.build(previous_enrs.clone()); + let network = builder.build(previous_enrs.clone()).await; previous_enrs.push(network.peer_enr().await?); validator_networks.push(network); @@ -67,7 +67,7 @@ async fn test_sequencer_network_propagation() -> anyhow::Result<()> { let envelope = seed_generator.random_valid_payload(PayloadVersion::V1)?; - sequencer_network.inbound_data.gossip_payload_tx.send(envelope.clone()).await?; + sequencer_network.gossip_payload_tx.send(envelope.clone()).await?; // Check that the block propagates to all networks. for network in &mut validator_networks { diff --git a/rust/kona/crates/node/service/tests/rollup_boost_missing_jwt.rs b/rust/kona/crates/node/service/tests/rollup_boost_missing_jwt.rs deleted file mode 100644 index ba2a06032e2..00000000000 --- a/rust/kona/crates/node/service/tests/rollup_boost_missing_jwt.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Reproduces panic: "failed to create rollup boost server: Missing Client JWT secret" when -//! constructing rollup-boost without an L2 client JWT provided. - -#[cfg(test)] -mod tests { - use http::Uri; - use rollup_boost::{ - ExecutionMode, FlashblocksWebsocketConfig, FlashblocksWsArgs, Probes, RollupBoostLibArgs, - RollupBoostServer, - }; - use std::sync::Arc; - - #[test] - fn repro_missing_client_jwt_secret() { - // Build args with execution enabled and flashblocks enabled but NO L2 JWT provided. - // This mirrors the failing acceptance configuration when no client JWT is wired through. - let args = RollupBoostLibArgs { - builder: rollup_boost::BuilderArgs { - // Any URI; builder may be disabled at runtime, but server creation still validates - // args. - builder_url: "http://127.0.0.1:8551".parse::().unwrap(), - builder_jwt_token: None, - builder_jwt_path: None, // intentionally missing - builder_timeout: 1000, - }, - l2_client: rollup_boost::L2ClientArgs { - l2_url: "http://127.0.0.1:8551".parse::().unwrap(), - l2_jwt_token: None, - l2_jwt_path: None, /* intentionally missing -> triggers the panic/error in server - * ctor */ - l2_timeout: 1000, - }, - // Default is ExecutionMode::Enabled in the crate; rely on that or set explicitly if - // needed. - flashblocks_ws: Some(FlashblocksWsArgs { - flashblocks_ws: true, - flashblocks_builder_url: "ws://127.0.0.1:1111".parse().unwrap(), - flashblocks_host: "127.0.0.1".to_string(), - flashblocks_port: 1112, - flashblocks_ws_config: FlashblocksWebsocketConfig { - flashblock_builder_ws_initial_reconnect_ms: 5000, - flashblock_builder_ws_max_reconnect_ms: 5000, - flashblock_builder_ws_connect_timeout_ms: 5000, - flashblock_builder_ws_ping_interval_ms: 500, - flashblock_builder_ws_pong_timeout_ms: 1500, - }, - }), - flashblocks_p2p: None, - block_selection_policy: None, - execution_mode: ExecutionMode::Enabled, - external_state_root: false, - ignore_unhealthy_builders: false, - health_check_interval: 10, - max_unsafe_interval: 10, - }; - - let probes = Arc::new(Probes::default()); - let err = RollupBoostServer::new_from_args(args, probes) - .expect_err("expected missing JWT to error"); - let msg = format!("{err}"); - assert!( - msg.to_lowercase().contains("missing client jwt secret"), - "unexpected error: {msg}" - ); - } -} diff --git a/rust/kona/crates/proof/driver/src/core.rs b/rust/kona/crates/proof/driver/src/core.rs index 4aef06f0af8..0754a17712b 100644 --- a/rust/kona/crates/proof/driver/src/core.rs +++ b/rust/kona/crates/proof/driver/src/core.rs @@ -240,7 +240,8 @@ where // If we are in interop mode, this error must be handled by the caller. // Otherwise, we continue the loop to halt derivation on the next iteration. - if cfg.is_interop_active(self.cursor.read().l2_safe_head().block_info.number) { + if cfg.is_interop_active(self.cursor.read().l2_safe_head().block_info.timestamp) + { return Err(PipelineError::EndOfSource.crit().into()); } continue; diff --git a/rust/kona/crates/proof/driver/src/cursor.rs b/rust/kona/crates/proof/driver/src/cursor.rs index 31406d6ac20..bea7bdf6a62 100644 --- a/rust/kona/crates/proof/driver/src/cursor.rs +++ b/rust/kona/crates/proof/driver/src/cursor.rs @@ -165,6 +165,7 @@ impl PipelineCursor { if self.tips.len() >= self.capacity { let key = self.origins.pop_front().unwrap(); self.tips.remove(&key); + self.origin_infos.remove(&key); } self.origin = origin; @@ -227,3 +228,51 @@ impl PipelineCursor { } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn mock_block_info(number: u64) -> BlockInfo { + BlockInfo { number, hash: B256::ZERO, parent_hash: B256::ZERO, timestamp: number } + } + + fn mock_tip() -> TipCursor { + TipCursor::new( + L2BlockInfo::default(), + Sealed::new_unchecked(Header::default(), B256::ZERO), + B256::ZERO, + ) + } + + #[test] + fn advance_evicts_origin_infos_when_full() { + let channel_timeout = 2; + let origin = mock_block_info(0); + let mut cursor = PipelineCursor::new(channel_timeout, origin); + let capacity = cursor.capacity; // channel_timeout + 5 = 7 + + // Advance well past capacity to trigger many evictions. + let total_advances = capacity * 3; + for i in 1..=total_advances { + cursor.advance(mock_block_info(i as u64), mock_tip()); + } + + // origin_infos must be bounded. Without the fix, it would contain + // total_advances + 1 entries (one from new() plus one per advance). + // With the fix, evicted keys are removed from origin_infos too. + assert!( + cursor.origin_infos.len() <= capacity + 1, + "origin_infos has {} entries but should be bounded near capacity ({}); \ + entries are not being evicted", + cursor.origin_infos.len(), + capacity, + ); + + // Verify that old block numbers were actually removed. + assert!( + !cursor.origin_infos.contains_key(&1), + "origin_infos must evict old entries to prevent unbounded memory growth" + ); + } +} diff --git a/rust/kona/crates/proof/driver/src/pipeline.rs b/rust/kona/crates/proof/driver/src/pipeline.rs index 40c8fe1ec59..0b457b45f4f 100644 --- a/rust/kona/crates/proof/driver/src/pipeline.rs +++ b/rust/kona/crates/proof/driver/src/pipeline.rs @@ -9,7 +9,7 @@ use async_trait::async_trait; use kona_protocol::{L2BlockInfo, OpAttributesWithParent}; use kona_derive::{ - ActivationSignal, Pipeline, PipelineError, PipelineErrorKind, ResetError, ResetSignal, + ActivationSignal, Pipeline, PipelineError, PipelineErrorKind, ResetError, ResetSignal, Signal, SignalReceiver, StepResult, }; @@ -114,41 +114,17 @@ where } PipelineErrorKind::Reset(e) => { warn!(target: "client_derivation_driver", "Failed to step derivation pipeline due to reset: {:?}", e); - let system_config = self - .system_config_by_number(l2_safe_head.block_info.number) - .await?; if matches!(e, ResetError::HoloceneActivation) { - let l1_origin = - self.origin().ok_or(PipelineError::MissingOrigin.crit())?; - self.signal( - ActivationSignal { - l2_safe_head, - l1_origin, - system_config: Some(system_config), - } - .signal(), - ) - .await?; + self.signal(Signal::Activation(ActivationSignal { l2_safe_head })) + .await?; } else { // Flushes cache if a reorg is detected. if matches!(e, ResetError::ReorgDetected(_, _)) { self.flush(); } - // Reset the pipeline to the initial L2 safe head and L1 origin, - // and try again. - let l1_origin = - self.origin().ok_or(PipelineError::MissingOrigin.crit())?; - self.signal( - ResetSignal { - l2_safe_head, - l1_origin, - system_config: Some(system_config), - } - .signal(), - ) - .await?; + self.signal(Signal::Reset(ResetSignal { l2_safe_head })).await?; } } PipelineErrorKind::Critical(_) => { diff --git a/rust/kona/crates/proof/executor/Cargo.toml b/rust/kona/crates/proof/executor/Cargo.toml index b7903687695..e02e1a24da7 100644 --- a/rust/kona/crates/proof/executor/Cargo.toml +++ b/rust/kona/crates/proof/executor/Cargo.toml @@ -56,6 +56,23 @@ alloy-rpc-client = { workspace = true, optional = true } alloy-transport = { workspace = true, optional = true } alloy-transport-http = { workspace = true, optional = true } +# Mirrors the optional `test-utils` feature deps so `cargo test -p kona-executor` +# compiles the `test_utils` module (and the post-exec unit tests in `core`) without +# requiring `--features test-utils`. +[dev-dependencies] +alloy-provider = { workspace = true, features = ["reqwest"] } +alloy-rpc-client.workspace = true +alloy-rpc-types-engine.workspace = true +alloy-transport.workspace = true +alloy-transport-http.workspace = true +kona-registry.workspace = true +rocksdb = { workspace = true, features = ["snappy"] } +rstest.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +tempfile.workspace = true +tokio = { workspace = true, features = ["full"] } + [features] test-utils = [ "dep:alloy-provider", diff --git a/rust/kona/crates/proof/executor/src/builder/assemble.rs b/rust/kona/crates/proof/executor/src/builder/assemble.rs index 29e22684e11..6f30f0f5618 100644 --- a/rust/kona/crates/proof/executor/src/builder/assemble.rs +++ b/rust/kona/crates/proof/executor/src/builder/assemble.rs @@ -48,7 +48,7 @@ where .root(); let receipts_root = compute_receipts_root(&ex_result.receipts, self.config, timestamp); let withdrawals_root = if self.config.is_isthmus_active(timestamp) { - Some(self.message_passer_account(block_env.number.saturating_to::())?) + Some(self.message_passer_account()?) } else if self.config.is_canyon_active(timestamp) { Some(EMPTY_ROOT_HASH) } else { @@ -109,6 +109,8 @@ where excess_blob_gas: excess_blob_gas.and_then(|x| x.try_into().ok()), parent_beacon_block_root: attrs.payload_attributes.parent_beacon_block_root, extra_data: encoded_base_fee_params, + block_access_list_hash: Default::default(), + slot_number: Default::default(), } .seal_slow(); @@ -133,7 +135,7 @@ where "Computing output root", ); - let storage_root = self.message_passer_account(parent_number)?; + let storage_root = self.message_passer_account()?; let parent_header = self.trie_db.parent_block_header(); // Construct the raw output and hash it. @@ -153,12 +155,12 @@ where } /// Fetches the L2 to L1 message passer account from the cache or underlying trie. - fn message_passer_account(&mut self, block_number: u64) -> Result { + fn message_passer_account(&mut self) -> Result { match self.trie_db.storage_roots().get(&Predeploys::L2_TO_L1_MESSAGE_PASSER) { Some(storage_root) => Ok(storage_root.blind()), None => Ok(self .trie_db - .get_trie_account(&Predeploys::L2_TO_L1_MESSAGE_PASSER, block_number)? + .get_trie_account(&Predeploys::L2_TO_L1_MESSAGE_PASSER)? .ok_or(TrieDBError::MissingAccountInfo)? .storage_root), } diff --git a/rust/kona/crates/proof/executor/src/builder/core.rs b/rust/kona/crates/proof/executor/src/builder/core.rs index dc141dee1e1..6849959c202 100644 --- a/rust/kona/crates/proof/executor/src/builder/core.rs +++ b/rust/kona/crates/proof/executor/src/builder/core.rs @@ -8,17 +8,19 @@ use crate::{ExecutorError, ExecutorResult, TrieDB, TrieDBError, TrieDBProvider}; use alloc::{string::ToString, vec::Vec}; use alloy_consensus::{Header, Sealed, crypto::RecoveryError}; use alloy_evm::{ - EvmFactory, FromRecoveredTx, FromTxWithEncoded, + EvmFactory, FromRecoveredTx, FromTxWithEncoded, RecoveredTx, block::{BlockExecutionResult, BlockExecutor, BlockExecutorFactory}, }; use alloy_op_evm::{ - OpBlockExecutionCtx, OpBlockExecutorFactory, + OpBlockExecutionCtx, OpBlockExecutorFactory, PostExecMode, block::{OpAlloyReceiptBuilder, OpTxEnv}, }; use core::fmt::Debug; use kona_genesis::RollupConfig; use kona_mpt::TrieHinter; -use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope}; +use op_alloy_consensus::{ + OpReceiptEnvelope, OpTxEnvelope, parse_post_exec_payload_from_transactions, +}; use op_alloy_rpc_types_engine::OpPayloadAttributes; use op_revm::OpSpecId; use revm::{ @@ -97,6 +99,9 @@ where /// understand OP-specific transaction types, system calls, and state /// management required for proper L2 block execution. pub(crate) factory: OpBlockExecutorFactory, + /// Test-only override for SDM activation while the fork is not yet scheduled. + #[cfg(any(test, feature = "test-utils"))] + pub(crate) sdm_active_override: Option, } impl<'a, P, H, Evm> StatelessL2Builder<'a, P, H, Evm> @@ -106,6 +111,12 @@ where Evm: EvmFactory + 'static, ::Tx: FromTxWithEncoded + FromRecoveredTx + OpTxEnv, + OpBlockExecutorFactory: for<'b> BlockExecutorFactory< + EvmFactory = Evm, + ExecutionCtx<'b> = OpBlockExecutionCtx, + Transaction = OpTxEnvelope, + Receipt = OpReceiptEnvelope, + >, { /// Creates a new stateless L2 block builder instance. /// @@ -145,7 +156,29 @@ where config.clone(), evm_factory, ); - Self { config, trie_db, factory } + Self { + config, + trie_db, + factory, + #[cfg(any(test, feature = "test-utils"))] + sdm_active_override: None, + } + } + + /// Overrides SDM activation for tests and fixture tooling. + #[cfg(any(test, feature = "test-utils"))] + pub const fn set_sdm_active_override(&mut self, sdm_active_override: Option) { + self.sdm_active_override = sdm_active_override; + } + + /// Returns whether SDM is active at the given timestamp. + const fn is_sdm_active(&self, timestamp: u64) -> bool { + #[cfg(any(test, feature = "test-utils"))] + if let Some(active) = self.sdm_active_override { + return active; + } + + self.config.is_sdm_active(timestamp) } /// Builds and executes a new L2 block using the provided payload attributes. @@ -247,26 +280,36 @@ where "Beginning block building." ); + // Compute SDM activation before borrowing `self.trie_db` mutably below. + let sdm_active = self.is_sdm_active(block_env.timestamp.saturating_to()); + // Step 2. Create the executor, using the trie database. - let mut state = State::builder() - .with_database(&mut self.trie_db) - .with_bundle_update() - .without_state_clear() - .build(); + let mut state = + State::builder().with_database(&mut self.trie_db).with_bundle_update().build(); let evm = self.factory.evm_factory().create_evm(&mut state, evm_env); + // Step 3. Decode and validate the block transactions within the payload attributes. + let transactions = attrs + .recovered_transactions_with_encoded() + .collect::, RecoveryError>>() + .map_err(ExecutorError::Recovery)?; + let post_exec_mode = parse_post_exec_payload_from_transactions( + transactions.iter().map(RecoveredTx::tx), + block_env.number.saturating_to(), + sdm_active, + ) + .map_err(|err| ExecutorError::InvalidPostExecPayload(err.into_string()))? + .map(|parsed| PostExecMode::Verify(parsed.payload)) + .unwrap_or_default(); + let ctx = OpBlockExecutionCtx { parent_hash, parent_beacon_block_root: attrs.payload_attributes.parent_beacon_block_root, // This field is unused for individual block building jobs. extra_data: Default::default(), + post_exec_mode, }; let executor = self.factory.create_executor(evm, ctx); - // Step 3. Execute the block containing the transactions within the payload attributes. - let transactions = attrs - .recovered_transactions_with_encoded() - .collect::, RecoveryError>>() - .map_err(ExecutorError::Recovery)?; let ex_result = executor.execute_block(transactions.iter())?; info!( @@ -316,18 +359,4 @@ impl From<(Sealed
, BlockExecutionResult)> for BlockBu } #[cfg(test)] -mod test { - use crate::test_utils::run_test_fixture; - use rstest::rstest; - use std::path::PathBuf; - - #[rstest] - #[tokio::test] - async fn test_statelessly_execute_block( - #[base_dir = "./testdata"] - #[files("*.tar.gz")] - path: PathBuf, - ) { - run_test_fixture(path).await; - } -} +mod tests; diff --git a/rust/kona/crates/proof/executor/src/builder/core/tests.rs b/rust/kona/crates/proof/executor/src/builder/core/tests.rs new file mode 100644 index 00000000000..ddff4a1d4f9 --- /dev/null +++ b/rust/kona/crates/proof/executor/src/builder/core/tests.rs @@ -0,0 +1,217 @@ +use crate::{ + ExecutorError, + test_utils::{execute_loaded_fixture, load_test_fixture, run_test_fixture}, +}; +use alloy_consensus::Header; +use alloy_eips::Encodable2718; +use op_alloy_consensus::{OpReceiptEnvelope, SDMGasEntry, build_post_exec_tx}; +use rstest::rstest; +use std::path::PathBuf; + +/// Path to the fixture used by all post-exec tests. +/// +/// The chosen fixture must contain a regular (non-deposit, non-post-exec) tx at index 1, since +/// several tests target that index when constructing payload entries. +fn post_exec_fixture_path() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/block-26207960.tar.gz") +} + +fn fixture_block_number(parent_header: &Header) -> u64 { + parent_header.number + 1 +} + +fn append_post_exec_tx( + transactions: &mut Vec, + block_number: u64, + gas_refund_entries: Vec, +) { + let tx = build_post_exec_tx(block_number, gas_refund_entries); + let mut encoded = Vec::with_capacity(tx.eip2718_encoded_length()); + tx.encode_2718(&mut encoded); + transactions.push(encoded.into()); +} + +/// Asserts that `err` is a post-exec validation failure containing `expected`. +/// +/// Matches both the parser-level [`ExecutorError::InvalidPostExecPayload`] and the +/// execution-level `OpBlockExecutionError::InvalidPostExecPayload` wrapped in +/// [`ExecutorError::ExecutionError`], since both render with the substring +/// `"invalid post-exec payload"`. +fn assert_post_exec_validation_failure(err: ExecutorError, expected: &str) { + let err = err.to_string(); + assert!(err.to_lowercase().contains("invalid post-exec payload"), "unexpected error: {err}"); + assert!(err.contains(expected), "expected {err:?} to contain {expected:?}"); +} + +#[rstest] +#[tokio::test] +async fn test_statelessly_execute_block( + #[base_dir = "./testdata"] + #[files("*.tar.gz")] + path: PathBuf, +) { + run_test_fixture(path).await; +} + +/// Verifies the default fallthrough: with no override, [`StatelessL2Builder`] consults the +/// rollup config, where SDM is currently unscheduled and reports inactive. +#[tokio::test] +async fn post_exec_sdm_inherit_rejects_post_exec_tx() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number, + Vec::new(), + ); + + let err = execute_loaded_fixture(loaded, None).unwrap_err(); + assert_post_exec_validation_failure(err, "SDM not active"); +} + +/// Verifies the explicit-override deactivation path. Pairs with +/// [`post_exec_sdm_inherit_rejects_post_exec_tx`] above, which exercises the inherit branch. +#[tokio::test] +async fn post_exec_sdm_forced_inactive_rejects_appended_post_exec_tx() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number, + Vec::new(), + ); + + let err = execute_loaded_fixture(loaded, Some(false)).unwrap_err(); + assert_post_exec_validation_failure(err, "SDM not active"); +} + +#[tokio::test] +async fn post_exec_sdm_enabled_rejects_wrong_block_number() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number + 1, + Vec::new(), + ); + + let err = execute_loaded_fixture(loaded, Some(true)).unwrap_err(); + assert_post_exec_validation_failure(err, "does not match block number"); +} + +#[tokio::test] +async fn post_exec_sdm_enabled_rejects_duplicate_post_exec_txs() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + let transactions = loaded.fixture.executing_payload.transactions.as_mut().unwrap(); + append_post_exec_tx(transactions, block_number, Vec::new()); + append_post_exec_tx(transactions, block_number, Vec::new()); + + let err = execute_loaded_fixture(loaded, Some(true)).unwrap_err(); + assert_post_exec_validation_failure(err, "multiple post-exec transactions"); +} + +#[tokio::test] +async fn post_exec_valid_empty_payload_executes_without_state_or_gas_change() { + let baseline = execute_loaded_fixture(load_test_fixture(post_exec_fixture_path()).await, None) + .expect("baseline fixture must execute"); + + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number, + Vec::new(), + ); + + let outcome = execute_loaded_fixture(loaded, Some(true)).expect("post-exec fixture executes"); + assert_eq!( + outcome.execution_result.receipts.len(), + baseline.execution_result.receipts.len() + 1 + ); + assert!(matches!( + outcome.execution_result.receipts.last(), + Some(OpReceiptEnvelope::PostExec(_)) + )); + assert_eq!(outcome.execution_result.gas_used, baseline.execution_result.gas_used); + assert_eq!(outcome.header.state_root, baseline.header.state_root); + assert_ne!(outcome.header.transactions_root, baseline.header.transactions_root); + assert_ne!(outcome.header.receipts_root, baseline.header.receipts_root); +} + +#[tokio::test] +async fn post_exec_payload_rejects_deposit_target() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number, + vec![SDMGasEntry { index: 0, gas_refund: 1 }], + ); + + let err = execute_loaded_fixture(loaded, Some(true)).unwrap_err(); + assert_post_exec_validation_failure(err, "payload entry targets deposit tx index 0"); +} + +#[tokio::test] +async fn post_exec_payload_rejects_post_exec_target() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + let post_exec_index = + loaded.fixture.executing_payload.transactions.as_ref().unwrap().len() as u64; + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number, + vec![SDMGasEntry { index: post_exec_index, gas_refund: 1 }], + ); + + let err = execute_loaded_fixture(loaded, Some(true)).unwrap_err(); + assert_post_exec_validation_failure( + err, + &format!("payload entry targets post-exec tx index {post_exec_index}"), + ); +} + +#[tokio::test] +async fn post_exec_payload_rejects_duplicate_entries() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number, + vec![SDMGasEntry { index: 1, gas_refund: 1 }, SDMGasEntry { index: 1, gas_refund: 2 }], + ); + + let err = execute_loaded_fixture(loaded, Some(true)).unwrap_err(); + assert_post_exec_validation_failure(err, "duplicate post-exec payload entry for tx index 1"); +} + +#[tokio::test] +async fn post_exec_payload_rejects_unconsumed_entry() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + let out_of_range_index = + loaded.fixture.executing_payload.transactions.as_ref().unwrap().len() as u64 + 1; + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number, + vec![SDMGasEntry { index: out_of_range_index, gas_refund: 1 }], + ); + + let err = execute_loaded_fixture(loaded, Some(true)).unwrap_err(); + assert_post_exec_validation_failure(err, "unconsumed post-exec payload entries"); +} + +#[tokio::test] +async fn post_exec_payload_rejects_refund_exceeding_gas_used() { + let mut loaded = load_test_fixture(post_exec_fixture_path()).await; + let block_number = fixture_block_number(&loaded.fixture.parent_header); + append_post_exec_tx( + loaded.fixture.executing_payload.transactions.as_mut().unwrap(), + block_number, + vec![SDMGasEntry { index: 1, gas_refund: u64::MAX }], + ); + + let err = execute_loaded_fixture(loaded, Some(true)).unwrap_err(); + assert_post_exec_validation_failure(err, "exceeds evm_gas_used"); +} diff --git a/rust/kona/crates/proof/executor/src/db/mod.rs b/rust/kona/crates/proof/executor/src/db/mod.rs index 4ff84f248ca..f2d9e608f9b 100644 --- a/rust/kona/crates/proof/executor/src/db/mod.rs +++ b/rust/kona/crates/proof/executor/src/db/mod.rs @@ -62,7 +62,7 @@ pub use traits::{NoopTrieDBProvider, TrieDBProvider}; /// let executor_factory = OpBlockExecutorFactory::new( /// OpAlloyReceiptBuilder::default(), /// OpChainHardforks::op_mainnet(), -/// OpEvmFactory::>::default(), +/// OpEvmFactory::::default(), /// ); /// let mut state = State::builder().with_database(trie_db).with_bundle_update().build(); /// let evm = executor_factory.evm_factory().create_evm(&mut state, EvmEnv::default()); @@ -175,14 +175,10 @@ where /// - `Ok(Some(TrieAccount))`: The [`TrieAccount`] of the account. /// - `Ok(None)`: If the account does not exist in the trie. /// - `Err(_)`: If the account could not be fetched. - pub fn get_trie_account( - &mut self, - address: &Address, - block_number: u64, - ) -> TrieDBResult> { + pub fn get_trie_account(&mut self, address: &Address) -> TrieDBResult> { // Send a hint to the host to fetch the account proof. self.hinter - .hint_account_proof(*address, block_number) + .hint_account_proof(*address, self.parent_block_header.hash()) .map_err(|e| TrieDBError::Provider(e.to_string()))?; // Fetch the account from the trie. @@ -222,11 +218,18 @@ where // Compute the path to the account in the trie. let account_path = Nibbles::unpack(hashed_address.as_slice()); - // If the account was destroyed, delete it from the trie. + // If the account was destroyed, delete it from the trie and wipe its storage. + // For DestroyedChanged accounts the account was re-created after destruction, so + // fall through to re-insert the new state rather than skipping it. if bundle_account.was_destroyed() { - self.root_node.delete(&account_path, &self.fetcher, &self.hinter)?; + match self.root_node.delete(&account_path, &self.fetcher, &self.hinter) { + Ok(()) | Err(TrieNodeError::KeyNotFound) => {} + Err(e) => return Err(e.into()), + } self.storage_roots.remove(address); - continue; + if bundle_account.account_info().is_none() { + continue; + } } let account_info = @@ -329,9 +332,7 @@ where fn basic(&mut self, address: Address) -> Result, Self::Error> { // Fetch the account from the trie. - let Some(trie_account) = - self.get_trie_account(&address, self.parent_block_header.number)? - else { + let Some(trie_account) = self.get_trie_account(&address)? else { // If the account does not exist in the trie, return `Ok(None)`. return Ok(None); }; @@ -360,7 +361,7 @@ where fn storage(&mut self, address: Address, index: U256) -> Result { // Send a hint to the host to fetch the storage proof. self.hinter - .hint_storage_proof(address, index, self.parent_block_header.number) + .hint_storage_proof(address, index, self.parent_block_header.hash()) .map_err(|e| TrieDBError::Provider(e.to_string()))?; // Fetch the account's storage root from the cache. If storage is being accessed, the @@ -417,8 +418,9 @@ where mod tests { use super::*; use alloy_consensus::Sealable; - use alloy_primitives::b256; + use alloy_primitives::{U256, b256}; use kona_mpt::NoopTrieHinter; + use revm::database::{AccountStatus, BundleAccount}; fn new_test_db() -> TrieDB { TrieDB::new(Header::default().seal_slow(), NoopTrieDBProvider, NoopTrieHinter) @@ -475,4 +477,150 @@ mod tests { b256!("78dec18c6d7da925bbe773c315653cdc70f6444ed6c1de9ac30bdb36cff74c3b") ); } + + fn bundle_with_account(address: Address, account: BundleAccount) -> BundleState { + let mut state = revm::primitives::HashMap::default(); + state.insert(address, account); + BundleState { state, ..Default::default() } + } + + // Pre-populate the trie with a live account so that subsequent destroy bundles have + // something to delete (TrieNode::delete errors on a missing key). + fn insert_account(db: &mut TrieDB, address: Address) { + let account = BundleAccount::new( + None, + Some(AccountInfo { balance: U256::from(100u64), ..Default::default() }), + Default::default(), + AccountStatus::InMemoryChange, + ); + db.state_root(&bundle_with_account(address, account)).unwrap(); + } + + // A plain Destroyed account (info = None) must be absent from the state root. + #[test] + fn test_destroyed_account_absent() { + let mut db = new_test_db(); + let address = Address::repeat_byte(0x01); + insert_account(&mut db, address); + + let root = db + .state_root(&bundle_with_account( + address, + BundleAccount::new( + Some(AccountInfo::default()), + None, + Default::default(), + AccountStatus::Destroyed, + ), + )) + .unwrap(); + assert_eq!(root, EMPTY_ROOT_HASH, "Destroyed account should not appear in trie"); + } + + // A DestroyedChanged account (destroyed then given a new balance) must appear in the state + // root with its new state. + #[test] + fn test_destroyed_changed_account_reinserted() { + let mut db = new_test_db(); + let address = Address::repeat_byte(0x02); + insert_account(&mut db, address); + + let new_info = + AccountInfo { balance: U256::from(1_000_000_000_000_000_000u64), ..Default::default() }; + let root = db + .state_root(&bundle_with_account( + address, + BundleAccount::new( + Some(AccountInfo::default()), + Some(new_info), + Default::default(), + AccountStatus::DestroyedChanged, + ), + )) + .unwrap(); + assert_ne!(root, EMPTY_ROOT_HASH, "DestroyedChanged account must be re-inserted into trie"); + } + + // A DestroyedChanged account with new storage slots must have those slots present in the + // trie — this exercises the or_insert_with(EMPTY_ROOT_HASH) + storage loop path and + // confirms the old storage is wiped and only new slots survive. + #[test] + fn test_destroyed_changed_account_storage_wiped_and_reinserted() { + use revm::database::states::StorageSlot as RvmStorageSlot; + + let mut db = new_test_db(); + let address = Address::repeat_byte(0x04); + insert_account(&mut db, address); + + // Re-create the account with one new storage slot. + let mut storage = revm::primitives::HashMap::default(); + let slot_key = U256::from(1u64); + storage.insert(slot_key, RvmStorageSlot::new_changed(U256::ZERO, U256::from(42u64))); + let new_info = + AccountInfo { balance: U256::from(1_000_000_000_000_000_000u64), ..Default::default() }; + let root = db + .state_root(&bundle_with_account( + address, + BundleAccount::new( + Some(AccountInfo::default()), + Some(new_info), + storage, + AccountStatus::DestroyedChanged, + ), + )) + .unwrap(); + assert_ne!( + root, EMPTY_ROOT_HASH, + "DestroyedChanged account with storage must appear in trie" + ); + } + + // A DestroyedChanged account that was never in the parent trie (e.g. CREATE+SELFDESTRUCT+ + // re-CREATE in a single block) must not fail when the delete is a no-op. + #[test] + fn test_destroyed_changed_new_account_never_in_trie() { + let mut db = new_test_db(); + let address = Address::repeat_byte(0x05); + // No insert_account — this address has never existed in the parent trie. + + let new_info = + AccountInfo { balance: U256::from(1_000_000_000_000_000_000u64), ..Default::default() }; + let root = db + .state_root(&bundle_with_account( + address, + BundleAccount::new( + None, + Some(new_info), + Default::default(), + AccountStatus::DestroyedChanged, + ), + )) + .unwrap(); + assert_ne!( + root, EMPTY_ROOT_HASH, + "DestroyedChanged account never in parent trie must be re-inserted" + ); + } + + // A DestroyedAgain account (destroyed, recreated, destroyed again; info = None) must be + // absent from the state root. + #[test] + fn test_destroyed_again_account_absent() { + let mut db = new_test_db(); + let address = Address::repeat_byte(0x03); + insert_account(&mut db, address); + + let root = db + .state_root(&bundle_with_account( + address, + BundleAccount::new( + Some(AccountInfo::default()), + None, + Default::default(), + AccountStatus::DestroyedAgain, + ), + )) + .unwrap(); + assert_eq!(root, EMPTY_ROOT_HASH, "DestroyedAgain account should not appear in trie"); + } } diff --git a/rust/kona/crates/proof/executor/src/errors.rs b/rust/kona/crates/proof/executor/src/errors.rs index e1482cb8711..ca471da3a7a 100644 --- a/rust/kona/crates/proof/executor/src/errors.rs +++ b/rust/kona/crates/proof/executor/src/errors.rs @@ -134,6 +134,9 @@ pub enum ExecutorError { /// - Gas accounting bugs in execution #[error("Block gas limit exceeded")] BlockGasLimitExceeded, + /// Invalid post-exec payload or transaction placement. + #[error("Invalid post-exec payload: {0}")] + InvalidPostExecPayload(String), /// Unsupported transaction type encountered during execution. /// /// This error occurs when the executor encounters a transaction type that diff --git a/rust/kona/crates/proof/executor/src/test_utils.rs b/rust/kona/crates/proof/executor/src/test_utils.rs index db9000a84ac..05bd9293256 100644 --- a/rust/kona/crates/proof/executor/src/test_utils.rs +++ b/rust/kona/crates/proof/executor/src/test_utils.rs @@ -1,6 +1,6 @@ //! Test utilities for the executor. -use crate::{StatelessL2Builder, TrieDBProvider}; +use crate::{BlockBuildingOutcome, ExecutorResult, StatelessL2Builder, TrieDBProvider}; use alloy_consensus::Header; use alloy_op_evm::OpEvmFactory; use alloy_primitives::{B256, Bytes, Sealable}; @@ -13,17 +13,24 @@ use kona_genesis::RollupConfig; use kona_mpt::{NoopTrieHinter, TrieNode, TrieProvider}; use kona_registry::ROLLUP_CONFIGS; use op_alloy_rpc_types_engine::OpPayloadAttributes; -use op_revm::OpTransaction; -use revm::context::TxEnv; use rocksdb::{DB, Options}; use serde::{Deserialize, Serialize}; use std::{path::PathBuf, sync::Arc}; use tokio::{fs, runtime::Handle, sync::Mutex}; -/// Executes a [`ExecutorTestFixture`] stored at the passed `fixture_path` and asserts that the -/// produced block hash matches the expected block hash. -pub async fn run_test_fixture(fixture_path: PathBuf) { - // First, untar the fixture. +/// A loaded executor test fixture and its backing temporary fixture directory. +#[derive(Debug)] +pub struct LoadedExecutorTestFixture { + /// Keeps the untarred fixture directory alive while the `RocksDB` provider is open. + pub fixture_dir: tempfile::TempDir, + /// The deserialized fixture metadata and payload. + pub fixture: ExecutorTestFixture, + /// Trie/database provider backed by the fixture's key-value store. + pub provider: DiskTrieNodeProvider, +} + +/// Loads a [`ExecutorTestFixture`] stored at the passed `fixture_path`. +pub async fn load_test_fixture(fixture_path: PathBuf) -> LoadedExecutorTestFixture { let fixture_dir = tempfile::tempdir().expect("Failed to create temporary directory"); tokio::process::Command::new("tar") .arg("-xvf") @@ -45,19 +52,39 @@ pub async fn run_test_fixture(fixture_path: PathBuf) { serde_json::from_slice(&fs::read(fixture_dir.path().join("fixture.json")).await.unwrap()) .expect("Failed to deserialize fixture"); + LoadedExecutorTestFixture { fixture_dir, fixture, provider } +} + +/// Executes a loaded fixture, optionally overriding SDM activation for the run. +pub fn execute_loaded_fixture( + loaded: LoadedExecutorTestFixture, + sdm_active_override: Option, +) -> ExecutorResult { + let LoadedExecutorTestFixture { fixture_dir: _fixture_dir, fixture, provider } = loaded; + let ExecutorTestFixture { rollup_config, parent_header, executing_payload, .. } = fixture; + let mut executor = StatelessL2Builder::new( - &fixture.rollup_config, - OpEvmFactory::>::default(), + &rollup_config, + OpEvmFactory::::default(), provider, NoopTrieHinter, - fixture.parent_header.seal_slow(), + parent_header.seal_slow(), ); + executor.set_sdm_active_override(sdm_active_override); + + executor.build_block(executing_payload) +} - let outcome = executor.build_block(fixture.executing_payload).unwrap(); +/// Executes a [`ExecutorTestFixture`] stored at the passed `fixture_path` and asserts that the +/// produced block hash matches the expected block hash. +pub async fn run_test_fixture(fixture_path: PathBuf) { + let loaded = load_test_fixture(fixture_path).await; + let expected_block_hash = loaded.fixture.expected_block_hash; + let outcome = execute_loaded_fixture(loaded, None).unwrap(); assert_eq!( outcome.header.hash(), - fixture.expected_block_hash, + expected_block_hash, "Produced header does not match the expected header" ); } @@ -153,6 +180,7 @@ impl ExecutorTestFixtureCreator { prev_randao: executing_header.mix_hash, withdrawals: Default::default(), suggested_fee_recipient: executing_header.beneficiary, + slot_number: Default::default(), }, gas_limit: Some(executing_header.gas_limit), transactions: Some(encoded_executing_transactions), @@ -183,7 +211,7 @@ impl ExecutorTestFixtureCreator { let mut executor = StatelessL2Builder::new( rollup_config, - OpEvmFactory::>::default(), + OpEvmFactory::::default(), self, NoopTrieHinter, parent_header, diff --git a/rust/kona/crates/proof/executor/src/util.rs b/rust/kona/crates/proof/executor/src/util.rs index 6170d11ed43..acae2d2c4f0 100644 --- a/rust/kona/crates/proof/executor/src/util.rs +++ b/rust/kona/crates/proof/executor/src/util.rs @@ -119,6 +119,7 @@ mod test { suggested_fee_recipient: Default::default(), withdrawals: Default::default(), parent_beacon_block_root: Default::default(), + slot_number: Default::default(), }, transactions: None, no_tx_pool: None, diff --git a/rust/kona/crates/proof/executor/testdata/block-26207960.tar.gz b/rust/kona/crates/proof/executor/testdata/block-26207960.tar.gz index cf3ee2cb542..2f2135f4a5c 100644 Binary files a/rust/kona/crates/proof/executor/testdata/block-26207960.tar.gz and b/rust/kona/crates/proof/executor/testdata/block-26207960.tar.gz differ diff --git a/rust/kona/crates/proof/executor/testdata/block-26207961.tar.gz b/rust/kona/crates/proof/executor/testdata/block-26207961.tar.gz index d46eca8d78a..e910ca0fdb3 100644 Binary files a/rust/kona/crates/proof/executor/testdata/block-26207961.tar.gz and b/rust/kona/crates/proof/executor/testdata/block-26207961.tar.gz differ diff --git a/rust/kona/crates/proof/executor/testdata/block-26207962.tar.gz b/rust/kona/crates/proof/executor/testdata/block-26207962.tar.gz index 3cd4d134999..4a4e9ebcc6e 100644 Binary files a/rust/kona/crates/proof/executor/testdata/block-26207962.tar.gz and b/rust/kona/crates/proof/executor/testdata/block-26207962.tar.gz differ diff --git a/rust/kona/crates/proof/executor/testdata/block-26207963.tar.gz b/rust/kona/crates/proof/executor/testdata/block-26207963.tar.gz index eceb474d20e..83b13e45e07 100644 Binary files a/rust/kona/crates/proof/executor/testdata/block-26207963.tar.gz and b/rust/kona/crates/proof/executor/testdata/block-26207963.tar.gz differ diff --git a/rust/kona/crates/proof/executor/testdata/block-26208384.tar.gz b/rust/kona/crates/proof/executor/testdata/block-26208384.tar.gz index 6451f8b0b19..cb11845f7cf 100644 Binary files a/rust/kona/crates/proof/executor/testdata/block-26208384.tar.gz and b/rust/kona/crates/proof/executor/testdata/block-26208384.tar.gz differ diff --git a/rust/kona/crates/proof/executor/testdata/block-26208858.tar.gz b/rust/kona/crates/proof/executor/testdata/block-26208858.tar.gz index 2d83e51a2a0..8739eede470 100644 Binary files a/rust/kona/crates/proof/executor/testdata/block-26208858.tar.gz and b/rust/kona/crates/proof/executor/testdata/block-26208858.tar.gz differ diff --git a/rust/kona/crates/proof/executor/testdata/block-26208927.tar.gz b/rust/kona/crates/proof/executor/testdata/block-26208927.tar.gz index d0ecbbf3022..891c71ee7fe 100644 Binary files a/rust/kona/crates/proof/executor/testdata/block-26208927.tar.gz and b/rust/kona/crates/proof/executor/testdata/block-26208927.tar.gz differ diff --git a/rust/kona/crates/proof/executor/testdata/block-26211680.tar.gz b/rust/kona/crates/proof/executor/testdata/block-26211680.tar.gz index 80fa22dfa44..d3718011b5a 100644 Binary files a/rust/kona/crates/proof/executor/testdata/block-26211680.tar.gz and b/rust/kona/crates/proof/executor/testdata/block-26211680.tar.gz differ diff --git a/rust/kona/crates/proof/mpt/src/noop.rs b/rust/kona/crates/proof/mpt/src/noop.rs index 7bd7f2ec967..8f555594fde 100644 --- a/rust/kona/crates/proof/mpt/src/noop.rs +++ b/rust/kona/crates/proof/mpt/src/noop.rs @@ -29,7 +29,7 @@ impl TrieHinter for NoopTrieHinter { Ok(()) } - fn hint_account_proof(&self, _address: Address, _block_number: u64) -> Result<(), Self::Error> { + fn hint_account_proof(&self, _address: Address, _block_hash: B256) -> Result<(), Self::Error> { Ok(()) } @@ -37,7 +37,7 @@ impl TrieHinter for NoopTrieHinter { &self, _address: Address, _slot: U256, - _block_number: u64, + _block_hash: B256, ) -> Result<(), Self::Error> { Ok(()) } diff --git a/rust/kona/crates/proof/mpt/src/traits.rs b/rust/kona/crates/proof/mpt/src/traits.rs index 92fd4047ef6..35d6f14fb84 100644 --- a/rust/kona/crates/proof/mpt/src/traits.rs +++ b/rust/kona/crates/proof/mpt/src/traits.rs @@ -41,12 +41,12 @@ pub trait TrieHinter { /// /// ## Takes /// - `address` - The address of the contract whose trie node preimages are to be fetched. - /// - `block_number` - The block number at which the trie node preimages are to be fetched. + /// - `block_hash` - The block hash at which the trie node preimages are to be fetched. /// /// ## Returns /// - Ok(()): If the hint was successful. /// - `Err(Self::Error)`: If the hint was unsuccessful. - fn hint_account_proof(&self, address: Address, block_number: u64) -> Result<(), Self::Error>; + fn hint_account_proof(&self, address: Address, block_hash: B256) -> Result<(), Self::Error>; /// Hints the host to fetch the trie node preimages on the path to the storage slot within the /// given account's storage trie. @@ -54,7 +54,7 @@ pub trait TrieHinter { /// ## Takes /// - `address` - The address of the contract whose trie node preimages are to be fetched. /// - `slot` - The storage slot whose trie node preimages are to be fetched. - /// - `block_number` - The block number at which the trie node preimages are to be fetched. + /// - `block_hash` - The block hash at which the trie node preimages are to be fetched. /// /// ## Returns /// - Ok(()): If the hint was successful. @@ -63,7 +63,7 @@ pub trait TrieHinter { &self, address: Address, slot: U256, - block_number: u64, + block_hash: B256, ) -> Result<(), Self::Error>; /// Hints the host to fetch the execution witness for the [`OpPayloadAttributes`] applied on top diff --git a/rust/kona/crates/proof/preimage/Cargo.toml b/rust/kona/crates/proof/preimage/Cargo.toml index 0752d0a7bf7..3d9e7ea70b3 100644 --- a/rust/kona/crates/proof/preimage/Cargo.toml +++ b/rust/kona/crates/proof/preimage/Cargo.toml @@ -21,6 +21,9 @@ alloy-primitives.workspace = true # `std` feature dependencies async-channel = { workspace = true, optional = true } +# `verify` feature dependencies +sha2 = { workspace = true, optional = true } + # `rkyv` feature dependencies rkyv = { workspace = true, optional = true } @@ -29,6 +32,7 @@ serde = { workspace = true, optional = true, features = ["derive"] } [dev-dependencies] tokio = { workspace = true, features = ["full"] } +sha2.workspace = true [features] default = [] @@ -36,8 +40,10 @@ std = [ "alloy-primitives/std", "dep:async-channel", "serde?/std", + "sha2?/std", "thiserror/std", "tracing/std", ] rkyv = [ "dep:rkyv" ] serde = [ "alloy-primitives/serde", "dep:serde" ] +verify = [ "dep:sha2" ] diff --git a/rust/kona/crates/proof/preimage/src/errors.rs b/rust/kona/crates/proof/preimage/src/errors.rs index 32659fc7229..4689981b3bd 100644 --- a/rust/kona/crates/proof/preimage/src/errors.rs +++ b/rust/kona/crates/proof/preimage/src/errors.rs @@ -1,5 +1,6 @@ //! Errors for the `kona-preimage` crate. +use crate::{PreimageKey, PreimageKeyType}; use alloc::string::String; use thiserror::Error; @@ -17,6 +18,12 @@ pub enum PreimageOracleError { /// Key not found. #[error("Key not found.")] KeyNotFound, + /// The preimage returned by the fetcher does not hash to the requested key. + #[error("Incorrect preimage data: hash does not match requested key {0}.")] + IncorrectData(PreimageKey), + /// The verifier was asked to verify a key type it does not support. + #[error("Unsupported preimage key type: {0:?}.")] + UnsupportedKeyType(PreimageKeyType), /// Timeout while waiting for preimage. #[error("Timeout while waiting for preimage.")] Timeout, diff --git a/rust/kona/crates/proof/preimage/src/lib.rs b/rust/kona/crates/proof/preimage/src/lib.rs index d778674f718..fd8802d97e3 100644 --- a/rust/kona/crates/proof/preimage/src/lib.rs +++ b/rust/kona/crates/proof/preimage/src/lib.rs @@ -20,6 +20,11 @@ pub use key::{PreimageKey, PreimageKeyType}; mod oracle; pub use oracle::{OracleReader, OracleServer}; +#[cfg(feature = "verify")] +mod verifier; +#[cfg(feature = "verify")] +pub use verifier::{VerifyingPreimageFetcher, verify_preimage}; + mod hint; pub use hint::{HintReader, HintWriter}; diff --git a/rust/kona/crates/proof/preimage/src/verifier.rs b/rust/kona/crates/proof/preimage/src/verifier.rs new file mode 100644 index 00000000000..af222d2da15 --- /dev/null +++ b/rust/kona/crates/proof/preimage/src/verifier.rs @@ -0,0 +1,255 @@ +//! A [`PreimageFetcher`] wrapper that re-hashes returned preimages against the requested key. +//! +//! The wrapper catches corrupt RPC responses, hint-handler bugs, and tampered +//! KV stores at fetch time rather than letting bad data propagate. + +use crate::{ + HintRouter, PreimageFetcher, PreimageKey, PreimageKeyType, + errors::{PreimageOracleError, PreimageOracleResult}, +}; +use alloc::{boxed::Box, string::String, vec::Vec}; +use alloy_primitives::keccak256; +use async_trait::async_trait; +use sha2::{Digest, Sha256}; + +/// Wraps an inner [`PreimageFetcher`] and verifies returned preimages by re-hashing +/// them against the requested key. +#[derive(Debug, Clone, Copy)] +pub struct VerifyingPreimageFetcher { + inner: F, +} + +impl VerifyingPreimageFetcher { + /// Create a new [`VerifyingPreimageFetcher`] from an inner fetcher. + pub const fn new(inner: F) -> Self { + Self { inner } + } + + /// Returns a reference to the inner fetcher. + pub const fn inner(&self) -> &F { + &self.inner + } +} + +/// Verify that `data` is a valid preimage for `key`, when the key type carries a +/// self-describing hash (Keccak256, Sha256). Other key types pass through. +/// +/// `GlobalGeneric` is reserved and unused; observing it is treated as a programmer +/// error and returns [`PreimageOracleError::UnsupportedKeyType`]. +pub fn verify_preimage(key: PreimageKey, data: &[u8]) -> PreimageOracleResult<()> { + let key_bytes: [u8; 32] = key.into(); + match key.key_type() { + PreimageKeyType::Keccak256 => { + let digest = keccak256(data); + if digest[1..] != key_bytes[1..] { + return Err(PreimageOracleError::IncorrectData(key)); + } + } + PreimageKeyType::Sha256 => { + let digest = Sha256::digest(data); + if digest[1..] != key_bytes[1..] { + return Err(PreimageOracleError::IncorrectData(key)); + } + } + // Pass through key types that aren't self-verifying from `(key, data)` alone: + // - Local: opaque local identifier, no hash relation. + // - Blob: individual field element; verification needs a KZG proof. + // - Precompile: result bytes; verification needs the precompile input. + PreimageKeyType::Local | PreimageKeyType::Blob | PreimageKeyType::Precompile => {} + // Reserved, no defined semantics, and no host path produces it today. Reject so a + // future accidental use surfaces loudly rather than silently passing through. + PreimageKeyType::GlobalGeneric => { + return Err(PreimageOracleError::UnsupportedKeyType(PreimageKeyType::GlobalGeneric)); + } + } + Ok(()) +} + +#[async_trait] +impl PreimageFetcher for VerifyingPreimageFetcher +where + F: PreimageFetcher + Send + Sync, +{ + async fn get_preimage(&self, key: PreimageKey) -> PreimageOracleResult> { + let data = self.inner.get_preimage(key).await?; + verify_preimage(key, &data)?; + Ok(data) + } +} + +#[async_trait] +impl HintRouter for VerifyingPreimageFetcher +where + F: HintRouter + Send + Sync, +{ + async fn route_hint(&self, hint: String) -> PreimageOracleResult<()> { + self.inner.route_hint(hint).await + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::PreimageKeyType; + use alloc::sync::Arc; + use alloy_primitives::keccak256; + use async_trait::async_trait; + use std::collections::HashMap; + use tokio::sync::Mutex; + + /// A test fetcher that returns whatever bytes are stored under each key, + /// regardless of whether the key actually hashes to those bytes. This is + /// the "corrupt source" the verifier must reject. + struct MockFetcher { + responses: Arc>>>, + } + + #[async_trait] + impl PreimageFetcher for MockFetcher { + async fn get_preimage(&self, key: PreimageKey) -> PreimageOracleResult> { + let guard = self.responses.lock().await; + guard.get(&key).cloned().ok_or(PreimageOracleError::KeyNotFound) + } + } + + fn make_fetcher(responses: HashMap>) -> MockFetcher { + MockFetcher { responses: Arc::new(Mutex::new(responses)) } + } + + #[tokio::test] + async fn keccak256_valid_data_passes() { + let data = b"hello world".to_vec(); + let key = PreimageKey::new(*keccak256(&data), PreimageKeyType::Keccak256); + let mut map = HashMap::new(); + map.insert(key, data.clone()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let got = verifier.get_preimage(key).await.unwrap(); + assert_eq!(got, data); + } + + #[tokio::test] + async fn keccak256_corrupt_data_rejected() { + let data = b"hello world".to_vec(); + let key = PreimageKey::new(*keccak256(&data), PreimageKeyType::Keccak256); + // Map the key to *wrong* bytes, simulating a corrupt RPC/KV/hint handler. + let mut map = HashMap::new(); + map.insert(key, b"goodbye".to_vec()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let err = verifier.get_preimage(key).await.unwrap_err(); + assert!(matches!(err, PreimageOracleError::IncorrectData(_))); + } + + #[tokio::test] + async fn keccak256_empty_data_rejected() { + let data = b"hello world".to_vec(); + let key = PreimageKey::new(*keccak256(&data), PreimageKeyType::Keccak256); + let mut map = HashMap::new(); + map.insert(key, Vec::new()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let err = verifier.get_preimage(key).await.unwrap_err(); + assert!(matches!(err, PreimageOracleError::IncorrectData(_))); + } + + #[tokio::test] + async fn sha256_valid_data_passes() { + let data = b"hello world".to_vec(); + let digest: [u8; 32] = Sha256::digest(&data).into(); + let key = PreimageKey::new(digest, PreimageKeyType::Sha256); + let mut map = HashMap::new(); + map.insert(key, data.clone()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let got = verifier.get_preimage(key).await.unwrap(); + assert_eq!(got, data); + } + + #[tokio::test] + async fn sha256_corrupt_data_rejected() { + let data = b"hello world".to_vec(); + let digest: [u8; 32] = Sha256::digest(&data).into(); + let key = PreimageKey::new(digest, PreimageKeyType::Sha256); + let mut map = HashMap::new(); + map.insert(key, b"tampered".to_vec()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let err = verifier.get_preimage(key).await.unwrap_err(); + assert!(matches!(err, PreimageOracleError::IncorrectData(_))); + } + + #[tokio::test] + async fn local_key_pass_through() { + let key = PreimageKey::new_local(7); + let data = b"anything goes".to_vec(); + let mut map = HashMap::new(); + map.insert(key, data.clone()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let got = verifier.get_preimage(key).await.unwrap(); + assert_eq!(got, data); + } + + #[tokio::test] + async fn blob_key_pass_through() { + let key = PreimageKey::new([0xAA; 32], PreimageKeyType::Blob); + let data = b"opaque blob field element".to_vec(); + let mut map = HashMap::new(); + map.insert(key, data.clone()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let got = verifier.get_preimage(key).await.unwrap(); + assert_eq!(got, data); + } + + #[tokio::test] + async fn precompile_key_pass_through() { + let key = PreimageKey::new([0xBB; 32], PreimageKeyType::Precompile); + let data = b"precompile result".to_vec(); + let mut map = HashMap::new(); + map.insert(key, data.clone()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let got = verifier.get_preimage(key).await.unwrap(); + assert_eq!(got, data); + } + + #[tokio::test] + async fn global_generic_key_rejected() { + let key = PreimageKey::new([0xCC; 32], PreimageKeyType::GlobalGeneric); + let mut map = HashMap::new(); + map.insert(key, b"whatever".to_vec()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let err = verifier.get_preimage(key).await.unwrap_err(); + assert!(matches!( + err, + PreimageOracleError::UnsupportedKeyType(PreimageKeyType::GlobalGeneric) + )); + } + + #[tokio::test] + async fn incorrect_data_error_carries_key() { + let data = b"hello world".to_vec(); + let key = PreimageKey::new(*keccak256(&data), PreimageKeyType::Keccak256); + let mut map = HashMap::new(); + map.insert(key, b"wrong".to_vec()); + + let verifier = VerifyingPreimageFetcher::new(make_fetcher(map)); + let err = verifier.get_preimage(key).await.unwrap_err(); + match err { + PreimageOracleError::IncorrectData(reported) => assert_eq!(reported, key), + other => panic!("expected IncorrectData, got {other:?}"), + } + } + + #[tokio::test] + async fn inner_error_propagates() { + let key = PreimageKey::new([0u8; 32], PreimageKeyType::Keccak256); + // No mapping for the key, inner returns KeyNotFound. + let verifier = VerifyingPreimageFetcher::new(make_fetcher(HashMap::new())); + let err = verifier.get_preimage(key).await.unwrap_err(); + assert!(matches!(err, PreimageOracleError::KeyNotFound)); + } +} diff --git a/rust/kona/crates/proof/proof-interop/Cargo.toml b/rust/kona/crates/proof/proof-interop/Cargo.toml index 892d1b5f453..73731e9c390 100644 --- a/rust/kona/crates/proof/proof-interop/Cargo.toml +++ b/rust/kona/crates/proof/proof-interop/Cargo.toml @@ -55,6 +55,7 @@ alloy-primitives = { workspace = true, features = ["rlp", "arbitrary"] } kona-interop = { workspace = true, features = ["arbitrary"] } arbitrary = { workspace = true, features = ["derive"] } rand.workspace = true +tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] } [features] arbitrary = [ @@ -69,4 +70,5 @@ arbitrary = [ "op-alloy-consensus/arbitrary", "op-alloy-rpc-types-engine/arbitrary", "revm/arbitrary", + "alloy-op-evm/arbitrary", ] diff --git a/rust/kona/crates/proof/proof-interop/src/boot.rs b/rust/kona/crates/proof/proof-interop/src/boot.rs index 2f9652bfca5..659bc425431 100644 --- a/rust/kona/crates/proof/proof-interop/src/boot.rs +++ b/rust/kona/crates/proof/proof-interop/src/boot.rs @@ -6,12 +6,13 @@ use alloc::{string::ToString, vec::Vec}; use alloy_primitives::{B256, Bytes, U256}; use alloy_rlp::Decodable; use kona_genesis::{L1ChainConfig, RollupConfig}; +use kona_interop::DependencySet; use kona_preimage::{ CommsClient, HintWriterClient, PreimageKey, PreimageKeyType, PreimageOracleClient, errors::PreimageOracleError, }; use kona_proof::errors::OracleProviderError; -use kona_registry::{HashMap, L1_CONFIGS, ROLLUP_CONFIGS}; +use kona_registry::{DEPENDENCY_SETS, HashMap, L1_CONFIGS, ROLLUP_CONFIGS}; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracing::warn; @@ -34,6 +35,9 @@ pub const L2_ROLLUP_CONFIG_KEY: U256 = U256::from_be_slice(&[6]); /// The local key ident for the l1 config. pub const L1_CONFIG_KEY: U256 = U256::from_be_slice(&[7]); +/// The local key ident for the dependency set. +pub const DEPENDENCY_SET_KEY: U256 = U256::from_be_slice(&[8]); + /// The boot information for the interop client program. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BootInfo { @@ -49,6 +53,8 @@ pub struct BootInfo { pub claimed_l2_timestamp: u64, /// The rollup config for the L2 chain. pub rollup_configs: HashMap, + /// The dependency set configuration for the interop cluster. + pub dependency_set: DependencySet, /// The L1 config for the L2 chain. pub l1_config: L1ChainConfig, } @@ -110,6 +116,20 @@ impl BootInfo { let agreed_pre_state = PreState::decode(&mut raw_pre_state.as_ref()).map_err(OracleProviderError::Rlp)?; + // INVARIANT: the honest actor never agrees to a prestate whose timestamp exceeds + // the dispute-game-pinned `claimed_l2_timestamp`. The oracle only verifies + // `key == keccak256(preimage)`, not the timestamp inside; without this guard a + // malicious proposer could register a future-timestamped prestate and use it as + // both starting and disputed claim at trace-extended bisection positions, where + // `claim == prestate ⇒ Ok(())` would resolve as `vmStatus = VALID`. Op-program + // panics on this condition (see `op-program/client/interop/interop.go:87-97`). + assert!( + agreed_pre_state.timestamp() <= l2_claim_block, + "agreed prestate timestamp {} is after the game timestamp {}", + agreed_pre_state.timestamp(), + l2_claim_block + ); + let chain_ids: Vec<_> = match agreed_pre_state { PreState::SuperRoot(ref super_root) => { super_root.output_roots.iter().map(|r| r.chain_id).collect() @@ -127,10 +147,12 @@ impl BootInfo { { chain_ids.iter().map(|id| (*id, ROLLUP_CONFIGS[id].clone())).collect() } else { + let missing_ids: Vec = + chain_ids.iter().copied().filter(|id| !ROLLUP_CONFIGS.contains_key(id)).collect(); warn!( target: "boot_loader", "No rollup config found for chain IDs {:?}, falling back to preimage oracle. This is insecure in production without additional validation!", - chain_ids + missing_ids ); let ser_cfg = oracle .get(PreimageKey::new_local(L2_ROLLUP_CONFIG_KEY.to())) @@ -139,6 +161,8 @@ impl BootInfo { serde_json::from_slice(&ser_cfg).map_err(OracleProviderError::Serde)? }; + let dependency_set = load_dependency_set(oracle, &chain_ids, &DEPENDENCY_SETS).await?; + // Attempt to load the l1 config from the chain ID. If there is no config for the chain, // fall back to loading the config from the preimage oracle. @@ -170,6 +194,7 @@ impl BootInfo { l1_head, l1_config, rollup_configs, + dependency_set, agreed_pre_state_commitment: l2_pre, agreed_pre_state, claimed_post_state: l2_post, @@ -237,3 +262,116 @@ where Ok(Bytes::from(pre)) } + +/// Loads the dependency set for the proof. +/// +/// Looks up the first chain id from the proof's `agreed_pre_state` in the +/// registry-embedded `DEPENDENCY_SETS` map. The build pipeline guarantees that every +/// chain id in any embedded cluster is keyed under that cluster's [`DependencySet`], so +/// a single lookup suffices — if the first chain id is in any embedded cluster, this +/// returns it. If no embedded entry exists, falls back to the preimage oracle, which +/// keeps host-synthesized depsets working for dev/test flows. +/// +/// Cross-cluster and partial-coverage proofs (chain ids spanning multiple embedded +/// clusters or mixing embedded/non-embedded chains) are not detected here. They fail +/// downstream during interop message validation when a message's source chain isn't in +/// the chosen depset's `dependencies`. +async fn load_dependency_set( + oracle: &O, + chain_ids: &[u64], + embedded: &HashMap, +) -> Result +where + O: PreimageOracleClient + Send, +{ + if let Some(ds) = chain_ids.first().and_then(|id| embedded.get(id)) { + return Ok(ds.clone()); + } + warn!( + target: "boot_loader", + "No embedded dependency set found for proof chain ids {:?}, falling back to preimage oracle. This is insecure in production without additional validation!", + chain_ids + ); + let ser_cfg = oracle + .get(PreimageKey::new_local(DEPENDENCY_SET_KEY.to())) + .await + .map_err(OracleProviderError::Preimage)?; + let ds: DependencySet = serde_json::from_slice(&ser_cfg).map_err(OracleProviderError::Serde)?; + Ok(ds) +} + +#[cfg(test)] +#[allow(clippy::zero_sized_map_values)] +mod tests { + use super::*; + use alloc::{boxed::Box, vec, vec::Vec}; + use kona_genesis::{ChainDependency, DependencySet}; + use kona_preimage::{HintWriterClient, errors::PreimageOracleError}; + use kona_registry::HashMap; + + #[derive(Default)] + struct MockOracle { + fetched: spin::Mutex>, + depset_payload: Vec, + } + + #[async_trait::async_trait] + impl PreimageOracleClient for MockOracle { + async fn get(&self, key: PreimageKey) -> Result, PreimageOracleError> { + self.fetched.lock().push(key); + Ok(self.depset_payload.clone()) + } + async fn get_exact( + &self, + _key: PreimageKey, + _buf: &mut [u8], + ) -> Result<(), PreimageOracleError> { + unimplemented!("not exercised by load_dependency_set tests") + } + } + + #[async_trait::async_trait] + impl HintWriterClient for MockOracle { + async fn write(&self, _hint: &str) -> Result<(), PreimageOracleError> { + unimplemented!("not exercised by load_dependency_set tests") + } + } + + fn make_depset(chain_ids: &[u64]) -> DependencySet { + let dependencies = chain_ids.iter().map(|id| (*id, ChainDependency {})).collect(); + DependencySet { dependencies, override_message_expiry_window: None } + } + + fn embed(depsets: Vec) -> HashMap { + let mut by_chain = HashMap::default(); + for ds in depsets { + for id in ds.dependencies.keys() { + by_chain.insert(*id, ds.clone()); + } + } + by_chain + } + + #[tokio::test] + async fn embedded_depset_covers_all_chains_does_not_call_oracle() { + let oracle = MockOracle::default(); + let depset = make_depset(&[1u64, 2u64]); + let embedded = embed(vec![depset.clone()]); + let out = load_dependency_set(&oracle, &[1, 2], &embedded).await.unwrap(); + assert_eq!(out, depset); + assert!(oracle.fetched.lock().is_empty()); + } + + #[tokio::test] + async fn no_embedded_depsets_falls_back_to_oracle() { + let oracle = MockOracle { + depset_payload: serde_json::to_vec(&make_depset(&[1u64])).unwrap(), + ..Default::default() + }; + let out = load_dependency_set(&oracle, &[1], &HashMap::default()).await.unwrap(); + assert_eq!(out.dependencies.len(), 1); + let fetched = oracle.fetched.lock().clone(); + assert_eq!(fetched.len(), 1); + assert_eq!(fetched[0], PreimageKey::new_local(DEPENDENCY_SET_KEY.to())); + } +} diff --git a/rust/kona/crates/proof/proof-interop/src/consolidation.rs b/rust/kona/crates/proof/proof-interop/src/consolidation.rs index 57874b0c484..048eaaa8564 100644 --- a/rust/kona/crates/proof/proof-interop/src/consolidation.rs +++ b/rust/kona/crates/proof/proof-interop/src/consolidation.rs @@ -1,22 +1,24 @@ //! Interop dependency resolution and consolidation logic. use crate::{BootInfo, OptimisticBlock, OracleInteropProvider, PreState}; -use alloc::vec::Vec; +use alloc::{collections::BTreeSet, vec::Vec}; use alloy_consensus::{Header, Sealed}; -use alloy_eips::Encodable2718; -use alloy_evm::{EvmFactory, FromRecoveredTx, FromTxWithEncoded}; -use alloy_op_evm::block::OpTxEnv; -use alloy_primitives::{Address, B256, Bytes, Sealable, TxKind, U256, address}; +use alloy_evm::{EvmFactory, FromRecoveredTx, FromTxWithEncoded, block::BlockExecutorFactory}; +use alloy_op_evm::{ + OpBlockExecutionCtx, OpBlockExecutorFactory, + block::{OpAlloyReceiptBuilder, OpTxEnv}, +}; +use alloy_primitives::Sealable; use alloy_rpc_types_engine::PayloadAttributes; use core::fmt::Debug; use kona_executor::{Eip1559ValidationError, ExecutorError, StatelessL2Builder}; +use kona_genesis::RollupConfig; use kona_interop::{MessageGraph, MessageGraphError}; use kona_mpt::OrderedListWalker; use kona_preimage::CommsClient; use kona_proof::{errors::OracleProviderError, l2::OracleL2ChainProvider}; -use kona_protocol::OutputRoot; use kona_registry::{HashMap, ROLLUP_CONFIGS}; -use op_alloy_consensus::{InteropBlockReplacementDepositSource, OpTxEnvelope, OpTxType, TxDeposit}; +use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope, OpTxType}; use op_alloy_rpc_types_engine::OpPayloadAttributes; use op_revm::OpSpecId; use revm::context::BlockEnv; @@ -40,6 +42,9 @@ where l2_providers: HashMap>, /// The inner [`EvmFactory`] to create EVM instances for re-execution of bad blocks. evm_factory: Evm, + /// Chain IDs that have already been replaced with deposit-only blocks. These are skipped + /// during validation since deposit-only blocks cannot contain executing messages. + replaced_chains: BTreeSet, } impl<'a, C, Evm> SuperchainConsolidator<'a, C, Evm> @@ -48,6 +53,12 @@ where Evm: EvmFactory + Send + Sync + Debug + Clone + 'static, ::Tx: FromTxWithEncoded + FromRecoveredTx + OpTxEnv, + OpBlockExecutorFactory: for<'b> BlockExecutorFactory< + EvmFactory = Evm, + ExecutionCtx<'b> = OpBlockExecutionCtx, + Transaction = OpTxEnvelope, + Receipt = OpReceiptEnvelope, + >, { /// Creates a new [`SuperchainConsolidator`] with the given providers and [Header]s. /// @@ -58,7 +69,13 @@ where l2_providers: HashMap>, evm_factory: Evm, ) -> Self { - Self { boot_info, interop_provider, l2_providers, evm_factory } + Self { + boot_info, + interop_provider, + l2_providers, + evm_factory, + replaced_chains: BTreeSet::new(), + } } /// Recursively consolidates the dependencies of the blocks within the [`MessageGraph`]. @@ -74,8 +91,13 @@ where info!(target: "superchain_consolidator", "Superchain consolidation complete"); return Ok(()); } - Err(ConsolidationError::MessageGraph(MessageGraphError::InvalidMessages(_))) => { - // If invalid messages are still present in the graph, continue the loop. + Err(ConsolidationError::MessageGraph( + MessageGraphError::InvalidMessages(_) | + MessageGraphError::CyclicDependency { .. }, + )) => { + // If invalid messages or cyclic dependencies are found, continue the loop. + // The affected chains have been replaced with deposit-only blocks by + // consolidate_once, so the next iteration will exclude them. } Err(e) => { error!(target: "superchain_consolidator", "Error consolidating superchain: {:?}", e); @@ -95,20 +117,41 @@ where /// /// [Header]: alloy_consensus::Header async fn consolidate_once(&mut self) -> Result<(), ConsolidationError> { - // Derive the message graph from the current set of block headers. + // Filter out chains that have already been replaced with deposit-only blocks. + // Deposit-only blocks cannot contain executing messages, so they are already + // cross-safe and do not need to be re-validated. + let heads_to_check: HashMap> = self + .interop_provider + .local_safe_heads() + .iter() + .filter(|(chain_id, _)| !self.replaced_chains.contains(chain_id)) + .map(|(k, v)| (*k, v.clone())) + .collect(); + + // Derive the message graph from the non-replaced block headers. let graph = MessageGraph::derive( - self.interop_provider.local_safe_heads(), + &heads_to_check, &self.interop_provider, &self.boot_info.rollup_configs, + &self.boot_info.dependency_set, + self.boot_info.dependency_set.get_message_expiry_window(), ) .await?; - // Attempt to resolve the message graph. If there were any invalid messages found, we must - // initiate a re-execution of the original block, with only deposit transactions. - if let Err(MessageGraphError::InvalidMessages(invalid_chains)) = graph.resolve().await { - self.re_execute_deposit_only(&invalid_chains.keys().copied().collect::>()) - .await?; - return Err(MessageGraphError::InvalidMessages(invalid_chains).into()); + // Attempt to resolve the message graph. If there were any invalid messages or cyclic + // dependencies found, re-execute the affected chains with deposit-only transactions. + match graph.resolve().await { + Err(MessageGraphError::InvalidMessages(invalid_chains)) => { + self.re_execute_deposit_only(&invalid_chains.keys().copied().collect::>()) + .await?; + return Err(MessageGraphError::InvalidMessages(invalid_chains).into()); + } + Err(MessageGraphError::CyclicDependency { chain_ids }) => { + self.re_execute_deposit_only(&chain_ids).await?; + return Err(MessageGraphError::CyclicDependency { chain_ids }.into()); + } + Err(e) => return Err(e.into()), + Ok(()) => {} } Ok(()) @@ -126,12 +169,16 @@ where .interop_provider .local_safe_heads() .get(chain_id) - .ok_or(MessageGraphError::EmptyDependencySet)?; + .ok_or(MessageGraphError::EmptyDependencySet)? + .clone(); // Look up the parent header for the block. let parent_header = self.interop_provider.header_by_hash(*chain_id, header.parent_hash).await?; + // Send a hint for the block's transactions so the host pre-fetches the trie nodes. + self.interop_provider.hint_transactions(*chain_id, header.hash()).await?; + // Traverse the transactions trie of the block to re-execute. let trie_walker = OrderedListWalker::try_new_hydrated( header.transactions_root, @@ -140,13 +187,6 @@ where .map_err(OracleProviderError::TrieWalker)?; let transactions = trie_walker.into_iter().map(|(_, rlp)| rlp).collect::>(); - // Explicitly panic if a block sent off for re-execution already contains nothing but - // deposits. - assert!( - !transactions.iter().all(|f| !f.is_empty() && f[0] == OpTxType::Deposit), - "Impossible case; Block with only deposits found to be invalid. Something has gone horribly wrong!" - ); - // Fetch the rollup config + provider for the current chain ID. let rollup_config = ROLLUP_CONFIGS .get(chain_id) @@ -168,18 +208,12 @@ where .find(|block| block.block_hash == header.hash()) .ok_or(MessageGraphError::EmptyDependencySet)?; - // Filter out all transactions that are not deposits to start. - let mut transactions = transactions + // Filter out all transactions that are not deposits. + let transactions = transactions .into_iter() .filter(|t| !t.is_empty() && t[0] == OpTxType::Deposit) .collect::>(); - // Add the deposit replacement system transaction at the end of the list. - transactions.push(Self::craft_replacement_transaction( - header, - original_optimistic_block.output_root, - )); - // Re-craft the execution payload, trimming off all non-deposit transactions. let deposit_only_payload = OpPayloadAttributes { payload_attributes: PayloadAttributes { @@ -188,6 +222,7 @@ where suggested_fee_recipient: header.beneficiary, withdrawals: Default::default(), parent_beacon_block_root: header.parent_beacon_block_root, + slot_number: Default::default(), }, transactions: Some(transactions), no_tx_pool: Some(true), @@ -239,43 +274,13 @@ where // Replace the original optimistic block with the deposit only block. *original_optimistic_block = OptimisticBlock::new(new_header.hash(), new_output_root); - // Replace the original header with the new header. + // Replace the original header with the new header and mark the chain as replaced. self.interop_provider.replace_local_safe_head(*chain_id, new_header); + self.replaced_chains.insert(*chain_id); } Ok(()) } - - /// Forms the replacement transaction inserted into a deposit-only block in the event that a - /// block is reduced due to invalid messages. - /// - /// - fn craft_replacement_transaction(old_header: &Sealed
, old_output_root: B256) -> Bytes { - const REPLACEMENT_SENDER: Address = address!("deaddeaddeaddeaddeaddeaddeaddeaddead0002"); - const REPLACEMENT_GAS: u64 = 36000; - - let source = InteropBlockReplacementDepositSource::new(old_output_root); - let output_root = OutputRoot::from_parts( - old_header.state_root, - old_header.withdrawals_root.unwrap_or_default(), - old_header.hash(), - ); - let replacement_tx = OpTxEnvelope::Deposit( - TxDeposit { - source_hash: source.source_hash(), - from: REPLACEMENT_SENDER, - to: TxKind::Call(Address::ZERO), - mint: 0, - value: U256::ZERO, - gas_limit: REPLACEMENT_GAS, - is_system_transaction: false, - input: output_root.encode().into(), - } - .seal(), - ); - - replacement_tx.encoded_2718().into() - } } /// An error type for the [`SuperchainConsolidator`] struct. diff --git a/rust/kona/crates/proof/proof-interop/src/lib.rs b/rust/kona/crates/proof/proof-interop/src/lib.rs index b36e7afe912..72b2eb46e8c 100644 --- a/rust/kona/crates/proof/proof-interop/src/lib.rs +++ b/rust/kona/crates/proof/proof-interop/src/lib.rs @@ -19,7 +19,7 @@ mod hint; pub use hint::HintType; mod provider; -pub use provider::OracleInteropProvider; +pub use provider::{ChainScopedHinter, OracleInteropProvider}; pub mod boot; pub use boot::BootInfo; diff --git a/rust/kona/crates/proof/proof-interop/src/provider.rs b/rust/kona/crates/proof/proof-interop/src/provider.rs index c776421e927..b1556c5e169 100644 --- a/rust/kona/crates/proof/proof-interop/src/provider.rs +++ b/rust/kona/crates/proof/proof-interop/src/provider.rs @@ -13,7 +13,6 @@ use kona_preimage::{CommsClient, PreimageKey, PreimageKeyType, errors::PreimageO use kona_proof::{eip_2935_history_lookup, errors::OracleProviderError}; use kona_registry::HashMap; use op_alloy_consensus::OpReceiptEnvelope; -use spin::RwLock; /// A [`CommsClient`] backed [`InteropProvider`] implementation. #[derive(Debug, Clone)] @@ -24,8 +23,18 @@ pub struct OracleInteropProvider { boot: BootInfo, /// The local safe head block header cache. local_safe_heads: HashMap>, - /// The chain ID for the current call context. Used to declare the chain ID for the trie hints. - chain_id: Arc>>, +} + +/// A chain-scoped [`TrieHinter`] that annotates all hints with a fixed chain ID. +/// +/// Created via [`OracleInteropProvider::scoped_hinter`]. This avoids storing mutable chain ID +/// state in the provider, making it explicit which chain ID is used for each trie hint. +#[derive(Debug, Clone)] +pub struct ChainScopedHinter<'a, C> { + /// The oracle client, borrowed from the parent provider. + oracle: &'a Arc, + /// The chain ID to annotate hints with. + chain_id: u64, } impl OracleInteropProvider @@ -33,17 +42,25 @@ where C: CommsClient + Send + Sync, { /// Creates a new [`OracleInteropProvider`] with the given oracle client and [`BootInfo`]. - pub fn new( + pub const fn new( oracle: Arc, boot: BootInfo, local_safe_headers: HashMap>, ) -> Self { - Self { - oracle, - boot, - local_safe_heads: local_safe_headers, - chain_id: Arc::new(RwLock::new(None)), - } + Self { oracle, boot, local_safe_heads: local_safe_headers } + } + + /// Sends an [`HintType::L2Transactions`] hint for the given block, instructing the host to + /// pre-fetch the transaction trie nodes into the preimage oracle's key-value store. + pub async fn hint_transactions( + &self, + chain_id: u64, + block_hash: B256, + ) -> Result<(), ::Error> { + HintType::L2Transactions + .with_data(&[block_hash.as_slice(), chain_id.to_be_bytes().as_ref()]) + .send(self.oracle.as_ref()) + .await } /// Returns a reference to the local safe heads map. @@ -51,6 +68,15 @@ where &self.local_safe_heads } + /// Creates a [`ChainScopedHinter`] bound to the given chain ID. + /// + /// The returned hinter implements [`TrieHinter`] and annotates all hints with the specified + /// chain ID. This makes the chain context explicit at each call site rather than relying on + /// mutable state within the provider. + pub const fn scoped_hinter(&self, chain_id: u64) -> ChainScopedHinter<'_, C> { + ChainScopedHinter { oracle: &self.oracle, chain_id } + } + /// Replaces a local safe head with the given header. pub fn replace_local_safe_head(&mut self, chain_id: u64, header: Sealed
) { self.local_safe_heads.insert(chain_id, header); @@ -115,26 +141,22 @@ where /// Fetch a [Header] by its number. async fn header_by_number(&self, chain_id: u64, number: u64) -> Result { - let Some(mut header) = - self.local_safe_heads.get(&chain_id).cloned().map(|h| h.into_inner()) - else { + let Some(sealed) = self.local_safe_heads.get(&chain_id).cloned() else { return Err(PreimageOracleError::Other("Missing local safe header".to_string()).into()); }; + let mut current_hash = sealed.hash(); + let mut header = sealed.into_inner(); // Check if the block number is in range. If not, we can fail early. if number > header.number { return Err(OracleProviderError::BlockNumberPastHead(number, header.number)); } - // Set the chain ID for the trie hints, and explicitly drop the lock. - let mut chain_id_lock = self.chain_id.write(); - *chain_id_lock = Some(chain_id); - drop(chain_id_lock); - // Walk back the block headers to the desired block number. let rollup_config = self.boot.rollup_config(chain_id).ok_or_else(|| { PreimageOracleError::Other("Missing rollup config for chain ID".to_string()) })?; + let hinter = self.scoped_hinter(chain_id); let mut linear_fallback = false; while header.number > number { @@ -142,19 +164,24 @@ where // If Isthmus is active, the EIP-2935 contract is used to perform leaping lookbacks // through consulting the ring buffer within the contract. If this // lookup fails for any reason, we fall back to linear walk back. - let block_hash = match eip_2935_history_lookup(&header, number, self, self).await { - Ok(hash) => hash, - Err(_) => { - // If the EIP-2935 lookup fails for any reason, attempt fallback to linear - // walk back. - linear_fallback = true; - continue; - } - }; + let block_hash = + match eip_2935_history_lookup(&header, number, current_hash, self, &hinter) + .await + { + Ok(hash) => hash, + Err(_) => { + // If the EIP-2935 lookup fails for any reason, attempt fallback to + // linear walk back. + linear_fallback = true; + continue; + } + }; + current_hash = block_hash; header = self.header_by_hash(chain_id, block_hash).await?; } else { // Walk back the block headers one-by-one until the desired block number is reached. + current_hash = header.parent_hash; header = self.header_by_hash(chain_id, header.parent_hash).await?; } } @@ -201,28 +228,24 @@ where } } -impl TrieHinter for OracleInteropProvider { +impl TrieHinter for ChainScopedHinter<'_, C> { type Error = OracleProviderError; fn hint_trie_node(&self, hash: B256) -> Result<(), Self::Error> { kona_proof::block_on(async move { HintType::L2StateNode .with_data(&[hash.as_slice()]) - .with_data( - self.chain_id.read().map_or_else(Vec::new, |id| id.to_be_bytes().to_vec()), - ) + .with_data(self.chain_id.to_be_bytes()) .send(self.oracle.as_ref()) .await }) } - fn hint_account_proof(&self, address: Address, block_number: u64) -> Result<(), Self::Error> { + fn hint_account_proof(&self, address: Address, block_hash: B256) -> Result<(), Self::Error> { kona_proof::block_on(async move { HintType::L2AccountProof - .with_data(&[block_number.to_be_bytes().as_ref(), address.as_slice()]) - .with_data( - self.chain_id.read().map_or_else(Vec::new, |id| id.to_be_bytes().to_vec()), - ) + .with_data(&[block_hash.as_slice(), address.as_slice()]) + .with_data(self.chain_id.to_be_bytes()) .send(self.oracle.as_ref()) .await }) @@ -232,18 +255,16 @@ impl TrieHinter for OracleInteropProvider { &self, address: alloy_primitives::Address, slot: alloy_primitives::U256, - block_number: u64, + block_hash: B256, ) -> Result<(), Self::Error> { kona_proof::block_on(async move { HintType::L2AccountStorageProof .with_data(&[ - block_number.to_be_bytes().as_ref(), + block_hash.as_slice(), address.as_slice(), slot.to_be_bytes::<32>().as_ref(), ]) - .with_data( - self.chain_id.read().map_or_else(Vec::new, |id| id.to_be_bytes().to_vec()), - ) + .with_data(self.chain_id.to_be_bytes()) .send(self.oracle.as_ref()) .await }) @@ -260,11 +281,263 @@ impl TrieHinter for OracleInteropProvider { HintType::L2PayloadWitness .with_data(&[parent_hash.as_slice(), &encoded_attributes]) - .with_data( - self.chain_id.read().map_or_else(Vec::new, |id| id.to_be_bytes().to_vec()), - ) + .with_data(self.chain_id.to_be_bytes()) .send(self.oracle.as_ref()) .await }) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloc::{collections::BTreeMap, format, string::String, sync::Arc, vec::Vec}; + use alloy_consensus::Header; + use alloy_primitives::{B256, Sealable, keccak256}; + use alloy_rlp::Decodable; + use async_trait::async_trait; + use kona_genesis::RollupConfig; + use kona_interop::{DependencySet, SuperRoot}; + use kona_preimage::{ + HintWriterClient, PreimageKey, PreimageKeyType, PreimageOracleClient, + errors::PreimageOracleResult, + }; + use kona_proof::errors::OracleProviderError; + use kona_registry::HashMap; + + use crate::{BootInfo, PreState}; + + /// A single step in the EIP-2935 lookup chain. Each step contains the trie proof + /// data needed for one iteration and the block header it resolves to. + #[derive(serde::Deserialize)] + struct ProofStep { + account_proof: Vec, + storage_proof: Vec, + resolved_block_hash: String, + resolved_block_header_rlp: String, + } + + /// Fixture data for EIP-2935 `header_by_number` tests. Works for both single-iteration + /// (1 step) and multi-iteration (2+ steps) lookups via the `steps` array. + #[derive(serde::Deserialize)] + struct FixtureData { + chain_id: u64, + safe_head_number: u64, + safe_head_header_rlp: String, + target_block_number: u64, + target_block_hash: String, + steps: Vec, + } + + /// In-memory preimage oracle for testing. + #[derive(Debug, Clone)] + struct MockCommsClient { + preimages: BTreeMap<[u8; 32], Vec>, + } + + #[async_trait] + impl PreimageOracleClient for MockCommsClient { + async fn get(&self, key: PreimageKey) -> PreimageOracleResult> { + let raw_key: [u8; 32] = key.into(); + self.preimages.get(&raw_key).cloned().ok_or_else(|| { + kona_preimage::errors::PreimageOracleError::Other(format!( + "preimage not found: 0x{}", + alloy_primitives::hex::encode(raw_key) + )) + }) + } + + async fn get_exact(&self, key: PreimageKey, buf: &mut [u8]) -> PreimageOracleResult<()> { + let data = self.get(key).await?; + if data.len() != buf.len() { + return Err(kona_preimage::errors::PreimageOracleError::Other( + "length mismatch".into(), + )); + } + buf.copy_from_slice(&data); + Ok(()) + } + } + + #[async_trait] + impl HintWriterClient for MockCommsClient { + async fn write(&self, _hint: &str) -> PreimageOracleResult<()> { + Ok(()) + } + } + + fn hex_to_bytes(hex: &str) -> Vec { + let hex = hex.strip_prefix("0x").unwrap_or(hex); + alloy_primitives::hex::decode(hex).expect("valid hex") + } + + fn load_fixture_from(json: &str) -> (MockCommsClient, FixtureData) { + let fixture: FixtureData = serde_json::from_str(json).expect("valid fixture JSON"); + + let mut preimages = BTreeMap::new(); + + for step in &fixture.steps { + // Load account proof nodes (state trie). + for node_hex in &step.account_proof { + let node_bytes = hex_to_bytes(node_hex); + let hash = keccak256(&node_bytes); + let key: [u8; 32] = PreimageKey::new(*hash, PreimageKeyType::Keccak256).into(); + preimages.insert(key, node_bytes); + } + + // Load storage proof nodes. + for node_hex in &step.storage_proof { + let node_bytes = hex_to_bytes(node_hex); + let hash = keccak256(&node_bytes); + let key: [u8; 32] = PreimageKey::new(*hash, PreimageKeyType::Keccak256).into(); + preimages.insert(key, node_bytes); + } + + // Load resolved block header RLP, keyed by its block hash. + let header_rlp = hex_to_bytes(&step.resolved_block_header_rlp); + let block_hash: B256 = step.resolved_block_hash.parse().expect("valid hash"); + assert_eq!( + keccak256(&header_rlp), + block_hash, + "resolved header RLP hash must match resolved block hash" + ); + let key: [u8; 32] = PreimageKey::new(*block_hash, PreimageKeyType::Keccak256).into(); + preimages.insert(key, header_rlp); + } + + (MockCommsClient { preimages }, fixture) + } + + fn load_fixture() -> (MockCommsClient, FixtureData) { + load_fixture_from(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/testdata/eip2935_header_by_number.json" + ))) + } + + fn load_multi_iter_fixture() -> (MockCommsClient, FixtureData) { + load_fixture_from(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/testdata/eip2935_multi_iteration.json" + ))) + } + + fn build_provider( + client: MockCommsClient, + fixture: &FixtureData, + ) -> OracleInteropProvider { + let safe_head_rlp = hex_to_bytes(&fixture.safe_head_header_rlp); + let safe_head_header = + Header::decode(&mut safe_head_rlp.as_ref()).expect("valid safe head header RLP"); + assert_eq!(safe_head_header.number, fixture.safe_head_number); + + let sealed_safe_head = safe_head_header.seal_slow(); + + let mut local_safe_heads = HashMap::default(); + local_safe_heads.insert(fixture.chain_id, sealed_safe_head); + + let mut rollup_config = RollupConfig::default(); + rollup_config.hardforks.isthmus_time = Some(1746806401); + + let mut rollup_configs = HashMap::default(); + rollup_configs.insert(fixture.chain_id, rollup_config); + + let boot = BootInfo { + l1_head: B256::ZERO, + agreed_pre_state_commitment: B256::ZERO, + agreed_pre_state: PreState::SuperRoot(SuperRoot::new(0, Vec::new())), + claimed_post_state: B256::ZERO, + claimed_l2_timestamp: 0, + rollup_configs, + dependency_set: DependencySet { + dependencies: Default::default(), + override_message_expiry_window: None, + }, + l1_config: Default::default(), + }; + + OracleInteropProvider::new(Arc::new(client), boot, local_safe_heads) + } + + /// Tests the EIP-2935 fast path: looking up a block at the boundary of the 8,191-block + /// history window using real OP Mainnet trie proof data (1 step). + /// + /// Safe head: block 149,340,000 + /// Target: block 149,331,809 (exactly 8,191 blocks behind — at the EIP-2935 window boundary) + /// + /// Exercises the full path: `header_by_number` → Isthmus check → `eip_2935_history_lookup` + /// (real state + storage trie traversal) → `header_by_hash` → return. + #[tokio::test(flavor = "multi_thread")] + async fn test_header_by_number_eip2935_fast_path() { + let (client, fixture) = load_fixture(); + let provider = build_provider(client, &fixture); + let expected_hash: B256 = fixture.target_block_hash.parse().unwrap(); + + let header = provider + .header_by_number(fixture.chain_id, fixture.target_block_number) + .await + .expect("header_by_number should succeed via EIP-2935 fast path"); + + assert_eq!(header.hash_slow(), expected_hash); + assert_eq!(header.number, fixture.target_block_number); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_header_by_number_block_past_head() { + let (client, fixture) = load_fixture(); + let provider = build_provider(client, &fixture); + + let result = + provider.header_by_number(fixture.chain_id, fixture.safe_head_number + 1).await; + + assert!(matches!(result, Err(OracleProviderError::BlockNumberPastHead(_, _)))); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_header_by_number_same_block() { + let (client, fixture) = load_fixture(); + let provider = build_provider(client, &fixture); + + let header = provider + .header_by_number(fixture.chain_id, fixture.safe_head_number) + .await + .expect("looking up current head should succeed"); + + assert_eq!(header.number, fixture.safe_head_number); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_header_by_number_missing_chain_id() { + let (client, fixture) = load_fixture(); + let provider = build_provider(client, &fixture); + + let result = provider.header_by_number(999, 1).await; + assert!(result.is_err()); + } + + /// Tests multi-iteration EIP-2935 lookup: target block is beyond the 8,191-block window, + /// requiring two EIP-2935 lookups through an intermediate block (2 steps). + /// + /// Safe head: block 149,388,609 + /// Intermediate: block 149,380,418 (8,191 blocks behind safe head — oldest in window) + /// Target: block 149,380,413 (5 blocks before intermediate — 8,196 behind safe head) + /// + /// Iteration 1: `eip_2935_history_lookup(N, M)` → target outside window → + /// reads slot `N % 8191` from N's state → returns intermediate block hash. + /// Iteration 2: `eip_2935_history_lookup(I, M)` → target inside window → + /// reads slot `M % 8191` from I's state → returns target block hash. + #[tokio::test(flavor = "multi_thread")] + async fn test_header_by_number_eip2935_multi_iteration() { + let (client, fixture) = load_multi_iter_fixture(); + let provider = build_provider(client, &fixture); + let expected_hash: B256 = fixture.target_block_hash.parse().unwrap(); + + let header = provider + .header_by_number(fixture.chain_id, fixture.target_block_number) + .await + .expect("header_by_number should succeed via multi-iteration EIP-2935 lookup"); + + assert_eq!(header.hash_slow(), expected_hash); + assert_eq!(header.number, fixture.target_block_number); + } +} diff --git a/rust/kona/crates/proof/proof-interop/testdata/eip2935_header_by_number.json b/rust/kona/crates/proof/proof-interop/testdata/eip2935_header_by_number.json new file mode 100644 index 00000000000..c1578c77d32 --- /dev/null +++ b/rust/kona/crates/proof/proof-interop/testdata/eip2935_header_by_number.json @@ -0,0 +1,29 @@ +{ + "chain_id": 10, + "safe_head_number": 149340000, + "safe_head_header_rlp": "0xf90278a00f1e79f99b07055dcd154a07f5c9e031d82376245ed1ea9c57c51a77cab94f47a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a0d33a57c4ef25b6f6e678130676f4bc23ededcc3779b4aa2892ee2b206f05ee64a0bf291c53038ee2a18a6070e6e53c8274239ff24a67a465c8167860cea63f8445a0187d24ab5e0db5853f30ee3af2991f1aa3d1ecbb863f382897a2bb4c2948c174b901004a4a3a800100000100001003020002241240208a50020810104440104c0bab03a30908107002020006248011a5023824804488004401288006a0024000350241006020000400000aa1103c088208101020400426c0642030490c0008a14020419400404a02480001e0010043014018000c00400009042608041a84320109100c084010621c08001008220020800810028000a521002d41002000801012000211220000000c4000001050444200001080a0100007019c0008806723400000529201700a0a041408d10801ce810042248198840002229100ac4000241042206c005094080840940200244041000402a02184800280100009400e04090000000800808408e6bf608402625a0083b02d038469c158799101000000fa000000020000000000000000a0e6a9da4fc633345b0c2a14549c1f3fd71d40a51f9c5b1bdc17c0de2e2eb42613880000000000000000820270a08f2de381c023c5f877d4e4803ba5e67ef7b7e1f04ff92a2e736080351d86cfd2831adc9080a03adc2b394af24051856fd6b28450f8896a338b72920009102825e8edf0a8236da0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "target_block_number": 149331809, + "target_block_hash": "0xaef335ed650ddd1da95a19930fdeab946485540a23e41965f96252089a04ddfd", + "steps": [ + { + "account_proof": [ + "0xf90211a08860558e65d6ddb9d39e8c946d0977e622aa6d41a5473ee43d0bba710e17d935a014848d36a729d2f1c9c0278b6a1afa52bcb508691c1ab3405a7c0f37b78d410ca0f5652c4b35b90fb2da0dddca3238af9fb14f9a26dabf7276df7d048a0084a481a0a6daca3231cf63b749e21075c06dc8fd10873e0ed6e895a303a0181d29508aeba07ecefb885fa778960b5579754dd66fd762bc52ee47eb2f82b9c19ee5dc501b53a04307ad09520432537b95a07cbfe36b97480dcc1801e6f05510b4d48d60940d18a0f7589f8966c4d0cfdc7db8889ce5a275642a5ed3a7ac1dd865c075f834b36263a01811876a02389888d26b03d3b3b9f3e58733c27a41bab6c5a77f2c1447ac0ed9a0d2462c74899096bf1e44c10020b3f12275ef8544de69a1c107a164fbd1c22c87a030f03164ad38447f20dcb5497149985e4a0273742d4e94de3ea51f4ce5f45ed9a0885db62c4988cb0a3b06f61ed7c5b5062c2ba7e2b1bf421453a1a745049029f5a011b190f2299e690833ac8cd5b3f543a0c40a3ffdc82352aed23473c11471e03da0e1f096d304615c9c8a901c00a31d7ffc8b1f91f25c38857653003226de1bcb50a0d935738132b7b8454bf7e2c93dec7c67a21925d1cd6872f7b44eabbc18b9d4fea0e365c59ef76c5b81e27a69599e74cea0d324bc639cdde4f033f94481c72a3515a0471efc74977c366a7d5758bae2aa7d22de766a76aa9ad19be9fbbb0c8aeeefb580", + "0xf90211a0e576843707f5b5a140129d1b7d86719dee38a44de3b87237b00a4619b95f2a41a02c620dfa721f4eac4cf4110c0228c2cc5b476d07d551b38bbab8240a2168537fa02a100db39a025a061f303eaba600a09255d4b261e3e392e3da9ad6f6be4b29e6a097c723c78c5c1324af499fcd1b1abb3aeda7b8dc2349411df842bca6968d0ba3a0e97ca8242edec5d60d1cb24eb7ad53ae1b2864e07ebfcc98efd30ff54b65032ea0ad3e940df0232d2389693cdc95cf7973fd41347b1f80428f0fc66adda6b501a9a0983481862a322ace5789ef47b87a9e31eb8b8080fba8bc0073701269120adbf2a034b2f95312ee1591b0328497fef4d9b5b48fd8cc9ea35627a49f9411bee3c235a06ef486865e7a8957e14e4b427d7183459c31fe436c6aa2f76bca2144302fb84da01b14888fe30b92310dcc46962707f67da5421a3320cdd91b16dc4b56cd535d04a082eae82d8496bf3df35a7b9b731853e47257740b9f6d37b2c65fce92a1e74c3da07df6ba7510fdbf9d3dce82943ae109a1a6409dcdaea353cd6176ab6b0eb9c6f2a00723bf034ccc138b2251637c9c5257010f99d1a331e77556f8ac6759a7f150e6a04942a6a79ae46849074b671bc632750f5936923dae9b4e07eb06c9c438332dfba0dd257953fb2861eb8a900f5d65acc1d73ece0d9742987cc3b8a44df633c25005a09548acd4ae858fd378c4c9eef4ce69ea695242c85249c57bd70c93b683a0d8cc80", + "0xf90211a029b9b04d586cec613986acc073de92df0a2d114fe86a20f8ccbd281604533e0fa068542af068743d9e51e65a1bd8013b0442ad87c8055ec72d3dc905cdc221213ba0f046ae6b6af05fa07366ff3e6168d8f25bb2a4e6bdf7b32d54ef7ec49aaf6285a044b1a31d07f0ac93aa793a18558f789581836222f482a0c6e18570ac7ccce398a0069a9dbdc0a77a51f1c1882d021d533a8415bf83cc32a77430e8a2adfa90091ba0dd9946daae09d080f9404aa66435245fd9e09242c1dd6e4ebdc6851a661c772ea0c9cd03412e01bb797d722f4a7e87a9914cffd910c949ef60ee043036ed798099a044782d3ca9b4feb09adf8df5847e8025eb3f39d84c7c0d9bd1d8624e18b87808a0f4e41d768b0d82a2a56f8abc02319f0197fa71c091cf57243a7d43656481f1c7a0a5b1a606250c1dfca728076e7d0d6ec73d2e04accf8fe6246a630999a7cd0556a0626625b3d1f8cb1d7da5e47b58fef06a73919eba29594d636facf32cf737b931a0561baf4311512492cedde006fb3a574fc08d7fa95836c247b9894addb05af799a0da31b04f8e4d2048421004939d0306943b8fdf2f85a9d38eba29e601e418fec3a0cdecbb3f779311bc249da0f23c9d7097ae41e058f322217e7d7b1352268df74fa06e53887415453fba41a301057a1903cc9c8a31986516c04007a9745b3149fe9aa08f941fc7563fb94d7a08af3fcdd3f28e683dd03e3091c729023542070e8d759280", + "0xf90211a057fad76d5ea5e8f58dc03806b2b17b6864857c1f606da81f751024b8640accf1a0ef00e4b2783c6f2e8f7431a77c2b1093e7d1b296d27acb5509a31b6f19b31066a01b7b1862792a868d6590ee6a0162ea9b6b20e9c6778ee42360569fe3113e9a74a08a807db814c0175bac2e710d6113856f139cb9a6b1b9a42fd495c1f01236f210a08c67629ff495d2898d6f0cf4ce1c79264933eb7bd17e8d9cb3c136657da28644a03df3b0bb7cec807c3d238fff296197a2434e89f4581df4b88bf6e3d84e0054b3a0aaac7ad49d6490f31b1a30415861614eda985a4a7466534ac7104d0c83d4b622a03cbf2b530b73de6e0d166222271d773cfda426d8b2a6bc7b00ef48bc7debc49da087d67a1b00deaba360507f7407d7a98775269f220af5ecc53f33e5a26432bda1a0dfb044f1d51359999bff71e1a7dede829749546542cdd762aac6c09aec95dde1a022f008b3ae7411baa2db8f6d1e67dd5371e4d17f78fe01e8a511b2d6d41514caa0f8b8907d91dcb7f3946f4f8e6e0db5c64df653c2e54a7661a07be9018e36b94ca0b8fa49c7c5c65c6eb2f71b3ef217769e5247839bc2d8dd035463fd641e614884a0dc1e8ef3a8bfd4741905fc42949265238556ed40e157e85b5a474c059ef0fee0a0d9299e6e3135eb44282653a1660ef30e2c94cec3961684103fbbe8b440aabaf7a0484df52f3c941e1cd4188f54f9bb4453630e87baf825fe936229ffaf730e27b180", + "0xf90211a019c6b2a6578fe9d5a4e536db877d4ada2e51f57188432f7544e71afd42cdb251a0bc5891f64941a4cd079f7d71f6c8f19a5aa3f184fdfb990501037cea6b4ab93ca05e5af4fc9a89a4ebdaac8dcc349d46cf57137ec3665b21b09c4b6707016bf723a0fc4e7c0dcc33cd0d8c752c0c3f137d1d0c0ae974d88aa915cdd5d97684724394a008022aa2cfec06df782f8dbb8a577a70375b46689bcbd0db31f13fae1ab78959a0a53af19c8d3235dad2bd8fed08a940ff7b43d031a51d2081836f5d3413498e3ca06adad3808d143b3d517fc79e0dae83409e894264b939a89ca0bff58624a16a5ba0e107794d889da1fde79eb5aab5a6db954a593940ce742d9087ed572da1a191dea033b90c729aa65c8fe4a1b571934655f127eabc2c872585d522a1e5b6ac46748ca0756bb055730eaee96521bdb04f6f4a2ee610c60edb72253e3e81974faa6fcdeea09306cea554c54e36573aafe506034eddaeaa717cebd0cc96783b53d48822f9d9a0362ae8a30e9c2dfce3193213e54a17fe87d0365375f5a5af478eb6953c14b01ea033eda9e056fd3df23525f6b0f30f341efc53a59ea4fad42d3b44739d9a84a7cfa0504969cc6a570b7c75e73335b088a9e50b414fd45472da0b0632d6eb53e15787a05266ce7906ef73f39b52002322b48bd700f55e7cb0fc48c6dc0c7e4dfefb0abba0d5ca273be00f95d95aa654a71c31720c7f87b130e19d506118b11ad31c0f505d80", + "0xf90211a0d6e772ee666a547d57f11ddbdf65ccd708860aecc3e20625062b2ef988560735a0d6707c02552db1b120f5ca06b7c556c37ea5bc0e63576702ed7d7534368e3939a00b19d78196db2d476577ab95ebe8556b6a05db81ad0d3ec65c2654a31552f161a04005b16a41832698e8996834e54c9fe494b738eebf765404966c1ae101ef1545a09066cefe141b62ad518ae3b7d6f9d356dda6b7a9d9de4f314ce04ec440bc1376a03c24501dfab6fe1c608095f83b23138e1be42a850552409c50c38578d6b9ec50a003e051681ad49d5af6b77edea6b7dab556178b5d81e60c3f9b709366d286a40ea044336251a6e177f621ef8368d5dbf36e018e305a5af56730bf23015badc45a7da03b054a1ea445984a83cbcf3c299744ff0fdcea3a532f17dc4b78b8fb0a219cafa0dafd345febbc5ae8e4755cbc98165a8495cee45c17a9f55eee2a9ce728a82e2da01d9f05d6a54d4323f9f9bf0d2dd937020bd8fa5187a4749ad16d83ba71e98360a0625d298e33f3fb6b425bd1e692eaaf841ead55e4d9260c086c2e60fc4262cdd9a0d4365be3dde0f4b31dc517bf3b57f0dbd70b803934d9fafd224ad112139299bca0ed608040880100cf6fa16d84dbfe3ac7b30ca7f8a830ca4178f05153517bd24aa06ff216c4f498430ab3982066098fabe77f04d90d852dd72bdb1463eba19a108aa08612ff2d49678ff4df58d66e20fbef91361dca52a28f72da4cbdbefee301573b80", + "0xf9019180a00c61583833436a2e5165c1be085fd9adc8a3c3a04bfe30c97d1ebc2c4c2327a2a004201764b886aa0fe2e0625d9535b4208c1818e8a9bc08e6622b8c941171498b80a0de5cb0bab728cb8ada672f97fc29e67dd3ec2f218cea5019c0a293d20bbc3bffa07b5b602239c299d85c1d1f6e371e6b4b5b2d1a4d883d5c13ab41edbc1a6daf58a03ac33bb949bbcca3b42a9ccdcad1736f9d6309681eaf7d931df24ce804ce9c34a05db775dcc6570d8f5a85f448ae79f5e0be5f08fb4bb4a683ed3fd2cdfb262709a0e8f5897e48ba2c56b08e3b3d91f67367f388ca44a18ac044a5da690f807cd41ca0eab4f182ba3add6a44a541aacff89de8813923e70c76b2bc22fffc803d689579a0e04f3198f175934b490ca963b5d597d5600fc77e5fefccb604351f2f9b8264d8a0a1c86b3f19c01d9de1ca36f2305bd7b4701b30a2fc914c27435157dc13028cc38080a03d3a50e3363e41cb1c453023f272da16e94b26ba279c3f7717309db01ab01fbaa0298f090aed6f7513a017a2bdb0d6e526432eeae772761558dd121bc612275f1680", + "0xf8669d3e05dd69371c4dd2e871bce6e9f4124236825bb612ee18a45e5675be51b846f8440180a00ed4bf3edbb27b34fc1c0af0f9dbf01e54ce4cd2484ef76902b18d4b44504ca4a06e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d" + ], + "storage_proof": [ + "0xf90211a0fd1943d872269f05ab78fd961b09fb0ee232db75d776e5e401f67b59d116167ca0a7860b1291448dd72d358fef2979b3a6eb699f11971a00f544c489364f6a535fa089d1fa258526e193bae662b72da86259b52908f0174a9caa1939e0bccc1082a4a0c1b3d769cb59783a66bfea2993a905d90d5d073e51d7b1994c7278af45ad04aba08a757954384ecfc59ff81b3592c37572eb6612071fc6fb82f45f9481e8b68a98a0c348116a59e00062a0743ffe88a191500bbe8a6802e219c19f2b6e601f1b5b31a07df855f7dbbd78683f8f43abce74cb51825ff776ad990801e45ce90a9a559536a0f7c0f7a516121be749254b38a859e8f32a3ff71fb0fc3fb3b141cd57f2fdbef9a08d6768f944fb5af239cac358a1d8fedd11d77a514912126305c74cbfb96316c2a0b5e0e70b2d60b00b07234f86f5e0de684751713b420065bb4cafd3dc6369cec4a0f8aa850c08d276cfaf39bab7e89a05870b0ee0ae13d253f7c4b28bc914084330a0ba3d9228459490718fcb447dee6f44d9f117f68d71a56c862251a1b0071a0f64a03cb0b3df8624056909ae0e0d67e76c3f2da75a0f0c493a58aa434296b6f9bf11a0b1a66ab99f8e7122384f54067dd79d70e4bdb5b5f7ae237b0da65130cbd1c051a062f780a06ae24a1e1ce7aeadb33ae029511876390907927c5997223336ba026aa0b694fd4cc7772596892735e6fb1a0529d3731abcdc7ca8874b310f0b81fea09280", + "0xf90211a0c547813e64b0c8cea11f42ffc1abe8c222b5a6127e258f59e5524594138d8509a07762e9386f8af9b61c9fc1e42ca998fcba1d81719e14cd6236b14c343fb25fa3a0c44a550d5292194b71d7b361aafc5d5e45352e4a4de56ac677e3045820a84a2ba042c060fdea37b64d780e76b187294063613f7af3f4c7ceba1128d4253b25ee88a00f9a5783ca78ecc8cceaba98524cbd84d5e6872b19b6747d79c295430ac6d78fa02b74b23934a5b037ecd1258f320283b75b8d9689702493a40685b54154ed581ba0a119b17a683dd46533ae4e697d4555d6b932e4a4a4edb751a75f0ca2332c7533a081eba60bc856ed930f5218320582979d228121698f235ba848eea3378afbb225a003a835dbb903cfc2230000657f8201efad35b6172be75167eff5422f0f3d328fa0218fe5828780df5955418c046ebce7d3599522fc0efd5b06cd506854325880e5a04d9f53c0dc984dcf5598f1252186e613546e4e81417be92e486cf966b4cca8aba0b02ddc6292109d9ab09268a2427a39f7f993a4f97d6a6346de463c21a4b8a9c7a0ae028580d7626be4d8605c9c46ddf47b138a1140b4c365edb74570797d75f1a7a046827c19cfd3f628220c5937e1b6a58a07d350516b0077ce75187598fd8aa8bfa0aedaad4212fb930690690edbed4ba3f53a0fda2cc30267053d7a465a85c5d2c1a0ba8e6e6c76f75979f8485000823e0ee808b71e5f7048dc59c2a46367b5fd36d380", + "0xf90191a0caad99a3521ef26c0f3550d8311e5f094f71f32915cbf13d33659683fd48262980a0ee6eb2059e186a7d658bf66f82820bb6ed19b40c7e828509f9405978c937227aa0a9123fae9fb9d99ceaf701ee4086bf8d3bd8de67c68ff939543d08711e21c4e280a0e113071c5fe4561750dd5281e07f93426c45d828e728ce20cd0b4896e188c2dfa099d01e862aba45d240d2094b4ec5c6dc9fdad274e055ea9d584395eff800eb11a02c8f1ed25b0c8c8a53f10f93f25d4e3c16661d41398c8cd3cdf047ff476cddbea01c179b94555d5015f201f5737acd8b40f73b8e445f50ed2683c9cce33db1bbcfa0d29ffa3a2af92fb215182a3ba20220cc41fc3ff876ff8a31ea6a5b31ba7900b2a0d25790a92daeaae78aae373a9e82cf5a6efbe671c9bbcdd862927ccb002a0566a0fc64b26ef56718c617d5cbcc53c43480b2f82ecb57dc311a0b64a536eb699ad0a03e020703de40fc5a4a7cb4e79188b0b6f901879b2b9ecd899f3b4062d38f98eda022ff4b295dbf5fdadc28ff8970f88bd7a55c0ab2a83364943c0367bae6ac610d808080", + "0xf8429f3b5f11fea4ac1f4814c55c438d84fd4dec22a03ca29a7e3c711ce90ff8e68ca1a0aef335ed650ddd1da95a19930fdeab946485540a23e41965f96252089a04ddfd" + ], + "resolved_block_hash": "0xaef335ed650ddd1da95a19930fdeab946485540a23e41965f96252089a04ddfd", + "resolved_block_header_rlp": "0xf90279a01a53e8c431546fa7e8c8b7ed8774b2817783f26cabaadcaf9ca531142bd0b9d1a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a026985fcc4180d1ee65b612320ca4340bf3e756702bb43d458d41642da1f831e4a0147f07057837a652c19cdeea480b2a4c522f41bee66ce1ccaf39ccde4b797617a000570e9e918fc33bff0a74c2ac9725365139368cf7c0379d32b8d8889ed49e64b90100524022a004010088008510a202c0012e00a0248a41090940120c4010465bfb000108100034000000003100100c42b0440044008240220492062281400021014130000000080010022328d08d032a3024a21010228900013468080000200020011880824962440040a284044105001801040a1002888420000002945120040000100821520f880800883a0041802859a28288cdc8002100000080a0e1a2421032028000001c4100310080802108001002200000020099460801330140810008940030486a0004860111100ae10502008898c8000103810408c00200482b8824000216084a50940280a28200908000223005800090148408208410000440004802808408e69f618402625a0084010ab99f8469c1187b9101000000fa000000020000000000000000a0c2182c6a6f4f30647942805622d5036b353b990a203ec4bf5ddee0549e6b00a8880000000000000000820204a0cd8f1f21bd2ec1a75d3b46f4ece3b56a4decbb2a3e7c2bfe63f655f48b83f7e28327f42080a0cb6e1bbcc810ffa29fc542181a3fde816346e83e8c25722ce2fd757fa5ed9e19a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ] +} diff --git a/rust/kona/crates/proof/proof-interop/testdata/eip2935_multi_iteration.json b/rust/kona/crates/proof/proof-interop/testdata/eip2935_multi_iteration.json new file mode 100644 index 00000000000..03d6a17739b --- /dev/null +++ b/rust/kona/crates/proof/proof-interop/testdata/eip2935_multi_iteration.json @@ -0,0 +1,51 @@ +{ + "chain_id": 10, + "safe_head_number": 149388609, + "safe_head_header_rlp": "0xf90279a03153947e2988af369c6e0d762f960e05843194e8f40b1a7a79028eba8fcebaf0a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a010775f13a6fcdfae4ffb365e47a3947f517f8c65da6e24e5e7b144827b76352fa08ea07352558de5f5bd730034acd724d63491a8c157bd12ae779f26517ca7fbf7a0396c949656d79db1b16deb3a2869ff8b06931d6f68eafdfd88047f4790f758d3b9010052513a819300409a005118924600426e81ac25cac104281218044011649bbb102508480134000800006001102403b14424440043400007c286480040c031224184100000440821c22b0080080288100271818002808009307868c011a020a0011000444c42453041b8004cc301041801450c00024a8c82010b00ce5249020089003144635c18014840280228804a91128190b51f0021440b0243a4062a820050868822002c4001c210000088001510802000481009d00448c1f30140c00028940032184a0c0c000330108b84046b3180d88420800489038c4001212002802442013e088b40b4021028008040014067210980888051c088480e8248289cc10882808408e77d418402625a00840104d61b8469c2d43b9101000000fa000000020000000000000000a00c03f608212281b8368ab289bb54880cb47fcce94ed824c5aa350e7bed5cf2aa880000000000000000820293a00c1ac7bb9bfece1d59faf9d80d3e358746692fbaeb9d4a975551b890ba3c46f983236e5080a014e1fc0a9ad76148f39fd9f028a63c012a93a0c899a9e86665dcbc8dc1e1043ca0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "target_block_number": 149380413, + "target_block_hash": "0x8d6354dbc825917e6be02105273f5bf26831d8c5917ae919a69592d276f6982e", + "steps": [ + { + "account_proof": [ + "0xf90211a0b844fd5d7a9a310535ddc4a6d35491ac7bb02102e2d19ff5d817e08525f874dea0f0832f393342d50e5a6d13f3a3bc22529845e9ac2c2e7260cd335dc7a225d6a0a07ef6a201dc16f64aa81d60f79b3307a82380f6f820e6fb7d6810b76ab7d0e24ea01dda85dbbdc5e03fb94d0c959bb5e89100dc3755e261e214bc2431c0ab1b2764a0f44c8a2376e139861a89a7675acc639856112bd2edc5ab399c42e44d25416c9ba06468809cc6dda944d19422203bfc71585158b4e6534c0e8900471b41005039f5a0e145137d58d19ecdc90aaa40ac48a5b651ef26a66b5ba9765fd1fad9f2c3aaffa051b27fac04d2f3a907183ac121f39a0bd64a87deb7055bf275c642a5c69aede9a00cac842e1c9110e88e6585881a2d993a94cdf65b8c6828bdae5ad7ceb44c16bea0e8392178bc3bc083546a4288835f568634b71ee2c1dd99db9935358594a44dd3a0756c47d599d3c309296c31241d5994446436e11420be0699fce792473b1dfccfa0d93c9c15842b12a38f1e4f74e5b715ea98038ed275a8da4c1c53cb60509b462ea0b8b37c7df8ed0fc6896bf35b5c778d3ef6791b4007975ffc35003e7a371e2238a05fd65d139246efd5c7a06a80cbf69117d7b29cd8c8356db47619e335246ca728a0efeed0c603c5af6e006d723983fbca80ffea6da82bb071e7522a81b304451895a0af682204e3e66911ef21d1f3a9abcd6ff30b72358d1b22e77465f23401ab388d80", + "0xf90211a06a04f70c5d5357d26af5b58b1f0e88fad402a8a430cb5af0b37d805bf771d4c7a08e64315f1be8dc8d7020617ce84d78a18429949671f7873b30aed70590dfbbe6a0d66885bca0755b71d394f22b19519e757d8f0c2bfd2e3da099b8ff36128fa99fa0bf58b253002743cafc2c38d900d88a14a3b4a369fa0a5bea6dd615a0a4244b3ba0157323de63f8e32b8d6b02962a07c7ce4c4c0b544bf3431af6c833f0c63e3e0fa075beff5946d614156d6b0af58ffd6bdb54a3b24e40bc19b6329e79cafc26484da0d88067fcae9a7cd675b431e2c220ceb03ef0fd3cc602022eb5cf2597130706d3a09daea8f040bb91ac7e5863f22b74ca5aba43ec67972335297babe63f36a3c9eca0ff16959317535455c1c9a76899e2c26fcbe8a2a561da3a274666cd850cfca165a06aca7fc67b54fd526136eecf40314c70744a2d563bf7360a11d7c579f11407faa09e935ff383de7324dacc96d94d16b41869bc453076ece2b360f1ea6e119b7c1fa0df2c4bc4ae3fbb285be53d154e44d6a4702414fd95a5a196764ab96e2f889aa1a0a3af96438258ccffd9481eca3b7d4b130b95047f2b37e26b9a282fddf5b621b8a0b10b8d022fcf11064b20239e7e842cf36e0910df5f137263709ca8863128415fa045fa5e6f114b0f06b4f35eb9464a6e77b1ae6ae7749afc31d67d1d0a012ac40ca0a2ff59d9f1ff4b69479109919882e0b54cfc7c5957abd8970eeb12c02fa12ba180", + "0xf90211a0e0cfd88a70759e224a64b2dbe9955c87094770ae6779201b50a553d6308c366ea08f46367c3f06569f5012d56ee9c1fd4feb6270b8c1ece075d889b34c0b8fe4e3a0f5eb8466fbe2a508145998b0030a086babafa4c7a6bbb1839bc3cba0ac9af6bda05c2876d4e0539772f47a815720622d1a0fb4dd3ced2f371c5ee83556e4b0724da099fb0cd923be6c30523f02a1d315c0431239e163ee5be9b26f844e2f12b564aaa01d55afb5583248f40174eb796c475bd465ae073f5a81c0727c61f28e85d3e197a0ec133dc5452a26705f63993602e9feb7f49b918244aeb990f0892ea6ad47b809a01828c75ccec7edc835e252501aaf3739cfdfa83a938d1a5d3aacab6ae69c2b38a0f6ced5c8dc14521a0257e1b5f3d8a05b342fe9f7c8ec38c327f6b2612eef76f4a06533e851e9095ad391ec37f6ae2c5928ccdb213d7d619f070123c9f0dfd9a263a044dadcfc844609ae4104b9b24b843572de5c858e0fa232c7e8746c1602d84a4ea0978cfa57bda1631532df1b7b3764ebe32aca5bdf75cafc6326477864a5d0e80ba0c61198d37f6d843212cf7e550b5504fea7ac69c3f29680a18e99dffe1ccb5cb9a07efb2861f35a6638bb7bf47c9d99f7b3eb0def0f7b73fe93888acc0a50dc3abaa0baec3ff1051f3f25b2e2f900be9f8c3c558db8001dd5b3b1f978b2ccb7114cb4a0026ee50324a03f7f71b754bcb8e648c2a7d6076657d752fcfd12e95f225c24a280", + "0xf90211a054867aaad3b53352249260691677b98ce5798c900660e290dd9a6c13f32d1a1ba0ef00e4b2783c6f2e8f7431a77c2b1093e7d1b296d27acb5509a31b6f19b31066a0e5499f5ba647ad324a437f07ec17e0f2cf1a068dee8cde2cccf9962894d339e3a0d39d3bfdd6a0bd43ee8b07618438816ab49f152a8941c39e4edb4f3cb7ab2f69a07f096bae9c1c7e1e673942ed15061b790d42120f97ef8185836b67a6e178fcdaa040359089b62f92a363aefa18348d0c696cc7c304282109692e93015551806de6a027a5b2480bfeb90961bf2abdcc74ac48835248b8f915ea9d826767f46000e974a080e894e5ad71d4a60d4cc90f5072ceba91ef9970f1fb16097885e590a0d522a7a0c8bc2b6cf4fd02d024338d8deafdcd2fe4ffe74b029bba964e135348172feddca0dfb044f1d51359999bff71e1a7dede829749546542cdd762aac6c09aec95dde1a0f955bdc6ea35912bf0682cb2a1c6ab5c24d620d57cc98cf64fd866199a07d84aa083714a5b1d319e1564ae9c671c188ed79486a69abf6f11006b91519acd48fd5da04a7d596c24206cb528bacb8490871c9f4dcf7bf88f8e492d78d7835882298082a006e3e7e6f0009531cae63794ad586ebe5227354ae1e2dbc7bfae2d0a00be27eda058ead203ad5f0aeef393763ffbbb33ba19efe7af92de7ad0bdde80765baac018a0484df52f3c941e1cd4188f54f9bb4453630e87baf825fe936229ffaf730e27b180", + "0xf90211a019c6b2a6578fe9d5a4e536db877d4ada2e51f57188432f7544e71afd42cdb251a0bc5891f64941a4cd079f7d71f6c8f19a5aa3f184fdfb990501037cea6b4ab93ca05e5af4fc9a89a4ebdaac8dcc349d46cf57137ec3665b21b09c4b6707016bf723a0fc4e7c0dcc33cd0d8c752c0c3f137d1d0c0ae974d88aa915cdd5d97684724394a008022aa2cfec06df782f8dbb8a577a70375b46689bcbd0db31f13fae1ab78959a0680a4f68c9df5309dbbb9a8e883d5dde0924cf58aab88799805096d01bf916cfa06adad3808d143b3d517fc79e0dae83409e894264b939a89ca0bff58624a16a5ba0cf17f53ab5b7b4b866f40f2700fa05bb9cdd4842a93441d1eecbb84e0d7b39eca033b90c729aa65c8fe4a1b571934655f127eabc2c872585d522a1e5b6ac46748ca0756bb055730eaee96521bdb04f6f4a2ee610c60edb72253e3e81974faa6fcdeea09306cea554c54e36573aafe506034eddaeaa717cebd0cc96783b53d48822f9d9a0362ae8a30e9c2dfce3193213e54a17fe87d0365375f5a5af478eb6953c14b01ea033eda9e056fd3df23525f6b0f30f341efc53a59ea4fad42d3b44739d9a84a7cfa0504969cc6a570b7c75e73335b088a9e50b414fd45472da0b0632d6eb53e15787a05266ce7906ef73f39b52002322b48bd700f55e7cb0fc48c6dc0c7e4dfefb0abba0d5ca273be00f95d95aa654a71c31720c7f87b130e19d506118b11ad31c0f505d80", + "0xf90211a0d6e772ee666a547d57f11ddbdf65ccd708860aecc3e20625062b2ef988560735a0d6707c02552db1b120f5ca06b7c556c37ea5bc0e63576702ed7d7534368e3939a00b19d78196db2d476577ab95ebe8556b6a05db81ad0d3ec65c2654a31552f161a04005b16a41832698e8996834e54c9fe494b738eebf765404966c1ae101ef1545a09066cefe141b62ad518ae3b7d6f9d356dda6b7a9d9de4f314ce04ec440bc1376a03c24501dfab6fe1c608095f83b23138e1be42a850552409c50c38578d6b9ec50a003e051681ad49d5af6b77edea6b7dab556178b5d81e60c3f9b709366d286a40ea0babb7132212ac658aa1ca14f3f27b5b7cd3f8c42893fa15091e94b8b963b8d19a03b054a1ea445984a83cbcf3c299744ff0fdcea3a532f17dc4b78b8fb0a219cafa0dafd345febbc5ae8e4755cbc98165a8495cee45c17a9f55eee2a9ce728a82e2da01d9f05d6a54d4323f9f9bf0d2dd937020bd8fa5187a4749ad16d83ba71e98360a0625d298e33f3fb6b425bd1e692eaaf841ead55e4d9260c086c2e60fc4262cdd9a0d4365be3dde0f4b31dc517bf3b57f0dbd70b803934d9fafd224ad112139299bca0ed608040880100cf6fa16d84dbfe3ac7b30ca7f8a830ca4178f05153517bd24aa06ff216c4f498430ab3982066098fabe77f04d90d852dd72bdb1463eba19a108aa08612ff2d49678ff4df58d66e20fbef91361dca52a28f72da4cbdbefee301573b80", + "0xf9019180a00c61583833436a2e5165c1be085fd9adc8a3c3a04bfe30c97d1ebc2c4c2327a2a004201764b886aa0fe2e0625d9535b4208c1818e8a9bc08e6622b8c941171498b80a0de5cb0bab728cb8ada672f97fc29e67dd3ec2f218cea5019c0a293d20bbc3bffa07b5b602239c299d85c1d1f6e371e6b4b5b2d1a4d883d5c13ab41edbc1a6daf58a03ac33bb949bbcca3b42a9ccdcad1736f9d6309681eaf7d931df24ce804ce9c34a05db775dcc6570d8f5a85f448ae79f5e0be5f08fb4bb4a683ed3fd2cdfb262709a0e8f5897e48ba2c56b08e3b3d91f67367f388ca44a18ac044a5da690f807cd41ca0eab4f182ba3add6a44a541aacff89de8813923e70c76b2bc22fffc803d689579a0e04f3198f175934b490ca963b5d597d5600fc77e5fefccb604351f2f9b8264d8a01495955950b16dcdf9ff6bf662da3dbe40722ed8d5a52e39f93f632e8335688e8080a03d3a50e3363e41cb1c453023f272da16e94b26ba279c3f7717309db01ab01fbaa0298f090aed6f7513a017a2bdb0d6e526432eeae772761558dd121bc612275f1680", + "0xf8669d3e05dd69371c4dd2e871bce6e9f4124236825bb612ee18a45e5675be51b846f8440180a0ee86ad540cbbd9c9df3f267a6d39aa6ea8c823949c23afcbce987657d25830b0a06e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d" + ], + "storage_proof": [ + "0xf90211a044063dbde506aaa087df2d6e4e2312500dca0969013d2ef47e8e41b15a4025c7a06a6e8a552ce1618f58b8cc6b1ae4e384e8908319f54bb1f61c34f24618ae592ea0dc376c91d81597b8b5eaef218cb95a4213312474f6fc0769b83ef352a4b13437a0d45bd202bcf4f7abc4778116c1941cce6df3dde5cfa3fb356a638c852cf63a04a01bdf5d34e5366bb48ba7ed6687072e9213637303f50324a5767beea37d82b0fda087dc3204a689a7d2f0519356436cf61482f3f29f47013667f0b1907dacf45b48a09826f024b0135e78f851961c2f0a7729aa8311e7cc9310d10400be28537df599a0358d96d98668426d8cd95b7a7386ca0bbe635993bfa109d778d2e3245cbb2059a0bcf980232da3fa3b8f6e0119739186a380478f885ce0979a5f1c6338c9db7acea0e07b80b52d243c1fb662f6507f30566c9997e2a671ce210c21cf7e0418fb6efda0cc8875605fa7ec7565a6cffa8e59b6967e737b5e1038fb7d829cf0f2f2fba895a09b7ca22aa3a917e2fe7c54a7b8c1e14a2facdea42185cb4a42ecd71417a9ff5ba015e4a07a58265fcab37ff3e1a5e7cf4cad9f4c546e8064cbb69eb6f732af6d4aa08265682021f4ed9b90b92927d660c9f8072b64bf2af0c7457041381520f81df8a0ab92443ca77a34d9be011fa930bbb9fedc13dd33f1a621ba9270214aaa899c07a0143ece886a0661a9a4851da68ed23126374a7cdfed932d16a442e80d49398dcf80", + "0xf90211a01f5f10a4552d9ce38577fd1ae6a8bf81efe90cab5be13861d14189a03b6543f8a063e9819e942b9dda0ae9d318fab07b232553a2fed2d508d59f5be863456e63f3a07daa5afbb74731d8bf775c0a9c731519ff95b103d077fc0d10179574a7ba2b3aa0b003f031874c0318e25a7a28ae59f36808daeb796aee18c306740d69fc8d6ee3a068ea1be1a4b8143d0b7201c5e499bb69231325708298141778f59c01248de515a02f8776e674da2448924aa1839392fc07cb91f7bbfddaee55c5f80c94042e8445a0eca3526102ec30a467e36edc5765bfd3d487ab861688289497217eef147faa4fa075b3b5195cc93ef8d8a763e3596f225bd8b8130ecbf0f7dc1e92c0c08342694aa0a21065a9b7a85a317e23ac9deb0c1c3e3b0245757a530aa37a2b1898dddcfe51a0b3301ec8e52dccec962cf7059b1e50f7e5f0d38994c49a3e6eca5f7e15025674a01c4d81e35b98343e76fc84ad9c3af332ccd31d29d731af43f44b463dc0842c06a09e0e8a36b3c757b8e4f0bc72c072aeccc97329435c1b27c2c5377f2ba3ea859ea0ecb19c1909abf1cb645edc243cbfd60ae857fa86f71a5b3e2bf1fc6619348247a06d092d55d752ddc7948863ea7d6c092f3cef407a97bdd06172ef2afddc726b63a070e039b4b8ee5f293430dd5f8fe7a6774bdc8b122d2aa1e50b6e8f859d7a2568a00300dbfd1814e244262192f6fed841020b0666a7af9a49e595f498e481c7217c80", + "0xf901d1a092f6df7fda427dd06ef3cbe2550920d4de9bdb60cff79d24141801282b52ff73a0baf51ec60f15e999847480c3b69040d3421791dfdd8c200d19ae78740739522ba06949d744f22807b31a4a404394f6cbc5832ca46beec1d1e8da3b433ab63ada47a004409a5e2336e02a051a8aeb39e720b7f6b8ce0cd13271e697ec80b0cc8af103a0f6a57686c4820b3d051169a2d085d6e0af22217092406ef13ddd4f86c48b54a480a0ea7afef10a0eb1237191b508c878c6acd1f0330133570642408d9fc4409fa2d1a08c7d499096b88c180b5394acde11ee38f38b43793cbdf703a5811aaa7631662ba0de2d710d1aad9b48bed7fb7d87de4addec2b62ec9af3fb9752ae383d129d0ed5a04c4b4680946a7c3dbce8f6d57654d800bfc8fc1b8e3282381034371b1d9aac39a03b23d8e65bdb18f121e0f5411999043459dca95e34a2e8d723d9fc5eb7843210a0d387c07d42f787621a378b03fe61d765f8cb7a30b8a129858f2d929f8e1b380ba0219a4f81591c673b4295373c71ae4ff8f3b2e9169a66c6707a8f3029de7dad55a04faef17ef736fe1017409961baea2578b0b7ad0cffc23b5b67d9a2a636bddccd80a03ead9a5383b2a00a85c666026f577fdfd93c9d55ff1d53412fb9f7b57c47896a80", + "0xf871a0be89e6e843ff4cf1acf53e282fba3cc2d935f92d93539a0cc814483c689383948080808080808080808080a03aea06e66849b6193fb52efb395ad51c8e0b197fea9a1c11643ec198dbdd8cffa04d487c1dbf0881a48683def007b20214d2e75e8d913e20d8982a9dd5b5de6854808080", + "0xf8429f20cdaa1b976905e76b11bac04f24b1406db93b60064d7e3b46247ebcec875fa1a05b0065462bb3b549e576bdbf6cc00b283ca53ddacd311abfdf07e989adf5947e" + ], + "resolved_block_hash": "0x5b0065462bb3b549e576bdbf6cc00b283ca53ddacd311abfdf07e989adf5947e", + "resolved_block_header_rlp": "0xf90279a05d849418983d7b3c077cbc8c818a3651a9005a6902883434df64d4c5e14e2316a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a042385dc624e5eb247aaad4074977ea0b5ed0244e6e2e8652412353024241afd4a0711146b49c5cdb0a7aa3b3c14bdd38202ee0bc2f876ca12e49b6edbd1fea6419a091804c17a1f3d89c8fd2c8900469a4e8ec14595e153846657087f01329a6cb5cb90100dbf7b2fd31b7ce31c56b52878201402e03bcea8a5d32ada95b05d15e4dffbb467f8edc3a3036274e07022f592c867d64314fc242dc3fefa6869b57c0c869e6c9ea5a19e8586a679e732775295f2b107230a4818f919508b66e48f2d9e413a2b1fe00e26bdacea86df92913c3a534990e979820a68e1d84a01138c63a1dc8586f1e285d73ac28cf41dc702e02c01e111e75b8f7d3f5bd64f88760f454eff2183d9e84e1025e5c491a81080f81a8381292ad48f1f828d12c0942a327791a427c9561b01b6f9c9c24b7fc038e84956e10fabcc6165517b9e3fc70102cbadb32a4dc07bdab2ff4beaa93a9fa884cc9e0e363d8b83be87ea71b581ebbdc89ba43aeaa808408e75d428402625a0084010a4b9d8469c2943d9101000000fa000000020000000000000000a046e44f41f5dd13333d9668e232eb0e023cfb4c996e4143fea714c380dc4e28bb880000000000000000820268a0621e9285216d74954870406ea54562838ac6eebcc9ad183fb60df9364f6634178357e40080a0feeba74a2440f7acf7b2613ec6e293367b8653bdac8d04360e9103d80cae8a1fa0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "account_proof": [ + "0xf90211a0780497964cbd8b87baeba6fecec084c09bfe3b35cdf0780f916bd7f73548ef0ca070e585352030a0edb7d0731f7417e092380cf2201013e442f76f217f51312356a050349717f4a1aff65e0c548e9eeb58e87e29ec75047b29f2e9348cbe1752e79fa03b54beff53788651c1e02c3fb9bed8b3823e9f4c1b2c594d97c65e3fd0f5bb4ba0ae827487e28e759f32d0779f23c87655c18851aedf548adf79878f5082e7b619a0cf90f4da1e1e18618f26c6e5b10fb4abdf95d057092061f14a943460e4a02280a0bf66a4f6b12dd42f18a3113b0a2e390007ceca86393665476e87d8bccaa8de51a0811724f3f9fcc0928d36b6cc8e56dfa7242f94210c27ba73f7f5d9e5f0812544a07768eb02ec0230e84bf7feed30246a5e2fbb95642f25dea58e94041fba38cc6da006beae090d25b2c47d98fdfae2162f55d102db46df322feb6531298c3b686651a02fc4bfd8738853e5f055928db436db524d8206e98d06a9591e30766f786e786ea017811b01105b098b1f8f20a3866852929dfa924446ebe7cace4d373248d1f902a02d1b32205fbce0274948bcd04e133f2bf82b0b3841e30da97b5e7cb8ad7f5093a096fff092e5b800d024eade6e05d87dd3890554d02989d148e0b7e59903263f72a049bda156525c7804f44b7484211595c6c5ad639ee16a1ef7951afe38d843c8aca0634a6d9ffa57ff639cec41685dc189242f690c22e6cc5936456f4ef99eff104b80", + "0xf90211a07aeabdfdaa535a72d0aeaed42f7f4068073846ac99bed00893ed08314ce21c6da00cc4467bb3f41eec41712bb5b819f8643b592fcca8623d81211a3454a25d0c89a02c3ef4cad7425e62a9f3495bd9e80225bdfde67cd1db386235a3d9e8e81bce95a0c696ba602d354943874dd58bf43f9a720fee4414857a17eddc366a27e8700a94a016a072f61de677a0be376625dd7f82500471198af3141b2e914687c3f7708774a01207ca9b0742f71fc61d0f4a30fff3185b0b9ce66c59697db86ee8d8a92e74a3a04a3c3d86cea5c9b440dde59115ab475f51bbde143f2f848a1560ae12eb56849da017cfd10eb57457e667c6a0a879e725c33e41816bc03eaee2e321ffa2eaf54a56a011cb2a52dd8b737fcd9f134138bee19270f74202cd09246c623e3c72945e0dc0a0d177f5f91b97330d160c0a42fef373cd5fd8fa35fc2683ee1b3de3c70adfbfbea06fabddc63ac814b5a36412392c7e513d126820736623ecfdf27ab4c55d690422a048b8acbf0ee1666d53bc3699a9135427fc04241e76576282dc87ba597e8d3e8da0873f140b32f5c1778548858fa689afbfb7ebd42c3917024133ed8be7880ec81ba055422b3dd327adf10bce96b074230399522c3b4047393ac61f1ac235d6684bf3a03819e93a1178f1faa42ea50bcd159cbc016c12cbb7f4a50a8a93948b8e9a5e0ba0eed9ee5b6e25881e41f726e3b2964ade12f00134cc13223eb2ce497b98c7d7f880", + "0xf90211a0f4fdc20968b0610a0a42ed0b8091a9f69039828c7dac422efc564097a6db8e54a0159166942bf90f8d1f32dea6b045f2b1758e7be5294155303787e8ae3051b55ca0280329a2ef25099e876da8b79f8878960b99ed40f73c7f0a0ae62144b0a3aca8a05c2876d4e0539772f47a815720622d1a0fb4dd3ced2f371c5ee83556e4b0724da0e5cec873f704b89b5e1b6913e4281684aa1bc99d213cfb40ad739f368a4507e8a07297c4e27635641ecd68403946a7964854ebf507f0b60c48773f94922fdeac78a097e44b4431d5fc82be1a719fd74262c53e910b91f732f483c31321d20e2bf3a0a0e9b445d129f929dd51911031eb9730af96d0cdbc17475cf4c5b549c1f3a2f17da0394e2108200d18dd55b4a47036567cebbffccbe3577242d77b5f5462af533249a01322a2dc324e4286d18872974f87a17b88c25a1192a48b71f48300666346e93da055f7be7af4bf3e6a93b294cf10bdc77574f9b0da16c4771943806b6772e947d3a0edceb5d217f39ef5e4c3e56513c220bae86241990381600d7cc86dfb18f22a7da0bc72cc93aaf2be02f238b8185237a37ca1f547318569840c7b3c20a71874438ba065752f15af0b4172e8bb400228aec4ce0695d13ffa8625c754395abd4bbd1435a013e31015608737ab0512831b20e5481d3e1e74ec5e497b026325544fb24d91a3a0d996f7044a02a383c7491a3af5b249c3039375933aa49b2704dcca330e60440180", + "0xf90211a054867aaad3b53352249260691677b98ce5798c900660e290dd9a6c13f32d1a1ba0ef00e4b2783c6f2e8f7431a77c2b1093e7d1b296d27acb5509a31b6f19b31066a0665d04a40d10bac0e98da138bc9ae7fc671dfb75fc286767169edfe584adf78aa0d39d3bfdd6a0bd43ee8b07618438816ab49f152a8941c39e4edb4f3cb7ab2f69a07f096bae9c1c7e1e673942ed15061b790d42120f97ef8185836b67a6e178fcdaa040359089b62f92a363aefa18348d0c696cc7c304282109692e93015551806de6a027a5b2480bfeb90961bf2abdcc74ac48835248b8f915ea9d826767f46000e974a080e894e5ad71d4a60d4cc90f5072ceba91ef9970f1fb16097885e590a0d522a7a0c8bc2b6cf4fd02d024338d8deafdcd2fe4ffe74b029bba964e135348172feddca0dfb044f1d51359999bff71e1a7dede829749546542cdd762aac6c09aec95dde1a0f955bdc6ea35912bf0682cb2a1c6ab5c24d620d57cc98cf64fd866199a07d84aa083714a5b1d319e1564ae9c671c188ed79486a69abf6f11006b91519acd48fd5da04a7d596c24206cb528bacb8490871c9f4dcf7bf88f8e492d78d7835882298082a0f8ced24e77ce0b13e0316d47e8888a3dbe523b617fd8dcae0963471def80b913a058ead203ad5f0aeef393763ffbbb33ba19efe7af92de7ad0bdde80765baac018a0484df52f3c941e1cd4188f54f9bb4453630e87baf825fe936229ffaf730e27b180", + "0xf90211a019c6b2a6578fe9d5a4e536db877d4ada2e51f57188432f7544e71afd42cdb251a0bc5891f64941a4cd079f7d71f6c8f19a5aa3f184fdfb990501037cea6b4ab93ca05e5af4fc9a89a4ebdaac8dcc349d46cf57137ec3665b21b09c4b6707016bf723a0fc4e7c0dcc33cd0d8c752c0c3f137d1d0c0ae974d88aa915cdd5d97684724394a008022aa2cfec06df782f8dbb8a577a70375b46689bcbd0db31f13fae1ab78959a0b48d22e9a4f156908c3624167f7fd989eb396e68330b99ff83b08aa432cc7907a06adad3808d143b3d517fc79e0dae83409e894264b939a89ca0bff58624a16a5ba0cf17f53ab5b7b4b866f40f2700fa05bb9cdd4842a93441d1eecbb84e0d7b39eca033b90c729aa65c8fe4a1b571934655f127eabc2c872585d522a1e5b6ac46748ca0756bb055730eaee96521bdb04f6f4a2ee610c60edb72253e3e81974faa6fcdeea09306cea554c54e36573aafe506034eddaeaa717cebd0cc96783b53d48822f9d9a0362ae8a30e9c2dfce3193213e54a17fe87d0365375f5a5af478eb6953c14b01ea033eda9e056fd3df23525f6b0f30f341efc53a59ea4fad42d3b44739d9a84a7cfa0504969cc6a570b7c75e73335b088a9e50b414fd45472da0b0632d6eb53e15787a05266ce7906ef73f39b52002322b48bd700f55e7cb0fc48c6dc0c7e4dfefb0abba0d5ca273be00f95d95aa654a71c31720c7f87b130e19d506118b11ad31c0f505d80", + "0xf90211a0d6e772ee666a547d57f11ddbdf65ccd708860aecc3e20625062b2ef988560735a0d6707c02552db1b120f5ca06b7c556c37ea5bc0e63576702ed7d7534368e3939a00b19d78196db2d476577ab95ebe8556b6a05db81ad0d3ec65c2654a31552f161a04005b16a41832698e8996834e54c9fe494b738eebf765404966c1ae101ef1545a09066cefe141b62ad518ae3b7d6f9d356dda6b7a9d9de4f314ce04ec440bc1376a03c24501dfab6fe1c608095f83b23138e1be42a850552409c50c38578d6b9ec50a003e051681ad49d5af6b77edea6b7dab556178b5d81e60c3f9b709366d286a40ea0a4396a827c0ab80e181368cdd633058c941c5510e62cf34855cfb37298bb27f4a03b054a1ea445984a83cbcf3c299744ff0fdcea3a532f17dc4b78b8fb0a219cafa0dafd345febbc5ae8e4755cbc98165a8495cee45c17a9f55eee2a9ce728a82e2da01d9f05d6a54d4323f9f9bf0d2dd937020bd8fa5187a4749ad16d83ba71e98360a0625d298e33f3fb6b425bd1e692eaaf841ead55e4d9260c086c2e60fc4262cdd9a0d4365be3dde0f4b31dc517bf3b57f0dbd70b803934d9fafd224ad112139299bca0ed608040880100cf6fa16d84dbfe3ac7b30ca7f8a830ca4178f05153517bd24aa06ff216c4f498430ab3982066098fabe77f04d90d852dd72bdb1463eba19a108aa08612ff2d49678ff4df58d66e20fbef91361dca52a28f72da4cbdbefee301573b80", + "0xf9019180a00c61583833436a2e5165c1be085fd9adc8a3c3a04bfe30c97d1ebc2c4c2327a2a004201764b886aa0fe2e0625d9535b4208c1818e8a9bc08e6622b8c941171498b80a0de5cb0bab728cb8ada672f97fc29e67dd3ec2f218cea5019c0a293d20bbc3bffa07b5b602239c299d85c1d1f6e371e6b4b5b2d1a4d883d5c13ab41edbc1a6daf58a03ac33bb949bbcca3b42a9ccdcad1736f9d6309681eaf7d931df24ce804ce9c34a05db775dcc6570d8f5a85f448ae79f5e0be5f08fb4bb4a683ed3fd2cdfb262709a0e8f5897e48ba2c56b08e3b3d91f67367f388ca44a18ac044a5da690f807cd41ca0eab4f182ba3add6a44a541aacff89de8813923e70c76b2bc22fffc803d689579a0e04f3198f175934b490ca963b5d597d5600fc77e5fefccb604351f2f9b8264d8a0b16fbb56ee8decd7737eed63d905fce9c89fd56312f022ffc6f15ce7f41d443c8080a03d3a50e3363e41cb1c453023f272da16e94b26ba279c3f7717309db01ab01fbaa0298f090aed6f7513a017a2bdb0d6e526432eeae772761558dd121bc612275f1680", + "0xf8669d3e05dd69371c4dd2e871bce6e9f4124236825bb612ee18a45e5675be51b846f8440180a0f6e65cf0cc5bb9e0788d684dcab99e892d8e726a9b04bfeee4d4e19f241dc12fa06e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d" + ], + "storage_proof": [ + "0xf90211a024cd055a1c13120d2256edb20976892065e6f23c6ee4dfb71b1951b4d93b9504a0ad2c1f2d4c208eed4af02d28bb02dd0732e28a65823853e32491db9313bed6d7a0ecdd82a1e887dceaa64140a5a71df0368dab0d53a959d36f124f694b33b57591a0c08db779ed692eb52ddd92b7bdaf6cc15fa331035dd8f050e5c752d82faa4cc7a01005a29930b4f8881c31d1f855ab7758c98800abf312a17eabbc0d70c4e4beada0e8b1285488faf095e3d543d850b798a140d320417f90495dd1a97a1cf55f3b20a0373c9520f99028bba04d752584d2b4a447b82c0eb01a74df0b304f17964e61e8a0922c2e0cea2ae99b0cf39068a74674e590c80cd6d28ba38f10512a2a37320325a0238f03cee74e467ffa19872e5c0e8e5d6e32c878a3573e0652ecb106f1b418e8a00d0e84ca203b7ec2aec2f79a8ef09dd56da0464aaec91c82c06d6c537671df62a0e46d3b34c8efb2f51bcf9302f1ef3bf34404adf5a0a7807040c1e451963e15a3a0f32219d6d945d5571e0b49e87316d11e139c91a307c85ad50dcd979466395fd0a0aecd798d66c67ca1abdb43a9693bb21efd1d6c42abb0698fffeac1896f0f0934a02f8262f002ba46ddd4cca61abe6e96d7e8e0c5b7315054c876e9790d40d93be1a0354e721eab338c28ee7f3afbe8b5ae8ff17c0c24f44d0342bc8baaf3be6ba984a0dc941f187b3a799e4d3d85ae38f051541426a8d7b39e717316aaacf538ee74b680", + "0xf90211a098956d2d306b6cb1e3dbe2868179922222b4008dbdf57dc384abdb1ed9b44e1ca0d8a66b03b06046add61ee04ddb403d4ee2b0f40e15029c32802af09a5de22559a04ea77b14d055e91a8443c1340298cae322a04c0d54a4baab55e834c8aecb65b0a0f1023c62753299e36738b2485f8774078fc9d874204d799a8ae7418f90e5504ca02165c847450fba80813d577c1025881d0aaa5e87f2aeb41349005f7142fb1575a0c8023393ba92f214b306fd1e3b0d3e5716eeabb0f3e4ceb92bcf8b9fc83f0eada0a3d431881277d057def6e943512701e7dcfc16fac20bad8967746e79a6314291a03934de718b446223723ea2b028abdc1e8b20b48eed167dbe426f247948f41c62a069ebea5acedc525741c45c8b1d5717645fa98d038cdaf33d21ab01eff5cd891fa0c9a6833acfca44a2564caf277b10c6b182bc5b9f8c96941bfec1362f02508923a0e76b576928e505b400bc5db43aa3b18ac76ba65f3738dc21cf3c1051407b5b01a04ee7f15b43f80a4cf07b5514191440b2964221abaa517745b5882973eee7cdf4a0c5a819caacb0be365b1652f01cdb97212a9f1c3f541c798d9d78d52dbf235056a07854a1cb47efeef498ee4b7edcd70d28a90eb16c788451a2f967628376985b12a0c0b0743538dfa039277ce6d05797b04b94bb318e5b2bcbb3c6d40983a2e30cf2a04a1dc5f517595d1aec32028c923fbb0fbf9e2645b58054265a7485392129d9fb80", + "0xf90191a0942a2cd8261cd8bdb2f135e961f19b5bfa8d58657310b56aa7f0191bff96a54ca0a01f10c12ae9941512f41f2f462ddc1c1de3c9fdc633b8185379fa7c95eb765da09cf9d20366e4dd70cb5c33aa09b41b92d9d36d671fc3f9e72ba276b5d643348fa0f60cdd6f5895d9b228d28d2d3cc548c4d3ea4e25ee43366030f1ebb1bc34ced2a0f825ab43b3a3d88d6be66efd4999d5b46314bd659b4af792b21208eb49e7c9fca0a04b911e811bc1d0b09d49dd45b81b91280df662f46b66adce9e182a33d92670a088c9e98976c9c46f1513a6e97a4c1bd03cc3453792aba167e482c97cab25fc9da0e2804a7805ecac49883e23b3b14426298fd8c93cf025e376af3d43a671e40de6a00664160fcc4d5430c2e199814752d3ff47650a51bbf9005327cc581f8f78426580a01dab5cf3994bbb363247e839e90d3025de76b9f6bffe39d4d284df2fe0cded89a0efa547992cf79e144d583e58405cd827f2d4643404095eca61a96b72be741ec58080a0e5e2533038e63b8c78350dd30c9653117a667521e9c509deedd7ff602607f5458080", + "0xf8918080a04c950dd5b5a3fb3145e019689d137b58e81e6f1c6492267d556c773c65a856ed8080808080808080a0d6ca65b88e9d7c20a4fc9e72134f5cd90dfec09a78db6e9284350003802855f98080a02edef4641e8a26154b4d0d233c8fe297bc1849804717d73e1691e38aef10ecaaa088201e94d878961905e6e18010389d7e137de6dacc5bea9aac3188f92040d28f80", + "0xf8429f20b3e55b58d097aa799170c771299ddaebee51eb4718e22ca79255c3cbe900a1a08d6354dbc825917e6be02105273f5bf26831d8c5917ae919a69592d276f6982e" + ], + "resolved_block_hash": "0x8d6354dbc825917e6be02105273f5bf26831d8c5917ae919a69592d276f6982e", + "resolved_block_header_rlp": "0xf90278a03c0781ec7ca17205cf8b389d22725c29118227f0cc941b7025571679a59dee9ba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944200000000000000000000000000000000000011a00bda88192e201db9ad3d611d45241dfec6efa3250badd9617d2138915fd310cba0878b0a1c30785e98b03f92be248acc7e193219ca7ea799ed0d37f1bd7a11b699a000f8d90b776dc1b01dd6c46043bce038018f41f52dbf035b66eeaa7edac33146b90100d07232801008401800001282430040041110228b4040290118044010448b3b00051818183200000003000811a4227524a04ec04044226ea00608065040610141480008000028208a24080009c2a8193226f080b68168043048484000a0042a2110000158624c0041b82001414112180805800000080da40001008432890a1008002004432c4884085a200200c00814122180958100234040102480540a820631c28000001c4000000040022d081011002000000480d0000820a71b400000009501301c1a0814004123010a84804200c098d420020281038c6004001202022400417c090941b402222c4000802002a121009a208150a4085c06424001169308a0808408e75d3d8402625a0083c555cc8469c294339101000000fa000000020000000000000000a046e44f41f5dd13333d9668e232eb0e023cfb4c996e4143fea714c380dc4e28bb880000000000000000820268a0621e9285216d74954870406ea54562838ac6eebcc9ad183fb60df9364f66341783296e4080a0feeba74a2440f7acf7b2613ec6e293367b8653bdac8d04360e9103d80cae8a1fa0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ] +} diff --git a/rust/kona/crates/proof/proof/Cargo.toml b/rust/kona/crates/proof/proof/Cargo.toml index 7e1a51ad3d0..5ffb9cc0210 100644 --- a/rust/kona/crates/proof/proof/Cargo.toml +++ b/rust/kona/crates/proof/proof/Cargo.toml @@ -16,6 +16,7 @@ workspace = true kona-mpt.workspace = true kona-derive.workspace = true kona-driver.workspace = true +kona-interop.workspace = true kona-preimage.workspace = true kona-executor.workspace = true kona-protocol.workspace = true @@ -75,6 +76,7 @@ std = [ "ark-ff/std", "dep:tokio", "kona-genesis/std", + "kona-interop/std", "kona-preimage/std", "kona-protocol/std", "kona-registry/std", diff --git a/rust/kona/crates/proof/proof/src/eip2935.rs b/rust/kona/crates/proof/proof/src/eip2935.rs index f0a05f129be..4c43d8ba399 100644 --- a/rust/kona/crates/proof/proof/src/eip2935.rs +++ b/rust/kona/crates/proof/proof/src/eip2935.rs @@ -23,6 +23,7 @@ const HISTORY_SERVE_WINDOW: u64 = 2u64.pow(13) - 1; pub async fn eip_2935_history_lookup( header: &Header, block_number: u64, + block_hash: B256, provider: &P, hinter: &H, ) -> Result @@ -42,7 +43,7 @@ where // Send a hint to fetch the storage slot proof prior to traversing the state / account tries. hinter - .hint_storage_proof(HISTORY_STORAGE_ADDRESS, U256::from(slot), header.number) + .hint_storage_proof(HISTORY_STORAGE_ADDRESS, U256::from(slot), block_hash) .map_err(|e| PreimageOracleError::Other(e.to_string()))?; // Fetch the trie account for the history accumulator. @@ -57,7 +58,19 @@ where let slot_key = Nibbles::unpack(keccak256(U256::from(slot).to_be_bytes::<32>())); let slot_value = storage_trie.open(&slot_key, provider)?.ok_or(TrieNodeError::KeyNotFound)?; - B256::decode(&mut slot_value.as_ref()).map_err(OracleProviderError::Rlp) + decode_history_storage_value(slot_value.as_ref()) +} + +fn decode_history_storage_value(slot_value: &[u8]) -> Result { + // Storage trie values are canonical RLP-encoded integers. Decoding as `U256` preserves that + // canonicality and restores leading zeroes before interpreting the word as a block hash. + let mut encoded = slot_value; + let value = U256::decode(&mut encoded).map_err(OracleProviderError::Rlp)?; + if !encoded.is_empty() { + return Err(OracleProviderError::Rlp(alloy_rlp::Error::UnexpectedLength)); + } + + Ok(B256::from(value.to_be_bytes::<32>())) } #[cfg(test)] @@ -86,8 +99,9 @@ mod tests { let mut storage_hb = HashBuilder::default().with_proof_retainer(ProofRetainer::new(vec![slot_key])); - let mut encoded = Vec::with_capacity(block_hash.length()); - block_hash.encode(&mut encoded); + let value = U256::from_be_slice(block_hash.as_slice()); + let mut encoded = Vec::with_capacity(value.length()); + value.encode(&mut encoded); storage_hb.add_leaf(slot_key, encoded.as_slice()); let storage_root = storage_hb.root(); @@ -134,6 +148,37 @@ mod tests { } } + #[rstest] + #[case::rlp_encoded_word({ + let value = U256::from_be_bytes([0x33; 32]); + let mut encoded = Vec::with_capacity(value.length()); + value.encode(&mut encoded); + encoded + }, B256::from([0x33; 32]))] + #[case::rlp_encoded_trimmed_word({ + let mut value = [0x44; 32]; + value[0] = 0; + let value = U256::from_be_bytes(value); + let mut encoded = Vec::with_capacity(value.length()); + value.encode(&mut encoded); + encoded + }, { + let mut expected = [0x44; 32]; + expected[0] = 0; + B256::from(expected) + })] + fn test_decode_history_storage_value(#[case] slot_value: Vec, #[case] expected: B256) { + let result = decode_history_storage_value(&slot_value).unwrap(); + assert_eq!(result, expected); + } + + #[rstest] + #[case::raw_full_word(vec![0xAA; 32])] + #[case::raw_trimmed_word(vec![0x22; 31])] + fn test_decode_history_storage_value_rejects_raw_values(#[case] slot_value: Vec) { + decode_history_storage_value(&slot_value).unwrap_err(); + } + #[rstest] #[case::block_number_in_window(1000, 999)] #[case::block_number_outside_window(9000, 100)] @@ -158,10 +203,16 @@ mod tests { ..Default::default() }; - let result = - eip_2935_history_lookup(&header, target_block_number, &provider, &NoopTrieHinter) - .await - .unwrap(); + let block_hash = B256::from([0xAA; 32]); + let result = eip_2935_history_lookup( + &header, + target_block_number, + block_hash, + &provider, + &NoopTrieHinter, + ) + .await + .unwrap(); assert_eq!(result, expected_hash); } diff --git a/rust/kona/crates/proof/proof/src/errors.rs b/rust/kona/crates/proof/proof/src/errors.rs index 6956e0280b3..c4c35e4d671 100644 --- a/rust/kona/crates/proof/proof/src/errors.rs +++ b/rust/kona/crates/proof/proof/src/errors.rs @@ -5,6 +5,7 @@ //! context about failures during proof generation and data retrieval. use alloc::string::{String, ToString}; +use alloy_primitives::B256; use kona_derive::{PipelineError, PipelineErrorKind}; use kona_mpt::{OrderedListWalkerError, TrieNodeError}; use kona_preimage::errors::PreimageOracleError; @@ -102,6 +103,10 @@ pub enum OracleProviderError { /// * `0` - The unknown chain ID that was encountered #[error("Unknown chain ID: {0}")] UnknownChainId(u64), + /// Output-root preimage carries an unrecognized version word. + /// Only `OutputVersionV0` (the zero word) is currently defined. + #[error("Unknown L2 output version: {0}")] + UnknownOutputVersion(B256), } impl From for PipelineErrorKind { diff --git a/rust/kona/crates/proof/proof/src/executor.rs b/rust/kona/crates/proof/proof/src/executor.rs index 3745c59c8bd..ff07cb7408d 100644 --- a/rust/kona/crates/proof/proof/src/executor.rs +++ b/rust/kona/crates/proof/proof/src/executor.rs @@ -2,8 +2,14 @@ use alloc::boxed::Box; use alloy_consensus::{Header, Sealed}; -use alloy_evm::{EvmFactory, FromRecoveredTx, FromTxWithEncoded, revm::context::BlockEnv}; -use alloy_op_evm::block::OpTxEnv; +use alloy_evm::{ + EvmFactory, FromRecoveredTx, FromTxWithEncoded, block::BlockExecutorFactory, + revm::context::BlockEnv, +}; +use alloy_op_evm::{ + OpBlockExecutionCtx, OpBlockExecutorFactory, + block::{OpAlloyReceiptBuilder, OpTxEnv}, +}; use alloy_primitives::B256; use async_trait::async_trait; use core::fmt::Debug; @@ -11,7 +17,7 @@ use kona_driver::Executor; use kona_executor::{BlockBuildingOutcome, StatelessL2Builder, TrieDBProvider}; use kona_genesis::RollupConfig; use kona_mpt::TrieHinter; -use op_alloy_consensus::OpTxEnvelope; +use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope}; use op_alloy_rpc_types_engine::OpPayloadAttributes; use op_revm::OpSpecId; @@ -61,6 +67,12 @@ where Evm: EvmFactory + Send + Sync + Clone + 'static, ::Tx: FromTxWithEncoded + FromRecoveredTx + OpTxEnv, + OpBlockExecutorFactory: for<'b> BlockExecutorFactory< + EvmFactory = Evm, + ExecutionCtx<'b> = OpBlockExecutionCtx, + Transaction = OpTxEnvelope, + Receipt = OpReceiptEnvelope, + >, { type Error = kona_executor::ExecutorError; diff --git a/rust/kona/crates/proof/proof/src/l1/chain_provider.rs b/rust/kona/crates/proof/proof/src/l1/chain_provider.rs index 8eaf2c7eb65..f9a0f6ec797 100644 --- a/rust/kona/crates/proof/proof/src/l1/chain_provider.rs +++ b/rust/kona/crates/proof/proof/src/l1/chain_provider.rs @@ -1,7 +1,7 @@ //! Contains the concrete implementation of the [`ChainProvider`] trait for the proof. use crate::{HintType, errors::OracleProviderError}; -use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Receipt, ReceiptEnvelope, TxEnvelope}; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::B256; @@ -19,12 +19,18 @@ pub struct OracleL1ChainProvider { pub l1_head: B256, /// The preimage oracle client. pub oracle: Arc, + /// Cache of [`BlockInfo`] by block number for ancestors of [`Self::l1_head`]. + /// + /// The L1 chain seen by this provider is fixed and canonical (rooted at `l1_head`), so + /// entries never need invalidation. Populated as a side effect of + /// [`Self::block_info_by_number`] walking back from the head. + block_info_by_number: BTreeMap, } impl OracleL1ChainProvider { /// Creates a new [`OracleL1ChainProvider`] with the given boot information and oracle client. pub const fn new(l1_head: B256, oracle: Arc) -> Self { - Self { l1_head, oracle } + Self { l1_head, oracle, block_info_by_number: BTreeMap::new() } } } @@ -42,25 +48,50 @@ impl ChainProvider for OracleL1ChainProvider { } async fn block_info_by_number(&mut self, block_number: u64) -> Result { - // Fetch the starting block header. - let mut header = self.header_by_hash(self.l1_head).await?; - - // Check if the block number is in range. If not, we can fail early. - if block_number > header.number { - return Err(OracleProviderError::BlockNumberPastHead(block_number, header.number)); + if let Some(cached) = self.block_info_by_number.get(&block_number) { + return Ok(*cached); } - // Walk back the block headers to the desired block number. - while header.number > block_number { - header = self.header_by_hash(header.parent_hash).await?; + // Start from the closest cached ancestor whose number is greater than the target, + // falling back to the L1 head when no usable cache entry exists. + let cached_ancestor = block_number + .checked_add(1) + .and_then(|n| self.block_info_by_number.range(n..).next().map(|(_, info)| *info)); + let mut current = match cached_ancestor { + Some(info) => info, + None => { + let header = self.header_by_hash(self.l1_head).await?; + if block_number > header.number { + return Err(OracleProviderError::BlockNumberPastHead( + block_number, + header.number, + )); + } + let info = BlockInfo { + hash: self.l1_head, + number: header.number, + parent_hash: header.parent_hash, + timestamp: header.timestamp, + }; + self.block_info_by_number.insert(info.number, info); + info + } + }; + + // Walk back the block headers to the desired block number, caching each visited block. + while current.number > block_number { + let parent_hash = current.parent_hash; + let header = self.header_by_hash(parent_hash).await?; + current = BlockInfo { + hash: parent_hash, + number: header.number, + parent_hash: header.parent_hash, + timestamp: header.timestamp, + }; + self.block_info_by_number.insert(current.number, current); } - Ok(BlockInfo { - hash: header.hash_slow(), - number: header.number, - parent_hash: header.parent_hash, - timestamp: header.timestamp, - }) + Ok(current) } async fn receipts_by_hash(&mut self, hash: B256) -> Result, Self::Error> { @@ -139,3 +170,137 @@ impl TrieProvider for OracleL1ChainProvider { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_rlp::Encodable; + use core::sync::atomic::{AtomicUsize, Ordering}; + use kona_preimage::{ + HintWriterClient, PreimageKey, PreimageKeyType, PreimageOracleClient, + errors::PreimageOracleResult, + }; + + /// A minimal in-memory [`CommsClient`] used to drive [`OracleL1ChainProvider`] in tests. + #[derive(Clone, Default)] + struct MockOracle { + preimages: Arc>>, + get_calls: Arc, + } + + #[async_trait] + impl PreimageOracleClient for MockOracle { + async fn get(&self, key: PreimageKey) -> PreimageOracleResult> { + self.get_calls.fetch_add(1, Ordering::SeqCst); + Ok(self.preimages.get(&key).expect("missing preimage in mock").clone()) + } + + async fn get_exact(&self, key: PreimageKey, buf: &mut [u8]) -> PreimageOracleResult<()> { + let v = self.get(key).await?; + buf.copy_from_slice(&v); + Ok(()) + } + } + + #[async_trait] + impl HintWriterClient for MockOracle { + async fn write(&self, _hint: &str) -> PreimageOracleResult<()> { + Ok(()) + } + } + + /// Build a linear chain of `n` headers and return the headers (oldest first) plus a + /// preimage map keyed by `Keccak256(header_hash)`. + fn build_chain(n: u64) -> (Vec
, BTreeMap>) { + let mut headers = Vec::with_capacity(n as usize); + let mut parent_hash = B256::ZERO; + for i in 0..n { + let header = + Header { number: i, parent_hash, timestamp: 1_000 + i, ..Default::default() }; + parent_hash = header.hash_slow(); + headers.push(header); + } + let mut preimages = BTreeMap::new(); + for h in &headers { + let mut rlp = Vec::new(); + h.encode(&mut rlp); + preimages.insert(PreimageKey::new(*h.hash_slow(), PreimageKeyType::Keccak256), rlp); + } + (headers, preimages) + } + + fn provider( + headers: &[Header], + preimages: BTreeMap>, + ) -> (OracleL1ChainProvider, Arc) { + let oracle = + MockOracle { preimages: Arc::new(preimages), get_calls: Arc::new(AtomicUsize::new(0)) }; + let calls = oracle.get_calls.clone(); + let head = headers.last().unwrap().hash_slow(); + (OracleL1ChainProvider::new(head, Arc::new(oracle)), calls) + } + + #[tokio::test] + async fn block_info_by_number_returns_correct_block() { + let (headers, preimages) = build_chain(10); + let (mut p, _) = provider(&headers, preimages); + + for target in 0..10 { + let info = p.block_info_by_number(target).await.unwrap(); + assert_eq!(info.number, target); + assert_eq!(info.hash, headers[target as usize].hash_slow()); + assert_eq!(info.timestamp, 1_000 + target); + } + } + + #[tokio::test] + async fn block_info_by_number_caches_repeat_lookups() { + let (headers, preimages) = build_chain(10); + let (mut p, calls) = provider(&headers, preimages); + + // First lookup walks from head (number 9) back to 0: 10 fetches. + p.block_info_by_number(0).await.unwrap(); + let after_first = calls.load(Ordering::SeqCst); + assert_eq!(after_first, 10); + + // Repeating any lookup we have already walked through hits the cache for free. + for target in 0..10 { + p.block_info_by_number(target).await.unwrap(); + } + assert_eq!(calls.load(Ordering::SeqCst), after_first); + } + + #[tokio::test] + async fn block_info_by_number_resumes_from_closest_cached_ancestor() { + let (headers, preimages) = build_chain(20); + let (mut p, calls) = provider(&headers, preimages); + + // Walk head (19) down to 15: 5 fetches. + p.block_info_by_number(15).await.unwrap(); + let after_first = calls.load(Ordering::SeqCst); + assert_eq!(after_first, 5); + + // Walk further back to 10. Should resume from cached block 15, not from the head, so + // only 5 additional fetches (15 -> 14 -> 13 -> 12 -> 11 -> 10). + p.block_info_by_number(10).await.unwrap(); + assert_eq!(calls.load(Ordering::SeqCst), after_first + 5); + } + + #[tokio::test] + async fn block_info_by_number_rejects_numbers_past_head() { + let (headers, preimages) = build_chain(5); + let (mut p, _) = provider(&headers, preimages); + + let err = p.block_info_by_number(99).await.unwrap_err(); + assert!(matches!(err, OracleProviderError::BlockNumberPastHead(99, 4))); + } + + #[tokio::test] + async fn block_info_by_number_handles_u64_max_without_overflow() { + let (headers, preimages) = build_chain(5); + let (mut p, _) = provider(&headers, preimages); + + let err = p.block_info_by_number(u64::MAX).await.unwrap_err(); + assert!(matches!(err, OracleProviderError::BlockNumberPastHead(n, 4) if n == u64::MAX)); + } +} diff --git a/rust/kona/crates/proof/proof/src/l1/pipeline.rs b/rust/kona/crates/proof/proof/src/l1/pipeline.rs index 5d954d5e8c0..c752f357df5 100644 --- a/rust/kona/crates/proof/proof/src/l1/pipeline.rs +++ b/rust/kona/crates/proof/proof/src/l1/pipeline.rs @@ -11,6 +11,7 @@ use kona_derive::{ }; use kona_driver::{DriverPipeline, PipelineCursor}; use kona_genesis::{L1ChainConfig, RollupConfig, SystemConfig}; +use kona_interop::DependencySet; use kona_preimage::CommsClient; use kona_protocol::{BlockInfo, L2BlockInfo, OpAttributesWithParent}; use spin::RwLock; @@ -48,6 +49,12 @@ where DA: DataAvailabilityProvider + Send + Sync + Debug + Clone, { /// Constructs a new oracle-backed derivation pipeline. + /// + /// `dependency_set` must be `Some` when the rollup config schedules the + /// Interop hardfork. The [`StatefulAttributesBuilder`] panics at + /// construction otherwise. Pass `None` only when interop is not scheduled + /// for this chain. + #[allow(clippy::too_many_arguments)] pub async fn new( cfg: Arc, l1_cfg: Arc, @@ -55,17 +62,17 @@ where caching_oracle: Arc, da_provider: DA, chain_provider: L1, - mut l2_chain_provider: L2, + l2_chain_provider: L2, + dependency_set: Option>, ) -> PipelineResult { let attributes = StatefulAttributesBuilder::new( cfg.clone(), l1_cfg, l2_chain_provider.clone(), chain_provider.clone(), + dependency_set, ); - let cfg_for_reset = cfg.clone(); - let mut pipeline = PipelineBuilder::new() .rollup_config(cfg) .dap_source(da_provider) @@ -77,19 +84,7 @@ where // Reset the pipeline to populate the initial system configuration in L1 Traversal. let l2_safe_head = *sync_start.read().l2_safe_head(); - pipeline - .signal( - ResetSignal { - l2_safe_head, - l1_origin: sync_start.read().origin(), - system_config: l2_chain_provider - .system_config_by_number(l2_safe_head.block_info.number, cfg_for_reset) - .await - .ok(), - } - .signal(), - ) - .await?; + pipeline.signal(Signal::Reset(ResetSignal { l2_safe_head })).await?; Ok(Self { pipeline, caching_oracle }) } diff --git a/rust/kona/crates/proof/proof/src/l2/chain_provider.rs b/rust/kona/crates/proof/proof/src/l2/chain_provider.rs index 678f2fea665..a6d4264c339 100644 --- a/rust/kona/crates/proof/proof/src/l2/chain_provider.rs +++ b/rust/kona/crates/proof/proof/src/l2/chain_provider.rs @@ -62,7 +62,8 @@ impl OracleL2ChainProvider { /// L2 safe head. async fn header_by_number(&self, block_number: u64) -> Result { // Fetch the starting block header. - let mut header = self.header_by_hash(self.l2_safe_head().await?)?; + let mut current_hash = self.l2_safe_head().await?; + let mut header = self.header_by_hash(current_hash)?; // Check if the block number is in range. If not, we can fail early. if block_number > header.number { @@ -76,7 +77,9 @@ impl OracleL2ChainProvider { // through consulting the ring buffer within the contract. If this // lookup fails for any reason, we fall back to linear walk back. let block_hash = - match eip_2935_history_lookup(&header, block_number, self, self).await { + match eip_2935_history_lookup(&header, block_number, current_hash, self, self) + .await + { Ok(hash) => hash, Err(_) => { // If the EIP-2935 lookup fails for any reason, attempt fallback to @@ -86,9 +89,11 @@ impl OracleL2ChainProvider { } }; + current_hash = block_hash; header = self.header_by_hash(block_hash)?; } else { // Walk back the block headers one-by-one until the desired block number is reached. + current_hash = header.parent_hash; header = self.header_by_hash(header.parent_hash)?; } } @@ -233,10 +238,10 @@ impl TrieHinter for OracleL2ChainProvider { }) } - fn hint_account_proof(&self, address: Address, block_number: u64) -> Result<(), Self::Error> { + fn hint_account_proof(&self, address: Address, block_hash: B256) -> Result<(), Self::Error> { crate::block_on(async move { HintType::L2AccountProof - .with_data(&[block_number.to_be_bytes().as_ref(), address.as_slice()]) + .with_data(&[block_hash.as_slice(), address.as_slice()]) .with_data(self.chain_id.map_or_else(Vec::new, |id| id.to_be_bytes().to_vec())) .send(self.oracle.as_ref()) .await @@ -247,12 +252,12 @@ impl TrieHinter for OracleL2ChainProvider { &self, address: alloy_primitives::Address, slot: alloy_primitives::U256, - block_number: u64, + block_hash: B256, ) -> Result<(), Self::Error> { crate::block_on(async move { HintType::L2AccountStorageProof .with_data(&[ - block_number.to_be_bytes().as_ref(), + block_hash.as_slice(), address.as_slice(), slot.to_be_bytes::<32>().as_ref(), ]) diff --git a/rust/kona/crates/proof/proof/src/sync.rs b/rust/kona/crates/proof/proof/src/sync.rs index 46dca4273ad..cc96fa07309 100644 --- a/rust/kona/crates/proof/proof/src/sync.rs +++ b/rust/kona/crates/proof/proof/src/sync.rs @@ -15,6 +15,7 @@ use spin::RwLock; pub async fn new_oracle_pipeline_cursor( rollup_config: &RollupConfig, safe_header: Sealed
, + agreed_l2_output_root: B256, chain_provider: &mut L1, l2_chain_provider: &mut L2, ) -> Result>, OracleProviderError> @@ -38,7 +39,7 @@ where // Construct the cursor. let mut cursor = PipelineCursor::new(channel_timeout, origin); - let tip = TipCursor::new(safe_head_info, safe_header, B256::ZERO); + let tip = TipCursor::new(safe_head_info, safe_header, agreed_l2_output_root); cursor.advance(origin, tip); // Wrap the cursor in a shared read-write lock diff --git a/rust/kona/crates/protocol/derive/Cargo.toml b/rust/kona/crates/protocol/derive/Cargo.toml index 4fe3311d4c4..59c5ce3e924 100644 --- a/rust/kona/crates/protocol/derive/Cargo.toml +++ b/rust/kona/crates/protocol/derive/Cargo.toml @@ -18,6 +18,7 @@ kona-macros.workspace = true kona-genesis.workspace = true kona-protocol.workspace = true kona-hardforks.workspace = true +kona-interop.workspace = true # Alloy alloy-eips.workspace = true @@ -64,6 +65,7 @@ serde = [ "alloy-rpc-types-engine/serde", "dep:serde", "kona-genesis/serde", + "kona-interop/serde", "kona-protocol/serde", "op-alloy-consensus/serde", "op-alloy-rpc-types-engine/serde", @@ -72,5 +74,6 @@ serde = [ test-utils = [ "dep:spin", "dep:tracing-subscriber", + "kona-interop/test-utils", "kona-protocol/test-utils", ] diff --git a/rust/kona/crates/protocol/derive/README.md b/rust/kona/crates/protocol/derive/README.md index 0ec3b9bd9b6..8a7e357731f 100644 --- a/rust/kona/crates/protocol/derive/README.md +++ b/rust/kona/crates/protocol/derive/README.md @@ -10,7 +10,7 @@ The intended way of working with `kona-derive` is to use the [`DerivationPipelin ```rust,ignore use std::sync::Arc; -use kona_genesis::RollupConfig; +use kona_genesis::{L1ChainConfig, RollupConfig}; use kona_derive::EthereumDataSource; use kona_derive::PipelineBuilder; use kona_derive::StatefulAttributesBuilder; @@ -19,12 +19,20 @@ let chain_provider = todo!(); let l2_chain_provider = todo!(); let blob_provider = todo!(); let l1_origin = todo!(); +let l1_cfg: Arc = todo!(); let cfg = Arc::new(RollupConfig::default()); +// `dependency_set` must be `Some` when the rollup uses interop. +// The constructor panics at build time otherwise; +// turning silent state divergence into a startup crash. Live-node +// operators populate it from `--interop.dependency-set`. +let dependency_set = None; let attributes = StatefulAttributesBuilder::new( cfg.clone(), + l1_cfg, l2_chain_provider.clone(), chain_provider.clone(), + dependency_set, ); let dap = EthereumDataSource::new( chain_provider.clone(), diff --git a/rust/kona/crates/protocol/derive/src/attributes/stateful.rs b/rust/kona/crates/protocol/derive/src/attributes/stateful.rs index a2e232b92c9..135fb92a58f 100644 --- a/rust/kona/crates/protocol/derive/src/attributes/stateful.rs +++ b/rust/kona/crates/protocol/derive/src/attributes/stateful.rs @@ -12,7 +12,8 @@ use alloy_rlp::Encodable; use alloy_rpc_types_engine::PayloadAttributes; use async_trait::async_trait; use kona_genesis::{L1ChainConfig, RollupConfig}; -use kona_hardforks::{Hardfork, Hardforks}; +use kona_hardforks::{Hardfork, Hardforks, Interop}; +use kona_interop::DependencySet; use kona_protocol::{ DEPOSIT_EVENT_ABI_HASH, L1BlockInfoTx, L2BlockInfo, Predeploys, decode_deposit, }; @@ -33,6 +34,9 @@ where config_fetcher: L2P, /// The L1 receipts fetcher. receipts_fetcher: L1P, + /// Optional interop dependency set. Required when interop is scheduled for the + /// chain (`rollup_cfg.hardforks.interop_time.is_some()`); ignored otherwise. + dependency_set: Option>, } impl StatefulAttributesBuilder @@ -40,18 +44,35 @@ where L1P: ChainProvider + Debug, L2P: L2ChainProvider + Debug, { - /// Create a new [`StatefulAttributesBuilder`] with the given epoch. - pub const fn new( + /// Create a new [`StatefulAttributesBuilder`]. + /// + /// # Panics + /// + /// Panics if `rcfg.hardforks.interop_time.is_some() && dependency_set.is_none()`. + /// A chain that has interop scheduled must have a dependency set provided, + /// otherwise the builder would silently diverge from op-node on interop + /// activation (emitting different number of upgrade transactions, or the wrong + /// ordering). See issue #19311. + pub fn new( rcfg: Arc, l1_cfg: Arc, sys_cfg_fetcher: L2P, receipts: L1P, + dependency_set: Option>, ) -> Self { + assert!( + !(rcfg.hardforks.interop_time.is_some() && dependency_set.is_none()), + "StatefulAttributesBuilder: interop is scheduled for this chain \ + (interop_time = {:?}) but no DependencySet was provided. \ + This would silently diverge from op-node on interop activation.", + rcfg.hardforks.interop_time, + ); Self { rollup_cfg: rcfg, l1_cfg, config_fetcher: sys_cfg_fetcher, receipts_fetcher: receipts, + dependency_set, } } } @@ -111,14 +132,16 @@ where derive_deposits(epoch.hash, &receipts, self.rollup_cfg.deposit_contract_address) .await .map_err(|e| PipelineError::BadEncoding(e).crit())?; - // Failure to update the system config is non-fatal: one or more receipts may - // contain malformed or invalid config update logs. Log a warning and continue. - if let Err(err) = sys_config.update_with_receipts( + let (updates, errors) = sys_config.update_with_receipts( &receipts, self.rollup_cfg.l1_system_config_address, self.rollup_cfg.is_ecotone_active(header.timestamp), - ) { - warn!(target: "attributes", ?err, "Failed to update system config at epoch {} (non-fatal, continuing)", epoch.number); + ); + for kind in &updates { + info!(target: "attributes", epoch = epoch.number, %kind, "Applied system config update"); + } + for err in &errors { + warn!(target: "attributes", ?err, epoch = epoch.number, "Malformed system config update (skipped)"); } l1_header = header; deposit_transactions = deposits; @@ -163,15 +186,32 @@ where { upgrade_transactions.append(&mut Hardforks::JOVIAN.txs().collect()); } + // Starting with Karst, upgrade transactions carry their own gas budget that is + // added to the block gas limit at the fork activation block. + let mut upgrade_gas: u64 = 0; if self.rollup_cfg.is_karst_active(next_l2_time) && !self.rollup_cfg.is_karst_active(l2_parent.block_info.timestamp) { upgrade_transactions.append(&mut Hardforks::KARST.txs().collect()); + upgrade_gas += Hardforks::KARST.upgrade_gas(); } if self.rollup_cfg.is_interop_active(next_l2_time) && !self.rollup_cfg.is_interop_active(l2_parent.block_info.timestamp) { + // Base 7 txs: always emitted on interop activation. upgrade_transactions.append(&mut Hardforks::INTEROP.txs().collect()); + + // CrossL2Inbox pair: only emitted when the dependency set has >1 chains. + // Matches op-node's gate at op-node/rollup/derive/attributes.go:178. + // `dependency_set` is guaranteed Some(_) here because the constructor + // panics when interop_time.is_some() && dependency_set.is_none(), and + // we only reach this branch when interop is active. + let dependency_set = self.dependency_set.as_ref().expect( + "dependency_set must be Some when interop is active — constructor invariant", + ); + if dependency_set.dependencies.len() > 1 { + upgrade_transactions.extend(Interop::cross_l2_inbox_txs()); + } } // Build and encode the L1 info transaction for the current payload. @@ -209,12 +249,14 @@ where suggested_fee_recipient: Predeploys::SEQUENCER_FEE_VAULT, parent_beacon_block_root: parent_beacon_root, withdrawals, + slot_number: None, }, transactions: Some(txs), no_tx_pool: Some(true), - gas_limit: Some(u64::from_be_bytes( - alloy_primitives::U64::from(sys_config.gas_limit).to_be_bytes(), - )), + gas_limit: Some( + u64::from_be_bytes(alloy_primitives::U64::from(sys_config.gas_limit).to_be_bytes()) + + upgrade_gas, + ), eip_1559_params: sys_config.eip_1559_params( &self.rollup_cfg, l2_parent.block_info.timestamp, @@ -384,7 +426,7 @@ mod tests { let header = Header::default(); let hash = header.hash_slow(); provider.insert_header(hash, header); - let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider); + let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, None); let epoch = BlockNumHash { hash, number: l2_number }; let l2_parent = L2BlockInfo { block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, @@ -410,7 +452,7 @@ mod tests { let header = Header::default(); let hash = header.hash_slow(); provider.insert_header(hash, header); - let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider); + let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, None); let epoch = BlockNumHash { hash, number: l2_number }; let l2_parent = L2BlockInfo { block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, @@ -437,7 +479,7 @@ mod tests { let header = Header { timestamp, ..Default::default() }; let hash = header.hash_slow(); provider.insert_header(hash, header); - let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider); + let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, None); let epoch = BlockNumHash { hash, number: l2_number }; let l2_parent = L2BlockInfo { block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, @@ -470,7 +512,7 @@ mod tests { let prev_randao = header.mix_hash; let hash = header.hash_slow(); provider.insert_header(hash, header); - let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider); + let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, None); let epoch = BlockNumHash { hash, number: l2_number }; let l2_parent = L2BlockInfo { block_info: BlockInfo { @@ -491,6 +533,7 @@ mod tests { suggested_fee_recipient: Predeploys::SEQUENCER_FEE_VAULT, parent_beacon_block_root: None, withdrawals: None, + slot_number: None, }, transactions: payload.transactions.clone(), no_tx_pool: Some(true), @@ -522,7 +565,7 @@ mod tests { let prev_randao = header.mix_hash; let hash = header.hash_slow(); provider.insert_header(hash, header); - let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider); + let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, None); let epoch = BlockNumHash { hash, number: l2_number }; let l2_parent = L2BlockInfo { block_info: BlockInfo { @@ -543,6 +586,7 @@ mod tests { suggested_fee_recipient: Predeploys::SEQUENCER_FEE_VAULT, parent_beacon_block_root: None, withdrawals: Some(Vec::default()), + slot_number: None, }, transactions: payload.transactions.clone(), no_tx_pool: Some(true), @@ -575,7 +619,7 @@ mod tests { let prev_randao = header.mix_hash; let hash = header.hash_slow(); provider.insert_header(hash, header); - let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider); + let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, None); let epoch = BlockNumHash { hash, number: l2_number }; let l2_parent = L2BlockInfo { block_info: BlockInfo { @@ -596,6 +640,7 @@ mod tests { suggested_fee_recipient: Predeploys::SEQUENCER_FEE_VAULT, parent_beacon_block_root, withdrawals: Some(vec![]), + slot_number: None, }, transactions: payload.transactions.clone(), no_tx_pool: Some(true), @@ -627,7 +672,7 @@ mod tests { let prev_randao = header.mix_hash; let hash = header.hash_slow(); provider.insert_header(hash, header); - let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider); + let mut builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, None); let epoch = BlockNumHash { hash, number: l2_number }; let l2_parent = L2BlockInfo { block_info: BlockInfo { @@ -648,6 +693,7 @@ mod tests { suggested_fee_recipient: Predeploys::SEQUENCER_FEE_VAULT, parent_beacon_block_root: Some(B256::ZERO), withdrawals: Some(vec![]), + slot_number: None, }, transactions: payload.transactions.clone(), no_tx_pool: Some(true), @@ -708,7 +754,8 @@ mod tests { let mut fetcher = TestSystemConfigL2Fetcher::default(); fetcher.insert(l2_number, SystemConfig::default()); - let mut builder = StatefulAttributesBuilder::new(cfg.clone(), l1_cfg, fetcher, provider); + let mut builder = + StatefulAttributesBuilder::new(cfg.clone(), l1_cfg, fetcher, provider, None); let epoch = BlockNumHash { hash: epoch_hash, number: 1 }; let l2_parent = L2BlockInfo { block_info: BlockInfo { @@ -729,4 +776,269 @@ mod tests { result.unwrap_err() ); } + + // --------------------------------------------------------------------------- + // Interop activation gating tests (issue #19311) + // + // These tests verify that kona's interop activation tx stream byte-matches + // op-node's output: 7 base txs always, + 2 CrossL2Inbox txs only when the + // dependency set has >1 chain. Go reference: op-node/rollup/derive/ + // attributes.go:171-185. + // --------------------------------------------------------------------------- + + fn build_interop_dep_set(chain_count: usize) -> Arc { + use alloc::collections::BTreeMap; + use kona_interop::ChainDependency; + // `ChainDependency` is zero-sized today; the set's cardinality is what + // gates the CrossL2Inbox predeploy pair, not the values. + #[allow(clippy::zero_sized_map_values)] + let dependencies = + (1..=chain_count as u64).map(|id| (id, ChainDependency {})).collect::>(); + Arc::new(DependencySet { dependencies, override_message_expiry_window: None }) + } + + /// Interop-activated, single-chain `dep-set` → base 7 interop txs, no `CrossL2Inbox` pair. + #[tokio::test] + async fn test_prepare_payload_with_interop_single_chain() { + let block_time = 2; + let timestamp = 100; + let cfg = Arc::new(RollupConfig { + block_time, + hardforks: HardForkConfig { + regolith_time: Some(50), + canyon_time: Some(50), + delta_time: Some(50), + ecotone_time: Some(50), + fjord_time: Some(50), + granite_time: Some(50), + holocene_time: Some(50), + isthmus_time: Some(50), + jovian_time: Some(50), + karst_time: Some(50), + interop_time: Some(102), + ..Default::default() + }, + ..Default::default() + }); + let l1_cfg = Arc::new(L1Config::sepolia().into()); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::default(); + fetcher.insert(l2_number, SystemConfig::default()); + let mut provider = TestChainProvider::default(); + let header = Header { timestamp, ..Default::default() }; + let hash = header.hash_slow(); + provider.insert_header(hash, header); + let dep_set = build_interop_dep_set(1); + let mut builder = + StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, Some(dep_set)); + let epoch = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + // 1 L1InfoTx + 7 interop base txs + 0 CrossL2Inbox txs = 8. + assert_eq!(payload.transactions.unwrap().len(), 1 + 7); + } + + /// Interop-activated, multi-chain `dep-set` → base 7 + `CrossL2Inbox` 2 = 9 upgrade txs. + #[tokio::test] + async fn test_prepare_payload_with_interop_multi_chain() { + let block_time = 2; + let timestamp = 100; + let cfg = Arc::new(RollupConfig { + block_time, + hardforks: HardForkConfig { + regolith_time: Some(50), + canyon_time: Some(50), + delta_time: Some(50), + ecotone_time: Some(50), + fjord_time: Some(50), + granite_time: Some(50), + holocene_time: Some(50), + isthmus_time: Some(50), + jovian_time: Some(50), + karst_time: Some(50), + interop_time: Some(102), + ..Default::default() + }, + ..Default::default() + }); + let l1_cfg = Arc::new(L1Config::sepolia().into()); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::default(); + fetcher.insert(l2_number, SystemConfig::default()); + let mut provider = TestChainProvider::default(); + let header = Header { timestamp, ..Default::default() }; + let hash = header.hash_slow(); + provider.insert_header(hash, header); + let dep_set = build_interop_dep_set(2); + let mut builder = + StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, Some(dep_set)); + let epoch = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + // 1 L1InfoTx + 7 interop base txs + 2 CrossL2Inbox txs = 10. + assert_eq!(payload.transactions.unwrap().len(), 1 + 7 + 2); + } + + /// Single-chain interop: ordering matches Go — base txs [0..7] equal the + /// golden `interop_base_tx_*.hex` files. + #[tokio::test] + async fn test_prepare_payload_with_interop_base_tx_ordering_matches_go() { + use alloy_primitives::hex; + let block_time = 2; + let timestamp = 100; + let cfg = Arc::new(RollupConfig { + block_time, + hardforks: HardForkConfig { + regolith_time: Some(50), + canyon_time: Some(50), + delta_time: Some(50), + ecotone_time: Some(50), + fjord_time: Some(50), + granite_time: Some(50), + holocene_time: Some(50), + isthmus_time: Some(50), + jovian_time: Some(50), + karst_time: Some(50), + interop_time: Some(102), + ..Default::default() + }, + ..Default::default() + }); + let l1_cfg = Arc::new(L1Config::sepolia().into()); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::default(); + fetcher.insert(l2_number, SystemConfig::default()); + let mut provider = TestChainProvider::default(); + let header = Header { timestamp, ..Default::default() }; + let hash = header.hash_slow(); + provider.insert_header(hash, header); + let dep_set = build_interop_dep_set(1); + let mut builder = + StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, Some(dep_set)); + let epoch = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + let txs = payload.transactions.unwrap(); + let expected: [&str; 7] = [ + include_str!("../../../hardforks/src/bytecode/interop_base_tx_0.hex"), + include_str!("../../../hardforks/src/bytecode/interop_base_tx_1.hex"), + include_str!("../../../hardforks/src/bytecode/interop_base_tx_2.hex"), + include_str!("../../../hardforks/src/bytecode/interop_base_tx_3.hex"), + include_str!("../../../hardforks/src/bytecode/interop_base_tx_4.hex"), + include_str!("../../../hardforks/src/bytecode/interop_base_tx_5.hex"), + include_str!("../../../hardforks/src/bytecode/interop_base_tx_6.hex"), + ]; + for (i, expected_hex) in expected.iter().enumerate() { + let expected_bytes: Bytes = hex::decode(expected_hex.replace('\n', "")).unwrap().into(); + // txs[0] is the L1 info tx; interop base txs start at txs[1]. + assert_eq!(txs[1 + i], expected_bytes, "interop base tx {i} diverges from Go"); + } + } + + /// Multi-chain interop: `CrossL2Inbox` pair appended at tail (positions 8..10), + /// matching op-node's ordering. + #[tokio::test] + async fn test_prepare_payload_with_interop_multi_chain_appends_cross_l2_inbox_at_tail() { + use alloy_primitives::hex; + let block_time = 2; + let timestamp = 100; + let cfg = Arc::new(RollupConfig { + block_time, + hardforks: HardForkConfig { + regolith_time: Some(50), + canyon_time: Some(50), + delta_time: Some(50), + ecotone_time: Some(50), + fjord_time: Some(50), + granite_time: Some(50), + holocene_time: Some(50), + isthmus_time: Some(50), + jovian_time: Some(50), + karst_time: Some(50), + interop_time: Some(102), + ..Default::default() + }, + ..Default::default() + }); + let l1_cfg = Arc::new(L1Config::sepolia().into()); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::default(); + fetcher.insert(l2_number, SystemConfig::default()); + let mut provider = TestChainProvider::default(); + let header = Header { timestamp, ..Default::default() }; + let hash = header.hash_slow(); + provider.insert_header(hash, header); + let dep_set = build_interop_dep_set(2); + let mut builder = + StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, Some(dep_set)); + let epoch = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + let txs = payload.transactions.unwrap(); + let expected_0: Bytes = hex::decode( + include_str!("../../../hardforks/src/bytecode/interop_cross_l2_inbox_tx_0.hex") + .replace('\n', ""), + ) + .unwrap() + .into(); + let expected_1: Bytes = hex::decode( + include_str!("../../../hardforks/src/bytecode/interop_cross_l2_inbox_tx_1.hex") + .replace('\n', ""), + ) + .unwrap() + .into(); + // txs[0]=L1Info, txs[1..=7]=base, txs[8..=9]=CrossL2Inbox. + assert_eq!(txs[8], expected_0); + assert_eq!(txs[9], expected_1); + } + + /// Constructor panics fast when interop is scheduled but no dependency set was provided. + #[test] + #[should_panic(expected = "no DependencySet was provided")] + fn test_stateful_builder_new_panics_when_interop_scheduled_without_dependency_set() { + let cfg = Arc::new(RollupConfig { + hardforks: HardForkConfig { interop_time: Some(100), ..Default::default() }, + ..Default::default() + }); + let l1_cfg = Arc::new(L1Config::sepolia().into()); + let fetcher = TestSystemConfigL2Fetcher::default(); + let provider = TestChainProvider::default(); + let _builder = StatefulAttributesBuilder::new(cfg, l1_cfg, fetcher, provider, None); + } } diff --git a/rust/kona/crates/protocol/derive/src/lib.rs b/rust/kona/crates/protocol/derive/src/lib.rs index 945f22d3ec9..6e23bd43a34 100644 --- a/rust/kona/crates/protocol/derive/src/lib.rs +++ b/rust/kona/crates/protocol/derive/src/lib.rs @@ -43,7 +43,7 @@ mod traits; pub use traits::{ AttributesBuilder, AttributesProvider, BatchValidationProviderDerive, BlobProvider, ChainProvider, DataAvailabilityProvider, L2ChainProvider, NextAttributes, OriginAdvancer, - OriginProvider, Pipeline, ResetProvider, SignalReceiver, + OriginProvider, Pipeline, ResetProvider, SignalReceiver, Stage, }; mod types; diff --git a/rust/kona/crates/protocol/derive/src/pipeline/core.rs b/rust/kona/crates/protocol/derive/src/pipeline/core.rs index 379581aa508..5abb534174f 100644 --- a/rust/kona/crates/protocol/derive/src/pipeline/core.rs +++ b/rust/kona/crates/protocol/derive/src/pipeline/core.rs @@ -2,10 +2,11 @@ use crate::{ ActivationSignal, L2ChainProvider, NextAttributes, OriginAdvancer, OriginProvider, Pipeline, - PipelineError, PipelineErrorKind, PipelineResult, ResetSignal, Signal, SignalReceiver, + PipelineError, PipelineErrorKind, PipelineResult, ResetSignal, Signal, SignalReceiver, Stage, StepResult, }; use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; use core::fmt::Debug; use kona_genesis::{RollupConfig, SystemConfig}; @@ -15,7 +16,7 @@ use kona_protocol::{BlockInfo, L2BlockInfo, OpAttributesWithParent}; #[derive(Debug)] pub struct DerivationPipeline where - S: NextAttributes + SignalReceiver + OriginProvider + OriginAdvancer + Debug + Send, + S: NextAttributes + Stage + OriginProvider + OriginAdvancer + Debug + Send, P: L2ChainProvider + Send + Sync + Debug, { /// A handle to the next attributes. @@ -32,7 +33,7 @@ where impl DerivationPipeline where - S: NextAttributes + SignalReceiver + OriginProvider + OriginAdvancer + Debug + Send, + S: NextAttributes + Stage + OriginProvider + OriginAdvancer + Debug + Send, P: L2ChainProvider + Send + Sync + Debug, { /// Creates a new instance of the [`DerivationPipeline`]. @@ -43,11 +44,50 @@ where ) -> Self { Self { attributes, prepared: VecDeque::new(), rollup_config, l2_chain_provider } } + + /// Walks back the L2 chain to find the correct L1 origin for a pipeline reset. + /// This mirrors op-node's `initialReset` algorithm. + pub(crate) async fn initial_reset( + &mut self, + l2_safe_head: L2BlockInfo, + ) -> Result<(BlockNumHash, SystemConfig), PipelineErrorKind> { + let l1_origin_number = l2_safe_head.l1_origin.number; + let channel_timeout = self.rollup_config.channel_timeout(l2_safe_head.block_info.timestamp); + + let mut current = l2_safe_head; + loop { + let before_l2_genesis = + current.block_info.number <= self.rollup_config.genesis.l2.number; + let before_l1_genesis = + current.l1_origin.number <= self.rollup_config.genesis.l1.number; + let before_channel_timeout = + current.l1_origin.number + channel_timeout <= l1_origin_number; + if before_l2_genesis || before_l1_genesis || before_channel_timeout { + break; + } + + current = self + .l2_chain_provider + .l2_block_info_by_number(current.block_info.number - 1) + .await + .map_err(|e| { + PipelineError::Provider(alloc::string::ToString::to_string(&e)).temp() + })?; + } + + let system_config = self + .l2_chain_provider + .system_config_by_number(current.block_info.number, Arc::clone(&self.rollup_config)) + .await + .map_err(|e| PipelineError::Provider(alloc::string::ToString::to_string(&e)).temp())?; + + Ok((current.l1_origin, system_config)) + } } impl OriginProvider for DerivationPipeline where - S: NextAttributes + SignalReceiver + OriginProvider + OriginAdvancer + Debug + Send, + S: NextAttributes + Stage + OriginProvider + OriginAdvancer + Debug + Send, P: L2ChainProvider + Send + Sync + Debug, { fn origin(&self) -> Option { @@ -57,7 +97,7 @@ where impl Iterator for DerivationPipeline where - S: NextAttributes + SignalReceiver + OriginProvider + OriginAdvancer + Debug + Send + Sync, + S: NextAttributes + Stage + OriginProvider + OriginAdvancer + Debug + Send + Sync, P: L2ChainProvider + Send + Sync + Debug, { type Item = OpAttributesWithParent; @@ -75,36 +115,14 @@ where #[async_trait] impl SignalReceiver for DerivationPipeline where - S: NextAttributes + SignalReceiver + OriginProvider + OriginAdvancer + Debug + Send + Sync, + S: NextAttributes + Stage + OriginProvider + OriginAdvancer + Debug + Send + Sync, P: L2ChainProvider + Send + Sync + Debug, { - /// Signals the pipeline by calling the [`SignalReceiver::signal`] method. - /// - /// During a [`Signal::Reset`], each stage is recursively called from the top-level - /// [`crate::stages::AttributesQueue`] to the bottom [`crate::PollingTraversal`] - /// with a head-recursion pattern. This effectively clears the internal state - /// of each stage in the pipeline from bottom on up. - /// - /// [`Signal::Activation`] does a similar thing to the reset, with different - /// holocene-specific reset rules. - /// - /// ### Parameters - /// - /// The `signal` is contains the signal variant with any necessary parameters. async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { match signal { - mut s @ (Signal::Reset(ResetSignal { l2_safe_head, .. }) | - Signal::Activation(ActivationSignal { l2_safe_head, .. })) => { - let system_config = self - .l2_chain_provider - .system_config_by_number( - l2_safe_head.block_info.number, - Arc::clone(&self.rollup_config), - ) - .await - .map_err(Into::into)?; - s = s.with_system_config(system_config); - match self.attributes.signal(s).await { + Signal::Reset(ResetSignal { l2_safe_head }) => { + let (l1_origin, system_config) = self.initial_reset(l2_safe_head).await?; + match self.attributes.reset(l1_origin, system_config).await { Ok(()) => trace!(target: "pipeline", "Stages reset"), Err(err) => { if err == PipelineErrorKind::Temporary(PipelineError::Eof) { @@ -116,8 +134,26 @@ where } } } - Signal::FlushChannel | Signal::ProvideBlock(_) => { - self.attributes.signal(signal).await?; + Signal::Activation(ActivationSignal { .. }) => { + // Activation is a soft reset for hardfork boundaries. It clears + // buffered data but preserves derivation state. No walkback needed. + match self.attributes.activate().await { + Ok(()) => trace!(target: "pipeline", "Stages activated"), + Err(err) => { + if err == PipelineErrorKind::Temporary(PipelineError::Eof) { + trace!(target: "pipeline", "Stages activated with EOF"); + } else { + error!(target: "pipeline", "Stage activation errored: {:?}", err); + return Err(err); + } + } + } + } + Signal::FlushChannel => { + self.attributes.flush_channel().await?; + } + Signal::ProvideBlock(block) => { + self.attributes.provide_block(block).await?; } } kona_macros::inc!( @@ -132,7 +168,7 @@ where #[async_trait] impl Pipeline for DerivationPipeline where - S: NextAttributes + SignalReceiver + OriginProvider + OriginAdvancer + Debug + Send + Sync, + S: NextAttributes + Stage + OriginProvider + OriginAdvancer + Debug + Send + Sync, P: L2ChainProvider + Send + Sync + Debug, { /// Peeks at the next prepared [`OpAttributesWithParent`] from the pipeline. @@ -240,6 +276,7 @@ mod tests { suggested_fee_recipient: Default::default(), withdrawals: None, parent_beacon_block_root: None, + slot_number: None, }, transactions: None, no_tx_pool: None, @@ -321,8 +358,7 @@ mod tests { let attributes = TestNextAttributes::default(); let mut pipeline = DerivationPipeline::new(attributes, rollup_config, l2_chain_provider); - // Signal the pipeline to reset. - let result = pipeline.signal(ActivationSignal::default().signal()).await; + let result = pipeline.signal(Signal::Activation(ActivationSignal::default())).await; assert!(result.is_ok()); } @@ -333,7 +369,6 @@ mod tests { let attributes = TestNextAttributes::default(); let mut pipeline = DerivationPipeline::new(attributes, rollup_config, l2_chain_provider); - // Signal the pipeline to reset. let result = pipeline.signal(Signal::FlushChannel).await; assert!(result.is_ok()); } @@ -345,8 +380,7 @@ mod tests { let attributes = TestNextAttributes::default(); let mut pipeline = DerivationPipeline::new(attributes, rollup_config, l2_chain_provider); - // Signal the pipeline to reset. - let result = pipeline.signal(ResetSignal::default().signal()).await.unwrap_err(); + let result = pipeline.signal(Signal::Reset(ResetSignal::default())).await.unwrap_err(); assert_eq!(result, PipelineError::Provider("System config not found".to_string()).temp()); } @@ -358,8 +392,114 @@ mod tests { let attributes = TestNextAttributes::default(); let mut pipeline = DerivationPipeline::new(attributes, rollup_config, l2_chain_provider); - // Signal the pipeline to reset. - let result = pipeline.signal(ResetSignal::default().signal()).await; + let result = pipeline.signal(Signal::Reset(ResetSignal::default())).await; assert!(result.is_ok()); } + + #[tokio::test] + async fn test_initial_reset_walks_back_system_config() { + use alloy_primitives::address; + + let rollup_config = RollupConfig { channel_timeout: 10, ..Default::default() }; + + let mut l2_chain_provider = TestL2ChainProvider::default(); + // L2 blocks 89..=100: block N has L1 origin (N - 50). + // Safe head at block 100, L1 origin 50. + // Walkback: block 90 has L1 origin 40. 40 + 10 = 50, NOT > 50, so walkback stops. + for n in 89u64..=100 { + l2_chain_provider.blocks.push(L2BlockInfo { + block_info: BlockInfo { number: n, ..Default::default() }, + l1_origin: BlockNumHash { number: n - 50, ..Default::default() }, + seq_num: 0, + }); + } + + // Old batcher at walked-back block 90. + l2_chain_provider.system_configs.insert( + 90, + SystemConfig { + batcher_address: address!("000000000000000000000000000000000000aaaa"), + ..Default::default() + }, + ); + // New batcher at safe head block 100. + l2_chain_provider.system_configs.insert( + 100, + SystemConfig { + batcher_address: address!("000000000000000000000000000000000000bbbb"), + ..Default::default() + }, + ); + + let rollup_config = Arc::new(rollup_config); + let attributes = TestNextAttributes::default(); + let mut pipeline = DerivationPipeline::new(attributes, rollup_config, l2_chain_provider); + + let l2_safe_head = L2BlockInfo { + block_info: BlockInfo { number: 100, ..Default::default() }, + l1_origin: BlockNumHash { number: 50, ..Default::default() }, + seq_num: 0, + }; + + let (l1_origin, sys_cfg) = pipeline.initial_reset(l2_safe_head).await.unwrap(); + assert_eq!(l1_origin.number, 40); + assert_eq!( + sys_cfg.batcher_address, + address!("000000000000000000000000000000000000aaaa"), + "Expected old batcher from walked-back block 90" + ); + } + + #[tokio::test] + async fn test_initial_reset_respects_genesis() { + let rollup_config = RollupConfig { + channel_timeout: 100, + genesis: kona_genesis::ChainGenesis { + l2: alloy_eips::BlockNumHash { number: 5, ..Default::default() }, + l1: alloy_eips::BlockNumHash { number: 3, ..Default::default() }, + ..Default::default() + }, + ..Default::default() + }; + + let mut l2_chain_provider = TestL2ChainProvider::default(); + l2_chain_provider.blocks.push(L2BlockInfo { + block_info: BlockInfo { number: 5, ..Default::default() }, + l1_origin: BlockNumHash { number: 3, ..Default::default() }, + seq_num: 0, + }); + l2_chain_provider.blocks.push(L2BlockInfo { + block_info: BlockInfo { number: 6, ..Default::default() }, + l1_origin: BlockNumHash { number: 4, ..Default::default() }, + seq_num: 0, + }); + l2_chain_provider.system_configs.insert(5, SystemConfig::default()); + + let rollup_config = Arc::new(rollup_config); + let attributes = TestNextAttributes::default(); + let mut pipeline = DerivationPipeline::new(attributes, rollup_config, l2_chain_provider); + + let l2_safe_head = L2BlockInfo { + block_info: BlockInfo { number: 6, ..Default::default() }, + l1_origin: BlockNumHash { number: 4, ..Default::default() }, + seq_num: 0, + }; + + let (l1_origin, _) = pipeline.initial_reset(l2_safe_head).await.unwrap(); + assert_eq!(l1_origin.number, 3, "Should stop at genesis L1 origin"); + } + + #[tokio::test] + async fn test_initial_reset_no_walkback_zero_timeout() { + let rollup_config = Arc::new(RollupConfig::default()); + let mut l2_chain_provider = TestL2ChainProvider::default(); + l2_chain_provider.system_configs.insert(0, SystemConfig::default()); + + let attributes = TestNextAttributes::default(); + let mut pipeline = DerivationPipeline::new(attributes, rollup_config, l2_chain_provider); + + let (l1_origin, sys_cfg) = pipeline.initial_reset(L2BlockInfo::default()).await.unwrap(); + assert_eq!(l1_origin.number, 0); + assert_eq!(sys_cfg, SystemConfig::default()); + } } diff --git a/rust/kona/crates/protocol/derive/src/stages/attributes_queue.rs b/rust/kona/crates/protocol/derive/src/stages/attributes_queue.rs index 72e3e720626..b38d5c51e0a 100644 --- a/rust/kona/crates/protocol/derive/src/stages/attributes_queue.rs +++ b/rust/kona/crates/protocol/derive/src/stages/attributes_queue.rs @@ -4,14 +4,15 @@ use crate::{ errors::{PipelineError, ResetError}, traits::{ AttributesBuilder, AttributesProvider, NextAttributes, OriginAdvancer, OriginProvider, - SignalReceiver, + Stage, }, - types::{PipelineResult, Signal}, + types::PipelineResult, }; use alloc::{boxed::Box, sync::Arc}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; use core::fmt::Debug; -use kona_genesis::RollupConfig; +use kona_genesis::{RollupConfig, SystemConfig}; use kona_protocol::{BlockInfo, L2BlockInfo, OpAttributesWithParent, SingleBatch}; use op_alloy_rpc_types_engine::OpPayloadAttributes; @@ -31,7 +32,7 @@ use op_alloy_rpc_types_engine::OpPayloadAttributes; #[derive(Debug)] pub struct AttributesQueue where - P: AttributesProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: AttributesProvider + OriginAdvancer + OriginProvider + Stage + Debug, AB: AttributesBuilder + Debug, { /// The rollup config. @@ -48,7 +49,7 @@ where impl AttributesQueue where - P: AttributesProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: AttributesProvider + OriginAdvancer + OriginProvider + Stage + Debug, AB: AttributesBuilder + Debug, { /// Create a new [`AttributesQueue`] stage. @@ -147,7 +148,7 @@ where #[async_trait] impl OriginAdvancer for AttributesQueue where - P: AttributesProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug + Send, + P: AttributesProvider + OriginAdvancer + OriginProvider + Stage + Debug + Send, AB: AttributesBuilder + Debug + Send, { async fn advance_origin(&mut self) -> PipelineResult<()> { @@ -158,7 +159,7 @@ where #[async_trait] impl NextAttributes for AttributesQueue where - P: AttributesProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug + Send, + P: AttributesProvider + OriginAdvancer + OriginProvider + Stage + Debug + Send, AB: AttributesBuilder + Debug + Send, { async fn next_attributes( @@ -171,7 +172,7 @@ where impl OriginProvider for AttributesQueue where - P: AttributesProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: AttributesProvider + OriginAdvancer + OriginProvider + Stage + Debug, AB: AttributesBuilder + Debug, { fn origin(&self) -> Option { @@ -180,28 +181,36 @@ where } #[async_trait] -impl SignalReceiver for AttributesQueue +impl Stage for AttributesQueue where - P: AttributesProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: AttributesProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, AB: AttributesBuilder + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - s @ (Signal::Reset(_) | Signal::Activation(_)) => { - self.prev.signal(s).await?; - self.batch = None; - self.is_last_in_span = false; - } - s @ Signal::FlushChannel => { - self.batch = None; - self.prev.signal(s).await?; - } - s @ Signal::ProvideBlock(_) => { - self.prev.signal(s).await?; - } - } + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; + self.batch = None; + self.is_last_in_span = false; + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await?; + self.batch = None; Ok(()) } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.batch = None; + self.prev.flush_channel().await + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] @@ -210,7 +219,6 @@ mod tests { use crate::{ errors::{BuilderError, PipelineErrorKind}, test_utils::{TestAttributesBuilder, TestAttributesProvider, new_test_attributes_provider}, - types::ResetSignal, }; use alloc::{sync::Arc, vec, vec::Vec}; use alloy_primitives::{Address, B256, Bytes, b256}; @@ -224,6 +232,7 @@ mod tests { prev_randao: B256::default(), withdrawals: None, parent_beacon_block_root: None, + slot_number: None, }, no_tx_pool: Some(false), transactions: None, @@ -250,7 +259,7 @@ mod tests { let mut attributes_queue = new_attributes_queue(None, None, vec![], vec![]); attributes_queue.batch = Some(SingleBatch::default()); assert!(!attributes_queue.prev.flushed); - attributes_queue.signal(Signal::FlushChannel).await.unwrap(); + attributes_queue.flush_channel().await.unwrap(); assert!(attributes_queue.prev.flushed); assert!(attributes_queue.batch.is_none()); } @@ -263,7 +272,7 @@ mod tests { let mut aq = AttributesQueue::new(Arc::new(cfg), mock, mock_builder); aq.batch = Some(SingleBatch::default()); assert!(!aq.prev.reset); - aq.signal(ResetSignal::default().signal()).await.unwrap(); + aq.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); assert!(aq.batch.is_none()); assert!(aq.prev.reset); } diff --git a/rust/kona/crates/protocol/derive/src/stages/batch/batch_provider.rs b/rust/kona/crates/protocol/derive/src/stages/batch/batch_provider.rs index 1d1a12fd91f..a5dbb1eb1fa 100644 --- a/rust/kona/crates/protocol/derive/src/stages/batch/batch_provider.rs +++ b/rust/kona/crates/protocol/derive/src/stages/batch/batch_provider.rs @@ -3,12 +3,13 @@ use super::NextBatchProvider; use crate::{ AttributesProvider, BatchQueue, BatchValidator, L2ChainProvider, OriginAdvancer, - OriginProvider, PipelineError, PipelineResult, Signal, SignalReceiver, + OriginProvider, PipelineError, PipelineResult, Stage, }; use alloc::{boxed::Box, sync::Arc}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; use core::fmt::Debug; -use kona_genesis::RollupConfig; +use kona_genesis::{RollupConfig, SystemConfig}; use kona_protocol::{BlockInfo, L2BlockInfo, SingleBatch}; /// The [`BatchProvider`] stage is a mux between the [`BatchQueue`] and [`BatchValidator`] stages. @@ -22,7 +23,7 @@ use kona_protocol::{BlockInfo, L2BlockInfo, SingleBatch}; #[derive(Debug)] pub struct BatchProvider where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, F: L2ChainProvider + Clone + Debug, { /// The rollup configuration. @@ -48,7 +49,7 @@ where impl BatchProvider where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, F: L2ChainProvider + Clone + Debug, { /// Creates a new [`BatchProvider`] with the given configuration and previous stage. @@ -93,7 +94,7 @@ where #[async_trait] impl OriginAdvancer for BatchProvider where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, F: L2ChainProvider + Clone + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { @@ -111,7 +112,7 @@ where impl OriginProvider for BatchProvider where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, F: L2ChainProvider + Clone + Debug, { fn origin(&self) -> Option { @@ -127,29 +128,51 @@ where } } +/// Dispatches a method call to the active inner batch stage. +macro_rules! dispatch_inner { + ($self:ident, $method:ident $(, $arg:expr)*) => {{ + $self.attempt_update()?; + if let Some(inner) = $self.batch_validator.as_mut() { + inner.$method($($arg),*).await + } else if let Some(inner) = $self.batch_queue.as_mut() { + inner.$method($($arg),*).await + } else { + Err(PipelineError::NotEnoughData.temp()) + } + }}; +} + #[async_trait] -impl SignalReceiver for BatchProvider +impl Stage for BatchProvider where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, F: L2ChainProvider + Clone + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - self.attempt_update()?; + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + dispatch_inner!(self, reset, l1_origin, system_config) + } - if let Some(batch_validator) = self.batch_validator.as_mut() { - batch_validator.signal(signal).await - } else if let Some(batch_queue) = self.batch_queue.as_mut() { - batch_queue.signal(signal).await - } else { - Err(PipelineError::NotEnoughData.temp()) - } + async fn activate(&mut self) -> PipelineResult<()> { + dispatch_inner!(self, activate) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + dispatch_inner!(self, flush_channel) + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + dispatch_inner!(self, provide_block, block) } } #[async_trait] impl AttributesProvider for BatchProvider where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug + Send, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug + Send, F: L2ChainProvider + Clone + Send + Debug, { fn is_last_in_span(&self) -> bool { @@ -177,11 +200,11 @@ mod test { use super::BatchProvider; use crate::{ test_utils::{TestL2ChainProvider, TestNextBatchProvider}, - traits::{OriginProvider, SignalReceiver}, - types::ResetSignal, + traits::{OriginProvider, Stage}, }; use alloc::{sync::Arc, vec}; - use kona_genesis::{HardForkConfig, RollupConfig}; + use alloy_eips::BlockNumHash; + use kona_genesis::{HardForkConfig, RollupConfig, SystemConfig}; use kona_protocol::BlockInfo; #[test] @@ -281,12 +304,12 @@ mod test { let mut batch_provider = BatchProvider::new(cfg, provider, l2_provider); // Reset the batch provider. - batch_provider.signal(ResetSignal::default().signal()).await.unwrap(); + batch_provider.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); let Some(bq) = batch_provider.batch_queue else { panic!("Expected BatchQueue"); }; - assert!(bq.l1_blocks.len() == 1); + assert_eq!(bq.l1_blocks.len(), 1); } #[tokio::test] @@ -300,12 +323,12 @@ mod test { let mut batch_provider = BatchProvider::new(cfg, provider, l2_provider); // Reset the batch provider. - batch_provider.signal(ResetSignal::default().signal()).await.unwrap(); + batch_provider.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); let Some(bv) = batch_provider.batch_validator else { panic!("Expected BatchValidator"); }; - assert!(bv.l1_blocks.len() == 1); + assert_eq!(bv.l1_blocks.len(), 1); } // On Holocene activation, BatchProvider.attempt_update() must copy BOTH l1_blocks diff --git a/rust/kona/crates/protocol/derive/src/stages/batch/batch_queue.rs b/rust/kona/crates/protocol/derive/src/stages/batch/batch_queue.rs index d3e506358d6..0908607a221 100644 --- a/rust/kona/crates/protocol/derive/src/stages/batch/batch_queue.rs +++ b/rust/kona/crates/protocol/derive/src/stages/batch/batch_queue.rs @@ -3,13 +3,14 @@ use super::NextBatchProvider; use crate::{ errors::{PipelineEncodingError, PipelineError, PipelineErrorKind, ResetError}, - traits::{AttributesProvider, L2ChainProvider, OriginAdvancer, OriginProvider, SignalReceiver}, - types::{PipelineResult, ResetSignal, Signal}, + traits::{AttributesProvider, L2ChainProvider, OriginAdvancer, OriginProvider, Stage}, + types::PipelineResult, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; use core::fmt::Debug; -use kona_genesis::RollupConfig; +use kona_genesis::{RollupConfig, SystemConfig}; use kona_protocol::{ Batch, BatchValidity, BatchWithInclusionBlock, BlockInfo, L2BlockInfo, SingleBatch, }; @@ -31,7 +32,7 @@ use kona_protocol::{ #[derive(Debug)] pub struct BatchQueue where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, BF: L2ChainProvider + Debug, { /// The rollup config. @@ -59,7 +60,7 @@ where impl BatchQueue where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, BF: L2ChainProvider + Debug, { /// Creates a new [`BatchQueue`] stage. @@ -258,7 +259,7 @@ where #[async_trait] impl OriginAdvancer for BatchQueue where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, BF: L2ChainProvider + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { @@ -269,7 +270,7 @@ where #[async_trait] impl AttributesProvider for BatchQueue where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, BF: L2ChainProvider + Send + Debug, { /// Returns the next valid batch upon the given safe head. @@ -292,22 +293,6 @@ where self.next_spans.clear(); } - // If the epoch is advanced, update the l1 blocks. - // Advancing epoch must be done after the pipeline successfully applies the entire span - // batch to the chain. - // Because the span batch can be reverted during processing the batch, then we must - // preserve existing l1 blocks to verify the epochs of the next candidate batch. - if !self.l1_blocks.is_empty() && parent.l1_origin.number > self.l1_blocks[0].number { - for (i, block) in self.l1_blocks.iter().enumerate() { - if parent.l1_origin.number == block.number { - self.l1_blocks.drain(0..i); - info!(target: "batch_queue", "Advancing epoch"); - break; - } - } - // If the origin of the parent block is not included, we must advance the origin. - } - // NOTE: The origin is used to determine if it's behind. // It is the future origin that gets saved into the l1 blocks array. // We always update the origin of this stage if it's not the same so @@ -338,6 +323,18 @@ where info!(target: "batch_queue", "Advancing batch queue origin: {:?}", self.origin); } + // If the epoch is advanced, update the l1 blocks. + // This must run after update_origins so newly pushed blocks are available for draining. + if !self.l1_blocks.is_empty() && parent.l1_origin.number > self.l1_blocks[0].number { + for (i, block) in self.l1_blocks.iter().enumerate() { + if parent.l1_origin.number == block.number { + self.l1_blocks.drain(0..i); + info!(target: "batch_queue", "Advancing epoch"); + break; + } + } + } + // Load more data into the batch queue. let mut out_of_data = false; match self.prev.next_batch(parent, &self.l1_blocks).await { @@ -416,7 +413,7 @@ where impl OriginProvider for BatchQueue where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, BF: L2ChainProvider + Debug, { fn origin(&self) -> Option { @@ -425,35 +422,43 @@ where } #[async_trait] -impl SignalReceiver for BatchQueue +impl Stage for BatchQueue where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, BF: L2ChainProvider + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - s @ Signal::Reset(ResetSignal { l1_origin, .. }) => { - self.prev.signal(s).await?; - self.origin = Some(l1_origin); - self.batches.clear(); - // Include the new origin as an origin to build on. - // This is only for the initialization case. - // During normal resets we will later throw out this block. - self.l1_blocks.clear(); - self.l1_blocks.push(l1_origin); - self.next_spans.clear(); - } - s @ (Signal::Activation(_) | Signal::FlushChannel) => { - self.prev.signal(s).await?; - self.batches.clear(); - self.next_spans.clear(); - } - s @ Signal::ProvideBlock(_) => { - self.prev.signal(s).await?; - } - } + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; + let origin = self.prev.origin().ok_or(PipelineError::MissingOrigin.crit())?; + self.origin = Some(origin); + self.batches.clear(); + self.l1_blocks.clear(); + self.l1_blocks.push(origin); + self.next_spans.clear(); Ok(()) } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await?; + self.batches.clear(); + self.next_spans.clear(); + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.prev.flush_channel().await?; + self.batches.clear(); + self.next_spans.clear(); + Ok(()) + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] @@ -479,7 +484,7 @@ mod tests { let file_contents = &(&*file_contents)[..file_contents.len() - 1]; let data = alloy_primitives::hex::decode(file_contents).unwrap(); let bytes: alloy_primitives::Bytes = data.into(); - BatchReader::new(bytes, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize) + BatchReader::new(bytes, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize, 0) } #[test] @@ -509,7 +514,7 @@ mod tests { batch: Batch::Single(SingleBatch::default()), }); assert!(!bq.prev.reset); - bq.signal(ResetSignal::default().signal()).await.unwrap(); + bq.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); assert!(bq.prev.reset); assert_eq!(bq.origin, Some(BlockInfo::default())); assert!(bq.batches.is_empty()); @@ -529,7 +534,7 @@ mod tests { inclusion_block: BlockInfo::default(), batch: Batch::Single(SingleBatch::default()), }); - bq.signal(Signal::FlushChannel).await.unwrap(); + bq.flush_channel().await.unwrap(); assert!(bq.prev.flushed); assert!(bq.batches.is_empty()); assert!(!bq.l1_blocks.is_empty()); diff --git a/rust/kona/crates/protocol/derive/src/stages/batch/batch_stream.rs b/rust/kona/crates/protocol/derive/src/stages/batch/batch_stream.rs index 447b03ec257..06d51e6f13b 100644 --- a/rust/kona/crates/protocol/derive/src/stages/batch/batch_stream.rs +++ b/rust/kona/crates/protocol/derive/src/stages/batch/batch_stream.rs @@ -2,12 +2,13 @@ use crate::{ L2ChainProvider, NextBatchProvider, OriginAdvancer, OriginProvider, PipelineError, - PipelineResult, Signal, SignalReceiver, + PipelineResult, Stage, }; use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; use core::fmt::Debug; -use kona_genesis::RollupConfig; +use kona_genesis::{RollupConfig, SystemConfig}; use kona_protocol::{ Batch, BatchValidity, BatchWithInclusionBlock, BlockInfo, L2BlockInfo, SingleBatch, SpanBatch, SpanBatchError, @@ -35,7 +36,7 @@ pub trait BatchStreamProvider { #[derive(Debug)] pub struct BatchStream where - P: BatchStreamProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: BatchStreamProvider + OriginAdvancer + OriginProvider + Stage + Debug, BF: L2ChainProvider + Debug, { /// The previous stage in the derivation pipeline. @@ -53,7 +54,7 @@ where impl BatchStream where - P: BatchStreamProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: BatchStreamProvider + OriginAdvancer + OriginProvider + Stage + Debug, BF: L2ChainProvider + Debug, { /// Create a new [`BatchStream`] stage. @@ -104,7 +105,7 @@ where #[async_trait] impl NextBatchProvider for BatchStream where - P: BatchStreamProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: BatchStreamProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, BF: L2ChainProvider + Send + Debug, { fn flush(&mut self) { @@ -210,7 +211,7 @@ where #[async_trait] impl OriginAdvancer for BatchStream where - P: BatchStreamProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: BatchStreamProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, BF: L2ChainProvider + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { @@ -220,7 +221,7 @@ where impl OriginProvider for BatchStream where - P: BatchStreamProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: BatchStreamProvider + OriginAdvancer + OriginProvider + Stage + Debug, BF: L2ChainProvider + Debug, { fn origin(&self) -> Option { @@ -229,25 +230,46 @@ where } #[async_trait] -impl SignalReceiver for BatchStream +impl Stage for BatchStream where - P: BatchStreamProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug + Send, + P: BatchStreamProvider + OriginAdvancer + OriginProvider + Stage + Debug + Send, BF: L2ChainProvider + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - self.prev.signal(signal).await?; + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; self.buffer.clear(); self.span.take(); Ok(()) } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await?; + self.buffer.clear(); + self.span.take(); + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.prev.flush_channel().await?; + self.buffer.clear(); + self.span.take(); + Ok(()) + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] mod test { use super::*; - use crate::{ - test_utils::{CollectingLayer, TestBatchStreamProvider, TestL2ChainProvider, TraceStorage}, - types::ResetSignal, + use crate::test_utils::{ + CollectingLayer, TestBatchStreamProvider, TestL2ChainProvider, TraceStorage, }; use alloc::vec; use alloy_consensus::{BlockBody, Header}; @@ -286,7 +308,7 @@ mod test { stream.buffer.push_back(SingleBatch::default()); stream.span = Some(SpanBatch::default()); assert!(!stream.prev.reset); - stream.signal(ResetSignal::default().signal()).await.unwrap(); + stream.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); assert!(stream.prev.reset); assert!(stream.buffer.is_empty()); assert!(stream.span.is_none()); @@ -303,7 +325,7 @@ mod test { stream.buffer.push_back(SingleBatch::default()); stream.span = Some(SpanBatch::default()); assert!(!stream.prev.flushed); - stream.signal(Signal::FlushChannel).await.unwrap(); + stream.flush_channel().await.unwrap(); assert!(stream.prev.flushed); assert!(stream.buffer.is_empty()); assert!(stream.span.is_none()); diff --git a/rust/kona/crates/protocol/derive/src/stages/batch/batch_validator.rs b/rust/kona/crates/protocol/derive/src/stages/batch/batch_validator.rs index 9c641e1855c..a2efeeb0443 100644 --- a/rust/kona/crates/protocol/derive/src/stages/batch/batch_validator.rs +++ b/rust/kona/crates/protocol/derive/src/stages/batch/batch_validator.rs @@ -3,13 +3,14 @@ use super::NextBatchProvider; use crate::{ errors::{PipelineError, PipelineErrorKind, ResetError}, - traits::{AttributesProvider, OriginAdvancer, OriginProvider, SignalReceiver}, - types::{PipelineResult, ResetSignal, Signal}, + traits::{AttributesProvider, OriginAdvancer, OriginProvider, Stage}, + types::PipelineResult, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; use core::fmt::Debug; -use kona_genesis::RollupConfig; +use kona_genesis::{RollupConfig, SystemConfig}; use kona_protocol::{Batch, BatchValidity, BlockInfo, L2BlockInfo, SingleBatch}; /// The [`BatchValidator`] stage is responsible for validating the [`SingleBatch`]es from @@ -20,7 +21,7 @@ use kona_protocol::{Batch, BatchValidity, BlockInfo, L2BlockInfo, SingleBatch}; #[derive(Debug)] pub struct BatchValidator

where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// The rollup configuration. pub cfg: Arc, @@ -39,7 +40,7 @@ where impl

BatchValidator

where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// Create a new [`BatchValidator`] stage. pub const fn new(cfg: Arc, prev: P) -> Self { @@ -196,7 +197,7 @@ where #[async_trait] impl

AttributesProvider for BatchValidator

where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn next_batch(&mut self, parent: L2BlockInfo) -> PipelineResult { // Update the L1 origin blocks within the stage. @@ -282,7 +283,7 @@ where impl

OriginProvider for BatchValidator

where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Debug, { fn origin(&self) -> Option { self.prev.origin() @@ -292,7 +293,7 @@ where #[async_trait] impl

OriginAdvancer for BatchValidator

where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { self.prev.advance_origin().await @@ -300,40 +301,47 @@ where } #[async_trait] -impl

SignalReceiver for BatchValidator

+impl

Stage for BatchValidator

where - P: NextBatchProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextBatchProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - s @ Signal::Reset(ResetSignal { l1_origin, .. }) => { - self.prev.signal(s).await?; - self.origin = Some(l1_origin); - // Include the new origin as an origin to build on. - // This is only for the initialization case. - // During normal resets we will later throw out this block. - self.l1_blocks.clear(); - self.l1_blocks.push(l1_origin); - } - s @ (Signal::Activation(_) | Signal::FlushChannel | Signal::ProvideBlock(_)) => { - self.prev.signal(s).await?; - } - } + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; + let origin = self.prev.origin().ok_or(PipelineError::MissingOrigin.crit())?; + self.origin = Some(origin); + self.l1_blocks.clear(); + self.l1_blocks.push(origin); Ok(()) } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.prev.flush_channel().await + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] mod test { use crate::{ AttributesProvider, BatchValidator, NextBatchProvider, OriginAdvancer, PipelineError, - PipelineErrorKind, PipelineResult, ResetError, ResetSignal, Signal, SignalReceiver, + PipelineErrorKind, PipelineResult, ResetError, Stage, test_utils::{CollectingLayer, TestNextBatchProvider, TraceStorage}, }; use alloc::{sync::Arc, vec, vec::Vec}; - use alloy_eips::{BlockNumHash, NumHash}; + use alloy_eips::BlockNumHash; use alloy_primitives::B256; - use kona_genesis::{HardForkConfig, RollupConfig}; + use kona_genesis::{HardForkConfig, RollupConfig, SystemConfig}; use kona_protocol::{Batch, BlockInfo, L2BlockInfo, SingleBatch, SpanBatch}; use tracing::Level; use tracing_subscriber::layer::SubscriberExt; @@ -357,21 +365,17 @@ mod test { async fn test_batch_validator_origin_behind_startup() { let cfg = Arc::new(RollupConfig::default()); let mut mock = TestNextBatchProvider::new(vec![]); - mock.origin = Some(BlockInfo::default()); + mock.origin = Some(BlockInfo { number: 1, ..Default::default() }); let mut bv = BatchValidator::new(cfg, mock); // Reset the pipeline to add the L1 origin to the stage. - bv.signal(Signal::Reset(ResetSignal { - l1_origin: BlockInfo { number: 1, ..Default::default() }, - l2_safe_head: L2BlockInfo::new( - BlockInfo::default(), - NumHash::new(1, Default::default()), - 0, - ), - system_config: None, - })) - .await - .unwrap(); + bv.reset(BlockNumHash { number: 1, ..Default::default() }, SystemConfig::default()) + .await + .unwrap(); + + // Simulate origin regressing after reset (startup scenario where + // prev hasn't caught up yet). + bv.prev.origin = Some(BlockInfo::default()); let mock_parent = L2BlockInfo { l1_origin: BlockNumHash { number: 2, ..Default::default() }, @@ -386,21 +390,16 @@ mod test { async fn test_batch_validator_origin_behind_advance() { let cfg = Arc::new(RollupConfig::default()); let mut mock = TestNextBatchProvider::new(vec![]); - mock.origin = Some(BlockInfo { number: 2, ..Default::default() }); + mock.origin = Some(BlockInfo { number: 1, ..Default::default() }); let mut bv = BatchValidator::new(cfg, mock); // Reset the pipeline to add the L1 origin to the stage. - bv.signal(Signal::Reset(ResetSignal { - l1_origin: BlockInfo { number: 1, ..Default::default() }, - l2_safe_head: L2BlockInfo::new( - BlockInfo::default(), - NumHash::new(1, Default::default()), - 0, - ), - system_config: None, - })) - .await - .unwrap(); + bv.reset(BlockNumHash { number: 1, ..Default::default() }, SystemConfig::default()) + .await + .unwrap(); + + // After reset, advance mock origin so update_origins advances. + bv.prev.origin = Some(BlockInfo { number: 2, ..Default::default() }); let mock_parent = L2BlockInfo { l1_origin: BlockNumHash { number: 1, ..Default::default() }, @@ -415,21 +414,16 @@ mod test { async fn test_batch_validator_advance_epoch() { let cfg = Arc::new(RollupConfig::default()); let mut mock = TestNextBatchProvider::new(vec![]); - mock.origin = Some(BlockInfo { number: 2, ..Default::default() }); + mock.origin = Some(BlockInfo { number: 1, ..Default::default() }); let mut bv = BatchValidator::new(cfg, mock); // Reset the pipeline to add the L1 origin to the stage. - bv.signal(Signal::Reset(ResetSignal { - l1_origin: BlockInfo { number: 1, ..Default::default() }, - l2_safe_head: L2BlockInfo::new( - BlockInfo::default(), - NumHash::new(1, Default::default()), - 0, - ), - system_config: None, - })) - .await - .unwrap(); + bv.reset(BlockNumHash { number: 1, ..Default::default() }, SystemConfig::default()) + .await + .unwrap(); + + // Advance mock origin to simulate prev advancing past the reset origin. + bv.prev.origin = Some(BlockInfo { number: 2, ..Default::default() }); let mock_parent = L2BlockInfo { l1_origin: BlockNumHash { number: 2, ..Default::default() }, @@ -539,12 +533,9 @@ mod test { let mut bv = BatchValidator::new(cfg, mock); // Reset the pipeline to add the L1 origin to the stage. - bv.signal(Signal::Reset(ResetSignal { - l1_origin: BlockInfo { number: 1, ..Default::default() }, - ..Default::default() - })) - .await - .unwrap(); + bv.reset(BlockNumHash { number: 1, ..Default::default() }, SystemConfig::default()) + .await + .unwrap(); bv.l1_blocks.push(BlockInfo { number: 1, ..Default::default() }); // Grab the next batch. @@ -565,12 +556,9 @@ mod test { let mut bv = BatchValidator::new(cfg, mock); // Reset the pipeline to add the L1 origin to the stage. - bv.signal(Signal::Reset(ResetSignal { - l1_origin: BlockInfo { number: 1, ..Default::default() }, - ..Default::default() - })) - .await - .unwrap(); + bv.reset(BlockNumHash { number: 1, ..Default::default() }, SystemConfig::default()) + .await + .unwrap(); // Advance the origin of the previous stage to block #6. for _ in 0..6 { @@ -604,12 +592,9 @@ mod test { let mut bv = BatchValidator::new(cfg, mock); // Reset the pipeline to add the L1 origin to the stage. - bv.signal(Signal::Reset(ResetSignal { - l1_origin: BlockInfo { number: 1, ..Default::default() }, - ..Default::default() - })) - .await - .unwrap(); + bv.reset(BlockNumHash { number: 1, ..Default::default() }, SystemConfig::default()) + .await + .unwrap(); // Advance the origin of the previous stage to block #6. for _ in 0..6 { diff --git a/rust/kona/crates/protocol/derive/src/stages/channel/channel_assembler.rs b/rust/kona/crates/protocol/derive/src/stages/channel/channel_assembler.rs index 9741e7743d6..2eb1275aef8 100644 --- a/rust/kona/crates/protocol/derive/src/stages/channel/channel_assembler.rs +++ b/rust/kona/crates/protocol/derive/src/stages/channel/channel_assembler.rs @@ -3,40 +3,40 @@ use super::{ChannelReaderProvider, NextFrameProvider}; use crate::{ errors::PipelineError, - traits::{OriginAdvancer, OriginProvider, SignalReceiver}, - types::{PipelineResult, Signal}, + traits::{OriginAdvancer, OriginProvider, Stage}, + types::PipelineResult, }; use alloc::{boxed::Box, sync::Arc}; +use alloy_eips::BlockNumHash; use alloy_primitives::{Bytes, hex}; use async_trait::async_trait; use core::fmt::Debug; use kona_genesis::{ - MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, MAX_RLP_BYTES_PER_CHANNEL_FJORD, RollupConfig, + MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, MAX_RLP_BYTES_PER_CHANNEL_FJORD, RollupConfig, SystemConfig, }; -use kona_protocol::{BlockInfo, Channel}; +use kona_protocol::{BlockInfo, OrderedChannel}; /// The [`ChannelAssembler`] stage is responsible for assembling the [`Frame`]s from the -/// [`FrameQueue`] stage into a raw compressed [`Channel`]. +/// [`FrameQueue`] stage into a raw compressed [`OrderedChannel`]. /// /// [`Frame`]: kona_protocol::Frame /// [`FrameQueue`]: crate::stages::FrameQueue -/// [`Channel`]: kona_protocol::Channel #[derive(Debug)] pub struct ChannelAssembler

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// The rollup configuration. pub cfg: Arc, /// The previous stage of the derivation pipeline. pub prev: P, - /// The current [`Channel`] being assembled. - pub channel: Option, + /// The current [`OrderedChannel`] being assembled. + pub channel: Option, } impl

ChannelAssembler

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// Creates a new [`ChannelAssembler`] stage with the given configuration and previous stage. pub const fn new(cfg: Arc, prev: P) -> Self { @@ -57,7 +57,7 @@ where #[async_trait] impl

ChannelReaderProvider for ChannelAssembler

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn next_data(&mut self) -> PipelineResult> { let origin = self.origin().ok_or(PipelineError::MissingOrigin.crit())?; @@ -89,17 +89,17 @@ where hex::encode(next_frame.id), origin.number ); - self.channel = Some(Channel::new(next_frame.id, origin)); + self.channel = Some(OrderedChannel::new(next_frame.id, origin)); } - let count = if self.channel.is_some() { 1 } else { 0 }; - kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_CHANNEL_BUFFER, count); + let _count = if self.channel.is_some() { 1 } else { 0 }; + kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_CHANNEL_BUFFER, _count); if let Some(channel) = self.channel.as_mut() { // Track the number of blocks until the channel times out. let timeout = channel.open_block_number() + self.cfg.channel_timeout(origin.timestamp); - let margin = timeout.saturating_sub(origin.number) as f64; - kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_CHANNEL_TIMEOUT, margin); + let _margin = timeout.saturating_sub(origin.number) as f64; + kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_CHANNEL_TIMEOUT, _margin); // Add the frame to the channel. If this fails, return NotEnoughData and discard the // frame. @@ -120,8 +120,8 @@ where return Err(PipelineError::NotEnoughData.temp()); } - let size = channel.size() as f64; - kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_CHANNEL_MEM, size); + let _size = channel.size() as f64; + kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_CHANNEL_MEM, _size); let max_rlp_bytes_per_channel = if self.cfg.is_fjord_active(origin.timestamp) { MAX_RLP_BYTES_PER_CHANNEL_FJORD @@ -147,7 +147,7 @@ where // If the channel is ready, forward the channel to the next stage. if channel.is_ready() { let channel_bytes = - channel.frame_data().ok_or(PipelineError::ChannelNotFound.crit())?; + channel.data().map_err(|_| PipelineError::ChannelNotFound.crit())?; info!( target: "channel_assembler", @@ -170,7 +170,7 @@ where #[async_trait] impl

OriginAdvancer for ChannelAssembler

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { self.prev.advance_origin().await @@ -179,7 +179,7 @@ where impl

OriginProvider for ChannelAssembler

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { fn origin(&self) -> Option { self.prev.origin() @@ -187,15 +187,35 @@ where } #[async_trait] -impl

SignalReceiver for ChannelAssembler

+impl

Stage for ChannelAssembler

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - self.prev.signal(signal).await?; + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; self.channel = None; Ok(()) } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await?; + self.channel = None; + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.prev.flush_channel().await?; + self.channel = None; + Ok(()) + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] diff --git a/rust/kona/crates/protocol/derive/src/stages/channel/channel_bank.rs b/rust/kona/crates/protocol/derive/src/stages/channel/channel_bank.rs index 1a1f912d2da..6e142e010bd 100644 --- a/rust/kona/crates/protocol/derive/src/stages/channel/channel_bank.rs +++ b/rust/kona/crates/protocol/derive/src/stages/channel/channel_bank.rs @@ -2,13 +2,14 @@ use crate::{ ChannelReaderProvider, NextFrameProvider, OriginAdvancer, OriginProvider, PipelineError, - PipelineErrorKind, PipelineResult, Signal, SignalReceiver, + PipelineErrorKind, PipelineResult, Stage, }; use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; +use alloy_eips::BlockNumHash; use alloy_primitives::{Bytes, hex, map::HashMap}; use async_trait::async_trait; use core::fmt::Debug; -use kona_genesis::RollupConfig; +use kona_genesis::{RollupConfig, SystemConfig}; use kona_protocol::{BlockInfo, Channel, ChannelId, Frame}; /// The maximum size of a channel bank. @@ -31,7 +32,7 @@ pub(crate) const FJORD_MAX_CHANNEL_BANK_SIZE: usize = 1_000_000_000; #[derive(Debug)] pub struct ChannelBank

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// The rollup configuration. pub cfg: Arc, @@ -45,7 +46,7 @@ where impl

ChannelBank

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// Create a new [`ChannelBank`] stage. pub fn new(cfg: Arc, prev: P) -> Self { @@ -180,7 +181,7 @@ where #[async_trait] impl

OriginAdvancer for ChannelBank

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { self.prev.advance_origin().await @@ -190,7 +191,7 @@ where #[async_trait] impl

ChannelReaderProvider for ChannelBank

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn next_data(&mut self) -> PipelineResult> { match self.read() { @@ -217,7 +218,7 @@ where impl

OriginProvider for ChannelBank

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { fn origin(&self) -> Option { self.prev.origin() @@ -225,25 +226,44 @@ where } #[async_trait] -impl

SignalReceiver for ChannelBank

+impl

Stage for ChannelBank

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - self.prev.signal(signal).await?; + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; self.channels.clear(); self.channel_queue = VecDeque::with_capacity(10); Ok(()) } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await?; + self.channels.clear(); + self.channel_queue = VecDeque::with_capacity(10); + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.prev.flush_channel().await?; + self.channels.clear(); + self.channel_queue = VecDeque::with_capacity(10); + Ok(()) + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] mod tests { use super::*; - use crate::{ - test_utils::{CollectingLayer, TestNextFrameProvider, TraceStorage}, - types::ResetSignal, - }; + use crate::test_utils::{CollectingLayer, TestNextFrameProvider, TraceStorage}; use alloc::{vec, vec::Vec}; use kona_genesis::HardForkConfig; use tracing::Level; @@ -410,7 +430,7 @@ mod tests { channel_bank.channels.insert([0xFF; 16], Channel::default()); channel_bank.channel_queue.push_back([0xFF; 16]); assert!(!channel_bank.prev.reset); - channel_bank.signal(ResetSignal::default().signal()).await.unwrap(); + channel_bank.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); assert_eq!(channel_bank.channels.len(), 0); assert_eq!(channel_bank.channel_queue.len(), 0); assert!(channel_bank.prev.reset); @@ -513,7 +533,7 @@ mod tests { let configs: [RollupConfig; 2] = [ kona_registry::ROLLUP_CONFIGS.get(&10).cloned().unwrap(), - kona_registry::ROLLUP_CONFIGS.get(&8453).cloned().unwrap(), + kona_registry::ROLLUP_CONFIGS.get(&11155420).cloned().unwrap(), ]; for cfg in configs { diff --git a/rust/kona/crates/protocol/derive/src/stages/channel/channel_provider.rs b/rust/kona/crates/protocol/derive/src/stages/channel/channel_provider.rs index 01feda60c1d..3626dc5da41 100644 --- a/rust/kona/crates/protocol/derive/src/stages/channel/channel_provider.rs +++ b/rust/kona/crates/protocol/derive/src/stages/channel/channel_provider.rs @@ -3,14 +3,15 @@ use super::{ChannelAssembler, ChannelBank, ChannelReaderProvider, NextFrameProvider}; use crate::{ errors::PipelineError, - traits::{OriginAdvancer, OriginProvider, SignalReceiver}, - types::{PipelineResult, Signal}, + traits::{OriginAdvancer, OriginProvider, Stage}, + types::PipelineResult, }; use alloc::{boxed::Box, sync::Arc}; +use alloy_eips::BlockNumHash; use alloy_primitives::Bytes; use async_trait::async_trait; use core::fmt::Debug; -use kona_genesis::RollupConfig; +use kona_genesis::{RollupConfig, SystemConfig}; use kona_protocol::BlockInfo; /// The [`ChannelProvider`] stage is a mux between the [`ChannelBank`] and [`ChannelAssembler`] @@ -22,7 +23,7 @@ use kona_protocol::BlockInfo; #[derive(Debug)] pub struct ChannelProvider

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// The rollup configuration. pub cfg: Arc, @@ -45,7 +46,7 @@ where impl

ChannelProvider

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// Creates a new [`ChannelProvider`] with the given configuration and previous stage. pub const fn new(cfg: Arc, prev: P) -> Self { @@ -85,7 +86,7 @@ where #[async_trait] impl

OriginAdvancer for ChannelProvider

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { self.attempt_update()?; @@ -102,7 +103,7 @@ where impl

OriginProvider for ChannelProvider

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Debug, { fn origin(&self) -> Option { self.channel_assembler.as_ref().map_or_else( @@ -117,28 +118,50 @@ where } } +/// Dispatches a method call to the active inner channel stage. +macro_rules! dispatch_inner { + ($self:ident, $method:ident $(, $arg:expr)*) => {{ + $self.attempt_update()?; + if let Some(inner) = $self.channel_assembler.as_mut() { + inner.$method($($arg),*).await + } else if let Some(inner) = $self.channel_bank.as_mut() { + inner.$method($($arg),*).await + } else { + Err(PipelineError::NotEnoughData.temp()) + } + }}; +} + #[async_trait] -impl

SignalReceiver for ChannelProvider

+impl

Stage for ChannelProvider

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - self.attempt_update()?; + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + dispatch_inner!(self, reset, l1_origin, system_config) + } - if let Some(channel_assembler) = self.channel_assembler.as_mut() { - channel_assembler.signal(signal).await - } else if let Some(channel_bank) = self.channel_bank.as_mut() { - channel_bank.signal(signal).await - } else { - Err(PipelineError::NotEnoughData.temp()) - } + async fn activate(&mut self) -> PipelineResult<()> { + dispatch_inner!(self, activate) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + dispatch_inner!(self, flush_channel) + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + dispatch_inner!(self, provide_block, block) } } #[async_trait] impl

ChannelReaderProvider for ChannelProvider

where - P: NextFrameProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: NextFrameProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn next_data(&mut self) -> PipelineResult> { self.attempt_update()?; @@ -156,11 +179,12 @@ where #[cfg(test)] mod test { use crate::{ - ChannelProvider, ChannelReaderProvider, OriginProvider, PipelineError, ResetSignal, - SignalReceiver, test_utils::TestNextFrameProvider, + ChannelProvider, ChannelReaderProvider, OriginProvider, PipelineError, Stage, + test_utils::TestNextFrameProvider, }; use alloc::{sync::Arc, vec}; - use kona_genesis::{HardForkConfig, RollupConfig}; + use alloy_eips::BlockNumHash; + use kona_genesis::{HardForkConfig, RollupConfig, SystemConfig}; use kona_protocol::BlockInfo; #[test] @@ -319,7 +343,7 @@ mod test { assert!(channel_bank.channel_queue.len() == 1); // Reset the channel provider. - channel_provider.signal(ResetSignal::default().signal()).await.unwrap(); + channel_provider.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); // Ensure the channel queue is empty after reset. let Some(channel_bank) = channel_provider.channel_bank.as_mut() else { @@ -353,7 +377,7 @@ mod test { assert!(channel_assembler.channel.is_some()); // Reset the channel provider. - channel_provider.signal(ResetSignal::default().signal()).await.unwrap(); + channel_provider.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); // Ensure the channel assembler is empty after reset. let Some(channel_assembler) = channel_provider.channel_assembler.as_mut() else { diff --git a/rust/kona/crates/protocol/derive/src/stages/channel/channel_reader.rs b/rust/kona/crates/protocol/derive/src/stages/channel/channel_reader.rs index c80b474540d..ae5afc3253c 100644 --- a/rust/kona/crates/protocol/derive/src/stages/channel/channel_reader.rs +++ b/rust/kona/crates/protocol/derive/src/stages/channel/channel_reader.rs @@ -1,15 +1,15 @@ //! This module contains the `ChannelReader` struct. use crate::{ - BatchStreamProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Signal, - SignalReceiver, + BatchStreamProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Stage, }; use alloc::{boxed::Box, sync::Arc}; +use alloy_eips::BlockNumHash; use alloy_primitives::Bytes; use async_trait::async_trait; use core::fmt::Debug; use kona_genesis::{ - MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, MAX_RLP_BYTES_PER_CHANNEL_FJORD, RollupConfig, + MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, MAX_RLP_BYTES_PER_CHANNEL_FJORD, RollupConfig, SystemConfig, }; use kona_protocol::{Batch, BatchReader, BlockInfo}; use tracing::{debug, warn}; @@ -36,7 +36,7 @@ pub trait ChannelReaderProvider { #[derive(Debug)] pub struct ChannelReader

where - P: ChannelReaderProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: ChannelReaderProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// The previous stage of the derivation pipeline. pub prev: P, @@ -48,7 +48,7 @@ where impl

ChannelReader

where - P: ChannelReaderProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: ChannelReaderProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// Create a new [`ChannelReader`] stage. pub const fn new(prev: P, cfg: Arc) -> Self { @@ -68,8 +68,11 @@ where MAX_RLP_BYTES_PER_CHANNEL_BEDROCK }; - self.next_batch = - Some(BatchReader::new(&channel[..], max_rlp_bytes_per_channel as usize)); + self.next_batch = Some(BatchReader::new( + &channel[..], + max_rlp_bytes_per_channel as usize, + origin.timestamp, + )); kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_BATCH_READER_SET, 1); } Ok(()) @@ -86,7 +89,7 @@ where #[async_trait] impl

OriginAdvancer for ChannelReader

where - P: ChannelReaderProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: ChannelReaderProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { self.prev.advance_origin().await @@ -96,7 +99,7 @@ where #[async_trait] impl

BatchStreamProvider for ChannelReader

where - P: ChannelReaderProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: ChannelReaderProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { /// This method is called by the `BatchStream` if an invalid span batch is found. /// In the case of an invalid span batch, the associated channel must be flushed. @@ -121,8 +124,8 @@ where match next_batch.decompress() { Ok(()) => { // Record the decompressed size and type. - let size = next_batch.decompressed.len() as f64; - let ty = if next_batch.brotli_used { + let _size = next_batch.decompressed.len() as f64; + let _ty = if next_batch.brotli_used { BatchReader::CHANNEL_VERSION_BROTLI } else { BatchReader::ZLIB_DEFLATE_COMPRESSION_METHOD @@ -130,12 +133,12 @@ where kona_macros::set!( gauge, crate::metrics::Metrics::PIPELINE_LATEST_DECOMPRESSED_BATCH_SIZE, - size + _size ); kona_macros::set!( gauge, crate::metrics::Metrics::PIPELINE_LATEST_DECOMPRESSED_BATCH_TYPE, - ty as f64 + _ty as f64 ); } Err(err) => { @@ -165,7 +168,7 @@ where impl

OriginProvider for ChannelReader

where - P: ChannelReaderProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: ChannelReaderProvider + OriginAdvancer + OriginProvider + Stage + Debug, { fn origin(&self) -> Option { self.prev.origin() @@ -173,33 +176,42 @@ where } #[async_trait] -impl

SignalReceiver for ChannelReader

+impl

Stage for ChannelReader

where - P: ChannelReaderProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug + Send, + P: ChannelReaderProvider + OriginAdvancer + OriginProvider + Stage + Debug + Send, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - Signal::FlushChannel => { - // Drop the current in-progress channel. - warn!(target: "channel_reader", "Flushed channel"); - self.next_batch = None; - kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_BATCH_READER_SET, 0); - } - s => { - self.prev.signal(s).await?; - self.next_channel(); - } - } + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; + self.next_channel(); + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await?; + self.next_channel(); + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + warn!(target: "channel_reader", "Flushed channel"); + self.next_batch = None; + kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_BATCH_READER_SET, 0); Ok(()) } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] mod test { use super::*; - use crate::{ - errors::PipelineErrorKind, test_utils::TestChannelReaderProvider, types::ResetSignal, - }; + use crate::{errors::PipelineErrorKind, test_utils::TestChannelReaderProvider}; use alloc::vec; use kona_genesis::HardForkConfig; @@ -218,8 +230,9 @@ mod test { reader.next_batch = Some(BatchReader::new( new_compressed_batch_data(), MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize, + 0, )); - reader.signal(Signal::FlushChannel).await.unwrap(); + reader.flush_channel().await.unwrap(); assert!(reader.next_batch.is_none()); } @@ -230,9 +243,10 @@ mod test { reader.next_batch = Some(BatchReader::new( vec![0x00, 0x01, 0x02], MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize, + 0, )); assert!(!reader.prev.reset); - reader.signal(ResetSignal::default().signal()).await.unwrap(); + reader.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); assert!(reader.next_batch.is_none()); assert!(reader.prev.reset); } diff --git a/rust/kona/crates/protocol/derive/src/stages/frame_queue.rs b/rust/kona/crates/protocol/derive/src/stages/frame_queue.rs index 92518579a55..e85ed8f608d 100644 --- a/rust/kona/crates/protocol/derive/src/stages/frame_queue.rs +++ b/rust/kona/crates/protocol/derive/src/stages/frame_queue.rs @@ -1,14 +1,14 @@ //! This module contains the [`FrameQueue`] stage of the derivation pipeline. use crate::{ - NextFrameProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Signal, - SignalReceiver, + NextFrameProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Stage, }; use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; +use alloy_eips::BlockNumHash; use alloy_primitives::Bytes; use async_trait::async_trait; use core::fmt::Debug; -use kona_genesis::RollupConfig; +use kona_genesis::{RollupConfig, SystemConfig}; use kona_protocol::{BlockInfo, Frame}; /// Provides data frames for the [`FrameQueue`] stage. @@ -30,7 +30,7 @@ pub trait FrameQueueProvider { #[derive(Debug)] pub struct FrameQueue

where - P: FrameQueueProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: FrameQueueProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// The previous stage in the pipeline. pub prev: P, @@ -42,7 +42,7 @@ where impl

FrameQueue

where - P: FrameQueueProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: FrameQueueProvider + OriginAdvancer + OriginProvider + Stage + Debug, { /// Create a new [`FrameQueue`] stage with the given previous [`L1Retrieval`] stage. /// @@ -137,8 +137,8 @@ where crate::metrics::Metrics::PIPELINE_FRAME_QUEUE_BUFFER, self.queue.len() as f64 ); - let queue_size = self.queue.iter().map(|f| f.size()).sum::() as f64; - kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_FRAME_QUEUE_MEM, queue_size); + let _queue_size = self.queue.iter().map(|f| f.size()).sum::() as f64; + kona_macros::set!(gauge, crate::metrics::Metrics::PIPELINE_FRAME_QUEUE_MEM, _queue_size); // Prune frames if Holocene is active. let origin = self.origin().ok_or(PipelineError::MissingOrigin.crit())?; @@ -151,7 +151,7 @@ where #[async_trait] impl

OriginAdvancer for FrameQueue

where - P: FrameQueueProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: FrameQueueProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn advance_origin(&mut self) -> PipelineResult<()> { self.prev.advance_origin().await @@ -161,7 +161,7 @@ where #[async_trait] impl

NextFrameProvider for FrameQueue

where - P: FrameQueueProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: FrameQueueProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { async fn next_frame(&mut self) -> PipelineResult { self.load_frames().await?; @@ -178,7 +178,7 @@ where impl

OriginProvider for FrameQueue

where - P: FrameQueueProvider + OriginAdvancer + OriginProvider + SignalReceiver + Debug, + P: FrameQueueProvider + OriginAdvancer + OriginProvider + Stage + Debug, { fn origin(&self) -> Option { self.prev.origin() @@ -186,21 +186,41 @@ where } #[async_trait] -impl

SignalReceiver for FrameQueue

+impl

Stage for FrameQueue

where - P: FrameQueueProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send + Debug, + P: FrameQueueProvider + OriginAdvancer + OriginProvider + Stage + Send + Debug, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - self.prev.signal(signal).await?; + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; self.queue = VecDeque::default(); Ok(()) } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await?; + self.queue = VecDeque::default(); + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.prev.flush_channel().await?; + self.queue = VecDeque::default(); + Ok(()) + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::{test_utils::TestFrameQueueProvider, types::ResetSignal}; + use crate::test_utils::TestFrameQueueProvider; use alloc::vec; use kona_genesis::HardForkConfig; @@ -209,7 +229,7 @@ pub(crate) mod tests { let mock = TestFrameQueueProvider::new(vec![]); let mut frame_queue = FrameQueue::new(mock, Default::default()); assert!(!frame_queue.prev.reset); - frame_queue.signal(ResetSignal::default().signal()).await.unwrap(); + frame_queue.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); assert_eq!(frame_queue.queue.len(), 0); assert!(frame_queue.prev.reset); } diff --git a/rust/kona/crates/protocol/derive/src/stages/l1_retrieval.rs b/rust/kona/crates/protocol/derive/src/stages/l1_retrieval.rs index bc302550ec1..ab33f7e171b 100644 --- a/rust/kona/crates/protocol/derive/src/stages/l1_retrieval.rs +++ b/rust/kona/crates/protocol/derive/src/stages/l1_retrieval.rs @@ -1,12 +1,14 @@ //! Contains the [`L1Retrieval`] stage of the derivation pipeline. use crate::{ - ActivationSignal, DataAvailabilityProvider, FrameQueueProvider, OriginAdvancer, OriginProvider, - PipelineError, PipelineErrorKind, PipelineResult, ResetSignal, Signal, SignalReceiver, + DataAvailabilityProvider, FrameQueueProvider, OriginAdvancer, OriginProvider, PipelineError, + PipelineErrorKind, PipelineResult, Stage, }; use alloc::boxed::Box; +use alloy_eips::BlockNumHash; use alloy_primitives::Address; use async_trait::async_trait; +use kona_genesis::SystemConfig; use kona_protocol::BlockInfo; /// Provides L1 blocks for the [`L1Retrieval`] stage. @@ -36,7 +38,7 @@ pub trait L1RetrievalProvider { pub struct L1Retrieval where DAP: DataAvailabilityProvider, - P: L1RetrievalProvider + OriginAdvancer + OriginProvider + SignalReceiver, + P: L1RetrievalProvider + OriginAdvancer + OriginProvider + Stage, { /// The previous stage in the pipeline. pub prev: P, @@ -49,7 +51,7 @@ where impl L1Retrieval where DAP: DataAvailabilityProvider, - P: L1RetrievalProvider + OriginAdvancer + OriginProvider + SignalReceiver, + P: L1RetrievalProvider + OriginAdvancer + OriginProvider + Stage, { /// Creates a new [`L1Retrieval`] stage with the previous [`PollingTraversal`] stage and given /// [`DataAvailabilityProvider`]. @@ -64,7 +66,7 @@ where impl OriginAdvancer for L1Retrieval where DAP: DataAvailabilityProvider + Send, - P: L1RetrievalProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send, + P: L1RetrievalProvider + OriginAdvancer + OriginProvider + Stage + Send, { async fn advance_origin(&mut self) -> PipelineResult<()> { self.prev.advance_origin().await @@ -75,7 +77,7 @@ where impl FrameQueueProvider for L1Retrieval where DAP: DataAvailabilityProvider + Send, - P: L1RetrievalProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send, + P: L1RetrievalProvider + OriginAdvancer + OriginProvider + Stage + Send, { type Item = DAP::Item; @@ -107,7 +109,7 @@ where impl OriginProvider for L1Retrieval where DAP: DataAvailabilityProvider, - P: L1RetrievalProvider + OriginAdvancer + OriginProvider + SignalReceiver, + P: L1RetrievalProvider + OriginAdvancer + OriginProvider + Stage, { fn origin(&self) -> Option { self.prev.origin() @@ -115,22 +117,35 @@ where } #[async_trait] -impl SignalReceiver for L1Retrieval +impl Stage for L1Retrieval where DAP: DataAvailabilityProvider + Send, - P: L1RetrievalProvider + OriginAdvancer + OriginProvider + SignalReceiver + Send, + P: L1RetrievalProvider + OriginAdvancer + OriginProvider + Stage + Send, { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - self.prev.signal(signal).await?; - match signal { - Signal::Reset(ResetSignal { l1_origin, .. }) | - Signal::Activation(ActivationSignal { l1_origin, .. }) => { - self.next = Some(l1_origin); - } - _ => {} - } + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + self.prev.reset(l1_origin, system_config).await?; + self.provider.clear(); + self.next = self.prev.origin(); + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + self.prev.activate().await?; + self.provider.clear(); Ok(()) } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.prev.flush_channel().await + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.prev.provide_block(block).await + } } #[cfg(test)] @@ -148,7 +163,7 @@ mod tests { retrieval.prev.block = None; assert!(retrieval.prev.block.is_none()); retrieval.next = None; - retrieval.signal(Signal::FlushChannel).await.unwrap(); + retrieval.flush_channel().await.unwrap(); assert!(retrieval.next.is_none()); assert!(retrieval.prev.block.is_none()); } @@ -156,39 +171,26 @@ mod tests { #[tokio::test] async fn test_l1_retrieval_activation_signal() { let traversal = TraversalTestHelper::new_populated(); - let dap = TestDAP { results: vec![] }; + let dap = TestDAP { results: vec![Ok(Bytes::default())] }; let mut retrieval = L1Retrieval::new(traversal, dap); - retrieval.prev.block = None; - assert!(retrieval.prev.block.is_none()); - retrieval.next = None; - retrieval - .signal( - ActivationSignal { system_config: Some(Default::default()), ..Default::default() } - .signal(), - ) - .await - .unwrap(); - assert!(retrieval.next.is_some()); - assert_eq!(retrieval.prev.block, Some(BlockInfo::default())); + retrieval.activate().await.unwrap(); + // Provider must be cleared on activation to flush stale data. + assert!(retrieval.provider.results.is_empty()); } #[tokio::test] async fn test_l1_retrieval_reset_signal() { let traversal = TraversalTestHelper::new_populated(); - let dap = TestDAP { results: vec![] }; + let dap = TestDAP { results: vec![Ok(Bytes::default())] }; let mut retrieval = L1Retrieval::new(traversal, dap); retrieval.prev.block = None; assert!(retrieval.prev.block.is_none()); retrieval.next = None; - retrieval - .signal( - ResetSignal { system_config: Some(Default::default()), ..Default::default() } - .signal(), - ) - .await - .unwrap(); + retrieval.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); assert!(retrieval.next.is_some()); assert_eq!(retrieval.prev.block, Some(BlockInfo::default())); + // Provider must be cleared on reset to flush stale data. + assert!(retrieval.provider.results.is_empty()); } #[tokio::test] @@ -239,6 +241,30 @@ mod tests { assert!(retrieval.next.is_none()); } + /// Regression test: stale DAP data loaded before a reset must not be returned after the reset. + #[tokio::test] + async fn test_l1_retrieval_reset_clears_stale_data() { + let traversal = TraversalTestHelper::new_populated(); + let dap = TestDAP { results: vec![Ok(Bytes::from_static(b"stale"))] }; + let mut retrieval = L1Retrieval::new(traversal, dap); + retrieval.next = Some(BlockInfo::default()); + retrieval.reset(BlockNumHash::default(), SystemConfig::default()).await.unwrap(); + let err = retrieval.next_data().await.unwrap_err(); + assert_eq!(err, PipelineError::Eof.temp()); + } + + /// Regression test: stale DAP data loaded before an activation must not be returned after it. + #[tokio::test] + async fn test_l1_retrieval_activation_clears_stale_data() { + let traversal = TraversalTestHelper::new_populated(); + let dap = TestDAP { results: vec![Ok(Bytes::from_static(b"stale"))] }; + let mut retrieval = L1Retrieval::new(traversal, dap); + retrieval.next = Some(BlockInfo::default()); + retrieval.activate().await.unwrap(); + let err = retrieval.next_data().await.unwrap_err(); + assert_eq!(err, PipelineError::Eof.temp()); + } + #[tokio::test] async fn test_l1_retrieval_existing_data_errors() { let traversal = TraversalTestHelper::new_populated(); diff --git a/rust/kona/crates/protocol/derive/src/stages/traversal/indexed.rs b/rust/kona/crates/protocol/derive/src/stages/traversal/indexed.rs index fd70ea9660e..40253c46819 100644 --- a/rust/kona/crates/protocol/derive/src/stages/traversal/indexed.rs +++ b/rust/kona/crates/protocol/derive/src/stages/traversal/indexed.rs @@ -1,10 +1,11 @@ //! Contains the [`IndexedTraversal`] stage of the derivation pipeline. use crate::{ - ActivationSignal, ChainProvider, L1RetrievalProvider, OriginAdvancer, OriginProvider, - PipelineError, PipelineResult, ResetError, ResetSignal, Signal, SignalReceiver, + ChainProvider, L1RetrievalProvider, OriginAdvancer, OriginProvider, PipelineError, + PipelineResult, ResetError, Stage, }; use alloc::{boxed::Box, sync::Arc}; +use alloy_eips::BlockNumHash; use alloy_primitives::Address; use async_trait::async_trait; use kona_genesis::{RollupConfig, SystemConfig}; @@ -90,30 +91,25 @@ impl IndexedTraversal { let receipts = self.data_source.receipts_by_hash(block_info.hash).await.map_err(Into::into)?; - let addr = self.rollup_config.l1_system_config_address; - let active = self.rollup_config.is_ecotone_active(block_info.timestamp); - match self.system_config.update_with_receipts(&receipts[..], addr, active) { - Ok(true) => { - let next = block_info.number as f64; - kona_macros::set!(gauge, crate::Metrics::PIPELINE_LATEST_SYS_CONFIG_UPDATE, next); - info!(target: "traversal", "System config updated at block {next}."); - } - Ok(false) => { /* Ignore, no update applied */ } - Err(err) => { - // Failure to update the system config is non-fatal: one or more receipts may be - // malformed or invalid. Log a warning and continue. - warn!(target: "traversal", ?err, "Failed to update system config at block {} (non-fatal, continuing)", block_info.number); - kona_macros::set!( - gauge, - crate::Metrics::PIPELINE_SYS_CONFIG_UPDATE_ERROR, - block_info.number as f64 - ); - } - } + super::update_system_config_with_receipts( + &mut self.system_config, + &receipts, + self.rollup_config.l1_system_config_address, + self.rollup_config.is_ecotone_active(block_info.timestamp), + block_info.number, + ); + + let prev_block_holocene = self.rollup_config.is_holocene_active(block.timestamp); + let next_block_holocene = self.rollup_config.is_holocene_active(block_info.timestamp); // Update the origin block. self.update_origin(block_info); + // If Holocene activates on this block, flag it so the pipeline driver resets. + if !prev_block_holocene && next_block_holocene { + return Err(ResetError::HoloceneActivation.reset()); + } + Ok(()) } } @@ -136,20 +132,30 @@ impl OriginProvider for IndexedTraversal { } #[async_trait] -impl SignalReceiver for IndexedTraversal { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - Signal::Reset(ResetSignal { l1_origin, system_config, .. }) | - Signal::Activation(ActivationSignal { l1_origin, system_config, .. }) => { - self.update_origin(l1_origin); - self.system_config = system_config.expect("System config must be provided."); - } - Signal::ProvideBlock(block_info) => self.provide_next_block(block_info).await?, - _ => {} - } +impl Stage for IndexedTraversal { + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + let block_info = + self.data_source.block_info_by_number(l1_origin.number).await.map_err(Into::into)?; + self.update_origin(block_info); + self.system_config = system_config; + Ok(()) + } + async fn activate(&mut self) -> PipelineResult<()> { Ok(()) } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + Ok(()) + } + + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()> { + self.provide_next_block(block).await + } } #[cfg(test)] @@ -159,7 +165,7 @@ mod tests { use alloc::vec; use alloy_consensus::Receipt; use alloy_primitives::{B256, Bytes, Log, LogData, address, b256, hex}; - use kona_genesis::{CONFIG_UPDATE_EVENT_VERSION_0, CONFIG_UPDATE_TOPIC}; + use kona_genesis::{CONFIG_UPDATE_EVENT_VERSION_0, CONFIG_UPDATE_TOPIC, HardForkConfig}; const L1_SYS_CONFIG_ADDR: Address = address!("1337000000000000000000000000000000000000"); @@ -227,20 +233,10 @@ mod tests { let blocks = vec![BlockInfo::default(), BlockInfo::default()]; let receipts = new_receipts(); let mut traversal = new_test_managed(blocks, receipts); - let cfg = SystemConfig::default(); traversal.done = true; - assert!( - traversal - .signal(Signal::Activation(ActivationSignal { - system_config: Some(cfg), - ..Default::default() - })) - .await - .is_ok() - ); + assert!(traversal.activate().await.is_ok()); assert_eq!(traversal.origin(), Some(BlockInfo::default())); - assert_eq!(traversal.system_config, cfg); - assert!(!traversal.done); + assert!(traversal.done); } #[tokio::test] @@ -250,15 +246,7 @@ mod tests { let mut traversal = new_test_managed(blocks, receipts); let cfg = SystemConfig::default(); traversal.done = true; - assert!( - traversal - .signal(Signal::Reset(ResetSignal { - system_config: Some(cfg), - ..Default::default() - })) - .await - .is_ok() - ); + assert!(traversal.reset(BlockNumHash::default(), cfg).await.is_ok()); assert_eq!(traversal.origin(), Some(BlockInfo::default())); assert_eq!(traversal.system_config, cfg); assert!(!traversal.done); @@ -344,4 +332,32 @@ mod tests { let expected = address!("000000000000000000000000000000000000bEEF"); assert_eq!(traversal.system_config.batcher_address, expected); } + + #[tokio::test] + async fn test_managed_traversal_holocene_activation_reset() { + let first = b256!("3333333333333333333333333333333333333333333333333333333333333333"); + let second = b256!("4444444444444444444444444444444444444444444444444444444444444444"); + // Block before Holocene activation (timestamp 99), block at activation (timestamp 100). + let block1 = BlockInfo { hash: first, timestamp: 99, ..BlockInfo::default() }; + let block2 = BlockInfo { number: 1, hash: second, parent_hash: first, timestamp: 100 }; + + let mut provider = TestChainProvider::default(); + provider.insert_block(0, block1); + provider.insert_block(1, block2); + provider.insert_receipts(second, vec![]); + + let rollup_config = RollupConfig { + l1_system_config_address: L1_SYS_CONFIG_ADDR, + hardforks: HardForkConfig { holocene_time: Some(100), ..Default::default() }, + ..RollupConfig::default() + }; + let mut traversal = IndexedTraversal::new(provider, Arc::new(rollup_config)); + traversal.block = Some(block1); + traversal.done = true; + + let err = traversal.provide_next_block(block2).await.unwrap_err(); + assert_eq!(err, ResetError::HoloceneActivation.reset()); + // Origin should still be updated despite the reset error. + assert_eq!(traversal.origin(), Some(block2)); + } } diff --git a/rust/kona/crates/protocol/derive/src/stages/traversal/mod.rs b/rust/kona/crates/protocol/derive/src/stages/traversal/mod.rs index dac09066187..4125df29d75 100644 --- a/rust/kona/crates/protocol/derive/src/stages/traversal/mod.rs +++ b/rust/kona/crates/protocol/derive/src/stages/traversal/mod.rs @@ -9,6 +9,10 @@ //! - [`PollingTraversal`]: An active traversal stage that polls for the next block through its //! provider. +use alloy_consensus::Receipt; +use alloy_primitives::Address; +use kona_genesis::SystemConfig; + mod indexed; pub use indexed::IndexedTraversal; @@ -23,3 +27,34 @@ pub enum TraversalStage { /// An active traversal stage that polls for the next block through its provider. Polling, } + +/// Updates the system config with receipts, logging each applied update and any errors, +/// and setting the appropriate metrics gauges. +fn update_system_config_with_receipts( + system_config: &mut SystemConfig, + receipts: &[Receipt], + l1_system_config_address: Address, + ecotone_active: bool, + block_number: u64, +) { + let (updates, errors) = + system_config.update_with_receipts(receipts, l1_system_config_address, ecotone_active); + for kind in &updates { + info!(target: "traversal", %kind, block_number, "Applied system config update"); + } + if !updates.is_empty() { + kona_macros::set!( + gauge, + crate::Metrics::PIPELINE_LATEST_SYS_CONFIG_UPDATE, + block_number as f64 + ); + } + for err in &errors { + warn!(target: "traversal", ?err, "Malformed system config update at block {block_number} (skipped)"); + kona_macros::set!( + gauge, + crate::Metrics::PIPELINE_SYS_CONFIG_UPDATE_ERROR, + block_number as f64 + ); + } +} diff --git a/rust/kona/crates/protocol/derive/src/stages/traversal/polling.rs b/rust/kona/crates/protocol/derive/src/stages/traversal/polling.rs index ed46625c33e..67c75f56d7f 100644 --- a/rust/kona/crates/protocol/derive/src/stages/traversal/polling.rs +++ b/rust/kona/crates/protocol/derive/src/stages/traversal/polling.rs @@ -1,10 +1,11 @@ //! Contains the [`PollingTraversal`] stage of the derivation pipeline. use crate::{ - ActivationSignal, ChainProvider, L1RetrievalProvider, OriginAdvancer, OriginProvider, - PipelineError, PipelineResult, ResetError, ResetSignal, Signal, SignalReceiver, + ChainProvider, L1RetrievalProvider, OriginAdvancer, OriginProvider, PipelineError, + PipelineResult, ResetError, Stage, }; use alloc::{boxed::Box, sync::Arc}; +use alloy_eips::BlockNumHash; use alloy_primitives::Address; use async_trait::async_trait; use kona_genesis::{RollupConfig, SystemConfig}; @@ -98,26 +99,13 @@ impl OriginAdvancer for PollingTraversal { let receipts = self.data_source.receipts_by_hash(next_l1_origin.hash).await.map_err(Into::into)?; - let addr = self.rollup_config.l1_system_config_address; - let active = self.rollup_config.is_ecotone_active(next_l1_origin.timestamp); - match self.system_config.update_with_receipts(&receipts[..], addr, active) { - Ok(true) => { - let next = next_l1_origin.number as f64; - kona_macros::set!(gauge, crate::Metrics::PIPELINE_LATEST_SYS_CONFIG_UPDATE, next); - info!(target: "l1_traversal", "System config updated at block {next}."); - } - Ok(false) => { /* Ignore, no update applied */ } - Err(err) => { - // Failure to update the system config is non-fatal: one or more receipts may be - // malformed or invalid. Log a warning and continue. - warn!(target: "l1_traversal", ?err, "Failed to update system config at block {} (non-fatal, continuing)", next_l1_origin.number); - kona_macros::set!( - gauge, - crate::Metrics::PIPELINE_SYS_CONFIG_UPDATE_ERROR, - next_l1_origin.number as f64 - ); - } - } + super::update_system_config_with_receipts( + &mut self.system_config, + &receipts, + self.rollup_config.l1_system_config_address, + self.rollup_config.is_ecotone_active(next_l1_origin.timestamp), + next_l1_origin.number, + ); let prev_block_holocene = self.rollup_config.is_holocene_active(block.timestamp); let next_block_holocene = self.rollup_config.is_holocene_active(next_l1_origin.timestamp); @@ -153,24 +141,31 @@ impl OriginProvider for PollingTraversal { } #[async_trait] -impl SignalReceiver for PollingTraversal { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - Signal::Reset(ResetSignal { l1_origin, system_config, .. }) | - Signal::Activation(ActivationSignal { l1_origin, system_config, .. }) => { - self.update_origin(l1_origin); - self.system_config = system_config.expect("System config must be provided."); - } - Signal::ProvideBlock(_) => { - /* Not supported in this stage. */ - warn!(target: "traversal", "ProvideBlock signal not supported in PollingTraversal stage."); - return Err(PipelineError::UnsupportedSignal.temp()); - } - _ => {} - } +impl Stage for PollingTraversal { + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()> { + let block_info = + self.data_source.block_info_by_number(l1_origin.number).await.map_err(Into::into)?; + self.update_origin(block_info); + self.system_config = system_config; + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + Ok(()) + } + async fn flush_channel(&mut self) -> PipelineResult<()> { Ok(()) } + + async fn provide_block(&mut self, _: BlockInfo) -> PipelineResult<()> { + warn!(target: "traversal", "provide_block not supported in PollingTraversal stage."); + Err(PipelineError::UnsupportedSignal.temp()) + } } #[cfg(test)] @@ -199,7 +194,7 @@ pub(crate) mod tests { let mut traversal = TraversalTestHelper::new_from_blocks(blocks, receipts); assert!(traversal.advance_origin().await.is_ok()); traversal.done = true; - assert!(traversal.signal(Signal::FlushChannel).await.is_ok()); + assert!(traversal.flush_channel().await.is_ok()); assert_eq!(traversal.origin(), Some(BlockInfo::default())); assert!(traversal.done); } @@ -210,19 +205,10 @@ pub(crate) mod tests { let receipts = TraversalTestHelper::new_receipts(); let mut traversal = TraversalTestHelper::new_from_blocks(blocks, receipts); assert!(traversal.advance_origin().await.is_ok()); - let cfg = SystemConfig::default(); traversal.done = true; - assert!( - traversal - .signal( - ActivationSignal { system_config: Some(cfg), ..Default::default() }.signal() - ) - .await - .is_ok() - ); + assert!(traversal.activate().await.is_ok()); assert_eq!(traversal.origin(), Some(BlockInfo::default())); - assert_eq!(traversal.system_config, cfg); - assert!(!traversal.done); + assert!(traversal.done); } #[tokio::test] @@ -233,12 +219,7 @@ pub(crate) mod tests { assert!(traversal.advance_origin().await.is_ok()); let cfg = SystemConfig::default(); traversal.done = true; - assert!( - traversal - .signal(ResetSignal { system_config: Some(cfg), ..Default::default() }.signal()) - .await - .is_ok() - ); + assert!(traversal.reset(BlockNumHash::default(), cfg).await.is_ok()); assert_eq!(traversal.origin(), Some(BlockInfo::default())); assert_eq!(traversal.system_config, cfg); assert!(!traversal.done); diff --git a/rust/kona/crates/protocol/derive/src/test_utils/attributes_queue.rs b/rust/kona/crates/protocol/derive/src/test_utils/attributes_queue.rs index 51f52a39d1e..48c5ba990fd 100644 --- a/rust/kona/crates/protocol/derive/src/test_utils/attributes_queue.rs +++ b/rust/kona/crates/protocol/derive/src/test_utils/attributes_queue.rs @@ -2,14 +2,13 @@ use crate::{ errors::{PipelineError, PipelineErrorKind}, - traits::{ - AttributesBuilder, AttributesProvider, OriginAdvancer, OriginProvider, SignalReceiver, - }, - types::{PipelineResult, Signal}, + traits::{AttributesBuilder, AttributesProvider, OriginAdvancer, OriginProvider, Stage}, + types::PipelineResult, }; use alloc::{boxed::Box, vec::Vec}; use alloy_eips::BlockNumHash; use async_trait::async_trait; +use kona_genesis::SystemConfig; use kona_protocol::{BlockInfo, L2BlockInfo, SingleBatch}; use op_alloy_rpc_types_engine::OpPayloadAttributes; @@ -65,13 +64,23 @@ impl OriginAdvancer for TestAttributesProvider { } #[async_trait] -impl SignalReceiver for TestAttributesProvider { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - Signal::FlushChannel => self.flushed = true, - Signal::Reset { .. } => self.reset = true, - _ => {} - } +impl Stage for TestAttributesProvider { + async fn reset(&mut self, _: alloy_eips::BlockNumHash, _: SystemConfig) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.flushed = true; + Ok(()) + } + + async fn provide_block(&mut self, _: BlockInfo) -> PipelineResult<()> { Ok(()) } } diff --git a/rust/kona/crates/protocol/derive/src/test_utils/batch_provider.rs b/rust/kona/crates/protocol/derive/src/test_utils/batch_provider.rs index aa2c1f5c336..e12f0cb7306 100644 --- a/rust/kona/crates/protocol/derive/src/test_utils/batch_provider.rs +++ b/rust/kona/crates/protocol/derive/src/test_utils/batch_provider.rs @@ -3,11 +3,13 @@ use crate::{ errors::PipelineError, stages::NextBatchProvider, - traits::{OriginAdvancer, OriginProvider, SignalReceiver}, - types::{PipelineResult, Signal}, + traits::{OriginAdvancer, OriginProvider, Stage}, + types::PipelineResult, }; use alloc::{boxed::Box, vec::Vec}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; +use kona_genesis::SystemConfig; use kona_protocol::{Batch, BlockInfo, L2BlockInfo}; /// A mock provider for the [`NextBatchProvider`] stage. @@ -63,13 +65,23 @@ impl OriginAdvancer for TestNextBatchProvider { } #[async_trait] -impl SignalReceiver for TestNextBatchProvider { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - Signal::Reset { .. } => self.reset = true, - Signal::FlushChannel => self.flushed = true, - _ => {} - } +impl Stage for TestNextBatchProvider { + async fn reset(&mut self, _: BlockNumHash, _: SystemConfig) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.flushed = true; + Ok(()) + } + + async fn provide_block(&mut self, _: BlockInfo) -> PipelineResult<()> { Ok(()) } } diff --git a/rust/kona/crates/protocol/derive/src/test_utils/batch_stream.rs b/rust/kona/crates/protocol/derive/src/test_utils/batch_stream.rs index 7172be604cd..37830fbfcbc 100644 --- a/rust/kona/crates/protocol/derive/src/test_utils/batch_stream.rs +++ b/rust/kona/crates/protocol/derive/src/test_utils/batch_stream.rs @@ -3,11 +3,12 @@ //! [`BatchStream`]: crate::stages::BatchStream use crate::{ - BatchStreamProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Signal, - SignalReceiver, + BatchStreamProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Stage, }; use alloc::{boxed::Box, vec::Vec}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; +use kona_genesis::SystemConfig; use kona_protocol::{Batch, BlockInfo}; /// A mock provider for the [`BatchStream`] stage. @@ -55,13 +56,23 @@ impl OriginAdvancer for TestBatchStreamProvider { } #[async_trait] -impl SignalReceiver for TestBatchStreamProvider { - async fn signal(&mut self, signal: Signal) -> PipelineResult<()> { - match signal { - Signal::Reset { .. } => self.reset = true, - Signal::FlushChannel => self.flushed = true, - _ => {} - } +impl Stage for TestBatchStreamProvider { + async fn reset(&mut self, _: BlockNumHash, _: SystemConfig) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.flushed = true; + Ok(()) + } + + async fn provide_block(&mut self, _: BlockInfo) -> PipelineResult<()> { Ok(()) } } diff --git a/rust/kona/crates/protocol/derive/src/test_utils/channel_provider.rs b/rust/kona/crates/protocol/derive/src/test_utils/channel_provider.rs index e38732957ad..8183c61e931 100644 --- a/rust/kona/crates/protocol/derive/src/test_utils/channel_provider.rs +++ b/rust/kona/crates/protocol/derive/src/test_utils/channel_provider.rs @@ -3,11 +3,13 @@ use crate::{ errors::PipelineError, stages::NextFrameProvider, - traits::{OriginAdvancer, OriginProvider, SignalReceiver}, - types::{PipelineResult, Signal}, + traits::{OriginAdvancer, OriginProvider, Stage}, + types::PipelineResult, }; use alloc::{boxed::Box, vec::Vec}; +use alloy_eips::BlockNumHash; use async_trait::async_trait; +use kona_genesis::SystemConfig; use kona_protocol::{BlockInfo, Frame}; /// A mock [`NextFrameProvider`] for testing the [`ChannelBank`] stage. @@ -55,8 +57,23 @@ impl NextFrameProvider for TestNextFrameProvider { } #[async_trait] -impl SignalReceiver for TestNextFrameProvider { - async fn signal(&mut self, _: Signal) -> PipelineResult<()> { +impl Stage for TestNextFrameProvider { + async fn reset(&mut self, _: BlockNumHash, _: SystemConfig) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn provide_block(&mut self, _: BlockInfo) -> PipelineResult<()> { self.reset = true; Ok(()) } diff --git a/rust/kona/crates/protocol/derive/src/test_utils/channel_reader.rs b/rust/kona/crates/protocol/derive/src/test_utils/channel_reader.rs index ae66a2c4a37..7d34ed16524 100644 --- a/rust/kona/crates/protocol/derive/src/test_utils/channel_reader.rs +++ b/rust/kona/crates/protocol/derive/src/test_utils/channel_reader.rs @@ -1,12 +1,13 @@ //! Test utilities for the [`ChannelReader`](crate::stages::ChannelReader) stage. use crate::{ - ChannelReaderProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Signal, - SignalReceiver, + ChannelReaderProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Stage, }; use alloc::{boxed::Box, vec::Vec}; +use alloy_eips::BlockNumHash; use alloy_primitives::Bytes; use async_trait::async_trait; +use kona_genesis::SystemConfig; use kona_protocol::BlockInfo; /// A mock [`ChannelReaderProvider`] for testing the [`ChannelReader`] stage. @@ -50,9 +51,22 @@ impl ChannelReaderProvider for TestChannelReaderProvider { } #[async_trait] -impl SignalReceiver for TestChannelReaderProvider { - async fn signal(&mut self, _: Signal) -> PipelineResult<()> { +impl Stage for TestChannelReaderProvider { + async fn reset(&mut self, _: BlockNumHash, _: SystemConfig) -> PipelineResult<()> { self.reset = true; Ok(()) } + + async fn activate(&mut self) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.reset(BlockNumHash::default(), SystemConfig::default()).await + } + + async fn provide_block(&mut self, _: BlockInfo) -> PipelineResult<()> { + self.reset(BlockNumHash::default(), SystemConfig::default()).await + } } diff --git a/rust/kona/crates/protocol/derive/src/test_utils/frame_queue.rs b/rust/kona/crates/protocol/derive/src/test_utils/frame_queue.rs index 79e5bddecaf..4bb052b4b84 100644 --- a/rust/kona/crates/protocol/derive/src/test_utils/frame_queue.rs +++ b/rust/kona/crates/protocol/derive/src/test_utils/frame_queue.rs @@ -1,12 +1,13 @@ //! Mock types for the frame queue stage. use crate::{ - FrameQueueProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Signal, - SignalReceiver, + FrameQueueProvider, OriginAdvancer, OriginProvider, PipelineError, PipelineResult, Stage, }; use alloc::{boxed::Box, vec::Vec}; +use alloy_eips::BlockNumHash; use alloy_primitives::Bytes; use async_trait::async_trait; +use kona_genesis::SystemConfig; use kona_protocol::BlockInfo; /// A mock [`FrameQueueProvider`] for testing the frame queue stage. @@ -57,9 +58,22 @@ impl FrameQueueProvider for TestFrameQueueProvider { } #[async_trait] -impl SignalReceiver for TestFrameQueueProvider { - async fn signal(&mut self, _: Signal) -> PipelineResult<()> { +impl Stage for TestFrameQueueProvider { + async fn reset(&mut self, _: BlockNumHash, _: SystemConfig) -> PipelineResult<()> { self.reset = true; Ok(()) } + + async fn activate(&mut self) -> PipelineResult<()> { + self.reset = true; + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + self.reset(BlockNumHash::default(), SystemConfig::default()).await + } + + async fn provide_block(&mut self, _: BlockInfo) -> PipelineResult<()> { + self.reset(BlockNumHash::default(), SystemConfig::default()).await + } } diff --git a/rust/kona/crates/protocol/derive/src/test_utils/pipeline.rs b/rust/kona/crates/protocol/derive/src/test_utils/pipeline.rs index e1b968cd91e..4dc5261fd5c 100644 --- a/rust/kona/crates/protocol/derive/src/test_utils/pipeline.rs +++ b/rust/kona/crates/protocol/derive/src/test_utils/pipeline.rs @@ -13,9 +13,11 @@ use kona_protocol::{BlockInfo, L2BlockInfo, OpAttributesWithParent}; use crate::{ AttributesQueue, BatchStream, ChannelProvider, ChannelReader, DerivationPipeline, FrameQueue, L1Retrieval, NextAttributes, OriginAdvancer, OriginProvider, PipelineBuilder, PipelineError, - PollingTraversal, Signal, SignalReceiver, + PollingTraversal, Stage, test_utils::{TestAttributesBuilder, TestDAP}, }; +use alloy_eips::BlockNumHash; +use kona_genesis::SystemConfig; /// A fully custom [`NextAttributes`]. #[derive(Default, Debug, Clone)] @@ -25,9 +27,20 @@ pub struct TestNextAttributes { } #[async_trait::async_trait] -impl SignalReceiver for TestNextAttributes { - /// Resets the derivation stage to its initial state. - async fn signal(&mut self, _: Signal) -> PipelineResult<()> { +impl Stage for TestNextAttributes { + async fn reset(&mut self, _: BlockNumHash, _: SystemConfig) -> PipelineResult<()> { + Ok(()) + } + + async fn activate(&mut self) -> PipelineResult<()> { + Ok(()) + } + + async fn flush_channel(&mut self) -> PipelineResult<()> { + Ok(()) + } + + async fn provide_block(&mut self, _: BlockInfo) -> PipelineResult<()> { Ok(()) } } diff --git a/rust/kona/crates/protocol/derive/src/traits/mod.rs b/rust/kona/crates/protocol/derive/src/traits/mod.rs index 91f24c10fda..2dbc2269c9a 100644 --- a/rust/kona/crates/protocol/derive/src/traits/mod.rs +++ b/rust/kona/crates/protocol/derive/src/traits/mod.rs @@ -17,4 +17,4 @@ mod reset; pub use reset::ResetProvider; mod stages; -pub use stages::{OriginAdvancer, OriginProvider, SignalReceiver}; +pub use stages::{OriginAdvancer, OriginProvider, SignalReceiver, Stage}; diff --git a/rust/kona/crates/protocol/derive/src/traits/stages.rs b/rust/kona/crates/protocol/derive/src/traits/stages.rs index 6cb4b82cba2..d020ab2037e 100644 --- a/rust/kona/crates/protocol/derive/src/traits/stages.rs +++ b/rust/kona/crates/protocol/derive/src/traits/stages.rs @@ -1,18 +1,47 @@ //! This module contains common traits for stages within the derivation pipeline. use alloc::boxed::Box; +use alloy_eips::BlockNumHash; use async_trait::async_trait; +use kona_genesis::SystemConfig; use kona_protocol::BlockInfo; use crate::{PipelineResult, Signal}; -/// Providers a way for the pipeline to accept a signal from the driver. +/// Provides a way for the pipeline to accept a signal from the driver. #[async_trait] pub trait SignalReceiver { /// Receives a signal from the driver. async fn signal(&mut self, signal: Signal) -> PipelineResult<()>; } +/// Trait for pipeline stages. +/// +/// The [`DerivationPipeline`] receives external [`Signal`]s, computes +/// the correct L1 origin and system config, then dispatches to stages +/// via these methods. +/// +/// [`DerivationPipeline`]: crate::DerivationPipeline +#[async_trait] +pub trait Stage { + /// Reset the stage to derive from the given L1 origin with the given system config. + async fn reset( + &mut self, + l1_origin: BlockNumHash, + system_config: SystemConfig, + ) -> PipelineResult<()>; + + /// Soft-reset for hardfork activation. Clears buffered channels and batches + /// but preserves derivation state (origin, epoch tracking, system config). + async fn activate(&mut self) -> PipelineResult<()>; + + /// Flush the currently active channel. + async fn flush_channel(&mut self) -> PipelineResult<()>; + + /// Provide a new L1 block to the traversal stage. + async fn provide_block(&mut self, block: BlockInfo) -> PipelineResult<()>; +} + /// Provides a method for accessing the pipeline's current L1 origin. pub trait OriginProvider { /// Returns the optional L1 [`BlockInfo`] origin. diff --git a/rust/kona/crates/protocol/derive/src/types/signals.rs b/rust/kona/crates/protocol/derive/src/types/signals.rs index 35185f90d24..f64e7e1d7db 100644 --- a/rust/kona/crates/protocol/derive/src/types/signals.rs +++ b/rust/kona/crates/protocol/derive/src/types/signals.rs @@ -1,10 +1,12 @@ //! Signal types for the `kona-derive` pipeline. //! -//! Signals are the primary method of communication in the downwards direction -//! of the pipeline. They allow the pipeline driver to perform actions such as -//! resetting all stages in the pipeline through message passing. +//! Signals are the primary method of communication between the pipeline driver +//! and the [`DerivationPipeline`]. The pipeline receives [`Signal`]s and +//! dispatches to stages via [`Stage`] methods. +//! +//! [`DerivationPipeline`]: crate::DerivationPipeline +//! [`Stage`]: crate::Stage -use kona_genesis::SystemConfig; use kona_protocol::{BlockInfo, L2BlockInfo}; /// A signal to send to the pipeline. @@ -32,39 +34,11 @@ impl core::fmt::Display for Signal { } } -impl Signal { - /// Sets the [`SystemConfig`] for the signal. - pub const fn with_system_config(self, system_config: SystemConfig) -> Self { - match self { - Self::Reset(reset) => reset.with_system_config(system_config).signal(), - Self::Activation(activation) => activation.with_system_config(system_config).signal(), - Self::FlushChannel => Self::FlushChannel, - Self::ProvideBlock(block) => Self::ProvideBlock(block), - } - } -} - /// A pipeline reset signal. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct ResetSignal { /// The L2 safe head to reset to. pub l2_safe_head: L2BlockInfo, - /// The L1 origin to reset to. - pub l1_origin: BlockInfo, - /// The optional [`SystemConfig`] to reset with. - pub system_config: Option, -} - -impl ResetSignal { - /// Creates a new [`Signal::Reset`] from the [`ResetSignal`]. - pub const fn signal(self) -> Signal { - Signal::Reset(self) - } - - /// Sets the [`SystemConfig`] for the signal. - pub const fn with_system_config(self, system_config: SystemConfig) -> Self { - Self { system_config: Some(system_config), ..self } - } } /// A pipeline hardfork activation signal. @@ -72,22 +46,6 @@ impl ResetSignal { pub struct ActivationSignal { /// The L2 safe head to reset to. pub l2_safe_head: L2BlockInfo, - /// The L1 origin to reset to. - pub l1_origin: BlockInfo, - /// The optional [`SystemConfig`] to reset with. - pub system_config: Option, -} - -impl ActivationSignal { - /// Creates a new [`Signal::Activation`] from the [`ActivationSignal`]. - pub const fn signal(self) -> Signal { - Signal::Activation(self) - } - - /// Sets the [`SystemConfig`] for the signal. - pub const fn with_system_config(self, system_config: SystemConfig) -> Self { - Self { system_config: Some(system_config), ..self } - } } #[cfg(test)] @@ -97,31 +55,12 @@ mod tests { #[test] fn test_reset_signal() { let signal = ResetSignal::default(); - assert_eq!(signal.signal(), Signal::Reset(signal)); + assert_eq!(Signal::Reset(signal), Signal::Reset(signal)); } #[test] fn test_activation_signal() { let signal = ActivationSignal::default(); - assert_eq!(signal.signal(), Signal::Activation(signal)); - } - - #[test] - fn test_signal_with_system_config() { - let signal = ResetSignal::default(); - let system_config = SystemConfig::default(); - assert_eq!( - signal.with_system_config(system_config).signal(), - Signal::Reset(ResetSignal { system_config: Some(system_config), ..signal }) - ); - - let signal = ActivationSignal::default(); - let system_config = SystemConfig::default(); - assert_eq!( - signal.with_system_config(system_config).signal(), - Signal::Activation(ActivationSignal { system_config: Some(system_config), ..signal }) - ); - - assert_eq!(Signal::FlushChannel.with_system_config(system_config), Signal::FlushChannel); + assert_eq!(Signal::Activation(signal), Signal::Activation(signal)); } } diff --git a/rust/kona/crates/protocol/genesis/Cargo.toml b/rust/kona/crates/protocol/genesis/Cargo.toml index 87446eca090..0e0d017d056 100644 --- a/rust/kona/crates/protocol/genesis/Cargo.toml +++ b/rust/kona/crates/protocol/genesis/Cargo.toml @@ -51,6 +51,7 @@ alloy-primitives = { workspace = true, features = ["rand", "arbitrary"] } [features] default = [] +rollup_config_override = [] revm = [ "dep:op-revm" ] tabled = [ "dep:tabled", "std" ] std = [ diff --git a/rust/kona/crates/protocol/genesis/src/chain/config.rs b/rust/kona/crates/protocol/genesis/src/chain/config.rs index e15b892f2f9..2b1d8da2025 100644 --- a/rust/kona/crates/protocol/genesis/src/chain/config.rs +++ b/rust/kona/crates/protocol/genesis/src/chain/config.rs @@ -5,10 +5,12 @@ use alloy_chains::Chain; use alloy_eips::eip1559::BaseFeeParams; use alloy_primitives::Address; +#[cfg(feature = "rollup_config_override")] +use crate::FJORD_MAX_SEQUENCER_DRIFT; use crate::{ AddressList, AltDAConfig, BaseFeeConfig, ChainGenesis, GRANITE_CHANNEL_TIMEOUT, HardForkConfig, Roles, RollupConfig, SuperchainLevel, base_fee_params, base_fee_params_canyon, - params::base_fee_config, rollup::DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, + params::base_fee_config, }; /// L1 chain configuration from the `alloy-genesis` crate. @@ -106,6 +108,16 @@ pub struct ChainConfig { /// Addresses #[cfg_attr(feature = "serde", serde(rename = "Addresses", alias = "addresses"))] pub addresses: Option, + /// Interop configuration. Present when `[interop]` is set in the chain TOML. + /// + /// `serde(skip_serializing_if = "Option::is_none")` keeps the on-disk JSON + /// stable for chains that don't declare interop — round-tripping + /// `etc/configs.json` does not gain `"interop": null` lines. + #[cfg_attr( + feature = "serde", + serde(rename = "interop", default, skip_serializing_if = "Option::is_none") + )] + pub interop: Option, } impl ChainConfig { @@ -157,11 +169,6 @@ impl ChainConfig { .as_ref() .and_then(|a| a.system_config_proxy) .unwrap_or_default(), - protocol_versions_address: self - .addresses - .as_ref() - .and_then(|a| a.address_manager) - .unwrap_or_default(), superchain_config_address: None, blobs_enabled_l1_timestamp: None, da_challenge_address: self @@ -176,7 +183,8 @@ impl ChainConfig { // necessary. channel_timeout: 300, granite_channel_timeout: GRANITE_CHANNEL_TIMEOUT, - interop_message_expiry_window: DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, + #[cfg(feature = "rollup_config_override")] + fjord_max_sequencer_drift: FJORD_MAX_SEQUENCER_DRIFT, chain_op_config: self.base_fee_config(), alt_da_config: self.alt_da.clone(), } @@ -271,6 +279,24 @@ mod tests { assert_eq!(deserialized.name, "Base"); } + #[test] + fn test_chain_config_with_interop_dependencies() { + // Verbatim copy of the rehearsal-0-bn-0 chain TOML (interop cluster of 2). + let toml_src = include_str!("../../tests/fixtures/rehearsal-0-bn-0.toml"); + let cfg: ChainConfig = toml::from_str(toml_src).expect("parse rehearsal-0-bn-0.toml"); + let interop = cfg.interop.as_ref().expect("interop section present"); + assert!(interop.dependencies.contains_key(&420120009)); + assert!(interop.dependencies.contains_key(&420120010)); + } + + #[test] + fn test_chain_config_without_interop_skipped_in_json() { + // ChainConfig::default() has interop: None; serializing must omit the key entirely. + let cfg = ChainConfig::default(); + let json = serde_json::to_string(&cfg).unwrap(); + assert!(!json.contains("interop"), "expected `interop` key to be omitted; got: {json}"); + } + #[test] fn test_chain_config_unknown_field_json() { let raw: &str = r#" diff --git a/rust/kona/crates/protocol/genesis/src/interop/config.rs b/rust/kona/crates/protocol/genesis/src/interop/config.rs new file mode 100644 index 00000000000..14d77306828 --- /dev/null +++ b/rust/kona/crates/protocol/genesis/src/interop/config.rs @@ -0,0 +1,22 @@ +//! Per-chain interop configuration parsed from the chain TOML's `[interop]` block. + +use crate::ChainDependency; +use alloc::collections::BTreeMap; +use alloy_primitives::ChainId; + +/// Per-chain interop configuration. +/// +/// Chain TOMLs in `superchain-registry` may declare an `[interop]` section listing the +/// chain ids that participate in the same interop cluster. Every member of a cluster is +/// expected to declare a matching set; the registry build script validates this and +/// builds one [`crate::DependencySet`] per cluster from the union of equal entries. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[allow(clippy::zero_sized_map_values)] +pub struct InteropConfig { + /// Chain ids this chain depends on (interop cluster membership). + /// Keys are L2 chain ids; values are reserved per-dependency config (currently empty). + #[cfg_attr(feature = "serde", serde(default))] + pub dependencies: BTreeMap, +} diff --git a/rust/kona/crates/protocol/genesis/src/interop/constants.rs b/rust/kona/crates/protocol/genesis/src/interop/constants.rs new file mode 100644 index 00000000000..e444939bcc0 --- /dev/null +++ b/rust/kona/crates/protocol/genesis/src/interop/constants.rs @@ -0,0 +1,5 @@ +//! Constants for OP Stack interop primitives shared with the registry. + +/// The expiry window for relaying an initiating message (in seconds). +/// +pub const MESSAGE_EXPIRY_WINDOW: u64 = 7 * 24 * 60 * 60; diff --git a/rust/kona/crates/protocol/interop/src/depset.rs b/rust/kona/crates/protocol/genesis/src/interop/depset.rs similarity index 69% rename from rust/kona/crates/protocol/interop/src/depset.rs rename to rust/kona/crates/protocol/genesis/src/interop/depset.rs index 5b9b29e7efc..47814ab6199 100644 --- a/rust/kona/crates/protocol/interop/src/depset.rs +++ b/rust/kona/crates/protocol/genesis/src/interop/depset.rs @@ -1,21 +1,25 @@ -use crate::MESSAGE_EXPIRY_WINDOW; +//! Dependency set primitives shared by `kona-interop` and `kona-registry`. + +use super::MESSAGE_EXPIRY_WINDOW; +use alloc::collections::BTreeMap; use alloy_primitives::ChainId; -use kona_registry::HashMap; /// Configuration for a dependency of a chain #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ChainDependency {} /// Configuration for the dependency set #[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[allow(clippy::zero_sized_map_values)] pub struct DependencySet { /// Dependencies information per chain. - pub dependencies: HashMap, + pub dependencies: BTreeMap, /// Override message expiry window to use for this dependency set. pub override_message_expiry_window: Option, @@ -35,11 +39,11 @@ impl DependencySet { #[allow(clippy::zero_sized_map_values)] mod tests { use super::*; + use alloc::collections::BTreeMap; use alloy_primitives::ChainId; - use kona_registry::HashMap; const fn create_dependency_set( - dependencies: HashMap, + dependencies: BTreeMap, override_expiry: u64, ) -> DependencySet { DependencySet { dependencies, override_message_expiry_window: Some(override_expiry) } @@ -47,7 +51,7 @@ mod tests { #[test] fn test_get_message_expiry_window_default() { - let deps = HashMap::default(); + let deps = BTreeMap::default(); // override_message_expiry_window is 0, so default should be used let ds = create_dependency_set(deps, 0); assert_eq!( @@ -59,7 +63,7 @@ mod tests { #[test] fn test_get_message_expiry_window_override() { - let deps = HashMap::default(); + let deps = BTreeMap::default(); let override_value = 12345; let ds = create_dependency_set(deps, override_value); assert_eq!( @@ -68,4 +72,14 @@ mod tests { "Should return override expiry window when it's non-zero" ); } + + #[test] + #[cfg(feature = "serde")] + fn depset_json_round_trip() { + let json = include_str!("../../tests/fixtures/dependency_set.json"); + let parsed: DependencySet = serde_json::from_str(json).unwrap(); + let reserialized = serde_json::to_string(&parsed).unwrap(); + let parsed_again: DependencySet = serde_json::from_str(&reserialized).unwrap(); + assert_eq!(parsed, parsed_again); + } } diff --git a/rust/kona/crates/protocol/genesis/src/interop/mod.rs b/rust/kona/crates/protocol/genesis/src/interop/mod.rs new file mode 100644 index 00000000000..d06cd6fb5cf --- /dev/null +++ b/rust/kona/crates/protocol/genesis/src/interop/mod.rs @@ -0,0 +1,173 @@ +//! Interop-related genesis types shared by the registry and the interop crate. + +use alloc::{ + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; + +mod constants; +pub use constants::MESSAGE_EXPIRY_WINDOW; + +mod depset; +pub use depset::{ChainDependency, DependencySet}; + +mod config; +pub use config::InteropConfig; + +/// Errors raised by [`aggregate_clusters`]. +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum ClusterError { + /// Chain `a` claims `b` is in its cluster but `b`'s declared cluster differs. + #[error( + "inconsistent interop cluster: chain {a} and chain {b} declare different dependency sets" + )] + Inconsistent { + /// First chain id involved in the conflict. + a: u64, + /// Second chain id involved in the conflict. + b: u64, + }, + /// Chain `a` references chain `b` in its cluster but `b` does not declare any interop config. + #[error("chain {a} declares dependency on chain {b}, but {b} has no [interop] section")] + DanglingMember { + /// The chain that declared the dependency. + a: u64, + /// The dependency target that has no interop config. + b: u64, + }, +} + +/// Aggregate per-cluster [`DependencySet`]s from a set of chain interop sections. +/// +/// Each cluster is the set of chain ids that mutually declare each other in their +/// `[interop.dependencies]` blocks. Returns one [`DependencySet`] per cluster. +/// Errors when a cluster is internally inconsistent (a chain in the cluster declares a +/// different `interop.dependencies`) or when a chain references a peer that has no +/// `[interop]` section. +/// +/// The output is deterministic: clusters are sorted by their minimum chain id. +/// +/// `override_message_expiry_window` is always `None` on the returned depsets — the +/// chain TOML schema has no override field, so any override must come through a +/// custom-config `depsets.json`. If the schema grows one, [`InteropConfig`] and this +/// function need to change together. +pub fn aggregate_clusters<'a, I>(chains: I) -> Result, ClusterError> +where + I: IntoIterator, +{ + let by_chain: BTreeMap = chains.into_iter().collect(); + + // Validate every dependency target has its own interop section. + for (chain_id, cfg) in &by_chain { + for dep_id in cfg.dependencies.keys() { + if !by_chain.contains_key(dep_id) { + return Err(ClusterError::DanglingMember { a: *chain_id, b: *dep_id }); + } + } + } + + // Validate that every chain in a cluster declares the same dependency set. + // Two chains are in the same cluster iff they declare equal `dependencies` maps + // (and every chain in the cluster appears as a key in those maps). + for (chain_id, cfg) in &by_chain { + for dep_id in cfg.dependencies.keys() { + let peer_cfg = by_chain.get(dep_id).expect("dangling check above guarantees presence"); + if peer_cfg.dependencies != cfg.dependencies { + return Err(ClusterError::Inconsistent { a: *chain_id, b: *dep_id }); + } + } + } + + // Group chains by the (canonical) cluster identifier — the set of dependency keys. + let mut visited: BTreeSet = BTreeSet::new(); + let mut clusters: Vec = Vec::new(); + for (chain_id, cfg) in &by_chain { + if visited.contains(chain_id) { + continue; + } + if cfg.dependencies.is_empty() { + visited.insert(*chain_id); + continue; + } + for member in cfg.dependencies.keys() { + visited.insert(*member); + } + clusters.push(DependencySet { + dependencies: cfg.dependencies.clone(), + override_message_expiry_window: None, + }); + } + + // Sort clusters by their min chain id for determinism. + clusters.sort_by_key(|ds| ds.dependencies.keys().next().copied().unwrap_or_default()); + + Ok(clusters) +} + +#[cfg(test)] +#[allow(clippy::zero_sized_map_values)] +mod tests { + use super::*; + use alloc::vec; + + fn config(deps: &[u64]) -> InteropConfig { + let dependencies = deps.iter().map(|id| (*id, ChainDependency {})).collect(); + InteropConfig { dependencies } + } + + #[test] + fn aggregate_empty_input() { + let chains: Vec<(u64, &InteropConfig)> = vec![]; + let out = aggregate_clusters(chains).unwrap(); + assert!(out.is_empty()); + } + + #[test] + fn aggregate_single_chain_cluster_of_one() { + // A chain whose only dependency is itself. + let cfg = config(&[1]); + let out = aggregate_clusters(vec![(1u64, &cfg)]).unwrap(); + assert_eq!(out.len(), 1); + assert_eq!(out[0].dependencies.keys().copied().collect::>(), vec![1]); + } + + #[test] + fn aggregate_single_cluster_of_n() { + let cfg_a = config(&[1, 2, 3]); + let cfg_b = config(&[1, 2, 3]); + let cfg_c = config(&[1, 2, 3]); + let out = aggregate_clusters(vec![(1u64, &cfg_a), (2u64, &cfg_b), (3u64, &cfg_c)]).unwrap(); + assert_eq!(out.len(), 1); + assert_eq!(out[0].dependencies.keys().copied().collect::>(), vec![1, 2, 3]); + } + + #[test] + fn aggregate_two_disjoint_clusters() { + let a1 = config(&[1, 2]); + let a2 = config(&[1, 2]); + let b1 = config(&[10, 11]); + let b2 = config(&[10, 11]); + let out = + aggregate_clusters(vec![(1u64, &a1), (2u64, &a2), (10u64, &b1), (11u64, &b2)]).unwrap(); + assert_eq!(out.len(), 2); + assert_eq!(out[0].dependencies.keys().copied().collect::>(), vec![1, 2]); + assert_eq!(out[1].dependencies.keys().copied().collect::>(), vec![10, 11]); + } + + #[test] + fn aggregate_inconsistent_cluster() { + // Chain 1 declares {1,2} but chain 2 only declares {2}. + let a = config(&[1, 2]); + let b = config(&[2]); + let err = aggregate_clusters(vec![(1u64, &a), (2u64, &b)]).unwrap_err(); + assert_eq!(err, ClusterError::Inconsistent { a: 1, b: 2 }); + } + + #[test] + fn aggregate_dangling_member() { + // Chain 1 declares {1, 9} but chain 9 has no interop section. + let a = config(&[1, 9]); + let err = aggregate_clusters(vec![(1u64, &a)]).unwrap_err(); + assert_eq!(err, ClusterError::DanglingMember { a: 1, b: 9 }); + } +} diff --git a/rust/kona/crates/protocol/genesis/src/lib.rs b/rust/kona/crates/protocol/genesis/src/lib.rs index 25ef675f0d9..e74e977a626 100644 --- a/rust/kona/crates/protocol/genesis/src/lib.rs +++ b/rust/kona/crates/protocol/genesis/src/lib.rs @@ -56,11 +56,17 @@ pub use chain::{ HardForkConfig, L1ChainConfig, OP_MAINNET_CHAIN_ID, OP_SEPOLIA_CHAIN_ID, Roles, }; +pub mod interop; +pub use interop::{ + ChainDependency, ClusterError, DependencySet, InteropConfig, MESSAGE_EXPIRY_WINDOW, + aggregate_clusters, +}; + mod genesis; pub use genesis::ChainGenesis; mod rollup; pub use rollup::{ - DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, FJORD_MAX_SEQUENCER_DRIFT, GRANITE_CHANNEL_TIMEOUT, - MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, MAX_RLP_BYTES_PER_CHANNEL_FJORD, RollupConfig, + FJORD_MAX_SEQUENCER_DRIFT, GRANITE_CHANNEL_TIMEOUT, MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, + MAX_RLP_BYTES_PER_CHANNEL_FJORD, RollupConfig, }; diff --git a/rust/kona/crates/protocol/genesis/src/rollup.rs b/rust/kona/crates/protocol/genesis/src/rollup.rs index ff8ff646f78..2d72322aa2a 100644 --- a/rust/kona/crates/protocol/genesis/src/rollup.rs +++ b/rust/kona/crates/protocol/genesis/src/rollup.rs @@ -18,23 +18,22 @@ pub const FJORD_MAX_SEQUENCER_DRIFT: u64 = 1800; /// The channel timeout once the Granite hardfork is active. pub const GRANITE_CHANNEL_TIMEOUT: u64 = 50; -/// The default interop message expiry window. (1 hour, in seconds) -pub const DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW: u64 = 60 * 60; - #[cfg(feature = "serde")] const fn default_granite_channel_timeout() -> u64 { GRANITE_CHANNEL_TIMEOUT } -#[cfg(feature = "serde")] -const fn default_interop_message_expiry_window() -> u64 { - DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW +/// The max sequencer drift needs to be changes for some chains, e.g. those that build only on +/// finalized L1 blocks, where L1 finality delays can exceed the standard +/// [`FJORD_MAX_SEQUENCER_DRIFT`]. +#[cfg(all(feature = "serde", feature = "rollup_config_override"))] +const fn default_fjord_max_sequencer_drift() -> u64 { + FJORD_MAX_SEQUENCER_DRIFT } /// The Rollup configuration. #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct RollupConfig { /// The genesis state of the rollup. pub genesis: ChainGenesis, @@ -55,6 +54,10 @@ pub struct RollupConfig { /// The channel timeout after the Granite hardfork. #[cfg_attr(feature = "serde", serde(default = "default_granite_channel_timeout"))] pub granite_channel_timeout: u64, + /// The max sequencer drift after the Fjord hardfork. + #[cfg(feature = "rollup_config_override")] + #[cfg_attr(feature = "serde", serde(default = "default_fjord_max_sequencer_drift"))] + pub fjord_max_sequencer_drift: u64, /// The L1 chain ID pub l1_chain_id: u64, /// The L2 chain ID @@ -68,8 +71,6 @@ pub struct RollupConfig { pub deposit_contract_address: Address, /// `l1_system_config_address` is the L1 address that the system config is stored at. pub l1_system_config_address: Address, - /// `protocol_versions_address` is the L1 address that the protocol versions are stored at. - pub protocol_versions_address: Address, /// The superchain config address. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub superchain_config_address: Option

, @@ -84,10 +85,6 @@ pub struct RollupConfig { /// stored at. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub da_challenge_address: Option
, - /// `interop_message_expiry_window` is the maximum time (in seconds) that an initiating message - /// can be referenced on a remote chain before it expires. - #[cfg_attr(feature = "serde", serde(default = "default_interop_message_expiry_window"))] - pub interop_message_expiry_window: u64, /// `alt_da_config` is the chain-specific DA config for the rollup. #[cfg_attr(feature = "serde", serde(rename = "alt_da"))] pub alt_da_config: Option, @@ -115,17 +112,17 @@ impl<'a> arbitrary::Arbitrary<'a> for RollupConfig { seq_window_size: u.arbitrary()?, channel_timeout: u.arbitrary()?, granite_channel_timeout: u.arbitrary()?, + #[cfg(feature = "rollup_config_override")] + fjord_max_sequencer_drift: u.arbitrary()?, l1_chain_id: u.arbitrary()?, l2_chain_id: u.arbitrary()?, hardforks: HardForkConfig::arbitrary(u)?, batch_inbox_address: Address::arbitrary(u)?, deposit_contract_address: Address::arbitrary(u)?, l1_system_config_address: Address::arbitrary(u)?, - protocol_versions_address: Address::arbitrary(u)?, superchain_config_address: Option::
::arbitrary(u)?, blobs_enabled_l1_timestamp: Option::::arbitrary(u)?, da_challenge_address: Option::
::arbitrary(u)?, - interop_message_expiry_window: u.arbitrary()?, chain_op_config, alt_da_config: Option::::arbitrary(u)?, }) @@ -142,17 +139,17 @@ impl Default for RollupConfig { seq_window_size: 0, channel_timeout: 0, granite_channel_timeout: GRANITE_CHANNEL_TIMEOUT, + #[cfg(feature = "rollup_config_override")] + fjord_max_sequencer_drift: FJORD_MAX_SEQUENCER_DRIFT, l1_chain_id: 0, l2_chain_id: Chain::from_id(0), hardforks: HardForkConfig::default(), batch_inbox_address: Address::ZERO, deposit_contract_address: Address::ZERO, l1_system_config_address: Address::ZERO, - protocol_versions_address: Address::ZERO, superchain_config_address: None, blobs_enabled_l1_timestamp: None, da_challenge_address: None, - interop_message_expiry_window: DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, alt_da_config: None, chain_op_config: OP_MAINNET_BASE_FEE_CONFIG, } @@ -171,6 +168,8 @@ impl RollupConfig { pub fn spec_id(&self, timestamp: u64) -> op_revm::OpSpecId { if self.is_interop_active(timestamp) { op_revm::OpSpecId::INTEROP + } else if self.is_karst_active(timestamp) { + op_revm::OpSpecId::KARST } else if self.is_jovian_active(timestamp) { op_revm::OpSpecId::JOVIAN } else if self.is_isthmus_active(timestamp) { @@ -299,6 +298,14 @@ impl RollupConfig { !self.is_isthmus_active(timestamp.saturating_sub(self.block_time)) } + /// Returns true if SDM post-exec transactions are active at the given timestamp. + /// + /// SDM is currently unscheduled and must not activate as part of Jovian or Karst. + #[must_use] + pub const fn is_sdm_active(&self, _timestamp: u64) -> bool { + false + } + /// Returns true if Jovian is active at the given timestamp. pub fn is_jovian_active(&self, timestamp: u64) -> bool { self.hardforks.jovian_time.is_some_and(|t| timestamp >= t) || @@ -343,10 +350,12 @@ impl RollupConfig { /// Returns the max sequencer drift for the given timestamp. pub fn max_sequencer_drift(&self, timestamp: u64) -> u64 { if self.is_fjord_active(timestamp) { - FJORD_MAX_SEQUENCER_DRIFT - } else { - self.max_sequencer_drift + #[cfg(feature = "rollup_config_override")] + return self.fjord_max_sequencer_drift; + #[cfg(not(feature = "rollup_config_override"))] + return FJORD_MAX_SEQUENCER_DRIFT; } + self.max_sequencer_drift } /// Returns the max rlp bytes per channel for the given timestamp. @@ -527,6 +536,12 @@ mod tests { assert_eq!(config.spec_id(50), op_revm::OpSpecId::HOLOCENE); config.hardforks.isthmus_time = Some(60); assert_eq!(config.spec_id(60), op_revm::OpSpecId::ISTHMUS); + config.hardforks.jovian_time = Some(70); + assert_eq!(config.spec_id(70), op_revm::OpSpecId::JOVIAN); + config.hardforks.karst_time = Some(80); + assert_eq!(config.spec_id(80), op_revm::OpSpecId::KARST); + config.hardforks.interop_time = Some(90); + assert_eq!(config.spec_id(90), op_revm::OpSpecId::INTEROP); } #[test] @@ -684,6 +699,18 @@ mod tests { assert!(!config.is_karst_active(9)); } + #[test] + fn test_sdm_disabled_after_jovian_and_karst() { + let mut config = RollupConfig::default(); + config.hardforks.jovian_time = Some(10); + config.hardforks.karst_time = Some(20); + + assert!(config.is_jovian_active(10)); + assert!(!config.is_sdm_active(10)); + assert!(config.is_karst_active(20)); + assert!(!config.is_sdm_active(20)); + } + #[test] fn test_interop_active() { let mut config = RollupConfig::default(); @@ -817,11 +844,65 @@ mod tests { assert_eq!(config.max_sequencer_drift(10), FJORD_MAX_SEQUENCER_DRIFT); } + fn expected_rollup_config() -> RollupConfig { + use crate::{OP_MAINNET_BASE_FEE_CONFIG, SystemConfig}; + RollupConfig { + genesis: ChainGenesis { + l1: BlockNumHash { + hash: b256!("481724ee99b1f4cb71d826e2ec5a37265f460e9b112315665c977f4050b0af54"), + number: 10, + }, + l2: BlockNumHash { + hash: b256!("88aedfbf7dea6bfa2c4ff315784ad1a7f145d8f650969359c003bbed68c87631"), + number: 0, + }, + l2_time: 1725557164, + system_config: Some(SystemConfig { + batcher_address: address!("c81f87a644b41e49b3221f41251f15c6cb00ce03"), + overhead: U256::ZERO, + scalar: U256::from(0xf4240), + gas_limit: 30_000_000, + base_fee_scalar: Some(1234), + blob_base_fee_scalar: Some(5678), + eip1559_denominator: Some(10), + eip1559_elasticity: Some(20), + operator_fee_scalar: Some(30), + operator_fee_constant: Some(40), + min_base_fee: Some(50), + da_footprint_gas_scalar: Some(10), + }), + }, + block_time: 2, + max_sequencer_drift: 600, + seq_window_size: 3600, + channel_timeout: 300, + granite_channel_timeout: GRANITE_CHANNEL_TIMEOUT, + #[cfg(feature = "rollup_config_override")] + fjord_max_sequencer_drift: FJORD_MAX_SEQUENCER_DRIFT, + l1_chain_id: 3151908, + l2_chain_id: Chain::from_id(1337), + hardforks: HardForkConfig { + regolith_time: Some(0), + canyon_time: Some(0), + delta_time: Some(0), + ecotone_time: Some(0), + fjord_time: Some(0), + ..Default::default() + }, + batch_inbox_address: address!("ff00000000000000000000000000000000042069"), + deposit_contract_address: address!("08073dc48dde578137b8af042bcbc1c2491f1eb2"), + l1_system_config_address: address!("94ee52a9d8edd72a85dea7fae3ba6d75e4bf1710"), + superchain_config_address: None, + blobs_enabled_l1_timestamp: None, + da_challenge_address: None, + chain_op_config: OP_MAINNET_BASE_FEE_CONFIG, + alt_da_config: None, + } + } + #[test] #[cfg(feature = "serde")] fn test_deserialize_reference_rollup_config() { - use crate::{OP_MAINNET_BASE_FEE_CONFIG, SystemConfig}; - let raw: &str = r#" { "genesis": { @@ -863,7 +944,6 @@ mod tests { "batch_inbox_address": "0xff00000000000000000000000000000000042069", "deposit_contract_address": "0x08073dc48dde578137b8af042bcbc1c2491f1eb2", "l1_system_config_address": "0x94ee52a9d8edd72a85dea7fae3ba6d75e4bf1710", - "protocol_versions_address": "0x0000000000000000000000000000000000000000", "chain_op_config": { "eip1559Elasticity": 6, "eip1559Denominator": 50, @@ -873,59 +953,7 @@ mod tests { } "#; - let expected = RollupConfig { - genesis: ChainGenesis { - l1: BlockNumHash { - hash: b256!("481724ee99b1f4cb71d826e2ec5a37265f460e9b112315665c977f4050b0af54"), - number: 10, - }, - l2: BlockNumHash { - hash: b256!("88aedfbf7dea6bfa2c4ff315784ad1a7f145d8f650969359c003bbed68c87631"), - number: 0, - }, - l2_time: 1725557164, - system_config: Some(SystemConfig { - batcher_address: address!("c81f87a644b41e49b3221f41251f15c6cb00ce03"), - overhead: U256::ZERO, - scalar: U256::from(0xf4240), - gas_limit: 30_000_000, - base_fee_scalar: Some(1234), - blob_base_fee_scalar: Some(5678), - eip1559_denominator: Some(10), - eip1559_elasticity: Some(20), - operator_fee_scalar: Some(30), - operator_fee_constant: Some(40), - min_base_fee: Some(50), - da_footprint_gas_scalar: Some(10), - }), - }, - block_time: 2, - max_sequencer_drift: 600, - seq_window_size: 3600, - channel_timeout: 300, - granite_channel_timeout: GRANITE_CHANNEL_TIMEOUT, - l1_chain_id: 3151908, - l2_chain_id: Chain::from_id(1337), - hardforks: HardForkConfig { - regolith_time: Some(0), - canyon_time: Some(0), - delta_time: Some(0), - ecotone_time: Some(0), - fjord_time: Some(0), - ..Default::default() - }, - batch_inbox_address: address!("ff00000000000000000000000000000000042069"), - deposit_contract_address: address!("08073dc48dde578137b8af042bcbc1c2491f1eb2"), - l1_system_config_address: address!("94ee52a9d8edd72a85dea7fae3ba6d75e4bf1710"), - protocol_versions_address: Address::ZERO, - superchain_config_address: None, - blobs_enabled_l1_timestamp: None, - da_challenge_address: None, - interop_message_expiry_window: DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, - chain_op_config: OP_MAINNET_BASE_FEE_CONFIG, - alt_da_config: None, - }; - + let expected = expected_rollup_config(); let deserialized: RollupConfig = serde_json::from_str(raw).unwrap(); assert_eq!(deserialized, expected); } @@ -948,7 +976,15 @@ mod tests { "batcherAddr": "0xc81f87a644b41e49b3221f41251f15c6cb00ce03", "overhead": "0x0000000000000000000000000000000000000000000000000000000000000000", "scalar": "0x00000000000000000000000000000000000000000000000000000000000f4240", - "gasLimit": 30000000 + "gasLimit": 30000000, + "baseFeeScalar": 1234, + "blobBaseFeeScalar": 5678, + "eip1559Denominator": 10, + "eip1559Elasticity": 20, + "operatorFeeScalar": 30, + "operatorFeeConstant": 40, + "minBaseFee": 50, + "daFootprintGasScalar": 10 } }, "block_time": 2, @@ -965,18 +1001,18 @@ mod tests { "batch_inbox_address": "0xff00000000000000000000000000000000042069", "deposit_contract_address": "0x08073dc48dde578137b8af042bcbc1c2491f1eb2", "l1_system_config_address": "0x94ee52a9d8edd72a85dea7fae3ba6d75e4bf1710", - "protocol_versions_address": "0x0000000000000000000000000000000000000000", "chain_op_config": { - "eip1559_elasticity": 100, - "eip1559_denominator": 100, - "eip1559_denominator_canyon": 100 + "eip1559_elasticity": 6, + "eip1559_denominator": 50, + "eip1559_denominator_canyon": 250 }, "unknown_field": "unknown" } "#; - let err = serde_json::from_str::(raw).unwrap_err(); - assert_eq!(err.classify(), serde_json::error::Category::Data); + let expected = expected_rollup_config(); + let deserialized: RollupConfig = serde_json::from_str(raw).unwrap(); + assert_eq!(deserialized, expected); } #[test] @@ -990,4 +1026,41 @@ mod tests { assert_eq!(cfg.block_number_from_timestamp(20), 5); assert_eq!(cfg.block_number_from_timestamp(30), 10); } + + #[cfg(feature = "rollup_config_override")] + mod rollup_config_override_tests { + use super::*; + + #[test] + fn test_max_sequencer_drift_override() { + let mut config = RollupConfig { + max_sequencer_drift: 100, + fjord_max_sequencer_drift: 2892, + hardforks: HardForkConfig { fjord_time: Some(10), ..Default::default() }, + ..Default::default() + }; + assert_eq!(config.max_sequencer_drift(0), 100); + assert_eq!(config.max_sequencer_drift(10), 2892); + config.fjord_max_sequencer_drift = 3600; + assert_eq!(config.max_sequencer_drift(10), 3600); + } + + #[test] + #[cfg(feature = "serde")] + fn test_serde_fjord_max_sequencer_drift_override() { + // Default value survives round-trip. + let config = RollupConfig::default(); + assert_eq!(config.fjord_max_sequencer_drift, FJORD_MAX_SEQUENCER_DRIFT); + let serialized = serde_json::to_string(&config).unwrap(); + let deserialized: RollupConfig = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized.fjord_max_sequencer_drift, FJORD_MAX_SEQUENCER_DRIFT); + + // Custom value survives round-trip. + let mut config = config; + config.fjord_max_sequencer_drift = 2892; + let serialized = serde_json::to_string(&config).unwrap(); + let deserialized: RollupConfig = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized.fjord_max_sequencer_drift, 2892); + } + } } diff --git a/rust/kona/crates/protocol/genesis/src/superchain/chain.rs b/rust/kona/crates/protocol/genesis/src/superchain/chain.rs index 03fc9148472..12d3c6ecf38 100644 --- a/rust/kona/crates/protocol/genesis/src/superchain/chain.rs +++ b/rust/kona/crates/protocol/genesis/src/superchain/chain.rs @@ -101,7 +101,6 @@ mod tests { karst_time: None, interop_time: None, }, - protocol_versions_addr: None, superchain_config_addr: None, op_contracts_manager_proxy_addr: None, }, diff --git a/rust/kona/crates/protocol/genesis/src/superchain/chain_list.rs b/rust/kona/crates/protocol/genesis/src/superchain/chain_list.rs index 8115ba9ec69..f62f58514a3 100644 --- a/rust/kona/crates/protocol/genesis/src/superchain/chain_list.rs +++ b/rust/kona/crates/protocol/genesis/src/superchain/chain_list.rs @@ -129,7 +129,7 @@ mod tests { fn read_chain_list_file() { let chain_list = include_str!("../../../registry/etc/chainList.json"); let chains: Vec = serde_json::from_str(chain_list).unwrap(); - let base_chain = chains.iter().find(|c| c.name == "Base").unwrap(); - assert_eq!(base_chain.chain_id, 8453); + let op_chain = chains.iter().find(|c| c.name == "OP Mainnet").unwrap(); + assert_eq!(op_chain.chain_id, 10); } } diff --git a/rust/kona/crates/protocol/genesis/src/superchain/chains.rs b/rust/kona/crates/protocol/genesis/src/superchain/chains.rs index dea0ac218c4..9b0c3be6c88 100644 --- a/rust/kona/crates/protocol/genesis/src/superchain/chains.rs +++ b/rust/kona/crates/protocol/genesis/src/superchain/chains.rs @@ -108,7 +108,6 @@ mod tests { karst_time: None, interop_time: None, }, - protocol_versions_addr: None, superchain_config_addr: None, op_contracts_manager_proxy_addr: None, }, diff --git a/rust/kona/crates/protocol/genesis/src/superchain/config.rs b/rust/kona/crates/protocol/genesis/src/superchain/config.rs index 0377670a18f..1c860b13e9e 100644 --- a/rust/kona/crates/protocol/genesis/src/superchain/config.rs +++ b/rust/kona/crates/protocol/genesis/src/superchain/config.rs @@ -14,9 +14,6 @@ pub struct SuperchainConfig { pub l1: SuperchainL1Info, /// Default hardforks timestamps. pub hardforks: HardForkConfig, - /// Optional addresses for the superchain-wide default protocol versions contract. - #[cfg_attr(feature = "serde", serde(alias = "protocolVersionsAddr"))] - pub protocol_versions_addr: Option
, /// Optional address for the superchain-wide default superchain config contract. #[cfg_attr(feature = "serde", serde(alias = "superchainConfigAddr"))] pub superchain_config_addr: Option
, @@ -70,7 +67,6 @@ mod tests { karst_time: None, interop_time: None, }, - protocol_versions_addr: None, superchain_config_addr: None, op_contracts_manager_proxy_addr: None, }; @@ -185,7 +181,6 @@ mod tests { karst_time: None, interop_time: None, }, - protocol_versions_addr: None, superchain_config_addr: None, op_contracts_manager_proxy_addr: None, }; diff --git a/rust/kona/crates/protocol/genesis/src/system/config.rs b/rust/kona/crates/protocol/genesis/src/system/config.rs index 2a6b93445dd..de7f01f8e5f 100644 --- a/rust/kona/crates/protocol/genesis/src/system/config.rs +++ b/rust/kona/crates/protocol/genesis/src/system/config.rs @@ -4,6 +4,7 @@ use crate::{ CONFIG_UPDATE_TOPIC, RollupConfig, SystemConfigLog, SystemConfigUpdateError, SystemConfigUpdateKind, }; +use alloc::vec::Vec; use alloy_consensus::{Eip658Value, Receipt}; use alloy_primitives::{Address, B64, Log, U256}; @@ -118,33 +119,35 @@ impl<'a> serde::Deserialize<'a> for SystemConfig { impl SystemConfig { /// Filters all L1 receipts to find config updates and applies the config updates. /// - /// Returns `true` if any config updates were applied, `false` otherwise. + /// Each config update log is applied independently. Malformed or invalid updates are + /// skipped so that subsequent valid updates in the same block are still processed. + /// This matches the op-node reference behavior in `UpdateSystemConfigWithL1Receipts`. + /// + /// Returns the successfully applied update kinds and any errors encountered. pub fn update_with_receipts( &mut self, receipts: &[Receipt], l1_system_config_address: Address, ecotone_active: bool, - ) -> Result { - let mut updated = false; - for receipt in receipts { - if Eip658Value::Eip658(false) == receipt.status { - continue; - } - - receipt.logs.iter().try_for_each(|log| { + ) -> (Vec, Vec) { + receipts + .iter() + .filter(|r| r.status != Eip658Value::Eip658(false)) + .flat_map(|r| &r.logs) + .filter(|log| { let topics = log.topics(); - if log.address == l1_system_config_address && + log.address == l1_system_config_address && !topics.is_empty() && topics[0] == CONFIG_UPDATE_TOPIC - { - // Safety: Error is bubbled up by the trailing `?` - self.process_config_update_log(log, ecotone_active)?; - updated = true; + }) + .map(|log| self.process_config_update_log(log, ecotone_active)) + .fold((Vec::new(), Vec::new()), |(mut updates, mut errors), result| { + match result { + Ok(kind) => updates.push(kind), + Err(e) => errors.push(e), } - Ok::<(), SystemConfigUpdateError>(()) - })?; - } - Ok(updated) + (updates, errors) + }) } /// Returns the eip1559 parameters from a [`SystemConfig`] encoded as a [B64]. @@ -223,6 +226,17 @@ mod test { use alloc::vec; use alloy_primitives::{B256, LogData, address, b256, hex}; + const BATCHER_UPDATE_TYPE: B256 = + b256!("0000000000000000000000000000000000000000000000000000000000000000"); + const GAS_CONFIG_UPDATE_TYPE: B256 = + b256!("0000000000000000000000000000000000000000000000000000000000000001"); + const GAS_LIMIT_UPDATE_TYPE: B256 = + b256!("0000000000000000000000000000000000000000000000000000000000000002"); + const EIP1559_UPDATE_TYPE: B256 = + b256!("0000000000000000000000000000000000000000000000000000000000000004"); + const OPERATOR_FEE_UPDATE_TYPE: B256 = + b256!("0000000000000000000000000000000000000000000000000000000000000005"); + #[test] #[cfg(feature = "serde")] fn test_system_config_da_footprint_gas_scalar() { @@ -382,18 +396,16 @@ mod test { let l1_system_config_address = Address::ZERO; let ecotone_active = false; - let updated = system_config - .update_with_receipts(&receipts, l1_system_config_address, ecotone_active) - .unwrap(); - assert!(!updated); + let (updates, errors) = + system_config.update_with_receipts(&receipts, l1_system_config_address, ecotone_active); + assert!(updates.is_empty()); + assert!(errors.is_empty()); assert_eq!(system_config, SystemConfig::default()); } #[test] fn test_system_config_update_with_receipts_batcher_address() { - const UPDATE_TYPE: B256 = - b256!("0000000000000000000000000000000000000000000000000000000000000000"); let mut system_config = SystemConfig::default(); let l1_system_config_address = Address::ZERO; let ecotone_active = false; @@ -404,7 +416,7 @@ mod test { vec![ CONFIG_UPDATE_TOPIC, CONFIG_UPDATE_EVENT_VERSION_0, - UPDATE_TYPE, + BATCHER_UPDATE_TYPE, ], hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into() ) @@ -416,10 +428,13 @@ mod test { cumulative_gas_used: 0, }; - let updated = system_config - .update_with_receipts(&[receipt], l1_system_config_address, ecotone_active) - .unwrap(); - assert!(updated); + let (updates, errors) = system_config.update_with_receipts( + &[receipt], + l1_system_config_address, + ecotone_active, + ); + assert_eq!(updates, vec![SystemConfigUpdateKind::Batcher]); + assert!(errors.is_empty()); assert_eq!( system_config.batcher_address, @@ -429,9 +444,6 @@ mod test { #[test] fn test_system_config_update_batcher_log() { - const UPDATE_TYPE: B256 = - b256!("0000000000000000000000000000000000000000000000000000000000000000"); - let mut system_config = SystemConfig::default(); let update_log = Log { @@ -440,7 +452,7 @@ mod test { vec![ CONFIG_UPDATE_TOPIC, CONFIG_UPDATE_EVENT_VERSION_0, - UPDATE_TYPE, + BATCHER_UPDATE_TYPE, ], hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into() ) @@ -457,9 +469,6 @@ mod test { #[test] fn test_system_config_update_gas_config_log() { - const UPDATE_TYPE: B256 = - b256!("0000000000000000000000000000000000000000000000000000000000000001"); - let mut system_config = SystemConfig::default(); let update_log = Log { @@ -468,13 +477,13 @@ mod test { vec![ CONFIG_UPDATE_TOPIC, CONFIG_UPDATE_EVENT_VERSION_0, - UPDATE_TYPE, + GAS_CONFIG_UPDATE_TYPE, ], hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000babe000000000000000000000000000000000000000000000000000000000000beef").into() ) }; - // Update the batcher address. + // Update the gas config. system_config.process_config_update_log(&update_log, false).unwrap(); assert_eq!(system_config.overhead, U256::from(0xbabe)); @@ -483,9 +492,6 @@ mod test { #[test] fn test_system_config_update_gas_config_log_ecotone() { - const UPDATE_TYPE: B256 = - b256!("0000000000000000000000000000000000000000000000000000000000000001"); - let mut system_config = SystemConfig::default(); let update_log = Log { @@ -494,13 +500,13 @@ mod test { vec![ CONFIG_UPDATE_TOPIC, CONFIG_UPDATE_EVENT_VERSION_0, - UPDATE_TYPE, + GAS_CONFIG_UPDATE_TYPE, ], hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000babe000000000000000000000000000000000000000000000000000000000000beef").into() ) }; - // Update the gas limit. + // Update the gas config (ecotone). system_config.process_config_update_log(&update_log, true).unwrap(); assert_eq!(system_config.overhead, U256::from(0)); @@ -509,9 +515,6 @@ mod test { #[test] fn test_system_config_update_gas_limit_log() { - const UPDATE_TYPE: B256 = - b256!("0000000000000000000000000000000000000000000000000000000000000002"); - let mut system_config = SystemConfig::default(); let update_log = Log { @@ -520,7 +523,7 @@ mod test { vec![ CONFIG_UPDATE_TOPIC, CONFIG_UPDATE_EVENT_VERSION_0, - UPDATE_TYPE, + GAS_LIMIT_UPDATE_TYPE, ], hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into() ) @@ -534,9 +537,6 @@ mod test { #[test] fn test_system_config_update_eip1559_params_log() { - const UPDATE_TYPE: B256 = - b256!("0000000000000000000000000000000000000000000000000000000000000004"); - let mut system_config = SystemConfig::default(); let update_log = Log { address: Address::ZERO, @@ -544,7 +544,7 @@ mod test { vec![ CONFIG_UPDATE_TOPIC, CONFIG_UPDATE_EVENT_VERSION_0, - UPDATE_TYPE, + EIP1559_UPDATE_TYPE, ], hex!("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000babe0000beef").into() ) @@ -559,17 +559,14 @@ mod test { #[test] fn test_system_config_update_operator_fee_log() { - const UPDATE_TYPE: B256 = - b256!("0000000000000000000000000000000000000000000000000000000000000005"); - let mut system_config = SystemConfig::default(); - let update_log = Log { + let update_log = Log { address: Address::ZERO, data: LogData::new_unchecked( vec![ CONFIG_UPDATE_TOPIC, CONFIG_UPDATE_EVENT_VERSION_0, - UPDATE_TYPE, + OPERATOR_FEE_UPDATE_TYPE, ], hex!("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000babe000000000000beef").into() ) @@ -581,4 +578,52 @@ mod test { assert_eq!(system_config.operator_fee_scalar, Some(0xbabe_u32)); assert_eq!(system_config.operator_fee_constant, Some(0xbeef_u64)); } + + #[test] + fn test_update_with_receipts_continues_past_malformed_log() { + let mut system_config = SystemConfig::default(); + let l1_system_config_address = Address::ZERO; + + // Malformed batcher update: non-zero upper 12 bytes in batcher hash + let malformed_log = Log { + address: Address::ZERO, + data: LogData::new_unchecked( + vec![ + CONFIG_UPDATE_TOPIC, + CONFIG_UPDATE_EVENT_VERSION_0, + BATCHER_UPDATE_TYPE, + ], + // ABI-encoded address with dirty upper bytes — will fail address decoding + hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020dead00000000000000000000000000000000000000000000000000000000beef").into() + ) + }; + + // Valid gas limit update (different type to prove the malformed one didn't apply) + let valid_log = Log { + address: Address::ZERO, + data: LogData::new_unchecked( + vec![ + CONFIG_UPDATE_TOPIC, + CONFIG_UPDATE_EVENT_VERSION_0, + GAS_LIMIT_UPDATE_TYPE, + ], + hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000beef").into() + ) + }; + + let receipt = Receipt { + logs: vec![malformed_log, valid_log], + status: Eip658Value::Eip658(true), + cumulative_gas_used: 0, + }; + + let (updates, errors) = + system_config.update_with_receipts(&[receipt], l1_system_config_address, false); + assert_eq!(updates, vec![SystemConfigUpdateKind::GasLimit]); + assert_eq!(errors.len(), 1); + // Malformed batcher update was skipped — address unchanged. + assert_eq!(system_config.batcher_address, Address::ZERO); + // Valid gas limit update was still applied. + assert_eq!(system_config.gas_limit, 0xbeef_u64); + } } diff --git a/rust/kona/crates/protocol/genesis/src/system/kind.rs b/rust/kona/crates/protocol/genesis/src/system/kind.rs index 5c3e5b7357c..1f9b8f71459 100644 --- a/rust/kona/crates/protocol/genesis/src/system/kind.rs +++ b/rust/kona/crates/protocol/genesis/src/system/kind.rs @@ -1,7 +1,7 @@ //! Contains the kind of system config update. /// Represents type of update to the system config. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, derive_more::TryFrom)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, derive_more::Display, derive_more::TryFrom)] #[try_from(repr)] #[repr(u64)] pub enum SystemConfigUpdateKind { diff --git a/rust/kona/crates/protocol/genesis/tests/fixtures/dependency_set.json b/rust/kona/crates/protocol/genesis/tests/fixtures/dependency_set.json new file mode 100644 index 00000000000..0d43551ce38 --- /dev/null +++ b/rust/kona/crates/protocol/genesis/tests/fixtures/dependency_set.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "900": {}, + "901": {} + }, + "overrideMessageExpiryWindow": 15 +} diff --git a/rust/kona/crates/protocol/genesis/tests/fixtures/rehearsal-0-bn-0.toml b/rust/kona/crates/protocol/genesis/tests/fixtures/rehearsal-0-bn-0.toml new file mode 100755 index 00000000000..6ba51954e73 --- /dev/null +++ b/rust/kona/crates/protocol/genesis/tests/fixtures/rehearsal-0-bn-0.toml @@ -0,0 +1,56 @@ +name = "rehearsal-0-bn-0" +public_rpc = "" +sequencer_rpc = "" +explorer = "" +superchain_level = 0 +governed_by_optimism = false +superchain_time = 0 +data_availability_type = "eth-da" +chain_id = 420120009 +batch_inbox_addr = "0x00aA742D2EEf0B65Eb1dC7633cbAf8Ef24A945B3" +block_time = 2 +seq_window_size = 3600 +max_sequencer_drift = 600 + +[hardforks] + canyon_time = 0 # Thu 1 Jan 1970 00:00:00 UTC + delta_time = 0 # Thu 1 Jan 1970 00:00:00 UTC + ecotone_time = 0 # Thu 1 Jan 1970 00:00:00 UTC + fjord_time = 0 # Thu 1 Jan 1970 00:00:00 UTC + granite_time = 0 # Thu 1 Jan 1970 00:00:00 UTC + holocene_time = 0 # Thu 1 Jan 1970 00:00:00 UTC + isthmus_time = 0 # Thu 1 Jan 1970 00:00:00 UTC + interop_time = 1749150000 # Thu 5 Jun 2025 19:00:00 UTC + +[interop] + [interop.dependencies] + [interop.dependencies.420120009] + [interop.dependencies.420120010] + +[optimism] + eip1559_elasticity = 6 + eip1559_denominator = 50 + eip1559_denominator_canyon = 250 + +[genesis] + l2_time = 1748984964 + [genesis.l1] + hash = "0x9011befaa62e1a920aa3d14e9165107d522b709049c475843725f712bbf693f1" + number = 8470494 + [genesis.l2] + hash = "0x1055187f2eb4e8be192ee7e65756efa290267543bad721d3d8a6135addbe75a0" + number = 0 + [genesis.system_config] + batcherAddress = "0xD059234409A70327B48c03F7BbD5317a758E315B" + overhead = "0x0000000000000000000000000000000000000000000000000000000000000000" + scalar = "0x010000000000000000000000000000000000000000000000000c5fc500000558" + gasLimit = 60000000 + +[roles] + ProxyAdminOwner = "0x26DF14a0C889de2448d228Ee23B2530550b5B774" + +[addresses] + L1StandardBridgeProxy = "0x1b0Bf2AEA5acDF3c7e0EfeDe9389411333aAF9F9" + OptimismPortalProxy = "0xf26C2E762C0447443b951a74DfA5Bf380BcB8361" + SystemConfigProxy = "0xC5D517463C38567A644f2FCcEFC0e534CC991a57" + DisputeGameFactoryProxy = "0x1A1e1649eb6B8f09896498b6CFD73Eee6462447c" diff --git a/rust/kona/crates/protocol/hardforks/Cargo.toml b/rust/kona/crates/protocol/hardforks/Cargo.toml index 0210f18d8c1..5388f5a1260 100644 --- a/rust/kona/crates/protocol/hardforks/Cargo.toml +++ b/rust/kona/crates/protocol/hardforks/Cargo.toml @@ -25,18 +25,35 @@ alloy-primitives = { workspace = true, features = ["rlp"] } # OP Alloy op-alloy-consensus.workspace = true +# Caching for build-script-generated NUT bundle constructors. The `race` +# feature enables `once_cell::race::OnceBox`, which is lock-free and works +# on `no_std` targets without a critical-section implementation. +once_cell = { workspace = true, features = ["race"] } + +[build-dependencies] +anyhow.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true + [dev-dependencies] alloy-primitives = { workspace = true, features = ["rand", "arbitrary"] } -revm = { version = "34.0.0", default-features = false } -op-revm = { version = "15.0.0", default-features = false } +anyhow.workspace = true +revm.workspace = true +op-revm.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true [features] default = [] std = [ "alloy-eips/std", "alloy-primitives/std", + "anyhow/std", "kona-protocol/std", + "once_cell/std", "op-alloy-consensus/std", + "serde/std", + "serde_json/std", ] k256 = [ "alloy-primitives/k256", "op-alloy-consensus/k256" ] kzg = [ "alloy-eips/kzg", "op-alloy-consensus/kzg", "std" ] diff --git a/rust/kona/crates/protocol/hardforks/build.rs b/rust/kona/crates/protocol/hardforks/build.rs new file mode 100644 index 00000000000..27b990c3b60 --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/build.rs @@ -0,0 +1,59 @@ +//! Build script for `kona-hardforks`. +//! +//! Reads NUT bundle JSON files from `op-core/nuts/bundles/` and generates Rust source +//! that constructs [`op_alloy_consensus::NutBundle`] values at runtime without serde. +//! +//! The parsing and codegen logic lives in [`build_helpers`] so it can be shared +//! with integration tests under `tests/`. + +use std::{env, fs, path::PathBuf}; + +use anyhow::{Context, Result, anyhow}; + +#[path = "build_helpers.rs"] +mod build_helpers; + +use build_helpers::{capitalize, format_bundle, parse_bundle}; + +/// Read the bundle JSON, generate Rust source, and write it to `out_dir`. +fn generate(name: &str, json_path: &PathBuf, out_dir: &str) -> Result<()> { + let json = + fs::read_to_string(json_path).with_context(|| format!("read {}", json_path.display()))?; + let bundle = parse_bundle(&json).with_context(|| format!("parse {}", json_path.display()))?; + let code = format_bundle(name, &capitalize(name), &bundle); + let out_path = PathBuf::from(out_dir).join(format!("{name}_nut_bundle.rs")); + fs::write(&out_path, code).with_context(|| format!("write {}", out_path.display()))?; + Ok(()) +} + +fn run() -> Result<()> { + let out_dir = env::var("OUT_DIR").context("OUT_DIR not set")?; + let manifest_dir = + PathBuf::from(env::var("CARGO_MANIFEST_DIR").context("CARGO_MANIFEST_DIR not set")?); + + // Probe for the karst bundle file rather than the `op-core/` directory: Docker + // bind-mounts auto-create empty `op-core/` ancestor stubs on the host, and an + // `is_dir()` check picks those up before reaching the real op-core at the + // monorepo root. Probing for a known file inside the bundle path skips the stubs. + let monorepo_root = manifest_dir + .ancestors() + .find(|p| p.join("op-core/nuts/bundles/karst_nut_bundle.json").is_file()) + .ok_or_else(|| { + anyhow!( + "could not find op-core/nuts/bundles/karst_nut_bundle.json in any ancestor of {}", + manifest_dir.display() + ) + })? + .to_path_buf(); + + let karst_bundle = monorepo_root.join("op-core/nuts/bundles/karst_nut_bundle.json"); + println!("cargo::rerun-if-changed={}", karst_bundle.display()); + + generate("karst", &karst_bundle, &out_dir).context("generate karst bundle") +} + +fn main() { + if let Err(e) = run() { + panic!("{e:?}"); + } +} diff --git a/rust/kona/crates/protocol/hardforks/build_helpers.rs b/rust/kona/crates/protocol/hardforks/build_helpers.rs new file mode 100644 index 00000000000..e4838163d59 --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/build_helpers.rs @@ -0,0 +1,115 @@ +//! Shared NUT bundle parsing and codegen. +//! +//! Included by `build.rs` at build-script compile time and by +//! `tests/build_codegen.rs` at integration-test compile time. That dual role +//! is the reason these helpers live at the crate root instead of under `src/`. + +// This module is included via `#[path = ...]` from two different compilation +// units. Each unit uses a subset of the public items; suppress the +// per-compilation-unit dead-code warnings rather than gating every item. +#![allow(dead_code, unreachable_pub)] + +use anyhow::{Context, Result, anyhow}; +use serde::Deserialize; + +/// Supported NUT bundle schema version. +pub const SUPPORTED_VERSION: &str = "1.0.0"; + +#[derive(Deserialize)] +pub struct BundleMetadata { + pub version: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BundleTransaction { + pub intent: String, + pub from: String, + pub to: Option, + pub data: String, + pub gas_limit: u64, +} + +#[derive(Deserialize)] +pub struct BundleFile { + pub metadata: BundleMetadata, + pub transactions: Vec, +} + +/// Parse a NUT bundle from JSON, validating its schema version. +pub fn parse_bundle(json: &str) -> Result { + let bundle: BundleFile = serde_json::from_str(json).context("parse NUT bundle JSON")?; + if bundle.metadata.version != SUPPORTED_VERSION { + return Err(anyhow!( + "unsupported NUT bundle version: got {:?}, want {:?}", + bundle.metadata.version, + SUPPORTED_VERSION + )); + } + Ok(bundle) +} + +/// Capitalize the first character of `name` — `"karst"` becomes `"Karst"`. +/// +/// The NUT bundle's `fork_name` feeds into the source-hash derivation and must +/// match the capitalized form used by op-node. +pub fn capitalize(name: &str) -> String { + let mut c = name.chars(); + c.next().map_or_else(String::new, |first| first.to_uppercase().collect::() + c.as_str()) +} + +/// Generate the Rust source for a `fn {name}_nut_bundle() -> &'static NutBundle` constructor. +/// +/// The generated function wraps the bundle construction in a function-scoped +/// `once_cell::race::OnceBox` so the bundle is allocated at most once per +/// process, with subsequent calls returning a reference to the cached value. +/// `OnceBox` is lock-free (AtomicPtr-based) and works on `no_std` targets +/// without requiring a `critical-section` implementation — critical for the +/// cannon/asterisc fault-proof VM targets. +/// +/// `fork_display` is the capitalized form of `name` (see [`capitalize`]). +pub fn format_bundle(name: &str, fork_display: &str, bundle: &BundleFile) -> String { + let mut code = String::new(); + code.push_str("// Auto-generated by build.rs — do not edit.\n\n"); + code.push_str(&format!( + "pub(crate) fn {name}_nut_bundle() -> &'static op_alloy_consensus::NutBundle {{\n" + )); + code.push_str( + " static BUNDLE: once_cell::race::OnceBox\n = once_cell::race::OnceBox::new();\n", + ); + code.push_str(&format!( + " BUNDLE.get_or_init(|| alloc::boxed::Box::new(op_alloy_consensus::NutBundle {{\n fork_name: alloc::string::String::from({fork_display:?}),\n transactions: alloc::vec![\n" + )); + + for tx in &bundle.transactions { + let to_expr = tx.to.as_ref().map_or_else( + || "None".to_string(), + |addr| { + format!( + "Some(alloy_primitives::address!({:?}))", + addr.strip_prefix("0x").unwrap_or(addr) + ) + }, + ); + let data_hex = tx.data.strip_prefix("0x").unwrap_or(&tx.data); + let from_hex = tx.from.strip_prefix("0x").unwrap_or(&tx.from); + + code.push_str(" op_alloy_consensus::NetworkUpgradeTransaction {\n"); + code.push_str(&format!( + " intent: alloc::string::String::from({:?}),\n", + tx.intent + )); + code.push_str(&format!( + " from: alloy_primitives::address!({from_hex:?}),\n" + )); + code.push_str(&format!(" to: {to_expr},\n")); + code.push_str(&format!( + " data: alloy_primitives::Bytes::from_static(&alloy_primitives::hex!({data_hex:?})),\n" + )); + code.push_str(&format!(" gas_limit: {},\n", tx.gas_limit)); + code.push_str(" },\n"); + } + + code.push_str(" ],\n }))\n}\n"); + code +} diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/eth_liquidity_interop.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/eth_liquidity_interop.hex new file mode 100644 index 00000000000..24c5ccc845f --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/src/bytecode/eth_liquidity_interop.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50610393806100206000396000f3fe60806040526004361061003f5760003560e01c806344df8e701461004457806354fd4d501461004e578063a0712d68146100ad578063b60d4288146100cd575b600080fd5b61004c6100d5565b005b34801561005a57600080fd5b506100976040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b6040516100a491906102a1565b60405180910390f35b3480156100b957600080fd5b5061004c6100c8366004610314565b61015a565b61004c610229565b3373420000000000000000000000000000000000002414610122576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907f875e07afd7ce17c6531b1a6b7b34829dcd8b7e6639448afbd6a8e29fa1422b82906020015b60405180910390a2565b33734200000000000000000000000000000000000024146101a7576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80336040516101b590610295565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156101ef573d6000803e3d6000fd5b505060405181815233907f85719716ac5bd2744ae7ed3d16702129383049b97123b506320e7a5826ebbbba9060200160405180910390a250565b34600003610263576040517f2c5211c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907fbb9e497a5b82d1a37f9496dd70c6efb97ba0d98c66c3422d05010105d063359890602001610150565b60598061032e83390190565b600060208083528351808285015260005b818110156102ce578581018301518582016040015282016102b2565b818111156102e0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561032657600080fd5b503591905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_2.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_0.hex similarity index 100% rename from rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_2.hex rename to rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_0.hex diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_3.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_1.hex similarity index 79% rename from rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_3.hex rename to rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_1.hex index c40d0530f48..c5663574a78 100644 --- a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_3.hex +++ b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_1.hex @@ -1 +1 @@ -7ef876a0e54b4d06bbcc857f41ae00e89d820339ac5ce0034aac722c817b2873e03a7e68940000000000000000000000000000000000000000944200000000000000000000000000000000000023808082c35080a43659cfe60000000000000000000000000d0edd0ebd0e94d218670a8de867eb5c4d37cadd +7ef876a0e54b4d06bbcc857f41ae00e89d820339ac5ce0034aac722c817b2873e03a7e68940000000000000000000000000000000000000000944200000000000000000000000000000000000023808082c35080a43659cfe60000000000000000000000000d0edd0ebd0e94d218670a8de867eb5c4d37cadd \ No newline at end of file diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_2.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_2.hex new file mode 100644 index 00000000000..0423b269b8e --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_2.hex @@ -0,0 +1 @@ +7ef907eca053eccc738e298d613b3c3dcc8ad1d9e9626945a2f7b005252c2b57837176d9609442200000000000000000000000000000000000028080808307a12080b907ab608060405234801561001057600080fd5b5061078b806100206000396000f3fe6080604052600436106100345760003560e01c80634f0edcc91461003957806354fd4d501461005b57806364a197f3146100ba575b600080fd5b34801561004557600080fd5b506100596100543660046105ae565b6100db565b005b34801561006757600080fd5b506100a46040518060400160405280600581526020017f312e302e3100000000000000000000000000000000000000000000000000000081525081565b6040516100b1919061065a565b60405180910390f35b6100cd6100c8366004610674565b610340565b6040519081526020016100b1565b3373420000000000000000000000000000000000002314610128576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008073420000000000000000000000000000000000002373ffffffffffffffffffffffffffffffffffffffff16637936cbee6040518163ffffffff1660e01b81526004016040805180830381865afa158015610189573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ad91906106a0565b909250905073ffffffffffffffffffffffffffffffffffffffff82163014610201576040517fbc22e2aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa0712d68000000000000000000000000000000000000000000000000000000008152600481018490527342000000000000000000000000000000000000259063a0712d6890602401600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b50505050828460405161028d9061057d565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156102c7573d6000803e3d6000fd5b50508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d3598584604051610331929190918252602082015260400190565b60405180910390a35050505050565b600073ffffffffffffffffffffffffffffffffffffffff831661038f576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002573ffffffffffffffffffffffffffffffffffffffff166344df8e70346040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103eb57600080fd5b505af11580156103ff573d6000803e3d6000fd5b50506040805133602482015273ffffffffffffffffffffffffffffffffffffffff881660448201523460648083019190915282518083039091018152608490910182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f4f0edcc90000000000000000000000000000000000000000000000000000000017905290517f7056f41f0000000000000000000000000000000000000000000000000000000081527342000000000000000000000000000000000000239450637056f41f93506104de9250869130916004016106ce565b6020604051808303816000875af11580156104fd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610521919061070c565b604080513481526020810185905291925073ffffffffffffffffffffffffffffffffffffffff85169133917fed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402910160405180910390a392915050565b60598061072683390190565b73ffffffffffffffffffffffffffffffffffffffff811681146105ab57600080fd5b50565b6000806000606084860312156105c357600080fd5b83356105ce81610589565b925060208401356105de81610589565b929592945050506040919091013590565b6000815180845260005b81811015610615576020818501810151868301820152016105f9565b81811115610627576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061066d60208301846105ef565b9392505050565b6000806040838503121561068757600080fd5b823561069281610589565b946020939093013593505050565b600080604083850312156106b357600080fd5b82516106be81610589565b6020939093015192949293505050565b83815273ffffffffffffffffffffffffffffffffffffffff8316602082015260606040820152600061070360608301846105ef565b95945050505050565b60006020828403121561071e57600080fd5b505191905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a \ No newline at end of file diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_3.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_3.hex new file mode 100644 index 00000000000..5206133fade --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_3.hex @@ -0,0 +1 @@ +7ef876a050684989256294e3c64949ea1cf5bad586c7e6b91b8b7f21ee9ef7086efe60db940000000000000000000000000000000000000000944200000000000000000000000000000000000024808082c35080a43659cfe60000000000000000000000001913211d1257da0c92149a0b0f07086c5e4fc9e0 \ No newline at end of file diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_4.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_4.hex new file mode 100644 index 00000000000..12f8c56e52e --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_4.hex @@ -0,0 +1 @@ +7ef903f4a0ceec4ed75501efd5830d25045e10014464155345d91a8c78dba77aed02d5b08b9442200000000000000000000000000000000000038080808305b8d880b903b3608060405234801561001057600080fd5b50610393806100206000396000f3fe60806040526004361061003f5760003560e01c806344df8e701461004457806354fd4d501461004e578063a0712d68146100ad578063b60d4288146100cd575b600080fd5b61004c6100d5565b005b34801561005a57600080fd5b506100976040518060400160405280600581526020017f312e312e3000000000000000000000000000000000000000000000000000000081525081565b6040516100a491906102a1565b60405180910390f35b3480156100b957600080fd5b5061004c6100c8366004610314565b61015a565b61004c610229565b3373420000000000000000000000000000000000002414610122576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907f875e07afd7ce17c6531b1a6b7b34829dcd8b7e6639448afbd6a8e29fa1422b82906020015b60405180910390a2565b33734200000000000000000000000000000000000024146101a7576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80336040516101b590610295565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156101ef573d6000803e3d6000fd5b505060405181815233907f85719716ac5bd2744ae7ed3d16702129383049b97123b506320e7a5826ebbbba9060200160405180910390a250565b34600003610263576040517f2c5211c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405134815233907fbb9e497a5b82d1a37f9496dd70c6efb97ba0d98c66c3422d05010105d063359890602001610150565b60598061032e83390190565b600060208083528351808285015260005b818110156102ce578581018301518582016040015282016102b2565b818111156102e0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561032657600080fd5b503591905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a \ No newline at end of file diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_5.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_5.hex new file mode 100644 index 00000000000..8d0d84a2af8 --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_5.hex @@ -0,0 +1 @@ +7ef876a08c6c281c65cba9a9286233c61c3a1b4d606b899b1aee3b3a7221fd5212b22822940000000000000000000000000000000000000000944200000000000000000000000000000000000025808082c35080a43659cfe6000000000000000000000000efdda7c9172e7e4b634d7bf97d315dde24327fe0 \ No newline at end of file diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_6.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_6.hex new file mode 100644 index 00000000000..c0eba9c802b --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_base_tx_6.hex @@ -0,0 +1 @@ +7ef876a0a9b2a45c225d10db0a0a092d024192968cef10170a82f9d67d2bf0264d0c055594deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000002590ffffffffffffffffffffffffffffffff90ffffffffffffffffffffffffffffffff82c3508084b60d4288 \ No newline at end of file diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_0.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_cross_l2_inbox_tx_0.hex similarity index 100% rename from rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_0.hex rename to rust/kona/crates/protocol/hardforks/src/bytecode/interop_cross_l2_inbox_tx_0.hex diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_1.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/interop_cross_l2_inbox_tx_1.hex similarity index 100% rename from rust/kona/crates/protocol/hardforks/src/bytecode/interop_tx_1.hex rename to rust/kona/crates/protocol/hardforks/src/bytecode/interop_cross_l2_inbox_tx_1.hex diff --git a/rust/kona/crates/protocol/hardforks/src/bytecode/superchain_eth_bridge_interop.hex b/rust/kona/crates/protocol/hardforks/src/bytecode/superchain_eth_bridge_interop.hex new file mode 100644 index 00000000000..87adb2a74ff --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/src/bytecode/superchain_eth_bridge_interop.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5061078b806100206000396000f3fe6080604052600436106100345760003560e01c80634f0edcc91461003957806354fd4d501461005b57806364a197f3146100ba575b600080fd5b34801561004557600080fd5b506100596100543660046105ae565b6100db565b005b34801561006757600080fd5b506100a46040518060400160405280600581526020017f312e302e3100000000000000000000000000000000000000000000000000000081525081565b6040516100b1919061065a565b60405180910390f35b6100cd6100c8366004610674565b610340565b6040519081526020016100b1565b3373420000000000000000000000000000000000002314610128576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008073420000000000000000000000000000000000002373ffffffffffffffffffffffffffffffffffffffff16637936cbee6040518163ffffffff1660e01b81526004016040805180830381865afa158015610189573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ad91906106a0565b909250905073ffffffffffffffffffffffffffffffffffffffff82163014610201576040517fbc22e2aa00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa0712d68000000000000000000000000000000000000000000000000000000008152600481018490527342000000000000000000000000000000000000259063a0712d6890602401600060405180830381600087803b15801561026757600080fd5b505af115801561027b573d6000803e3d6000fd5b50505050828460405161028d9061057d565b73ffffffffffffffffffffffffffffffffffffffff90911681526020016040518091039082f09050801580156102c7573d6000803e3d6000fd5b50508373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fe5479bb8ebad3b9ac81f55f424a6289cf0a54ff2641708f41dcb2b26f264d3598584604051610331929190918252602082015260400190565b60405180910390a35050505050565b600073ffffffffffffffffffffffffffffffffffffffff831661038f576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002573ffffffffffffffffffffffffffffffffffffffff166344df8e70346040518263ffffffff1660e01b81526004016000604051808303818588803b1580156103eb57600080fd5b505af11580156103ff573d6000803e3d6000fd5b50506040805133602482015273ffffffffffffffffffffffffffffffffffffffff881660448201523460648083019190915282518083039091018152608490910182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f4f0edcc90000000000000000000000000000000000000000000000000000000017905290517f7056f41f0000000000000000000000000000000000000000000000000000000081527342000000000000000000000000000000000000239450637056f41f93506104de9250869130916004016106ce565b6020604051808303816000875af11580156104fd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610521919061070c565b604080513481526020810185905291925073ffffffffffffffffffffffffffffffffffffffff85169133917fed98a2ff78833375c368471a747cdf0633024dde3f870feb08a934ac5be83402910160405180910390a392915050565b60598061072683390190565b73ffffffffffffffffffffffffffffffffffffffff811681146105ab57600080fd5b50565b6000806000606084860312156105c357600080fd5b83356105ce81610589565b925060208401356105de81610589565b929592945050506040919091013590565b6000815180845260005b81811015610615576020818501810151868301820152016105f9565b81811115610627576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061066d60208301846105ef565b9392505050565b6000806040838503121561068757600080fd5b823561069281610589565b946020939093013593505050565b600080604083850312156106b357600080fd5b82516106be81610589565b6020939093015192949293505050565b83815273ffffffffffffffffffffffffffffffffffffffff8316602082015260606040820152600061070360608301846105ef565b95945050505050565b60006020828403121561071e57600080fd5b505191905056fe608060405260405160593803806059833981016040819052601e91602a565b806001600160a01b0316ff5b600060208284031215603b57600080fd5b81516001600160a01b0381168114605157600080fd5b939250505056fea164736f6c634300080f000a diff --git a/rust/kona/crates/protocol/hardforks/src/forks.rs b/rust/kona/crates/protocol/hardforks/src/forks.rs index 137580aeab2..c458f92af53 100644 --- a/rust/kona/crates/protocol/hardforks/src/forks.rs +++ b/rust/kona/crates/protocol/hardforks/src/forks.rs @@ -31,11 +31,20 @@ use crate::{Ecotone, Fjord, Interop, Isthmus, Jovian, Karst}; /// assert_eq!(isthmus_upgrade_tx.collect::>().len(), 8); /// ``` /// -/// Build interop hardfork upgrade transaction: +/// Build the base (always-on) interop hardfork upgrade transactions: /// ```rust /// use kona_hardforks::{Hardfork, Hardforks}; /// let interop_upgrade_tx = Hardforks::INTEROP.txs(); -/// assert_eq!(interop_upgrade_tx.collect::>().len(), 4); +/// assert_eq!(interop_upgrade_tx.collect::>().len(), 7); +/// ``` +/// +/// The `CrossL2Inbox` deploy+upgrade pair is gated on the interop dependency set +/// containing more than one chain and is emitted separately via +/// [`Interop::cross_l2_inbox_txs`]: +/// ```rust +/// use kona_hardforks::Interop; +/// let cross_l2_inbox_txs = Interop::cross_l2_inbox_txs(); +/// assert_eq!(cross_l2_inbox_txs.collect::>().len(), 2); /// ``` #[derive(Debug, Default, Clone, Copy)] #[non_exhaustive] @@ -82,9 +91,12 @@ mod tests { assert_eq!(jovian_upgrade_tx.collect::>().len(), 5); let karst_upgrade_tx = Hardforks::KARST.txs(); - assert_eq!(karst_upgrade_tx.collect::>().len(), 0); + assert_eq!(karst_upgrade_tx.collect::>().len(), 31); let interop_upgrade_tx = Hardforks::INTEROP.txs(); - assert_eq!(interop_upgrade_tx.collect::>().len(), 4); + assert_eq!(interop_upgrade_tx.collect::>().len(), 7); + + let cross_l2_inbox_txs = Interop::cross_l2_inbox_txs(); + assert_eq!(cross_l2_inbox_txs.collect::>().len(), 2); } } diff --git a/rust/kona/crates/protocol/hardforks/src/interop.rs b/rust/kona/crates/protocol/hardforks/src/interop.rs index 10a821907cf..d18a7046c2e 100644 --- a/rust/kona/crates/protocol/hardforks/src/interop.rs +++ b/rust/kona/crates/protocol/hardforks/src/interop.rs @@ -17,6 +17,9 @@ use crate::Hardfork; pub struct Interop; impl Interop { + /// The depositor account address. + pub const DEPOSITOR_ACCOUNT: Address = address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"); + /// The deployer of the `CrossL2Inbox` contract. pub const CROSS_L2_INBOX_DEPLOYER: Address = address!("0x4220000000000000000000000000000000000000"); @@ -25,6 +28,14 @@ impl Interop { pub const L2_TO_L2_XDM_DEPLOYER: Address = address!("0x4220000000000000000000000000000000000001"); + /// The deployer of the `SuperchainETHBridge` contract. + pub const SUPERCHAIN_ETH_BRIDGE_DEPLOYER: Address = + address!("0x4220000000000000000000000000000000000002"); + + /// The deployer of the `ETHLiquidity` contract. + pub const ETH_LIQUIDITY_DEPLOYER: Address = + address!("0x4220000000000000000000000000000000000003"); + /// The deployed address of the `CrossL2Inbox` implementation contract. pub const NEW_CROSS_L2_INBOX_IMPL: Address = address!("0x691300f512e48B463C2617b34Eef1A9f82EE7dBf"); @@ -41,6 +52,24 @@ impl Interop { pub const L2_TO_L2_XDM_IMPL_CODE_HASH: B256 = b256!("0x458925c90ec70736600bef3d6529643a0e7a0a848e62626d61314c057b4a71a9"); + /// The deployed address of the `SuperchainETHBridge` implementation contract. + pub const NEW_SUPERCHAIN_ETH_BRIDGE_IMPL: Address = + address!("0x1913211d1257Da0C92149A0b0F07086C5E4Fc9E0"); + + /// The deployed address of the `ETHLiquidity` implementation contract. + pub const NEW_ETH_LIQUIDITY_IMPL: Address = + address!("0xEFDdA7c9172e7E4B634d7bF97D315dDe24327fe0"); + + /// The `SuperchainETHBridge` proxy predeploy address. + pub const SUPERCHAIN_ETH_BRIDGE: Address = + address!("0x4200000000000000000000000000000000000024"); + + /// The `ETHLiquidity` proxy predeploy address. + pub const ETH_LIQUIDITY: Address = address!("0x4200000000000000000000000000000000000025"); + + /// The `fund()` selector for `ETHLiquidity`. + pub const ETH_LIQUIDITY_FUND_SELECTOR: [u8; 4] = hex!("b60d4288"); + /// Returns the source hash for the `CrossL2Inbox` contract deployment transaction. pub fn deploy_cross_l2_inbox_source() -> B256 { UpgradeDepositSource { intent: String::from("Interop: CrossL2Inbox Deployment") } @@ -69,6 +98,35 @@ impl Interop { .source_hash() } + /// Returns the source hash for the `SuperchainETHBridge` deployment transaction. + pub fn deploy_superchain_eth_bridge_source() -> B256 { + UpgradeDepositSource { intent: String::from("Interop: SuperchainETHBridge Deployment") } + .source_hash() + } + + /// Returns the source hash for the `SuperchainETHBridge` proxy upgrade transaction. + pub fn upgrade_superchain_eth_bridge_proxy_source() -> B256 { + UpgradeDepositSource { intent: String::from("Interop: SuperchainETHBridge Proxy Update") } + .source_hash() + } + + /// Returns the source hash for the `ETHLiquidity` deployment transaction. + pub fn deploy_eth_liquidity_source() -> B256 { + UpgradeDepositSource { intent: String::from("Interop: ETHLiquidity Deployment") } + .source_hash() + } + + /// Returns the source hash for the `ETHLiquidity` proxy upgrade transaction. + pub fn upgrade_eth_liquidity_proxy_source() -> B256 { + UpgradeDepositSource { intent: String::from("Interop: ETHLiquidity Proxy Update") } + .source_hash() + } + + /// Returns the source hash for the `ETHLiquidity` funding transaction. + pub fn fund_eth_liquidity_source() -> B256 { + UpgradeDepositSource { intent: String::from("Interop: ETHLiquidity Funding") }.source_hash() + } + /// Returns the `CrossL2Inbox` deployment bytecode. pub fn cross_l2_inbox_deployment_bytecode() -> Bytes { hex::decode(include_str!("./bytecode/crossl2inbox_interop.hex").replace('\n', "")) @@ -83,52 +141,143 @@ impl Interop { .into() } - /// Returns the list of [`TxDeposit`]s for the network upgrade. + /// Returns the `SuperchainETHBridge` deployment bytecode. + pub fn superchain_eth_bridge_deployment_bytecode() -> Bytes { + hex::decode(include_str!("./bytecode/superchain_eth_bridge_interop.hex").replace('\n', "")) + .expect("Expected hex byte string") + .into() + } + + /// Returns the `ETHLiquidity` deployment bytecode. + pub fn eth_liquidity_deployment_bytecode() -> Bytes { + hex::decode(include_str!("./bytecode/eth_liquidity_interop.hex").replace('\n', "")) + .expect("Expected hex byte string") + .into() + } + + /// Returns the calldata for the `ETHLiquidity.fund()` call. + pub fn eth_liquidity_fund_calldata() -> Bytes { + Bytes::copy_from_slice(&Self::ETH_LIQUIDITY_FUND_SELECTOR) + } + + /// Returns the list of always-on [`TxDeposit`]s for the Interop network upgrade. + /// + /// Mirrors op-node's `InteropNetworkUpgradeTransactions()` in + /// `op-node/rollup/derive/interop_upgrade_transactions.go`. The `CrossL2Inbox` + /// deploy+upgrade pair is emitted separately by [`Self::cross_l2_inbox_deposits`] + /// and is gated on the dependency set containing more than one chain. pub fn deposits() -> impl Iterator { ([ TxDeposit { - source_hash: Self::deploy_cross_l2_inbox_source(), - from: Self::CROSS_L2_INBOX_DEPLOYER, + source_hash: Self::deploy_l2_to_l2_xdm_source(), + from: Self::L2_TO_L2_XDM_DEPLOYER, to: TxKind::Create, mint: 0, value: U256::ZERO, - gas_limit: 420_000, + gas_limit: 1_100_000, is_system_transaction: false, - input: Self::cross_l2_inbox_deployment_bytecode(), + input: Self::l2_to_l2_xdm_deployment_bytecode(), }, TxDeposit { - source_hash: Self::upgrade_cross_l2_inbox_proxy_source(), + source_hash: Self::upgrade_l2_to_l2_xdm_proxy_source(), from: Address::ZERO, - to: TxKind::Call(Predeploys::CROSS_L2_INBOX), + to: TxKind::Call(Predeploys::L2_TO_L2_XDM), mint: 0, value: U256::ZERO, gas_limit: 50_000, is_system_transaction: false, - input: super::upgrade_to_calldata(Self::NEW_CROSS_L2_INBOX_IMPL), + input: super::upgrade_to_calldata(Self::NEW_L2_TO_L2_XDM_IMPL), }, TxDeposit { - source_hash: Self::deploy_l2_to_l2_xdm_source(), - from: Self::L2_TO_L2_XDM_DEPLOYER, + source_hash: Self::deploy_superchain_eth_bridge_source(), + from: Self::SUPERCHAIN_ETH_BRIDGE_DEPLOYER, to: TxKind::Create, mint: 0, value: U256::ZERO, - gas_limit: 1_100_000, + gas_limit: 500_000, is_system_transaction: false, - input: Self::l2_to_l2_xdm_deployment_bytecode(), + input: Self::superchain_eth_bridge_deployment_bytecode(), }, TxDeposit { - source_hash: Self::upgrade_l2_to_l2_xdm_proxy_source(), + source_hash: Self::upgrade_superchain_eth_bridge_proxy_source(), from: Address::ZERO, - to: TxKind::Call(Predeploys::L2_TO_L2_XDM), + to: TxKind::Call(Self::SUPERCHAIN_ETH_BRIDGE), mint: 0, value: U256::ZERO, gas_limit: 50_000, is_system_transaction: false, - input: super::upgrade_to_calldata(Self::NEW_L2_TO_L2_XDM_IMPL), + input: super::upgrade_to_calldata(Self::NEW_SUPERCHAIN_ETH_BRIDGE_IMPL), + }, + TxDeposit { + source_hash: Self::deploy_eth_liquidity_source(), + from: Self::ETH_LIQUIDITY_DEPLOYER, + to: TxKind::Create, + mint: 0, + value: U256::ZERO, + gas_limit: 375_000, + is_system_transaction: false, + input: Self::eth_liquidity_deployment_bytecode(), + }, + TxDeposit { + source_hash: Self::upgrade_eth_liquidity_proxy_source(), + from: Address::ZERO, + to: TxKind::Call(Self::ETH_LIQUIDITY), + mint: 0, + value: U256::ZERO, + gas_limit: 50_000, + is_system_transaction: false, + input: super::upgrade_to_calldata(Self::NEW_ETH_LIQUIDITY_IMPL), + }, + TxDeposit { + source_hash: Self::fund_eth_liquidity_source(), + from: Self::DEPOSITOR_ACCOUNT, + to: TxKind::Call(Self::ETH_LIQUIDITY), + mint: u128::MAX, + value: U256::from(u128::MAX), + gas_limit: 50_000, + is_system_transaction: false, + input: Self::eth_liquidity_fund_calldata(), }, ]) .into_iter() } + + /// Returns the list of `CrossL2Inbox` [`TxDeposit`]s that are only emitted when + /// the interop dependency set contains more than one chain. + /// + /// Mirrors op-node's `InteropActivateCrossL2InboxTransactions()`. The gate lives + /// in `StatefulAttributesBuilder::prepare_payload_attributes`. + pub fn cross_l2_inbox_deposits() -> impl Iterator { + ([ + TxDeposit { + source_hash: Self::deploy_cross_l2_inbox_source(), + from: Self::CROSS_L2_INBOX_DEPLOYER, + to: TxKind::Create, + mint: 0, + value: U256::ZERO, + gas_limit: 420_000, + is_system_transaction: false, + input: Self::cross_l2_inbox_deployment_bytecode(), + }, + TxDeposit { + source_hash: Self::upgrade_cross_l2_inbox_proxy_source(), + from: Address::ZERO, + to: TxKind::Call(Predeploys::CROSS_L2_INBOX), + mint: 0, + value: U256::ZERO, + gas_limit: 50_000, + is_system_transaction: false, + input: super::upgrade_to_calldata(Self::NEW_CROSS_L2_INBOX_IMPL), + }, + ]) + .into_iter() + } + + /// Returns the list of encoded `CrossL2Inbox` upgrade [`Bytes`] txs. Convenience + /// wrapper for callers that already 2718-encode the base upgrade stream. + pub fn cross_l2_inbox_txs() -> impl Iterator { + Self::cross_l2_inbox_deposits().map(|tx| tx.encoded_2718().into()) + } } impl Hardfork for Interop { @@ -177,9 +326,49 @@ mod test { ); } + #[test] + fn test_deploy_superchain_eth_bridge_source() { + assert_eq!( + Interop::deploy_superchain_eth_bridge_source(), + b256!("0x53eccc738e298d613b3c3dcc8ad1d9e9626945a2f7b005252c2b57837176d960") + ); + } + + #[test] + fn test_upgrade_superchain_eth_bridge_proxy_source() { + assert_eq!( + Interop::upgrade_superchain_eth_bridge_proxy_source(), + b256!("0x50684989256294e3c64949ea1cf5bad586c7e6b91b8b7f21ee9ef7086efe60db") + ); + } + + #[test] + fn test_deploy_eth_liquidity_source() { + assert_eq!( + Interop::deploy_eth_liquidity_source(), + b256!("0xceec4ed75501efd5830d25045e10014464155345d91a8c78dba77aed02d5b08b") + ); + } + + #[test] + fn test_upgrade_eth_liquidity_proxy_source() { + assert_eq!( + Interop::upgrade_eth_liquidity_proxy_source(), + b256!("0x8c6c281c65cba9a9286233c61c3a1b4d606b899b1aee3b3a7221fd5212b22822") + ); + } + + #[test] + fn test_fund_eth_liquidity_source() { + assert_eq!( + Interop::fund_eth_liquidity_source(), + b256!("0xa9b2a45c225d10db0a0a092d024192968cef10170a82f9d67d2bf0264d0c0555") + ); + } + #[test] fn test_deploy_cross_l2_inbox_address_and_code() { - let txs = Interop::deposits().collect::>(); + let txs = Interop::cross_l2_inbox_deposits().collect::>(); check_deployment_code( txs[0].clone(), Interop::NEW_CROSS_L2_INBOX_IMPL, @@ -191,28 +380,37 @@ mod test { fn test_deploy_l2_to_l2_xdm_address_and_code() { let txs = Interop::deposits().collect::>(); check_deployment_code( - txs[2].clone(), + txs[0].clone(), Interop::NEW_L2_TO_L2_XDM_IMPL, Interop::L2_TO_L2_XDM_IMPL_CODE_HASH, ); } #[test] - fn test_interop_txs_encoded() { + fn test_interop_base_txs_encoded() { let interop_upgrade_tx = Interop.txs().collect::>(); - assert_eq!(interop_upgrade_tx.len(), 4); + assert_eq!(interop_upgrade_tx.len(), 7); let expected_txs: Vec = vec![ - hex::decode(include_str!("./bytecode/interop_tx_0.hex").replace('\n', "")) + hex::decode(include_str!("./bytecode/interop_base_tx_0.hex").replace('\n', "")) + .unwrap() + .into(), + hex::decode(include_str!("./bytecode/interop_base_tx_1.hex").replace('\n', "")) + .unwrap() + .into(), + hex::decode(include_str!("./bytecode/interop_base_tx_2.hex").replace('\n', "")) + .unwrap() + .into(), + hex::decode(include_str!("./bytecode/interop_base_tx_3.hex").replace('\n', "")) .unwrap() .into(), - hex::decode(include_str!("./bytecode/interop_tx_1.hex").replace('\n', "")) + hex::decode(include_str!("./bytecode/interop_base_tx_4.hex").replace('\n', "")) .unwrap() .into(), - hex::decode(include_str!("./bytecode/interop_tx_2.hex").replace('\n', "")) + hex::decode(include_str!("./bytecode/interop_base_tx_5.hex").replace('\n', "")) .unwrap() .into(), - hex::decode(include_str!("./bytecode/interop_tx_3.hex").replace('\n', "")) + hex::decode(include_str!("./bytecode/interop_base_tx_6.hex").replace('\n', "")) .unwrap() .into(), ]; @@ -220,4 +418,26 @@ mod test { assert_eq!(interop_upgrade_tx[i], *expected); } } + + #[test] + fn test_interop_cross_l2_inbox_txs_encoded() { + let cross_l2_inbox_tx = Interop::cross_l2_inbox_txs().collect::>(); + assert_eq!(cross_l2_inbox_tx.len(), 2); + + let expected_txs: Vec = vec![ + hex::decode( + include_str!("./bytecode/interop_cross_l2_inbox_tx_0.hex").replace('\n', ""), + ) + .unwrap() + .into(), + hex::decode( + include_str!("./bytecode/interop_cross_l2_inbox_tx_1.hex").replace('\n', ""), + ) + .unwrap() + .into(), + ]; + for (i, expected) in expected_txs.iter().enumerate() { + assert_eq!(cross_l2_inbox_tx[i], *expected); + } + } } diff --git a/rust/kona/crates/protocol/hardforks/src/karst.rs b/rust/kona/crates/protocol/hardforks/src/karst.rs index 7b4c1cbc70e..a0cd44e2380 100644 --- a/rust/kona/crates/protocol/hardforks/src/karst.rs +++ b/rust/kona/crates/protocol/hardforks/src/karst.rs @@ -1,34 +1,53 @@ //! Module containing a `TxDeposit` builder for the Karst network upgrade transactions. //! //! Karst network upgrade transactions are defined in the [OP Stack Specs][specs]. +//! The transactions are loaded from a JSON NUT bundle at compile time via `build.rs`. //! //! [specs]: https://github.com/ethereum-optimism/specs/tree/main/specs/protocol/karst -use alloy_primitives::Bytes; - -use crate::Hardfork; +// Include the build-script-generated NUT bundle constructor. +include!(concat!(env!("OUT_DIR"), "/karst_nut_bundle.rs")); /// The Karst network upgrade transactions. #[derive(Debug, Default, Clone, Copy)] pub struct Karst; -impl Hardfork for Karst { - /// Constructs the network upgrade transactions. - /// Karst has no upgrade transactions (empty NUT bundle). - fn txs(&self) -> impl Iterator + '_ { - core::iter::empty() - } -} +impl_hardfork_from_bundle!(Karst, karst_nut_bundle); #[cfg(test)] mod tests { use super::*; + use crate::Hardfork; use alloc::vec::Vec; + use alloy_primitives::Bytes; + + #[test] + fn test_karst_upgrade_txs() { + let karst = Karst; + let txs: Vec = karst.txs().collect(); + assert_eq!(txs.len(), 31); + + // All encoded deposit txs start with the deposit type byte (0x7e). + for tx in &txs { + assert_eq!(tx[0], 0x7e); + } + } #[test] - fn test_karst_no_upgrade_txs() { + fn test_karst_upgrade_gas() { let karst = Karst; - let txs: Vec<_> = karst.txs().collect(); - assert!(txs.is_empty()); + assert_eq!(karst.upgrade_gas(), 55_370_657); + } + + #[test] + fn test_karst_bundle_valid() { + let bundle = karst_nut_bundle(); + assert_eq!(bundle.fork_name, "Karst"); + assert_eq!(bundle.transactions.len(), 31); + + // Verify all transactions have non-empty intents. + for tx in &bundle.transactions { + assert!(!tx.intent.is_empty()); + } } } diff --git a/rust/kona/crates/protocol/hardforks/src/lib.rs b/rust/kona/crates/protocol/hardforks/src/lib.rs index 4e1bc4c7b7e..a6a2f334c85 100644 --- a/rust/kona/crates/protocol/hardforks/src/lib.rs +++ b/rust/kona/crates/protocol/hardforks/src/lib.rs @@ -9,6 +9,9 @@ extern crate alloc; +#[macro_use] +mod nut_bundle; + mod traits; pub use traits::Hardfork; diff --git a/rust/kona/crates/protocol/hardforks/src/nut_bundle.rs b/rust/kona/crates/protocol/hardforks/src/nut_bundle.rs new file mode 100644 index 00000000000..86590f2c836 --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/src/nut_bundle.rs @@ -0,0 +1,38 @@ +//! Shared pattern for implementing [`crate::Hardfork`] from a NUT bundle constructor. + +/// Implements [`crate::Hardfork`] for a fork type backed by a NUT bundle constructor +/// generated by `build.rs`. +/// +/// Given a type and a `fn() -> NutBundle` returning the bundle, this generates +/// `txs` and `upgrade_gas` — EIP-2718 encoding each deposit transaction and +/// summing per-tx gas. +/// +/// Usage at a fork site: +/// +/// ```ignore +/// include!(concat!(env!("OUT_DIR"), "/karst_nut_bundle.rs")); +/// pub struct Karst; +/// impl_hardfork_from_bundle!(Karst, karst_nut_bundle); +/// ``` +macro_rules! impl_hardfork_from_bundle { + ($ty:ident, $bundle_fn:ident) => { + impl $crate::Hardfork for $ty { + fn txs(&self) -> impl ::core::iter::Iterator + '_ { + use ::alloy_eips::eip2718::Encodable2718; + let bundle = $bundle_fn(); + let deposits = bundle.to_deposit_transactions().unwrap_or_else(|e| { + panic!("{} NUT bundle is invalid: {:?}", ::core::stringify!($ty), e) + }); + deposits.into_iter().map(|tx| { + let mut encoded = ::alloc::vec::Vec::new(); + tx.encode_2718(&mut encoded); + ::alloy_primitives::Bytes::from(encoded) + }) + } + + fn upgrade_gas(&self) -> u64 { + $bundle_fn().total_gas() + } + } + }; +} diff --git a/rust/kona/crates/protocol/hardforks/src/traits.rs b/rust/kona/crates/protocol/hardforks/src/traits.rs index 5d59631de14..588f497e5d9 100644 --- a/rust/kona/crates/protocol/hardforks/src/traits.rs +++ b/rust/kona/crates/protocol/hardforks/src/traits.rs @@ -6,4 +6,13 @@ use alloy_primitives::Bytes; pub trait Hardfork { /// Returns the hardfork upgrade transactions as [`Bytes`]. fn txs(&self) -> impl Iterator + '_; + + /// Returns the additional gas required by upgrade transactions. + /// + /// Starting with Karst, upgrade transactions carry their own gas budget that is + /// added to the block gas limit at the fork activation block. Pre-Karst forks + /// return 0 (upgrade txs ran within the system tx gas allowance). + fn upgrade_gas(&self) -> u64 { + 0 + } } diff --git a/rust/kona/crates/protocol/hardforks/tests/build_codegen.rs b/rust/kona/crates/protocol/hardforks/tests/build_codegen.rs new file mode 100644 index 00000000000..0da4b928c58 --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/tests/build_codegen.rs @@ -0,0 +1,35 @@ +//! Regression test for the build-script bundle code generator. +//! +//! Runs [`format_bundle`] on a committed fixture JSON and asserts the +//! generated Rust source matches the committed expected output byte-for-byte. +//! Any change to the code-generator's output format will break this test. +//! +//! To regenerate the expected fixture after an intentional codegen change: +//! `cargo test -p kona-hardforks --test build_codegen -- --ignored regenerate_expected` + +#[path = "../build_helpers.rs"] +mod build_helpers; + +use build_helpers::{capitalize, format_bundle, parse_bundle}; + +const INPUT_JSON: &str = include_str!("fixtures/test_bundle.json"); +const EXPECTED_OUTPUT: &str = include_str!("fixtures/test_bundle_expected.rs"); + +#[test] +fn generates_expected_rust_source() { + let bundle = parse_bundle(INPUT_JSON).expect("parse fixture bundle"); + let generated = format_bundle("test", &capitalize("test"), &bundle); + assert_eq!(generated, EXPECTED_OUTPUT, "generated source does not match expected fixture"); +} + +/// Regenerate `tests/fixtures/test_bundle_expected.rs` from the current +/// generator output. Run manually after an intentional codegen change: +/// `cargo test -p kona-hardforks --test build_codegen -- --ignored regenerate_expected` +#[test] +#[ignore] +fn regenerate_expected() { + let bundle = parse_bundle(INPUT_JSON).expect("parse fixture bundle"); + let generated = format_bundle("test", &capitalize("test"), &bundle); + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/test_bundle_expected.rs"); + std::fs::write(path, generated).expect("write expected fixture"); +} diff --git a/rust/kona/crates/protocol/hardforks/tests/fixtures/test_bundle.json b/rust/kona/crates/protocol/hardforks/tests/fixtures/test_bundle.json new file mode 100644 index 00000000000..03604498976 --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/tests/fixtures/test_bundle.json @@ -0,0 +1,21 @@ +{ + "metadata": { + "version": "1.0.0" + }, + "transactions": [ + { + "intent": "Deploy FooProxy", + "from": "0x0000000000000000000000000000000000000001", + "to": null, + "data": "0xdeadbeef", + "gasLimit": 100000 + }, + { + "intent": "Upgrade Bar", + "from": "0x0000000000000000000000000000000000000002", + "to": "0x1234567890123456789012345678901234567890", + "data": "0xcafef00d", + "gasLimit": 200000 + } + ] +} diff --git a/rust/kona/crates/protocol/hardforks/tests/fixtures/test_bundle_expected.rs b/rust/kona/crates/protocol/hardforks/tests/fixtures/test_bundle_expected.rs new file mode 100644 index 00000000000..3df26c1e3ab --- /dev/null +++ b/rust/kona/crates/protocol/hardforks/tests/fixtures/test_bundle_expected.rs @@ -0,0 +1,25 @@ +// Auto-generated by build.rs — do not edit. + +pub(crate) fn test_nut_bundle() -> &'static op_alloy_consensus::NutBundle { + static BUNDLE: once_cell::race::OnceBox + = once_cell::race::OnceBox::new(); + BUNDLE.get_or_init(|| alloc::boxed::Box::new(op_alloy_consensus::NutBundle { + fork_name: alloc::string::String::from("Test"), + transactions: alloc::vec![ + op_alloy_consensus::NetworkUpgradeTransaction { + intent: alloc::string::String::from("Deploy FooProxy"), + from: alloy_primitives::address!("0000000000000000000000000000000000000001"), + to: None, + data: alloy_primitives::Bytes::from_static(&alloy_primitives::hex!("deadbeef")), + gas_limit: 100000, + }, + op_alloy_consensus::NetworkUpgradeTransaction { + intent: alloc::string::String::from("Upgrade Bar"), + from: alloy_primitives::address!("0000000000000000000000000000000000000002"), + to: Some(alloy_primitives::address!("1234567890123456789012345678901234567890")), + data: alloy_primitives::Bytes::from_static(&alloy_primitives::hex!("cafef00d")), + gas_limit: 200000, + }, + ], + })) +} diff --git a/rust/kona/crates/protocol/interop/src/constants.rs b/rust/kona/crates/protocol/interop/src/constants.rs index ab62c27e5a7..5ef57ee1f97 100644 --- a/rust/kona/crates/protocol/interop/src/constants.rs +++ b/rust/kona/crates/protocol/interop/src/constants.rs @@ -1,8 +1,7 @@ //! Constants for the OP Stack interop protocol. -/// The expiry window for relaying an initiating message (in seconds). -/// -pub const MESSAGE_EXPIRY_WINDOW: u64 = 7 * 24 * 60 * 60; +/// Re-export of the interop message expiry window. Now defined in `kona-genesis`. +pub use kona_genesis::MESSAGE_EXPIRY_WINDOW; /// The current version of the [`SuperRoot`](crate::SuperRoot) encoding format. pub const SUPER_ROOT_VERSION: u8 = 1; diff --git a/rust/kona/crates/protocol/interop/src/errors.rs b/rust/kona/crates/protocol/interop/src/errors.rs index 2184fbe4ff7..7c618380b60 100644 --- a/rust/kona/crates/protocol/interop/src/errors.rs +++ b/rust/kona/crates/protocol/interop/src/errors.rs @@ -1,6 +1,7 @@ //! Error types for the `kona-interop` crate. use crate::InteropProvider; +use alloc::vec::Vec; use alloy_primitives::{Address, B256}; use core::fmt::Debug; use kona_registry::HashMap; @@ -15,6 +16,9 @@ pub enum MessageGraphError { /// Missing a [`RollupConfig`](kona_genesis::RollupConfig) for a chain ID #[error("Missing a RollupConfig for chain ID {0}")] MissingRollupConfig(u64), + /// A message references a chain that is not in the dependency set. + #[error("chain {0} is not part of the dependency set")] + ChainNotInDependencySet(u64), /// Interop provider error #[error("Interop provider: {0}")] InteropProviderError(#[from] E), @@ -60,6 +64,16 @@ pub enum MessageGraphError { /// The timestamp of the initiating message initiating_message_time: u64, }, + /// Interop has not been activated for at least one block on the executing message's chain. + #[error( + "Interop has not been active for at least one block on executing message's chain. Activation time: {activation_time}, executing message time: {executing_message_time}" + )] + ExecutedTooEarly { + /// The timestamp of the interop activation on the executing chain. + activation_time: u64, + /// The timestamp of the executing message. + executing_message_time: u64, + }, /// Message is in the future #[error("Message is in the future. Expected timestamp to be <= {max}, got {actual}")] MessageInFuture { @@ -81,6 +95,19 @@ pub enum MessageGraphError { /// Invalid messages were found #[error("Invalid messages found on chains: {0:?}")] InvalidMessages(HashMap), + /// Cyclic dependency detected among same-timestamp executing messages. + /// + /// Each returned error describes the participants of a single cycle at a single + /// timestamp. Cycles that span multiple timestamps are not represented here: after + /// the reported chains are replaced with deposit-only blocks, the consolidation + /// retry loop re-derives the message graph and any remaining cycle at another + /// timestamp surfaces as a subsequent `CyclicDependency` error. + #[error("Cyclic dependency detected among chains: {chain_ids:?}")] + CyclicDependency { + /// The chain IDs participating in this cycle. Each chain appears at most once, + /// even if it has multiple executing messages inside the cycle. + chain_ids: Vec, + }, } /// A [Result] alias for the [`MessageGraphError`] type. @@ -105,26 +132,3 @@ pub enum SuperRootError { /// A [Result] alias for the [`SuperRootError`] type. pub type SuperRootResult = core::result::Result; - -/// Errors that can occur during interop validation. -#[derive(Debug, Error, PartialEq, Eq)] -pub enum InteropValidationError { - /// Interop is not enabled on one or both chains at the required timestamp. - #[error("interop not enabled")] - InteropNotEnabled, - - /// Executing timestamp is earlier than the initiating timestamp. - #[error( - "executing timestamp is earlier than initiating timestamp, executing: {executing}, initiating: {initiating}" - )] - InvalidTimestampInvariant { - /// Executing timestamp of the message - executing: u64, - /// Initiating timestamp of the message - initiating: u64, - }, - - /// Timestamp is outside the allowed interop expiry window. - #[error("timestamp outside allowed interop window, timestamp: {0}")] - InvalidInteropTimestamp(u64), -} diff --git a/rust/kona/crates/protocol/interop/src/graph.rs b/rust/kona/crates/protocol/interop/src/graph.rs index 92012c05cee..d5e94413ee5 100644 --- a/rust/kona/crates/protocol/interop/src/graph.rs +++ b/rust/kona/crates/protocol/interop/src/graph.rs @@ -1,20 +1,173 @@ //! Interop [`MessageGraph`]. use crate::{ - MESSAGE_EXPIRY_WINDOW, RawMessagePayload, + RawMessagePayload, errors::{MessageGraphError, MessageGraphResult}, - message::{EnrichedExecutingMessage, extract_executing_messages}, + message::{EnrichedExecutingMessage, parse_log_to_executing_message}, traits::InteropProvider, }; -use alloc::{string::ToString, vec::Vec}; +use alloc::{collections::BTreeMap, string::ToString, vec, vec::Vec}; use alloy_consensus::{Header, Sealed}; use alloy_primitives::keccak256; -use kona_genesis::RollupConfig; +use kona_genesis::{DependencySet, RollupConfig}; use kona_registry::{HashMap, ROLLUP_CONFIGS}; use tracing::{info, warn}; -/// The [`MessageGraph`] represents a set of blocks at a given timestamp and the interop -/// dependencies between them. +/// Static graph node representing an executing message in the cycle detection dependency graph. +#[derive(Debug)] +struct GraphNode { + /// The chain ID this executing message belongs to. + chain_id: u64, + /// The log index of the executing message within its block. + log_index: u32, + /// The chain ID of the initiating message this EM references. + target_chain_id: u64, + /// The log index of the initiating message this EM references. + target_log_index: u32, +} + +/// Finds the index of the latest node in `chain_node_indices` with `log_index <= target_log_idx`. +/// `chain_node_indices` must be sorted by `log_index` ascending. +fn executing_message_before( + nodes: &[GraphNode], + chain_node_indices: &[usize], + target_log_idx: u32, +) -> Option { + // partition_point returns the first index where the predicate is false, i.e. the first + // node with log_index > target. So pp - 1 is the last node with log_index <= target. + // If pp == 0, every node is past the target and there's no match. + let pp = chain_node_indices.partition_point(|&i| nodes[i].log_index <= target_log_idx); + (pp > 0).then(|| chain_node_indices[pp - 1]) +} + +/// Runs Kahn's topological sort algorithm to detect cycles. +/// +/// Operates on algorithm state (parallel vecs) separately from the immutable graph nodes. +/// Returns the indices of nodes participating in cycles, or an empty vec if acyclic. +fn check_cycles(depends_on: &[Vec], depended_on_by: &mut [Vec]) -> Vec { + let n = depends_on.len(); + if n == 0 { + return vec![]; + } + + let mut resolved = vec![false; n]; + + loop { + // Find nodes with no depended_on_by and mark them resolved. + let mut remove_set = Vec::new(); + for (i, deps) in depended_on_by.iter().enumerate() { + if !resolved[i] && deps.is_empty() { + resolved[i] = true; + remove_set.push(i); + } + } + + if remove_set.is_empty() { + // No progress, so we collect unresolved nodes (cycle participants). + return (0..n).filter(|&i| !resolved[i]).collect(); + } + + // Remove resolved nodes from depended_on_by of their dependencies. + for &removed_idx in &remove_set { + for &dep_idx in &depends_on[removed_idx] { + depended_on_by[dep_idx].retain(|&x| x != removed_idx); + } + } + } +} + +/// Builds a dependency graph from executing messages and checks for cycles. +/// Returns the chain IDs of cycle participants, or an empty vec if acyclic. +/// +/// Matches the semantics of op-supernode's `buildCycleGraph`: +/// - Only executing messages whose *executing* block timestamp *and* referenced *initiating* +/// message timestamp both equal `timestamp` are included as nodes. An EM that references a +/// historical initiating message is a dependency on finalized past state, not a concurrent +/// cross-chain dependency, and must not participate in the same-timestamp cycle graph. +/// - Intra-chain edges: each EM depends on the previous EM on the same chain. +/// - Cross-chain edges: each EM depends on `executingMessageBefore(targetChain, targetLogIdx)`. +fn detect_cycles(messages: &[EnrichedExecutingMessage], timestamp: u64) -> Vec { + // Filter to same-timestamp messages and create nodes. + let mut nodes = Vec::new(); + // BTreeMap for deterministic iteration order. + let mut chain_nodes: BTreeMap> = BTreeMap::new(); + + for msg in messages { + // Two filters, mirroring op-supernode (`verifyCycleMessages` + `buildCycleGraph`): + // 1. The EM's executing block must be at `timestamp`. + // 2. The EM's referenced initiating message must also be at `timestamp`. + // An EM that passes (1) but fails (2) references historical state and must not be + // admitted into the same-timestamp cycle graph. + if msg.executing_timestamp != timestamp || + msg.inner.identifier.timestamp.saturating_to::() != timestamp + { + continue; + } + + let initiating_chain_id: u64 = msg.inner.identifier.chainId.saturating_to(); + let initiating_log_index: u32 = msg.inner.identifier.logIndex.saturating_to(); + + let idx = nodes.len(); + nodes.push(GraphNode { + chain_id: msg.executing_chain_id, + log_index: msg.executing_log_index, + target_chain_id: initiating_chain_id, + target_log_index: initiating_log_index, + }); + chain_nodes.entry(msg.executing_chain_id).or_default().push(idx); + } + + if nodes.is_empty() { + return vec![]; + } + + // Sort each chain's node indices by log_index. + for indices in chain_nodes.values_mut() { + indices.sort_by_key(|&idx| nodes[idx].log_index); + } + + // Build algorithm state: parallel vecs for depends_on / depended_on_by. + let mut depends_on: Vec> = vec![Vec::new(); nodes.len()]; + let mut depended_on_by: Vec> = vec![Vec::new(); nodes.len()]; + + // Add edges. + for chain_indices in chain_nodes.values() { + for (i, &node_idx) in chain_indices.iter().enumerate() { + // Intra-chain: depends on previous EM on the same chain. + if i > 0 { + let prev_idx = chain_indices[i - 1]; + depends_on[node_idx].push(prev_idx); + depended_on_by[prev_idx].push(node_idx); + } + + // Cross-chain: depends on executingMessageBefore(targetChain, targetLogIdx). + let target_chain = nodes[node_idx].target_chain_id; + let target_log_idx = nodes[node_idx].target_log_index; + if let Some(target_indices) = chain_nodes.get(&target_chain) && + let Some(dep_idx) = + executing_message_before(&nodes, target_indices, target_log_idx) + { + depends_on[node_idx].push(dep_idx); + depended_on_by[dep_idx].push(node_idx); + } + } + } + + // Run Kahn's algorithm. + let cycle_indices = check_cycles(&depends_on, &mut depended_on_by); + if cycle_indices.is_empty() { + return vec![]; + } + + // Collect unique chain IDs of cycle participants. + let mut cycle_chains: Vec = cycle_indices.iter().map(|&i| nodes[i].chain_id).collect(); + cycle_chains.sort(); + cycle_chains.dedup(); + cycle_chains +} + +/// The [`MessageGraph`] represents a set of blocks — possibly at different timestamps, one per +/// chain — and the interop dependencies between them. /// /// This structure is used to determine whether or not any interop messages are invalid within the /// set of blocks within the graph. An "invalid message" is one that was relayed from one chain to @@ -36,6 +189,10 @@ pub struct MessageGraph<'a, P> { provider: &'a P, /// Backup rollup configs for each chain. rollup_configs: &'a HashMap, + /// The dependency set for the cluster being validated. + dependency_set: &'a DependencySet, + /// The message expiry window (in seconds) for validating initiating message timestamps. + message_expiry_window: u64, } impl<'a, P> MessageGraph<'a, P> @@ -50,6 +207,8 @@ where blocks: &HashMap>, provider: &'a P, rollup_configs: &'a HashMap, + dependency_set: &'a DependencySet, + message_expiry_window: u64, ) -> MessageGraphResult { info!( target: "message_graph", @@ -60,11 +219,23 @@ where let mut messages = Vec::with_capacity(blocks.len()); for (chain_id, header) in blocks { let receipts = provider.receipts_by_hash(*chain_id, header.hash()).await?; - let executing_messages = extract_executing_messages(receipts.as_slice()); - messages.extend(executing_messages.into_iter().map(|message| { - EnrichedExecutingMessage::new(message, *chain_id, header.timestamp) - })); + // Track the global log index across all receipts in the block so we can + // record each executing message's position, needed for cycle detection. + let mut global_log_index: u32 = 0; + for receipt in receipts.as_slice() { + for log in receipt.logs() { + if let Some(exec_msg) = parse_log_to_executing_message(log) { + messages.push(EnrichedExecutingMessage::new( + exec_msg, + *chain_id, + header.timestamp, + global_log_index, + )); + } + global_log_index += 1; + } + } } info!( @@ -73,18 +244,22 @@ where num_messages = messages.len(), "Derived message graph successfully", ); - Ok(Self { messages, provider, rollup_configs }) + Ok(Self { messages, provider, rollup_configs, dependency_set, message_expiry_window }) } /// Checks the validity of all messages within the graph. /// + /// First, detects cyclic dependencies among same-timestamp executing messages using Kahn's + /// topological sort. If a cycle is found, returns [`MessageGraphError::CyclicDependency`] + /// with the chain IDs of cycle participants. Then, validates each message independently. + /// /// _Note_: This function does not account for cascading dependency failures. When - /// [`MessageGraphError::InvalidMessages`] is returned by this function, the consumer must - /// re-execute the bad blocks with deposit transactions only per the [interop derivation - /// rules][int-block-replacement]. Once the bad blocks have been replaced, a new - /// [`MessageGraph`] should be constructed and resolution should be re-attempted. - /// This process should repeat recursively until no invalid dependencies remain, with the - /// terminal case being all blocks reduced to deposits-only. + /// [`MessageGraphError::InvalidMessages`] or [`MessageGraphError::CyclicDependency`] is + /// returned by this function, the consumer must re-execute the bad blocks with deposit + /// transactions only per the [interop derivation rules][int-block-replacement]. Once the bad + /// blocks have been replaced, a new [`MessageGraph`] should be constructed and resolution + /// should be re-attempted. This process should repeat recursively until no invalid + /// dependencies remain, with the terminal case being all blocks reduced to deposits-only. /// /// [int-block-replacement]: https://specs.optimism.io/interop/derivation.html#replacing-invalid-blocks pub async fn resolve(self) -> MessageGraphResult<(), P> { @@ -93,6 +268,34 @@ where "Checking the message graph for invalid messages" ); + // Check for cyclic dependencies among same-timestamp executing messages before + // validating individual messages. Cycles are a structural property of the graph + // that cannot be detected by per-message validation. + if !self.messages.is_empty() { + // Collect distinct timestamps present in the message set. + let mut timestamps: Vec = + self.messages.iter().map(|m| m.executing_timestamp).collect(); + timestamps.sort_unstable(); + timestamps.dedup(); + + for ts in timestamps { + let cycle_chains = detect_cycles(&self.messages, ts); + if !cycle_chains.is_empty() { + warn!( + target: "message_graph", + cycle_chains = %cycle_chains + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "), + timestamp = ts, + "Cyclic dependency detected among same-timestamp executing messages", + ); + return Err(MessageGraphError::CyclicDependency { chain_ids: cycle_chains }); + } + } + } + // Create a new vector to store invalid edges let mut invalid_messages = HashMap::default(); @@ -144,12 +347,34 @@ where &self, message: &EnrichedExecutingMessage, ) -> MessageGraphResult<(), P> { - // ChainID Invariant: The chain id of the initiating message MUST be in the dependency set - // This is enforced implicitly by the graph constructor and the provider. - let initiating_chain_id = message.inner.identifier.chainId.saturating_to(); let initiating_timestamp = message.inner.identifier.timestamp.saturating_to::(); + if !self.dependency_set.dependencies.contains_key(&message.executing_chain_id) { + return Err(MessageGraphError::ChainNotInDependencySet(message.executing_chain_id)); + } + if !self.dependency_set.dependencies.contains_key(&initiating_chain_id) { + return Err(MessageGraphError::ChainNotInDependencySet(initiating_chain_id)); + } + + // Attempt to fetch the rollup config for the executing chain from the registry. If the + // rollup config is not found, fall back to the local rollup configs. + let exec_rollup_config = ROLLUP_CONFIGS + .get(&message.executing_chain_id) + .or_else(|| self.rollup_configs.get(&message.executing_chain_id)) + .ok_or(MessageGraphError::MissingRollupConfig(message.executing_chain_id))?; + + // Activation invariant: Interop must be active on the executing chain AND the executing + // block must not be the activation block. + if !exec_rollup_config.is_interop_active(message.executing_timestamp) || + exec_rollup_config.is_first_interop_block(message.executing_timestamp) + { + return Err(MessageGraphError::ExecutedTooEarly { + activation_time: exec_rollup_config.hardforks.interop_time.unwrap_or_default(), + executing_message_time: message.executing_timestamp, + }); + } + // Attempt to fetch the rollup config for the initiating chain from the registry. If the // rollup config is not found, fall back to the local rollup configs. let rollup_config = ROLLUP_CONFIGS @@ -165,8 +390,8 @@ where max: message.executing_timestamp, actual: initiating_timestamp, }); - } else if initiating_timestamp < - rollup_config.hardforks.interop_time.unwrap_or_default() + rollup_config.block_time + } else if !rollup_config.is_interop_active(initiating_timestamp) || + rollup_config.is_first_interop_block(initiating_timestamp) { return Err(MessageGraphError::InitiatedTooEarly { activation_time: rollup_config.hardforks.interop_time.unwrap_or_default(), @@ -177,7 +402,8 @@ where // Message expiry invariant: The timestamp of the initiating message must be no more than // `MESSAGE_EXPIRY_WINDOW` seconds in the past, relative to the timestamp of the executing // message. - if initiating_timestamp < message.executing_timestamp.saturating_sub(MESSAGE_EXPIRY_WINDOW) + if initiating_timestamp < + message.executing_timestamp.saturating_sub(self.message_expiry_window) { return Err(MessageGraphError::MessageExpired { initiating_timestamp, @@ -244,17 +470,34 @@ where } #[cfg(test)] +#[allow(clippy::zero_sized_map_values)] mod test { - use super::{MESSAGE_EXPIRY_WINDOW, MessageGraph}; + use super::{MessageGraph, detect_cycles}; use crate::{ - MessageGraphError, + MESSAGE_EXPIRY_WINDOW, MessageGraphError, + message::EnrichedExecutingMessage, test_util::{ExecutingMessageBuilder, SuperchainBuilder}, }; - use alloy_primitives::{Address, hex, keccak256}; + use alloc::collections::BTreeMap; + use alloy_primitives::{Address, B256, U256, hex, keccak256}; + use kona_genesis::{ChainDependency, DependencySet}; + use std::sync::OnceLock; const MOCK_MESSAGE: [u8; 4] = hex!("deadbeef"); const CHAIN_A_ID: u64 = 1; const CHAIN_B_ID: u64 = 2; + const CHAIN_C_ID: u64 = 3; + + fn default_dep_set() -> &'static DependencySet { + static DEP_SET: OnceLock = OnceLock::new(); + DEP_SET.get_or_init(|| { + let mut dependencies = BTreeMap::new(); + dependencies.insert(CHAIN_A_ID, ChainDependency {}); + dependencies.insert(CHAIN_B_ID, ChainDependency {}); + dependencies.insert(CHAIN_C_ID, ChainDependency {}); + DependencySet { dependencies, override_message_expiry_window: None } + }) + } /// Returns a [`SuperchainBuilder`] with two chains (ids: `CHAIN_A_ID` and `CHAIN_B_ID`), /// configured with interop activating at timestamp `0`, the current block at timestamp `2`, @@ -291,40 +534,124 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); graph.resolve().await.unwrap(); } #[tokio::test] - async fn test_derive_and_resolve_simple_graph_with_cycles() { + async fn test_executing_chain_not_in_dep_set_rejected() { + let mut superchain = default_superchain(); + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let mut deps = BTreeMap::new(); + deps.insert(CHAIN_A_ID, ChainDependency {}); + let dep_set = DependencySet { dependencies: deps, override_message_expiry_window: None }; + + let graph = + MessageGraph::derive(&headers, &provider, &cfgs, &dep_set, MESSAGE_EXPIRY_WINDOW) + .await + .unwrap(); + let MessageGraphError::InvalidMessages(invalid) = graph.resolve().await.unwrap_err() else { + panic!("Expected InvalidMessages") + }; + assert_eq!( + *invalid.get(&CHAIN_B_ID).unwrap(), + MessageGraphError::ChainNotInDependencySet(CHAIN_B_ID) + ); + } + + #[tokio::test] + async fn test_initiating_chain_not_in_dep_set_rejected() { + let mut superchain = default_superchain(); + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let mut deps = BTreeMap::new(); + deps.insert(CHAIN_B_ID, ChainDependency {}); + let dep_set = DependencySet { dependencies: deps, override_message_expiry_window: None }; + + let graph = + MessageGraph::derive(&headers, &provider, &cfgs, &dep_set, MESSAGE_EXPIRY_WINDOW) + .await + .unwrap(); + let MessageGraphError::InvalidMessages(invalid) = graph.resolve().await.unwrap_err() else { + panic!("Expected InvalidMessages") + }; + assert_eq!( + *invalid.get(&CHAIN_B_ID).unwrap(), + MessageGraphError::ChainNotInDependencySet(CHAIN_A_ID) + ); + } + + #[tokio::test] + async fn test_derive_and_resolve_mutual_cycle_detected() { let mut superchain = default_superchain(); let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; let chain_b_time = superchain.chain(CHAIN_B_ID).header.timestamp; - superchain - .chain(CHAIN_A_ID) - .add_initiating_message(MOCK_MESSAGE.into()) - .add_executing_message( - ExecutingMessageBuilder::default() - .with_message_hash(keccak256(MOCK_MESSAGE)) - .with_origin_chain_id(CHAIN_B_ID) - .with_origin_timestamp(chain_b_time), - ); - superchain - .chain(CHAIN_B_ID) - .add_initiating_message(MOCK_MESSAGE.into()) - .add_executing_message( - ExecutingMessageBuilder::default() - .with_message_hash(keccak256(MOCK_MESSAGE)) - .with_origin_chain_id(CHAIN_A_ID) - .with_origin_timestamp(chain_a_time), - ); + // Chain A: executing message at log index 0, referencing chain B log index 0. + // Chain B: executing message at log index 0, referencing chain A log index 0. + // This creates a mutual cycle: A depends on B and B depends on A. + superchain.chain(CHAIN_A_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_B_ID) + .with_origin_timestamp(chain_b_time), + ); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); - graph.resolve().await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + let MessageGraphError::CyclicDependency { mut chain_ids } = + graph.resolve().await.unwrap_err() + else { + panic!("Expected CyclicDependency error") + }; + + chain_ids.sort(); + assert_eq!(chain_ids, vec![CHAIN_A_ID, CHAIN_B_ID]); } #[tokio::test] @@ -343,7 +670,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -376,7 +711,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -414,7 +757,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -450,7 +801,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -464,6 +823,206 @@ mod test { ); } + #[tokio::test] + async fn test_derive_and_resolve_graph_initiating_chain_interop_time_none_rejected() { + // A chain with no interop activation cannot produce valid init messages. + // Op-supervisor's `IsInterop(ts) = InteropTime != nil && ts >= *InteropTime` + // rejects in this case, and `check_single_dependency` must do the same via + // `is_interop_active`. + // + // Attack: an attacker plants an arbitrary log on a chain whose kona-visible + // rollup config has `interop_time = None` (stale registry, oracle-supplied + // config, or a misconfigured dep set), then references it from an executing + // message on another chain. Without a `None`-rejecting gate, kona accepts the + // forged init message and downstream consumers (e.g. `L2toL2CrossDomainMessenger`) + // deliver the call. + let mut superchain = default_superchain(); + + // Init chain (A): `interop_time = None`. Simulates a chain whose rollup config + // (bundled registry stale, oracle-supplied, or genuinely pre-interop but + // incorrectly included in the dep set) has no interop activation. + superchain.chain(CHAIN_A_ID).modify_rollup_cfg(|cfg| cfg.hardforks.interop_time = None); + // Sanity-check the precondition; otherwise we'd be testing the wrong thing. + assert!( + superchain.chain(CHAIN_A_ID).rollup_config.hardforks.interop_time.is_none(), + "test precondition: init chain must have interop_time = None" + ); + + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + + // Attacker plants an arbitrary log on A and references it from an executing + // message on B. With the broken gate, kona accepts. With a spec-correct gate, + // kona rejects because A has no interop activation configured. + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + let MessageGraphError::InvalidMessages(invalid_messages) = + graph.resolve().await.unwrap_err() + else { + panic!("Expected invalid messages — forged init message must be rejected") + }; + + assert!( + invalid_messages.contains_key(&CHAIN_B_ID), + "exec chain B's message referencing a non-interop init chain must be invalidated, got {:?}", + invalid_messages + ); + } + + #[tokio::test] + async fn test_derive_and_resolve_graph_executing_before_interop() { + let mut superchain = default_superchain(); + + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + let chain_b_time = superchain.chain(CHAIN_B_ID).header.timestamp; + + // Move CHAIN_B (the executing chain) activation to t=50, so its block at t=2 is + // pre-interop. CHAIN_A (initiating) stays at the default (interop_time=0, well + // past activation). The executing-chain guard must reject via `!is_interop_active`. + superchain.chain(CHAIN_B_ID).with_interop_activation_time(50); + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + let MessageGraphError::InvalidMessages(invalid_messages) = + graph.resolve().await.unwrap_err() + else { + panic!("Expected invalid messages") + }; + + assert_eq!(invalid_messages.len(), 1); + assert_eq!( + *invalid_messages.get(&CHAIN_B_ID).unwrap(), + MessageGraphError::ExecutedTooEarly { + activation_time: 50, + executing_message_time: chain_b_time, + } + ); + } + + #[tokio::test] + async fn test_derive_and_resolve_graph_executing_before_interop_unaligned_activation() { + let mut superchain = default_superchain(); + + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + let chain_b_time = superchain.chain(CHAIN_B_ID).header.timestamp; + + // CHAIN_B activates @ `1s`, unaligned with the block time of `2s`. The first + // CHAIN_B block at t=2 IS its activation block (`is_first_interop_block` is + // true under unaligned activation: `is_interop_active(2) && !is_interop_active(0)`). + superchain.chain(CHAIN_B_ID).with_interop_activation_time(1); + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + let MessageGraphError::InvalidMessages(invalid_messages) = + graph.resolve().await.unwrap_err() + else { + panic!("Expected invalid messages") + }; + + assert_eq!(invalid_messages.len(), 1); + assert_eq!( + *invalid_messages.get(&CHAIN_B_ID).unwrap(), + MessageGraphError::ExecutedTooEarly { + activation_time: 1, + executing_message_time: chain_b_time, + } + ); + } + + #[tokio::test] + async fn test_derive_and_resolve_graph_executing_at_interop_activation() { + let mut superchain = default_superchain(); + + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + let chain_b_time = superchain.chain(CHAIN_B_ID).header.timestamp; + + // CHAIN_B activates @ `chain_b_time`, exactly aligned with block_time. The block + // at `chain_b_time` IS the activation block. Mirrors the existing init-side test + // which uses `with_interop_activation_time(chain_a_time)`. + superchain.chain(CHAIN_B_ID).with_interop_activation_time(chain_b_time); + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + let MessageGraphError::InvalidMessages(invalid_messages) = + graph.resolve().await.unwrap_err() + else { + panic!("Expected invalid messages") + }; + + assert_eq!(invalid_messages.len(), 1); + assert_eq!( + *invalid_messages.get(&CHAIN_B_ID).unwrap(), + MessageGraphError::ExecutedTooEarly { + activation_time: chain_b_time, + executing_message_time: chain_b_time, + } + ); + } + #[tokio::test] async fn test_derive_and_resolve_graph_message_expired() { let mut superchain = default_superchain(); @@ -483,7 +1042,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -515,7 +1082,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -550,7 +1125,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -584,7 +1167,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -617,7 +1208,15 @@ mod test { let (headers, cfgs, provider) = superchain.build(); - let graph = MessageGraph::derive(&headers, &provider, &cfgs).await.unwrap(); + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); let MessageGraphError::InvalidMessages(invalid_messages) = graph.resolve().await.unwrap_err() else { @@ -633,4 +1232,521 @@ mod test { } ); } + + #[tokio::test] + async fn test_derive_and_resolve_graph_message_expired_custom_window() { + let mut superchain = default_superchain(); + const CUSTOM_EXPIRY: u64 = 10; + + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain + .chain(CHAIN_B_ID) + .with_timestamp(chain_a_time + CUSTOM_EXPIRY + 1) + .add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = + MessageGraph::derive(&headers, &provider, &cfgs, default_dep_set(), CUSTOM_EXPIRY) + .await + .unwrap(); + let MessageGraphError::InvalidMessages(invalid_messages) = + graph.resolve().await.unwrap_err() + else { + panic!("Expected invalid messages") + }; + + assert_eq!(invalid_messages.len(), 1); + assert_eq!( + *invalid_messages.get(&CHAIN_B_ID).unwrap(), + MessageGraphError::MessageExpired { + initiating_timestamp: chain_a_time, + executing_timestamp: chain_a_time + CUSTOM_EXPIRY + 1 + } + ); + } + + #[tokio::test] + async fn test_derive_and_resolve_graph_message_not_expired_within_custom_window() { + let mut superchain = default_superchain(); + const CUSTOM_EXPIRY: u64 = 10; + + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain + .chain(CHAIN_B_ID) + .with_timestamp(chain_a_time + CUSTOM_EXPIRY - 1) + .add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = + MessageGraph::derive(&headers, &provider, &cfgs, default_dep_set(), CUSTOM_EXPIRY) + .await + .unwrap(); + graph.resolve().await.unwrap(); + } + + /// When a chain has been replaced with a deposit-only block, it is excluded from the headers + /// passed to `derive` (since deposit-only blocks cannot contain executing messages). Executing + /// messages on other chains that reference initiating messages from the replaced chain must + /// still resolve successfully, because the provider retains the replaced chain's data. + #[tokio::test] + async fn test_resolve_with_replaced_chain_excluded_from_headers() { + let mut superchain = default_superchain(); + + let chain_a_time = superchain.chain(CHAIN_A_ID).header.timestamp; + + // Chain A has an initiating message. Chain B executes it. + superchain.chain(CHAIN_A_ID).add_initiating_message(MOCK_MESSAGE.into()); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(chain_a_time), + ); + + let (headers, cfgs, provider) = superchain.build(); + + // Simulate chain A having been replaced with a deposit-only block by excluding it + // from the headers passed to derive. The provider still has chain A's data. + let filtered_headers = + headers.into_iter().filter(|(chain_id, _)| *chain_id != CHAIN_A_ID).collect(); + + let graph = MessageGraph::derive( + &filtered_headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + graph.resolve().await.unwrap(); + } + + /// Triangle cycle: A→B→C→A. All three chains should be detected as cycle participants. + #[tokio::test] + async fn test_derive_and_resolve_triangle_cycle_detected() { + const CHAIN_C_ID: u64 = 3; + + let mut superchain = SuperchainBuilder::new(); + superchain + .chain(CHAIN_A_ID) + .with_timestamp(2) + .with_block_time(2) + .with_interop_activation_time(0); + superchain + .chain(CHAIN_B_ID) + .with_timestamp(2) + .with_block_time(2) + .with_interop_activation_time(0); + superchain + .chain(CHAIN_C_ID) + .with_timestamp(2) + .with_block_time(2) + .with_interop_activation_time(0); + + // A executes from C, B executes from A, C executes from B. + superchain.chain(CHAIN_A_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_C_ID) + .with_origin_timestamp(2), + ); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(2), + ); + superchain.chain(CHAIN_C_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_B_ID) + .with_origin_timestamp(2), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + let MessageGraphError::CyclicDependency { mut chain_ids } = + graph.resolve().await.unwrap_err() + else { + panic!("Expected CyclicDependency error") + }; + + chain_ids.sort(); + assert_eq!(chain_ids, vec![CHAIN_A_ID, CHAIN_B_ID, CHAIN_C_ID]); + } + + /// Bystander chain: A↔B form a cycle, but C has a one-way dependency on A (not in the cycle). + /// Only A and B should be reported as cycle participants. + #[tokio::test] + async fn test_derive_and_resolve_cycle_with_bystander_chain() { + const CHAIN_C_ID: u64 = 3; + + let mut superchain = SuperchainBuilder::new(); + superchain + .chain(CHAIN_A_ID) + .with_timestamp(2) + .with_block_time(2) + .with_interop_activation_time(0); + superchain + .chain(CHAIN_B_ID) + .with_timestamp(2) + .with_block_time(2) + .with_interop_activation_time(0); + superchain + .chain(CHAIN_C_ID) + .with_timestamp(2) + .with_block_time(2) + .with_interop_activation_time(0); + + // A↔B cycle: both EMs at log index 0, referencing each other at log index 0. + superchain.chain(CHAIN_A_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_B_ID) + .with_origin_timestamp(2), + ); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(2), + ); + + // C executes from A (one-way, not part of the cycle). + superchain.chain(CHAIN_C_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(2), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + let MessageGraphError::CyclicDependency { mut chain_ids } = + graph.resolve().await.unwrap_err() + else { + panic!("Expected CyclicDependency error") + }; + + chain_ids.sort(); + // Only A and B are in the cycle; C is a bystander. + assert_eq!(chain_ids, vec![CHAIN_A_ID, CHAIN_B_ID]); + } + + /// Helper to build an [`EnrichedExecutingMessage`] for direct `detect_cycles` tests. + /// + /// `target_timestamp` is the timestamp of the referenced initiating message and is + /// recorded in `MessageIdentifier.timestamp`. It is kept separate from + /// `executing_timestamp` so tests can construct EMs whose initiating message is at a + /// different (e.g. historical) timestamp than the executing block. + fn make_em( + executing_chain_id: u64, + executing_log_index: u32, + executing_timestamp: u64, + target_chain_id: u64, + target_log_index: u64, + target_timestamp: u64, + ) -> EnrichedExecutingMessage { + use crate::{ExecutingMessage, MessageIdentifier}; + EnrichedExecutingMessage::new( + ExecutingMessage { + payloadHash: B256::ZERO, + identifier: MessageIdentifier { + origin: Address::ZERO, + blockNumber: U256::ZERO, + logIndex: U256::from(target_log_index), + timestamp: U256::from(target_timestamp), + chainId: U256::from(target_chain_id), + }, + }, + executing_chain_id, + executing_timestamp, + executing_log_index, + ) + } + + /// An executing message with a past timestamp is filtered out of the cycle graph. + /// Mirrors op-supernode's "past timestamp filtered out" test case. + #[test] + fn test_detect_cycles_past_timestamp_filtered_out() { + let ts: u64 = 1000; + // Chain A has an EM at the wrong timestamp (ts - 100), referencing chain B. + let messages = vec![make_em(CHAIN_A_ID, 0, ts - 100, CHAIN_B_ID, 0, ts - 100)]; + let result = detect_cycles(&messages, ts); + assert!(result.is_empty(), "Past-timestamp EM should be excluded from cycle graph"); + } + + /// An executing message whose *referenced initiating message* is at a historical + /// timestamp must be filtered out of the cycle graph, even if its executing block + /// is at the current timestamp. + /// + /// This mirrors op-supernode's `buildCycleGraph` secondary filter (`em.Timestamp == ts`, + /// where `em.Timestamp` is the identifier timestamp). Two EMs that reference each + /// other at historical timestamps are dependencies on finalized past state, not a + /// concurrent same-timestamp cycle. + #[test] + fn test_detect_cycles_historical_identifier_timestamp_filtered_out() { + let ts: u64 = 1000; + let historical: u64 = 500; + // Both EMs are at the current timestamp but reference initiating messages at a + // historical timestamp. Without the secondary filter these would form a spurious + // A→B, B→A cycle. + let messages = vec![ + make_em(CHAIN_A_ID, 0, ts, CHAIN_B_ID, 0, historical), + make_em(CHAIN_B_ID, 0, ts, CHAIN_A_ID, 0, historical), + ]; + let result = detect_cycles(&messages, ts); + assert!( + result.is_empty(), + "EMs referencing historical initiating messages must not participate in the \ + same-timestamp cycle graph", + ); + } + + /// One-way reference to a chain with no executing messages — no cycle. + /// Mirrors op-supernode's "one-way ref to chain with no EMs - no cycle" test case. + #[test] + fn test_detect_cycles_one_way_ref_to_chain_with_no_ems() { + let ts: u64 = 1000; + // Chain A has an EM referencing chain B at log index 0, but chain B has no EMs. + let messages = vec![make_em(CHAIN_A_ID, 0, ts, CHAIN_B_ID, 0, ts)]; + let result = detect_cycles(&messages, ts); + assert!(result.is_empty(), "Reference to chain with no EMs should not create a cycle"); + } + + /// Reference before target EM — no dependency edge, no cycle. + /// Chain A references chain B at log index 2, but chain B's only EM is at log index 3. + /// Since there is no EM at or before index 2 on chain B, no cross-chain edge is created. + /// Mirrors op-supernode's "ref before target EM - no dependency, no cycle" test case. + #[test] + fn test_detect_cycles_ref_before_target_em_no_cycle() { + let ts: u64 = 1000; + let messages = vec![ + make_em(CHAIN_A_ID, 0, ts, CHAIN_B_ID, 2, ts), // A refs B@2 + make_em(CHAIN_B_ID, 3, ts, CHAIN_A_ID, 0, ts), // B's EM is at index 3, refs A@0 + ]; + let result = detect_cycles(&messages, ts); + // A references B at log index 2, but B's EM is at index 3 (> 2), so + // executingMessageBefore(B, 2) returns None. Only B→A edge exists, no cycle. + assert!(result.is_empty(), "Ref before target EM should not create a dependency edge"); + } + + /// Multiple EMs on the same chain with no cross-chain cycle — intra-chain sequential. + /// Mirrors op-supernode's "intra-chain sequential EMs - no cycle" test case. + #[test] + fn test_detect_cycles_intra_chain_sequential_ems_no_cycle() { + let ts: u64 = 1000; + // Chain A has two sequential EMs (indices 0 and 5), both referencing chain B. + // Chain B has no EMs, so no cross-chain edges are created. + let messages = vec![ + make_em(CHAIN_A_ID, 0, ts, CHAIN_B_ID, 0, ts), + make_em(CHAIN_A_ID, 5, ts, CHAIN_B_ID, 3, ts), + ]; + let result = detect_cycles(&messages, ts); + assert!( + result.is_empty(), + "Intra-chain sequential EMs without cross-chain cycle should not be flagged" + ); + } + + /// Regression test: executing messages whose referenced initiating messages are at + /// historical timestamps must not be treated as same-timestamp cycle participants. + /// + /// op-supernode's `buildCycleGraph` filters candidate nodes by the referenced + /// initiating-message's timestamp (`em.Timestamp == ts`), where + /// `ExecutingMessage.Timestamp` is populated from `Identifier.Timestamp` in + /// `op-supervisor/.../executing_message.go`. Executing messages whose initiating + /// message is at a historical timestamp therefore never participate in the + /// same-timestamp cycle graph — they represent a dependency on finalized historical + /// state, not a concurrent cross-chain dependency. + /// + /// This test sets up two chains at the same executing-block timestamp, each with a + /// single EM that references the *other* chain's initiating message at a historical + /// timestamp. No same-timestamp cycle can exist: both dependencies are on historical, + /// already-finalized state. The messages may independently fail per-message validation + /// (the test harness does not supply historical blocks), but they must not be flagged + /// as cyclic. + /// + /// See ethereum-optimism/optimism#20303 review discussion. + #[tokio::test] + async fn test_historical_cross_chain_refs_not_flagged_as_same_ts_cycle() { + const CURRENT_TS: u64 = 200; + const HISTORICAL_TS: u64 = 100; + + let mut superchain = SuperchainBuilder::new(); + superchain + .chain(CHAIN_A_ID) + .with_timestamp(CURRENT_TS) + .with_block_time(2) + .with_interop_activation_time(0); + superchain + .chain(CHAIN_B_ID) + .with_timestamp(CURRENT_TS) + .with_block_time(2) + .with_interop_activation_time(0); + + // Chain A's block at CURRENT_TS contains an EM referencing an initiating message + // on chain B at HISTORICAL_TS. Chain B's block at CURRENT_TS contains an EM + // referencing an initiating message on chain A at HISTORICAL_TS. Neither reference + // is a same-timestamp dependency, so no same-timestamp cycle exists. + superchain.chain(CHAIN_A_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_B_ID) + .with_origin_timestamp(HISTORICAL_TS), + ); + superchain.chain(CHAIN_B_ID).add_executing_message( + ExecutingMessageBuilder::default() + .with_message_hash(keccak256(MOCK_MESSAGE)) + .with_origin_chain_id(CHAIN_A_ID) + .with_origin_timestamp(HISTORICAL_TS), + ); + + let (headers, cfgs, provider) = superchain.build(); + + let graph = MessageGraph::derive( + &headers, + &provider, + &cfgs, + default_dep_set(), + MESSAGE_EXPIRY_WINDOW, + ) + .await + .unwrap(); + + // Cycle detection must not flag these messages as cyclic. Per-message validation + // may still reject them (the test provider has no historical blocks), but that is + // a separate failure mode; this test is scoped to cycle detection. + if let Err(MessageGraphError::CyclicDependency { chain_ids }) = graph.resolve().await { + panic!( + "Historical cross-chain references must not be flagged as a same-timestamp \ + cycle. Both EMs reference initiating messages at a historical timestamp and \ + have no concurrent cross-chain dependency. Chain IDs wrongly flagged: {:?}", + chain_ids, + ); + } + } + + /// Three-chain extension of `ref before target EM`: A:5 refs B:3, B:5 refs C:3, C:5 refs A:3. + /// Each chain's only EM is at logIdx 5, but every cross-ref targets logIdx 3, so + /// `executing_message_before` returns `None` for all three lookups and no cross-chain + /// edges form. Mirrors op-supernode's `triangle with missing leg - no cycle` case. + #[test] + fn test_detect_cycles_triangle_with_missing_leg_no_cycle() { + const CHAIN_C_ID: u64 = 3; + let ts: u64 = 1000; + let messages = vec![ + make_em(CHAIN_A_ID, 5, ts, CHAIN_B_ID, 3, ts), + make_em(CHAIN_B_ID, 5, ts, CHAIN_C_ID, 3, ts), + make_em(CHAIN_C_ID, 5, ts, CHAIN_A_ID, 3, ts), + ]; + let result = detect_cycles(&messages, ts); + assert!( + result.is_empty(), + "Triangle where every cross-ref points before the target chain's EM should not cycle" + ); + } + + /// Multi-hop one-way chain that terminates at an absent chain. A:0 refs B:5; B:3 refs C:0, + /// where chain C is not in the graph. The B-side ref resolves via `executing_message_before` + /// to B:3 (the only EM on B, logIdx 3 ≤ 5), giving A:0 → B:3. B:3's cross-ref to C produces + /// no edge because C has no EMs. Linear, acyclic. + /// Mirrors op-supernode's `one-way dependency - no cycle` case. + #[test] + fn test_detect_cycles_one_way_multi_hop_no_cycle() { + const CHAIN_C_ID: u64 = 3; + let ts: u64 = 1000; + let messages = vec![ + make_em(CHAIN_A_ID, 0, ts, CHAIN_B_ID, 5, ts), + make_em(CHAIN_B_ID, 3, ts, CHAIN_C_ID, 0, ts), + ]; + let result = detect_cycles(&messages, ts); + assert!( + result.is_empty(), + "Multi-hop one-way chain ending at an absent chain must not cycle" + ); + } + + /// Diamond shape: `em_d` depends on both `em_b` and `em_c`, and both `em_b` and `em_c` + /// depend on `em_a`. Acyclic. Mirrors op-supernode's `TestCheckCycle` diamond case. + /// + /// Each EM gets at most one cross-chain edge and one intra-chain edge, so to give + /// `em_d` two incoming dependencies we make it both the intra-chain successor of + /// `em_b` (same chain, higher logIdx) and the cross-chain reference to `em_c`. + /// `em_a` targets an absent chain so it has no outgoing edges. + #[test] + fn test_detect_cycles_diamond_no_cycle() { + const CHAIN_C_ID: u64 = 3; + const CHAIN_ABSENT: u64 = 99; + let ts: u64 = 1000; + let messages = vec![ + // em_a on chain 1, logIdx 0, targets absent chain → no edges. + make_em(CHAIN_A_ID, 0, ts, CHAIN_ABSENT, 0, ts), + // em_b on chain 2, logIdx 0, targets em_a → edge em_b → em_a. + make_em(CHAIN_B_ID, 0, ts, CHAIN_A_ID, 0, ts), + // em_c on chain 3, logIdx 0, targets em_a → edge em_c → em_a. + make_em(CHAIN_C_ID, 0, ts, CHAIN_A_ID, 0, ts), + // em_d on chain 2, logIdx 5, targets em_c → cross-chain edge em_d → em_c, + // plus intra-chain edge em_d → em_b (em_b precedes em_d on chain 2). + make_em(CHAIN_B_ID, 5, ts, CHAIN_C_ID, 0, ts), + ]; + let result = detect_cycles(&messages, ts); + assert!(result.is_empty(), "Diamond pattern is acyclic and must not be flagged"); + } + + /// Bystander chain whose EM references a chain absent from the graph entirely. + /// A↔C form a mutual cycle. B's only EM points at chain D, which has no EMs in the + /// message set. B must be spared. Complements the existing bystander test, where the + /// bystander references a chain that *is* in the graph. + /// Mirrors op-supernode's `A↔C cycle with B as bystander` case. + #[test] + fn test_detect_cycles_bystander_refs_absent_chain() { + const CHAIN_C_ID: u64 = 3; + const CHAIN_D_ID: u64 = 4; + let ts: u64 = 1000; + let messages = vec![ + make_em(CHAIN_A_ID, 0, ts, CHAIN_C_ID, 0, ts), + make_em(CHAIN_C_ID, 0, ts, CHAIN_A_ID, 0, ts), + make_em(CHAIN_B_ID, 0, ts, CHAIN_D_ID, 0, ts), + ]; + let mut result = detect_cycles(&messages, ts); + result.sort(); + assert_eq!(result, vec![CHAIN_A_ID, CHAIN_C_ID]); + assert!(!result.contains(&CHAIN_B_ID), "Bystander chain B must not be flagged"); + } } diff --git a/rust/kona/crates/protocol/interop/src/lib.rs b/rust/kona/crates/protocol/interop/src/lib.rs index dc15696475e..4b3dfab5f42 100644 --- a/rust/kona/crates/protocol/interop/src/lib.rs +++ b/rust/kona/crates/protocol/interop/src/lib.rs @@ -21,15 +21,13 @@ mod replacement; pub use replacement::BlockReplacement; mod traits; -pub use traits::{InteropProvider, InteropValidator}; +pub use traits::InteropProvider; mod safety; pub use safety::SafetyLevelParseError; mod errors; -pub use errors::{ - InteropValidationError, MessageGraphError, MessageGraphResult, SuperRootError, SuperRootResult, -}; +pub use errors::{MessageGraphError, MessageGraphResult, SuperRootError, SuperRootResult}; mod root; pub use root::{ChainRootInfo, OutputRootWithChain, SuperRoot, SuperRootOutput}; @@ -41,8 +39,7 @@ pub use message::{ parse_logs_to_executing_msgs, }; -mod depset; -pub use depset::{ChainDependency, DependencySet}; +pub use kona_genesis::{ChainDependency, DependencySet}; pub use op_alloy_consensus::interop::SafetyLevel; diff --git a/rust/kona/crates/protocol/interop/src/message.rs b/rust/kona/crates/protocol/interop/src/message.rs index 3ff0d530078..ad9ef3fb941 100644 --- a/rust/kona/crates/protocol/interop/src/message.rs +++ b/rust/kona/crates/protocol/interop/src/message.rs @@ -114,6 +114,8 @@ pub struct EnrichedExecutingMessage { pub executing_chain_id: u64, /// The timestamp of the block that the executing message was included in. pub executing_timestamp: u64, + /// The global log index of this executing message within its block. + pub executing_log_index: u32, } impl EnrichedExecutingMessage { @@ -122,8 +124,9 @@ impl EnrichedExecutingMessage { inner: ExecutingMessage, executing_chain_id: u64, executing_timestamp: u64, + executing_log_index: u32, ) -> Self { - Self { inner, executing_chain_id, executing_timestamp } + Self { inner, executing_chain_id, executing_timestamp, executing_log_index } } } diff --git a/rust/kona/crates/protocol/interop/src/traits.rs b/rust/kona/crates/protocol/interop/src/traits.rs index c463fca9710..46c3cbf830b 100644 --- a/rust/kona/crates/protocol/interop/src/traits.rs +++ b/rust/kona/crates/protocol/interop/src/traits.rs @@ -1,12 +1,10 @@ //! Traits for the `kona-interop` crate. -use crate::InteropValidationError; use alloc::{boxed::Box, vec::Vec}; use alloy_consensus::Header; -use alloy_primitives::{B256, ChainId}; +use alloy_primitives::B256; use async_trait::async_trait; use core::error::Error; -use kona_protocol::BlockInfo; use op_alloy_consensus::OpReceiptEnvelope; /// Describes the interface of the interop data provider. This provider is multiplexed over several @@ -33,43 +31,3 @@ pub trait InteropProvider { block_hash: B256, ) -> Result, Self::Error>; } - -/// Trait for validating interop-related timestamps and blocks. -pub trait InteropValidator: Send + Sync { - /// Validates that the provided timestamps and chain IDs are eligible for interop execution. - /// - /// # Arguments - /// * `initiating_chain_id` - The chain ID where the message was initiated - /// * `initiating_timestamp` - The timestamp when the message was initiated - /// * `executing_chain_id` - The chain ID where the message is being executed - /// * `executing_timestamp` - The timestamp when the message is being executed - /// * `timeout` - Optional timeout value to add to the execution deadline - /// - /// # Returns - /// * `Ok(())` if the timestamps are valid for interop execution - /// * `Err(InteropValidationError)` if validation fails - fn validate_interop_timestamps( - &self, - initiating_chain_id: ChainId, - initiating_timestamp: u64, - executing_chain_id: ChainId, - executing_timestamp: u64, - timeout: Option, - ) -> Result<(), InteropValidationError>; - - /// Returns `true` if the timestamp is strictly after the interop activation block. - /// - /// This function checks whether the provided timestamp is *after* that activation, - /// skipping the activation block itself. - /// - /// Returns `false` if `interop_time` is not configured. - fn is_post_interop(&self, chain_id: ChainId, timestamp: u64) -> bool; - - /// Returns `true` if the block is the interop activation block for the specified chain. - /// - /// An interop activation block is defined as the block that is right after the - /// interop activation time. - /// - /// Returns `false` if `interop_time` is not configured. - fn is_interop_activation_block(&self, chain_id: ChainId, block: BlockInfo) -> bool; -} diff --git a/rust/kona/crates/protocol/protocol/src/attributes.rs b/rust/kona/crates/protocol/protocol/src/attributes.rs index 21374eb7fc7..cf7ffb80856 100644 --- a/rust/kona/crates/protocol/protocol/src/attributes.rs +++ b/rust/kona/crates/protocol/protocol/src/attributes.rs @@ -65,7 +65,8 @@ impl OpAttributesWithParent { self.attributes .transactions .iter() - .all(|tx| tx.first().is_some_and(|tx| tx[0] == OpTxType::Deposit as u8)) + .flatten() + .all(|tx| tx.first() == Some(&(OpTxType::Deposit as u8))) } /// Converts the [`OpAttributesWithParent`] into a deposits-only payload. @@ -110,6 +111,35 @@ mod tests { assert_eq!(op_attributes_with_parent.derived_from(), None); } + /// Regression: `transactions` is `Option>`; `Option::iter` yields at most one + /// `&Vec`. Without `.flatten()`, only the **first** [`Bytes`] in that vec was inspected, + /// so a trailing non-deposit was incorrectly ignored. + #[test] + fn is_deposits_only_iterates_all_transactions() { + let deposit_then_legacy = OpPayloadAttributes { + transactions: Some(vec![ + vec![OpTxType::Deposit as u8, 0xaa].into(), + vec![OpTxType::Legacy as u8, 0xbb].into(), + ]), + ..OpPayloadAttributes::default() + }; + let op = + OpAttributesWithParent::new(deposit_then_legacy, L2BlockInfo::default(), None, true); + assert!(!op.is_deposits_only()); + + let two_deposits = OpPayloadAttributes { + transactions: Some(vec![ + vec![OpTxType::Deposit as u8, 0x01].into(), + vec![OpTxType::Deposit as u8, 0x02].into(), + ]), + ..OpPayloadAttributes::default() + }; + assert!( + OpAttributesWithParent::new(two_deposits, L2BlockInfo::default(), None, true) + .is_deposits_only() + ); + } + /// Test that the [`OpAttributesWithParent::as_deposits_only`] method strips out all /// transactions that are not deposits. #[test] diff --git a/rust/kona/crates/protocol/protocol/src/batch/core.rs b/rust/kona/crates/protocol/protocol/src/batch/core.rs index 7ac31bb08ac..0ebb9a50513 100644 --- a/rust/kona/crates/protocol/protocol/src/batch/core.rs +++ b/rust/kona/crates/protocol/protocol/src/batch/core.rs @@ -42,7 +42,7 @@ impl Batch { } // Read the batch type - let batch_type = BatchType::from(r[0]); + let batch_type = BatchType::try_from(r[0]).map_err(BatchDecodingError::UnknownBatchType)?; r.advance(1); match batch_type { @@ -131,6 +131,13 @@ mod tests { }), decoded); } + #[test] + fn test_unknown_batch_type_returns_error() { + let data = [0xFF, 0x00]; // unknown batch type 0xFF followed by dummy data + let result = Batch::decode(&mut data.as_slice(), &RollupConfig::default()); + assert_eq!(result, Err(BatchDecodingError::UnknownBatchType(0xFF))); + } + #[test] fn test_empty_span_batch() { let mut out = Vec::new(); diff --git a/rust/kona/crates/protocol/protocol/src/batch/errors.rs b/rust/kona/crates/protocol/protocol/src/batch/errors.rs index 33f621af5de..3dcb1912c30 100644 --- a/rust/kona/crates/protocol/protocol/src/batch/errors.rs +++ b/rust/kona/crates/protocol/protocol/src/batch/errors.rs @@ -40,6 +40,9 @@ pub enum BatchDecodingError { /// Empty buffer #[error("Empty buffer")] EmptyBuffer, + /// Unknown batch type + #[error("Unknown batch type: {0}")] + UnknownBatchType(u8), /// Error decoding an Alloy RLP #[error("Error decoding an Alloy RLP: {0}")] AlloyRlpError(alloy_rlp::Error), @@ -72,6 +75,9 @@ pub enum SpanDecodingError { /// Failed to decode transaction nonces #[error("Failed to decode transaction nonces")] TxNonces, + /// Failed to decode transaction gas limits + #[error("Failed to decode transaction gas limits")] + TxGases, /// Mismatch in length between the transaction type and signature arrays in a span batch /// transaction payload. #[error("Mismatch in length between the transaction type and signature arrays")] diff --git a/rust/kona/crates/protocol/protocol/src/batch/reader.rs b/rust/kona/crates/protocol/protocol/src/batch/reader.rs index 25582968b4f..23ffabb7a20 100644 --- a/rust/kona/crates/protocol/protocol/src/batch/reader.rs +++ b/rust/kona/crates/protocol/protocol/src/batch/reader.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use alloy_primitives::Bytes; use alloy_rlp::Decodable; use kona_genesis::RollupConfig; -use miniz_oxide::inflate::decompress_to_vec_zlib; +use miniz_oxide::inflate::{TINFLStatus, decompress_to_vec_zlib_with_limit}; /// Error type for decompression failures. #[derive(Debug, thiserror::Error)] @@ -22,13 +22,10 @@ pub enum DecompressionError { /// A zlib decompression error. #[error("zlib decompression error")] ZlibError, - /// The RLP data is too large for the configured maximum. - #[error("the RLP data is too large: {0} bytes, maximum allowed: {1} bytes")] - RlpTooLarge(usize, usize), } /// Batch Reader provides a function that iteratively consumes batches from the reader. -/// The `L1Inclusion` block is also provided at creation time. +/// The L1 origin timestamp is provided at creation time and used for hardfork activation checks. /// Warning: the batch reader can read every batch-type. /// The caller of the batch-reader should filter the results. #[derive(Debug)] @@ -43,6 +40,8 @@ pub struct BatchReader { pub max_rlp_bytes_per_channel: usize, /// Whether brotli decompression was used. pub brotli_used: bool, + /// The L1 origin block timestamp, used for hardfork activation checks. + pub origin_timestamp: u64, } impl BatchReader { @@ -55,9 +54,9 @@ impl BatchReader { /// Brotli Compression Channel Version. pub const CHANNEL_VERSION_BROTLI: u8 = 1; - /// Creates a new [`BatchReader`] from the given data and max decompressed RLP bytes per - /// channel. - pub fn new(data: T, max_rlp_bytes_per_channel: usize) -> Self + /// Creates a new [`BatchReader`] from the given data, max decompressed RLP bytes per + /// channel, and the L1 origin block timestamp (used for hardfork activation checks). + pub fn new(data: T, max_rlp_bytes_per_channel: usize, origin_timestamp: u64) -> Self where T: Into>, { @@ -67,41 +66,64 @@ impl BatchReader { cursor: 0, max_rlp_bytes_per_channel, brotli_used: false, + origin_timestamp, } } /// Helper method to decompress the data contained in the reader. + /// No-op if the data has already been decompressed. pub fn decompress(&mut self) -> Result<(), DecompressionError> { - if let Some(data) = self.data.take() { - // Peek at the data to determine the compression type. - if data.is_empty() { - return Err(DecompressionError::EmptyData); + if !self.decompressed.is_empty() { + return Ok(()); + } + match self.data.take() { + None => Err(DecompressionError::EmptyData), + Some(data) if data.is_empty() => Err(DecompressionError::EmptyData), + Some(data) => { + // Peek at the data to determine the compression type. + let compression_type = data[0]; + if (compression_type & 0x0F) == Self::ZLIB_DEFLATE_COMPRESSION_METHOD || + (compression_type & 0x0F) == Self::ZLIB_RESERVED_COMPRESSION_METHOD + { + self.decompress_zlib(data) + } else if compression_type == Self::CHANNEL_VERSION_BROTLI { + self.decompress_brotli(data) + } else { + Err(DecompressionError::UnsupportedType(compression_type)) + } } + } + } - let compression_type = data[0]; - if (compression_type & 0x0F) == Self::ZLIB_DEFLATE_COMPRESSION_METHOD || - (compression_type & 0x0F) == Self::ZLIB_RESERVED_COMPRESSION_METHOD - { - self.decompressed = - decompress_to_vec_zlib(&data).map_err(|_| DecompressionError::ZlibError)?; - - // Check the size of the decompressed channel RLP. - if self.decompressed.len() > self.max_rlp_bytes_per_channel { - return Err(DecompressionError::RlpTooLarge( - self.decompressed.len(), - self.max_rlp_bytes_per_channel, - )); - } - } else if compression_type == Self::CHANNEL_VERSION_BROTLI { - self.brotli_used = true; - self.decompressed = decompress_brotli(&data[1..], self.max_rlp_bytes_per_channel)?; - } else { - return Err(DecompressionError::UnsupportedType(compression_type)); + fn decompress_zlib(&mut self, data: Vec) -> Result<(), DecompressionError> { + // Decompress with a limit to prevent zip-bomb attacks. + // Per spec, if decompressed data exceeds the limit, the output is + // truncated to max_rlp_bytes_per_channel bytes (not rejected). + match decompress_to_vec_zlib_with_limit(&data, self.max_rlp_bytes_per_channel) { + Ok(decompressed) => { + self.decompressed = decompressed; + } + Err(e) if (e.status == TINFLStatus::HasMoreOutput || !e.output.is_empty()) => { + // Either: limit reached — truncate per spec and keep partial output. + // Or: decompression error with partial output — keep it so + // batches decoded before the error point are accepted. + self.decompressed = e.output; + } + Err(_) => { + return Err(DecompressionError::ZlibError); } } Ok(()) } + fn decompress_brotli(&mut self, data: Vec) -> Result<(), DecompressionError> { + self.brotli_used = true; + // Note: the first byte of the channel data is the Brotli channel version but not part of + // the compressed data, so it's skipped here but not for zlib. + self.decompressed = decompress_brotli(&data[1..], self.max_rlp_bytes_per_channel)?; + Ok(()) + } + /// Pulls out the next batch from the reader. pub fn next_batch(&mut self, cfg: &RollupConfig) -> Option { // Ensure the data is decompressed. @@ -114,8 +136,8 @@ impl BatchReader { return None; }; - // Confirm that brotli decompression was performed *after* the Fjord hardfork. - if self.brotli_used && !cfg.is_fjord_active(batch.timestamp()) { + // Accept brotli only after Fjord activation (per L1 origin timestamp). + if self.brotli_used && !cfg.is_fjord_active(self.origin_timestamp) { return None; } @@ -131,6 +153,10 @@ mod test { use kona_genesis::{ HardForkConfig, MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, MAX_RLP_BYTES_PER_CHANNEL_FJORD, }; + use miniz_oxide::{ + deflate::{CompressionLevel, compress_to_vec_zlib}, + inflate::decompress_to_vec_zlib, + }; fn new_compressed_batch_data() -> Bytes { let file_contents = @@ -144,7 +170,7 @@ mod test { fn test_batch_reader() { let raw = new_compressed_batch_data(); let decompressed_len = decompress_to_vec_zlib(&raw).unwrap().len(); - let mut reader = BatchReader::new(raw, MAX_RLP_BYTES_PER_CHANNEL_BEDROCK as usize); + let mut reader = BatchReader::new(raw, MAX_RLP_BYTES_PER_CHANNEL_BEDROCK as usize, 0); reader.next_batch(&RollupConfig::default()).unwrap(); assert_eq!(reader.cursor, decompressed_len); } @@ -153,7 +179,7 @@ mod test { fn test_batch_reader_fjord() { let raw = new_compressed_batch_data(); let decompressed_len = decompress_to_vec_zlib(&raw).unwrap().len(); - let mut reader = BatchReader::new(raw, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize); + let mut reader = BatchReader::new(raw, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize, 0); reader .next_batch(&RollupConfig { hardforks: HardForkConfig { fjord_time: Some(0), ..Default::default() }, @@ -162,4 +188,64 @@ mod test { .unwrap(); assert_eq!(reader.cursor, decompressed_len); } + + /// Builds zlib-compressed channel data containing `n` copies of the same + /// batch by duplicating the decompressed RLP content from the test fixture. + fn new_multi_batch_compressed_data(n: usize) -> (Bytes, usize) { + let raw = new_compressed_batch_data(); + let single = decompress_to_vec_zlib(&raw).unwrap(); + + let mut multi = Vec::with_capacity(single.len() * n); + for _ in 0..n { + multi.extend_from_slice(&single); + } + let decompressed_len = multi.len(); + (compress_to_vec_zlib(&multi, CompressionLevel::BestSpeed.into()).into(), decompressed_len) + } + + #[test] + fn test_zlib_truncation_instead_of_rejection() { + let raw = new_compressed_batch_data(); + let decompressed_len = decompress_to_vec_zlib(&raw).unwrap().len(); + assert!(decompressed_len > 1, "test data must decompress to >1 byte"); + + // Set limit below decompressed size — should truncate, not error. + let limit = decompressed_len / 2; + let mut reader = BatchReader::new(raw, limit, 0); + assert!(reader.decompress().is_ok()); + assert_eq!(reader.decompressed.len(), limit); + } + + #[test] + fn test_zlib_truncation_yields_decodable_batches() { + let n = 3; + let (compressed, full_len) = new_multi_batch_compressed_data(n); + let single_batch_len = full_len / n; + + // Full decompression should yield all n batches. + let mut reader = BatchReader::new(compressed.clone(), full_len, 0); + let mut count = 0; + while reader.next_batch(&RollupConfig::default()).is_some() { + count += 1; + } + assert_eq!(count, n, "should decode {n} batches from full channel"); + + // Truncate to just under the last batch — should yield n-1 batches. + let limit = full_len - 1; + let mut reader = BatchReader::new(compressed, limit, 0); + let mut count = 0; + while reader.next_batch(&RollupConfig::default()).is_some() { + count += 1; + } + assert_eq!( + count, + n - 1, + "truncated channel should yield {exp} batches (cursor at {cursor}, \ + single batch is {single_batch_len} bytes, limit {limit})", + exp = n - 1, + cursor = reader.cursor, + ); + // First n-1 batches should have been fully consumed. + assert_eq!(reader.cursor, single_batch_len * (n - 1)); + } } diff --git a/rust/kona/crates/protocol/protocol/src/batch/transactions.rs b/rust/kona/crates/protocol/protocol/src/batch/transactions.rs index 81f638b582c..fa3738d9710 100644 --- a/rust/kona/crates/protocol/protocol/src/batch/transactions.rs +++ b/rust/kona/crates/protocol/protocol/src/batch/transactions.rs @@ -185,7 +185,7 @@ impl SpanBatchTransactions { let mut gases = Vec::with_capacity(self.total_block_tx_count as usize); for _ in 0..self.total_block_tx_count { let (gas, remaining) = unsigned_varint::decode::u64(r) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::TxNonces))?; + .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::TxGases))?; gases.push(gas); *r = remaining; } @@ -406,6 +406,13 @@ mod tests { ); } + #[test] + fn test_decode_tx_gases_truncated() { + let mut txs = SpanBatchTransactions { total_block_tx_count: 1, ..Default::default() }; + let result = txs.decode_tx_gases(&mut [].as_slice()); + assert_eq!(result, Err(SpanBatchError::Decoding(SpanDecodingError::TxGases))); + } + #[test] fn test_span_batch_transactions_add_empty_txs() { let mut span_batch_txs = SpanBatchTransactions::default(); diff --git a/rust/kona/crates/protocol/protocol/src/batch/type.rs b/rust/kona/crates/protocol/protocol/src/batch/type.rs index 9bb9a481d9c..7ce2cacf96e 100644 --- a/rust/kona/crates/protocol/protocol/src/batch/type.rs +++ b/rust/kona/crates/protocol/protocol/src/batch/type.rs @@ -28,12 +28,14 @@ pub enum BatchType { Span = SPAN_BATCH_TYPE, } -impl From for BatchType { - fn from(val: u8) -> Self { +impl TryFrom for BatchType { + type Error = u8; + + fn try_from(val: u8) -> Result { match val { - SINGLE_BATCH_TYPE => Self::Single, - SPAN_BATCH_TYPE => Self::Span, - _ => panic!("Invalid batch type: {val}"), + SINGLE_BATCH_TYPE => Ok(Self::Single), + SPAN_BATCH_TYPE => Ok(Self::Span), + _ => Err(val), } } } @@ -51,7 +53,7 @@ impl Encodable for BatchType { impl Decodable for BatchType { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { let val = u8::decode(buf)?; - Ok(Self::from(val)) + Self::try_from(val).map_err(|_| alloy_rlp::Error::Custom("invalid batch type")) } } @@ -68,4 +70,25 @@ mod test { let decoded = BatchType::decode(&mut buf.as_slice()).unwrap(); assert_eq!(batch_type, decoded); } + + #[test] + fn test_try_from_valid_types() { + assert_eq!(BatchType::try_from(SINGLE_BATCH_TYPE), Ok(BatchType::Single)); + assert_eq!(BatchType::try_from(SPAN_BATCH_TYPE), Ok(BatchType::Span)); + } + + #[test] + fn test_try_from_unknown_type_returns_error() { + assert_eq!(BatchType::try_from(0xFF), Err(0xFF)); + assert_eq!(BatchType::try_from(0x02), Err(0x02)); + } + + #[test] + fn test_rlp_decode_unknown_type_returns_error() { + let mut buf = Vec::new(); + // RLP-encode an invalid batch type byte + 0xFFu8.encode(&mut buf); + let result = BatchType::decode(&mut buf.as_slice()); + assert!(result.is_err()); + } } diff --git a/rust/kona/crates/protocol/protocol/src/block.rs b/rust/kona/crates/protocol/protocol/src/block.rs index 832b6e1173f..b27092b6a7e 100644 --- a/rust/kona/crates/protocol/protocol/src/block.rs +++ b/rust/kona/crates/protocol/protocol/src/block.rs @@ -294,6 +294,7 @@ mod tests { block_number: Some(1), effective_gas_price: Some(1), transaction_index: Some(0), + block_timestamp: None, }; let block: alloy_rpc_types_eth::Block = alloy_rpc_types_eth::Block { diff --git a/rust/kona/crates/protocol/protocol/src/brotli.rs b/rust/kona/crates/protocol/protocol/src/brotli.rs index ea5d0c43358..36f49e6eedd 100644 --- a/rust/kona/crates/protocol/protocol/src/brotli.rs +++ b/rust/kona/crates/protocol/protocol/src/brotli.rs @@ -2,17 +2,15 @@ use alloc::{vec, vec::Vec}; use alloc_no_stdlib::*; -use brotli::*; +use brotli::{BrotliResult, *}; use core::ops; -use crate::MAX_SPAN_BATCH_ELEMENTS; - -/// A frame decompression error. -#[derive(thiserror::Error, Debug, PartialEq, Eq)] +/// A brotli decompression error. +#[derive(thiserror::Error, Debug)] pub enum BrotliDecompressionError { - /// The buffer exceeds the [`MAX_SPAN_BATCH_ELEMENTS`] protocol parameter. - #[error("The batch exceeds the maximum number of elements: {max_size}", max_size = MAX_SPAN_BATCH_ELEMENTS)] - BatchTooLarge, + /// Brotli decompression failed due to corrupt or invalid data. + #[error("brotli decompression failed: {0:?}")] + DecompressionFailed(BrotliResult), } /// Decompresses the given bytes data using the Brotli decompressor implemented @@ -32,17 +30,21 @@ pub fn decompress_brotli( let hc_allocator = MemPool::::new_allocator(&mut hc_buffer, bzero); let mut brotli_state = BrotliState::new(u8_allocator, u32_allocator, hc_allocator); - // Setup the decompressor inputs and outputs - let mut output = vec![0; data.len()]; + // Setup the decompressor inputs and outputs. + // Cap initial buffer at the limit to prevent over-allocation. + let mut output = vec![0; core::cmp::min(data.len(), max_rlp_bytes_per_channel)]; let mut available_in = data.len(); let mut input_offset = 0; let mut available_out = output.len(); let mut output_offset = 0; let mut written = 0; - // Decompress the data stream until success or failure - while matches!( - brotli::BrotliDecompressStream( + // Decompress the data stream until success or failure. + // The output buffer is grown as needed, capped at max_rlp_bytes_per_channel. + // Per spec, if decompressed data exceeds the limit, the output is truncated + // to max_rlp_bytes_per_channel bytes (not rejected). + loop { + let result = brotli::BrotliDecompressStream( &mut available_in, &mut input_offset, data, @@ -51,25 +53,39 @@ pub fn decompress_brotli( &mut output, &mut written, &mut brotli_state, - ), - brotli::BrotliResult::NeedsMoreOutput - ) { - // Resize the output buffer to double the size, following standard - // practice for buffer resizing in streams. + ); let old_len = output.len(); - let new_len = old_len * 2; - if new_len > max_rlp_bytes_per_channel { - return Err(BrotliDecompressionError::BatchTooLarge); + // `NeedsMoreOutput` means the buffer is full (`available_out == 0`). + // `NeedsMoreInput` is normally raised when input is exhausted while + // there's still output space — but when `available_in` and + // `available_out` both reach 0 in the same call, brotli returns + // `NeedsMoreInput` with priority, even though it could produce more + // output given more buffer space. Treat both cases identically: at the + // size cap, stop (spec truncation); otherwise grow the buffer and + // continue. + match result { + BrotliResult::NeedsMoreOutput | BrotliResult::NeedsMoreInput + if available_out == 0 && old_len >= max_rlp_bytes_per_channel => + { + break; + } + BrotliResult::NeedsMoreOutput | BrotliResult::NeedsMoreInput if available_out == 0 => { + let new_len = core::cmp::min((old_len * 2).max(1), max_rlp_bytes_per_channel); + output.resize(new_len, 0); + available_out += new_len - old_len; + } + // No output: error. + _ if written == 0 => { + return Err(BrotliDecompressionError::DecompressionFailed(result)); + } + // Success, NeedsMoreInput with output space remaining, or + // ResultFailure with some bytes written: return what we have. + _ => break, } - - output.resize(new_len, 0); - available_out += old_len; } - // Truncate the output buffer to the written bytes output.truncate(written); - Ok(output) } @@ -102,4 +118,115 @@ mod test { decompress_brotli(&raw_batch, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize).unwrap(); assert_eq!(decompressed, raw_batch_decompressed); } + + #[test] + fn test_brotli_truncation_instead_of_rejection() { + // Use the small test data to verify truncation behavior. + let expected = hex!("75ed184249e9bc19675e"); + let compressed = hex!("8b048075ed184249e9bc19675e03"); + let full_len = expected.len(); + + // Full limit — should decompress fully. + let decompressed = decompress_brotli(&compressed, full_len).unwrap(); + assert_eq!(decompressed, expected); + + // Limit smaller than data — should truncate, not error. + let limit = full_len / 2; + let decompressed = decompress_brotli(&compressed, limit).unwrap(); + assert!( + decompressed.len() <= limit, + "truncated output ({}) should not exceed limit ({})", + decompressed.len(), + limit + ); + } + + #[test] + fn test_decompress_truncated_matches_streaming_reader() { + // Regression test for a buffer-exhaustion bug where `decompress_brotli` + // returned fewer output bytes than the underlying brotli decoder could + // produce. + // + // When `available_in` and `available_out` reach 0 in the same iteration, + // `BrotliDecompressStream` returns `NeedsMoreInput` (priority over + // `NeedsMoreOutput`). The previous loop treated that as "done" without + // first growing the output buffer, so brotli never got a chance to flush + // bytes it could have produced from internal state. Go's + // `brotli.NewReader` doesn't have this issue (its own bufio indirection + // re-reads and provides more output space on the next call), so the bug + // manifested as a Go vs Rust derivation divergence on truncated brotli + // channels. + // + // Compare kona's output to the brotli crate's high-level Reader API on a + // truncated stream: the byte counts must match. + use std::io::Read; + + let data: Vec = (0..2000).map(|i| ((i * 7) % 256) as u8).collect(); + let compressed = { + let params = brotli::enc::BrotliEncoderParams::default(); + let mut output = alloc::vec::Vec::new(); + let mut input = &data[..]; + brotli::BrotliCompress(&mut input, &mut output, ¶ms).unwrap(); + output + }; + assert!(compressed.len() < data.len(), "brotli should compress this"); + + // Sweep truncations across the compressed stream. The bug only fires when + // brotli's output fills the (input-sized) initial buffer at the moment + // input is exhausted, so we need to try multiple offsets to find one + // that triggers it. + let mut any_partial = false; + for trunc_len in (1..compressed.len()).step_by(3) { + let truncated = &compressed[..trunc_len]; + + // Reference: the brotli crate's high-level Reader on the same input. + // It writes partial bytes to the output Vec even when it ultimately + // surfaces an error — that's the canonical "streaming decoder" + // output, matching Go's brotli.NewReader byte-for-byte. + let mut reader_out = alloc::vec::Vec::new(); + let mut reader = brotli::Decompressor::new(truncated, 4096); + let _ = reader.read_to_end(&mut reader_out); + + let kona_out = decompress_brotli(truncated, MAX_RLP_BYTES_PER_CHANNEL_FJORD as usize) + .unwrap_or_default(); + + if !reader_out.is_empty() { + any_partial = true; + } + assert_eq!( + kona_out, reader_out, + "decompress_brotli must match the streaming Reader at truncation len {trunc_len}", + ); + } + assert!(any_partial, "test fixture should produce at least one non-empty partial decode"); + } + + #[test] + fn test_brotli_buffer_doubling_regression() { + // Regression test for the buffer-doubling bug: the old code doubled the + // output buffer and rejected if the doubled size exceeded the limit, + // even if the actual decompressed data fit within the limit. + // + // Example: 100 bytes of data, initial buffer = compressed.len() (small), + // buffer doubles past 100 -> old code returns BatchTooLarge. + let data = vec![0xAA; 100]; + let compressed = { + let params = brotli::enc::BrotliEncoderParams::default(); + let mut output = alloc::vec::Vec::new(); + let mut input = &data[..]; + brotli::BrotliCompress(&mut input, &mut output, ¶ms).unwrap(); + output + }; + + // Limit = exact decompressed size. The old code would error because + // internal buffer doubling would overshoot. + let result = decompress_brotli(&compressed, data.len()); + assert!(result.is_ok(), "decompression at exact limit should succeed"); + assert_eq!(result.unwrap(), data); + + // Limit = data.len() + 1 — also succeeds. + let result = decompress_brotli(&compressed, data.len() + 1); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), data); + } } diff --git a/rust/kona/crates/protocol/protocol/src/channel.rs b/rust/kona/crates/protocol/protocol/src/channel.rs index 65e53b1e7aa..52eb39f5cef 100644 --- a/rust/kona/crates/protocol/protocol/src/channel.rs +++ b/rust/kona/crates/protocol/protocol/src/channel.rs @@ -34,6 +34,14 @@ pub enum ChannelError { /// The frame number is beyond the end frame. #[error("Frame number {0} is beyond end frame")] FrameBeyondEndFrame(usize), + /// The frame is out of order. + #[error("Frame out of order: expected {expected}, got {got}")] + FrameOutOfOrder { + /// The expected frame number. + expected: u16, + /// The actual frame number. + got: u16, + }, } /// A Channel is a set of batches that are split into at least one, but possibly multiple frames. diff --git a/rust/kona/crates/protocol/protocol/src/frame.rs b/rust/kona/crates/protocol/protocol/src/frame.rs index 2f19367b247..ccbeab5fd8a 100644 --- a/rust/kona/crates/protocol/protocol/src/frame.rs +++ b/rust/kona/crates/protocol/protocol/src/frame.rs @@ -56,6 +56,19 @@ pub const FRAME_OVERHEAD: usize = 200; /// - **Memory constraints**: Avoid excessive memory usage during processing pub const MAX_FRAME_LEN: usize = 1_000_000; +/// Base frame length of a frame without its data. +/// +/// ```text +/// frame = channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last +/// +/// channel_id = bytes16 +/// frame_number = uint16 +/// frame_data_length = uint32 +/// frame_data = bytes +/// is_last = bool +/// ``` +pub const BASE_FRAME_LEN: usize = 16 + 2 + 4 + 1; + /// A frame decoding error. #[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq, Hash)] pub enum FrameDecodingError { @@ -74,6 +87,9 @@ pub enum FrameDecodingError { /// Error decoding the frame data length. #[error("Invalid frame data length")] InvalidDataLength, + /// The `is_last` byte was neither 0 nor 1. + #[error("Invalid is_last byte: {0}")] + InvalidIsLast(u8), } /// Frame parsing error. @@ -178,7 +194,7 @@ impl Frame { /// Encode the frame into a byte vector. pub fn encode(&self) -> Vec { - let mut encoded = Vec::with_capacity(16 + 2 + 4 + self.data.len() + 1); + let mut encoded = Vec::with_capacity(BASE_FRAME_LEN + self.data.len()); encoded.extend_from_slice(&self.id); encoded.extend_from_slice(&self.number.to_be_bytes()); encoded.extend_from_slice(&(self.data.len() as u32).to_be_bytes()); @@ -189,26 +205,39 @@ impl Frame { /// Decode a frame from a byte vector. pub fn decode(encoded: &[u8]) -> Result<(usize, Self), FrameDecodingError> { - const BASE_FRAME_LEN: usize = 16 + 2 + 4 + 1; + const ID_LEN: usize = 16; // bytes16 + const FRAME_NUM_START: usize = 16; + const FRAME_NUM_LEN: usize = 2; // uint16 + const DATA_LENGTH_START: usize = 16 + 2; + const DATA_LENGTH_LEN: usize = 4; // uint32 + const DATA_START: usize = 16 + 2 + 4; if encoded.len() < BASE_FRAME_LEN { return Err(FrameDecodingError::DataTooShort(encoded.len())); } - let id = encoded[..16].try_into().map_err(|_| FrameDecodingError::InvalidId)?; + let id = encoded[..ID_LEN].try_into().map_err(|_| FrameDecodingError::InvalidId)?; let number = u16::from_be_bytes( - encoded[16..18].try_into().map_err(|_| FrameDecodingError::InvalidNumber)?, + encoded[FRAME_NUM_START..FRAME_NUM_START + FRAME_NUM_LEN] + .try_into() + .map_err(|_| FrameDecodingError::InvalidNumber)?, ); let data_len = u32::from_be_bytes( - encoded[18..22].try_into().map_err(|_| FrameDecodingError::InvalidDataLength)?, + encoded[DATA_LENGTH_START..DATA_LENGTH_START + DATA_LENGTH_LEN] + .try_into() + .map_err(|_| FrameDecodingError::InvalidDataLength)?, ) as usize; - if data_len > MAX_FRAME_LEN || data_len >= encoded.len() - (BASE_FRAME_LEN - 1) { + if data_len > MAX_FRAME_LEN || data_len > encoded.len() - BASE_FRAME_LEN { return Err(FrameDecodingError::DataTooLarge(data_len)); } - let data = encoded[22..22 + data_len].to_vec(); - let is_last = encoded[22 + data_len] == 1; + let data = encoded[DATA_START..DATA_START + data_len].to_vec(); + let is_last = match encoded[DATA_START + data_len] { + 0 => false, + 1 => true, + b => return Err(FrameDecodingError::InvalidIsLast(b)), + }; Ok((BASE_FRAME_LEN + data_len, Self { id, number, data, is_last })) } @@ -311,6 +340,29 @@ mod test { assert_eq!(frame, frame_decoded); } + #[test] + fn test_decode_invalid_is_last() { + // Mirrors op-node's TestFrameUnmarshalInvalidIsLast: any byte other than 0 or 1 + // for `is_last` must be rejected, per the derivation spec. + let frame = Frame { id: [0xFF; 16], number: 0xEE, data: vec![0xDD; 16], is_last: true }; + let mut encoded = frame.encode(); + let last = encoded.len() - 1; + + encoded[last] = 2; + let err = Frame::decode(&encoded).unwrap_err(); + assert_eq!(err, FrameDecodingError::InvalidIsLast(2)); + + encoded[last] = u8::MAX; + let err = Frame::decode(&encoded).unwrap_err(); + assert_eq!(err, FrameDecodingError::InvalidIsLast(u8::MAX)); + + // is_last = 0 and 1 must still decode correctly. + encoded[last] = 0; + assert!(!Frame::decode(&encoded).unwrap().1.is_last); + encoded[last] = 1; + assert!(Frame::decode(&encoded).unwrap().1.is_last); + } + #[test] fn test_decode_many() { let frame = Frame { id: [0xFF; 16], number: 0xEE, data: vec![0xDD; 50], is_last: true }; diff --git a/rust/kona/crates/protocol/protocol/src/lib.rs b/rust/kona/crates/protocol/protocol/src/lib.rs index 7a8c05facc6..c7ecf91b75d 100644 --- a/rust/kona/crates/protocol/protocol/src/lib.rs +++ b/rust/kona/crates/protocol/protocol/src/lib.rs @@ -37,7 +37,8 @@ pub use block::{BlockInfo, FromBlockError, L2BlockInfo}; mod frame; pub use frame::{ - DERIVATION_VERSION_0, FRAME_OVERHEAD, Frame, FrameDecodingError, FrameParseError, MAX_FRAME_LEN, + BASE_FRAME_LEN, DERIVATION_VERSION_0, FRAME_OVERHEAD, Frame, FrameDecodingError, + FrameParseError, MAX_FRAME_LEN, }; mod utils; @@ -49,6 +50,9 @@ pub use channel::{ MAX_RLP_BYTES_PER_CHANNEL, }; +mod ordered_channel; +pub use ordered_channel::{OrderedChannel, ReadError}; + mod deposits; pub use deposits::{ DEPOSIT_EVENT_ABI, DEPOSIT_EVENT_ABI_HASH, DEPOSIT_EVENT_VERSION_0, DepositError, diff --git a/rust/kona/crates/protocol/protocol/src/ordered_channel.rs b/rust/kona/crates/protocol/protocol/src/ordered_channel.rs new file mode 100644 index 00000000000..07ead660af5 --- /dev/null +++ b/rust/kona/crates/protocol/protocol/src/ordered_channel.rs @@ -0,0 +1,285 @@ +//! Ordered Channel Type +//! +//! An [`OrderedChannel`] enforces strict sequential frame ordering, rejecting any frame whose +//! number does not match the expected next frame. This is the post-Holocene channel type, matching +//! `op-node`'s `requireInOrder` behavior. + +use alloc::vec::Vec; +use alloy_primitives::Bytes; + +use crate::{BlockInfo, ChannelError, ChannelId, Frame}; + +/// An error returned when reading data from an [`OrderedChannel`]. +#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ReadError { + /// The channel is not ready (not all frames have been received). + #[error("Channel is not ready")] + NotReady, + /// The channel has no frames. + #[error("Channel is empty")] + Empty, +} + +/// An ordered channel that enforces strict sequential frame ingestion. +/// +/// Unlike [`Channel`], which accepts frames out of order and checks contiguity at read time, +/// `OrderedChannel` rejects any frame whose number does not equal the current frame count. +/// This matches `op-node`'s Holocene behavior where `requireInOrder` is true. +/// +/// [`Channel`]: crate::Channel +#[derive(Debug, Clone)] +pub struct OrderedChannel { + /// The unique identifier for this channel. + pub id: ChannelId, + /// The block that the channel was opened at. + pub open_block: BlockInfo, + /// Estimated memory size, used to drop the channel if we have too much data. + pub estimated_size: usize, + /// True if the last frame has been buffered. + pub closed: bool, + /// Frames stored in sequential order. + pub inputs: Vec, + /// The highest L1 inclusion block that a frame was included in. + pub highest_l1_inclusion_block: BlockInfo, +} + +impl OrderedChannel { + /// Create a new [`OrderedChannel`] with the given [`ChannelId`] and [`BlockInfo`]. + pub fn new(id: ChannelId, open_block: BlockInfo) -> Self { + Self { + id, + open_block, + estimated_size: 0, + closed: false, + inputs: Vec::new(), + highest_l1_inclusion_block: BlockInfo::default(), + } + } + + /// Returns the [`ChannelId`]. + pub const fn id(&self) -> ChannelId { + self.id + } + + /// Returns the number of frames ingested. + pub const fn len(&self) -> usize { + self.inputs.len() + } + + /// Returns if the channel is empty. + pub const fn is_empty(&self) -> bool { + self.inputs.is_empty() + } + + /// Returns the block number of the L1 block that contained the first [`Frame`]. + pub const fn open_block_number(&self) -> u64 { + self.open_block.number + } + + /// Returns the estimated size of the channel including [`Frame`] overhead. + pub const fn size(&self) -> usize { + self.estimated_size + } + + /// Add a frame to the channel. The frame number must equal the current frame count + /// (strict sequential ordering). + pub fn add_frame( + &mut self, + frame: Frame, + l1_inclusion_block: BlockInfo, + ) -> Result<(), ChannelError> { + if frame.id != self.id { + return Err(ChannelError::FrameIdMismatch); + } + if self.closed { + return Err(ChannelError::ChannelClosed); + } + + let expected = self.inputs.len() as u16; + if frame.number != expected { + return Err(ChannelError::FrameOutOfOrder { expected, got: frame.number }); + } + + if frame.is_last { + self.closed = true; + } + + if self.highest_l1_inclusion_block.number < l1_inclusion_block.number { + self.highest_l1_inclusion_block = l1_inclusion_block; + } + + self.estimated_size += frame.size(); + self.inputs.push(frame); + Ok(()) + } + + /// Returns `true` if the channel is ready to be read. + /// Since frames are ingested in order, the channel is ready as soon as it is closed. + pub const fn is_ready(&self) -> bool { + self.closed + } + + /// Returns all of the channel's [`Frame`] data concatenated together. + /// + /// Returns an error if the channel is empty or not yet ready. + pub fn data(&self) -> Result { + if self.inputs.is_empty() { + return Err(ReadError::Empty); + } + if !self.closed { + return Err(ReadError::NotReady); + } + Ok(self.inputs.iter().flat_map(|f| &f.data).copied().collect::>().into()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use alloc::string::ToString; + + fn test_id() -> ChannelId { + [0xFF; 16] + } + + fn frame(id: ChannelId, number: u16, data: &[u8], is_last: bool) -> Frame { + Frame { id, number, data: data.to_vec(), is_last } + } + + #[test] + fn test_ordered_channel_accessors() { + let id = test_id(); + let block = BlockInfo { number: 42, timestamp: 0, ..Default::default() }; + let channel = OrderedChannel::new(id, block); + + assert_eq!(channel.id(), id); + assert_eq!(channel.open_block_number(), 42); + assert_eq!(channel.size(), 0); + assert_eq!(channel.len(), 0); + assert!(channel.is_empty()); + assert!(!channel.is_ready()); + } + + #[test] + fn test_ordered_frames_accepted() { + let id = test_id(); + let block = BlockInfo::default(); + let mut channel = OrderedChannel::new(id, block); + + assert!(channel.add_frame(frame(id, 0, b"hello", false), block).is_ok()); + assert!(channel.add_frame(frame(id, 1, b"world", true), block).is_ok()); + assert!(channel.is_ready()); + assert_eq!(channel.len(), 2); + assert_eq!(channel.data().unwrap().as_ref(), b"helloworld"); + } + + #[test] + fn test_wrong_channel_id_rejected() { + let id = test_id(); + let block = BlockInfo::default(); + let mut channel = OrderedChannel::new(id, block); + + let err = channel.add_frame(frame([0xEE; 16], 0, b"bad", false), block).unwrap_err(); + assert_eq!(err, ChannelError::FrameIdMismatch); + } + + #[test] + fn test_out_of_order_frame_rejected() { + let id = test_id(); + let block = BlockInfo::default(); + let mut channel = OrderedChannel::new(id, block); + + // Frame 0 succeeds + assert!(channel.add_frame(frame(id, 0, b"first", false), block).is_ok()); + + // Frame 2 (skipping 1) is rejected + let err = channel.add_frame(frame(id, 2, b"skip", false), block).unwrap_err(); + assert_eq!(err, ChannelError::FrameOutOfOrder { expected: 1, got: 2 }); + assert_eq!(channel.len(), 1); + } + + #[test] + fn test_frame_after_close_rejected() { + let id = test_id(); + let block = BlockInfo::default(); + let mut channel = OrderedChannel::new(id, block); + + assert!(channel.add_frame(frame(id, 0, b"only", true), block).is_ok()); + assert!(channel.is_ready()); + + let err = channel.add_frame(frame(id, 1, b"extra", false), block).unwrap_err(); + assert_eq!(err, ChannelError::ChannelClosed); + } + + #[test] + fn test_attack_scenario_cross_tx_out_of_order() { + // Attack from the finding: T1=[F0,F1,F2], T2=[F4,F5,F6(is_last)], T3=[F3] + // OrderedChannel should accept F0-F2 then reject F4 (expected F3). + let id = test_id(); + let block = BlockInfo::default(); + let mut channel = OrderedChannel::new(id, block); + + // T1 frames arrive in order + assert!(channel.add_frame(frame(id, 0, b"f0", false), block).is_ok()); + assert!(channel.add_frame(frame(id, 1, b"f1", false), block).is_ok()); + assert!(channel.add_frame(frame(id, 2, b"f2", false), block).is_ok()); + + // T2 starts at frame 4 — out of order, rejected + let err = channel.add_frame(frame(id, 4, b"f4", false), block).unwrap_err(); + assert_eq!(err, ChannelError::FrameOutOfOrder { expected: 3, got: 4 }); + + // Channel is not ready and not closed + assert!(!channel.is_ready()); + assert_eq!(channel.len(), 3); + } + + #[test] + fn test_single_frame_channel() { + let id = test_id(); + let block = BlockInfo::default(); + let mut channel = OrderedChannel::new(id, block); + + assert!(channel.add_frame(frame(id, 0, b"all", true), block).is_ok()); + assert!(channel.is_ready()); + assert_eq!(channel.data().unwrap().as_ref(), b"all"); + } + + #[test] + fn test_data_empty_channel() { + let id = test_id(); + let block = BlockInfo::default(); + let channel = OrderedChannel::new(id, block); + + assert_eq!(channel.data(), Err(ReadError::Empty)); + } + + #[test] + fn test_data_not_ready() { + let id = test_id(); + let block = BlockInfo::default(); + let mut channel = OrderedChannel::new(id, block); + + assert!(channel.add_frame(frame(id, 0, b"partial", false), block).is_ok()); + assert_eq!(channel.data(), Err(ReadError::NotReady)); + } + + #[test] + fn test_l1_inclusion_block_tracking() { + let id = test_id(); + let block1 = BlockInfo { number: 10, ..Default::default() }; + let block2 = BlockInfo { number: 20, ..Default::default() }; + let mut channel = OrderedChannel::new(id, block1); + + assert!(channel.add_frame(frame(id, 0, b"a", false), block1).is_ok()); + assert_eq!(channel.highest_l1_inclusion_block.number, 10); + + assert!(channel.add_frame(frame(id, 1, b"b", true), block2).is_ok()); + assert_eq!(channel.highest_l1_inclusion_block.number, 20); + } + + #[test] + fn test_error_display() { + let err = ChannelError::FrameOutOfOrder { expected: 3, got: 5 }; + assert_eq!(err.to_string(), "Frame out of order: expected 3, got 5"); + } +} diff --git a/rust/kona/crates/protocol/protocol/src/utils.rs b/rust/kona/crates/protocol/protocol/src/utils.rs index c1c4251f306..cfc27103ddb 100644 --- a/rust/kona/crates/protocol/protocol/src/utils.rs +++ b/rust/kona/crates/protocol/protocol/src/utils.rs @@ -9,7 +9,7 @@ use op_alloy_consensus::{OpBlock, decode_holocene_extra_data, decode_jovian_extr use crate::{ L1BlockInfoBedrockOnlyFields as _, L1BlockInfoEcotoneBaseFields as _, L1BlockInfoTx, - OpBlockConversionError, SpanBatchError, SpanDecodingError, + MAX_SPAN_BATCH_ELEMENTS, OpBlockConversionError, SpanBatchError, SpanDecodingError, }; /// Converts the [`OpBlock`] to a partial [`SystemConfig`]. @@ -116,6 +116,12 @@ pub fn read_tx_data(r: &mut &[u8]) -> Result<(Vec, TxType), SpanBatchError> let tx_payload = if rlp_header.list { // Grab the raw RLP for the transaction data from `r`. It was unaffected since we copied it. let payload_length_with_header = rlp_header.payload_length + rlp_header.length(); + if payload_length_with_header > MAX_SPAN_BATCH_ELEMENTS as usize { + return Err(SpanBatchError::TooBigSpanBatchSize); + } + if r.len() < payload_length_with_header { + return Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)); + } let payload = r[0..payload_length_with_header].to_vec(); r.advance(payload_length_with_header); Ok(payload) @@ -380,4 +386,29 @@ mod tests { }; assert_eq!(config, expected); } + + #[test] + fn test_read_tx_data_truncated_payload() { + // RLP list header claiming 100 bytes of payload, but only 3 bytes actually present. + // 0xf8 0x64 = list header with 1-byte length prefix, payload length 100 + let mut data: &[u8] = &[0xf8, 0x64, 0x00, 0x00, 0x00]; + let err = read_tx_data(&mut data).unwrap_err(); + assert_eq!(err, SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)); + } + + #[test] + fn test_read_tx_data_exceeds_max_span_batch_elements() { + // RLP list header claiming MAX_SPAN_BATCH_ELEMENTS + 1 bytes of payload. + // 0xfa = list with 3-byte length prefix (0xf7 + 3), then 0x989681 = 10_000_001. + // Header::decode validates the claimed length against the buffer, so we must provide + // a buffer large enough for decoding to succeed before the max size check triggers. + let mut data = vec![0u8; MAX_SPAN_BATCH_ELEMENTS as usize + 5]; + data[0] = 0xfa; + data[1] = 0x98; + data[2] = 0x96; + data[3] = 0x81; + let mut slice: &[u8] = &data; + let err = read_tx_data(&mut slice).unwrap_err(); + assert_eq!(err, SpanBatchError::TooBigSpanBatchSize); + } } diff --git a/rust/kona/crates/protocol/registry/Cargo.toml b/rust/kona/crates/protocol/registry/Cargo.toml index ffe2511d58d..71611127e0f 100644 --- a/rust/kona/crates/protocol/registry/Cargo.toml +++ b/rust/kona/crates/protocol/registry/Cargo.toml @@ -47,6 +47,7 @@ alloy-eips.workspace = true [features] default = [] +rollup_config_override = ["kona-genesis/rollup_config_override"] tabled = [ "dep:tabled", "std" ] std = [ "alloy-chains/std", diff --git a/rust/kona/crates/protocol/registry/build.rs b/rust/kona/crates/protocol/registry/build.rs index 5d64bc5b163..ae704b86f85 100644 --- a/rust/kona/crates/protocol/registry/build.rs +++ b/rust/kona/crates/protocol/registry/build.rs @@ -6,11 +6,31 @@ use std::{ path::{Path, PathBuf}, }; -use kona_genesis::{Chain, ChainConfig, ChainList, Superchain, SuperchainConfig, Superchains}; +use kona_genesis::{ + Chain, ChainConfig, ChainList, DependencySet, InteropConfig, Superchain, SuperchainConfig, + Superchains, aggregate_clusters, +}; use serde::de::DeserializeOwned; fn main() { + // The three committed snapshots under `etc/` are `include_str!`d at compile time, + // but `include_str!` does not register file dependencies with cargo. Declare them + // here so a hand-edit, a `KONA_BIND=true` regeneration, or a custom-config merge + // busts the cache instead of silently reusing a stale compilation of `lib.rs`. + println!("cargo:rerun-if-changed=etc/chainList.json"); + println!("cargo:rerun-if-changed=etc/configs.json"); + println!("cargo:rerun-if-changed=etc/depsets.json"); + + let etc_dir = std::path::Path::new("etc"); + if !etc_dir.exists() { + std::fs::create_dir_all(etc_dir).unwrap(); + } + let depsets_path = std::path::Path::new("etc/depsets.json"); + // If the `KONA_BIND` environment variable is _not_ set, then return early. + // The committed `etc/depsets.json` snapshot is the authoritative input in this + // mode; do not touch it. (Custom-config merges, if enabled, additively layer + // on top of the committed snapshot.) let kona_bind: bool = std::env::var("KONA_BIND").unwrap_or_else(|_| "false".to_string()) == "true"; println!("cargo:rerun-if-env-changed=KONA_BIND"); @@ -19,18 +39,32 @@ fn main() { return; } - // Get the directory of this file from the environment - let src_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - - // Check if the `superchain-registry` directory exists - let superchain_registry = format!("{src_dir}/superchain-registry"); + // Reset `etc/depsets.json` to the empty list before re-deriving from the + // superchain-registry submodule, so the file content is deterministic for the + // configured inputs and never carries stale entries from a prior build. + write_depsets(depsets_path, &[]); + + // Resolve the monorepo root via `git rev-parse --show-toplevel` so we don't + // depend on this crate's location inside the workspace. + let repo_root = std::process::Command::new("git") + .args(["rev-parse", "--show-toplevel"]) + .output() + .expect("failed to run `git rev-parse --show-toplevel`"); + assert!(repo_root.status.success(), "`git rev-parse --show-toplevel` failed"); + let repo_root = String::from_utf8(repo_root.stdout).unwrap(); + let repo_root = repo_root.trim_end(); + + // The `superchain-registry` submodule lives under + // `packages/contracts-bedrock/lib/superchain-registry` at the monorepo root. + let superchain_registry = + format!("{repo_root}/packages/contracts-bedrock/lib/superchain-registry"); assert!( std::path::Path::new(&superchain_registry).exists(), "Git Submodule missing. Please run `just source` to initialize the submodule." ); // Copy the `superchain-registry/chainList.json` file to `etc/chainList.json` - let chain_list = format!("{src_dir}/superchain-registry/chainList.json"); + let chain_list = format!("{superchain_registry}/chainList.json"); let etc_dir = std::path::Path::new("etc"); if !etc_dir.exists() { std::fs::create_dir_all(etc_dir).unwrap(); @@ -38,7 +72,7 @@ fn main() { std::fs::copy(chain_list, "etc/chainList.json").unwrap(); // Get the `superchain-registry/superchain/configs` directory` - let configs_dir = format!("{src_dir}/superchain-registry/superchain/configs"); + let configs_dir = format!("{superchain_registry}/superchain/configs"); let configs = std::fs::read_dir(configs_dir).unwrap(); // Get all the directories in the `configs` directory @@ -83,6 +117,21 @@ fn main() { let output_path = std::path::Path::new("etc/configs.json"); std::fs::write(output_path, serde_json::to_string_pretty(&superchains).unwrap()).unwrap(); + + // Aggregate per-cluster `DependencySet`s from each chain's `[interop]` block and + // overwrite `etc/depsets.json` with the resulting list. + let interop_chains: Vec<(u64, &InteropConfig)> = superchains + .superchains + .iter() + .flat_map(|sc| sc.chains.iter()) + .filter_map(|c| c.interop.as_ref().map(|i| (c.chain_id, i))) + .collect(); + let depsets = aggregate_clusters(interop_chains.iter().map(|(id, cfg)| (*id, *cfg))) + .unwrap_or_else(|e| { + panic!("failed to aggregate interop clusters from superchain configs: {e}") + }); + write_depsets(depsets_path, &depsets); + merge_custom_configs(); } @@ -92,13 +141,6 @@ fn merge_custom_configs() { println!("cargo:rerun-if-env-changed=KONA_CUSTOM_CONFIGS"); println!("cargo:rerun-if-env-changed=KONA_CUSTOM_CONFIGS_TEST"); - // if we're running tests, bust the cache if the base etc configs are updated. This ensures that - // the test build can be repeated after modifying the base configs - if std::env::var("KONA_CUSTOM_CONFIGS_TEST") == Ok("true".to_string()) { - println!("cargo:rerun-if-changed=etc/chainList.json"); - println!("cargo:rerun-if-changed=etc/configs.json"); - } - if !kona_custom_configs { return; } @@ -121,12 +163,15 @@ fn merge_custom_configs() { let target_chain_list = Path::new("etc/chainList.json"); let target_superchains = Path::new("etc/configs.json"); + let target_depsets = Path::new("etc/depsets.json"); validate_chain_configs(&custom_chain_list_path, &custom_configs_path); merge_chain_list(&custom_chain_list_path, target_chain_list); merge_superchain_configs(&custom_configs_path, target_superchains); + merge_custom_depsets(&custom_configs_dir, target_depsets); validate_chain_configs(target_chain_list, target_superchains); + validate_depsets(target_depsets, target_chain_list); } fn merge_chain_list(custom_path: &Path, target_path: &Path) { @@ -311,3 +356,52 @@ fn write_pretty_json(path: &Path, value: &T) { ) .unwrap_or_else(|e| panic!("Failed to write {}: {e}", path.display())); } + +fn write_depsets(target: &Path, depsets: &[DependencySet]) { + let json = serde_json::to_string_pretty(depsets) + .unwrap_or_else(|e| panic!("Failed to serialize {}: {e}", target.display())); + fs::write(target, json).unwrap_or_else(|e| panic!("Failed to write {}: {e}", target.display())); +} + +fn merge_custom_depsets(custom_dir: &Path, target: &Path) { + let path = custom_dir.join("depsets.json"); + println!("cargo:rerun-if-changed={}", path.display()); + if !path.exists() { + return; + } + let custom: Vec = read_json(&path); + let mut existing: Vec = read_json(target); + for new_ds in custom { + for existing_ds in &existing { + let collisions: Vec = new_ds + .dependencies + .keys() + .filter(|k| existing_ds.dependencies.contains_key(k)) + .copied() + .collect(); + assert!( + collisions.is_empty() || existing_ds == &new_ds, + "Custom depset overlaps existing cluster on chain ids {collisions:?} but the cluster contents differ" + ); + } + if !existing.iter().any(|d| d == &new_ds) { + existing.push(new_ds); + } + } + write_depsets(target, &existing); +} + +fn validate_depsets(target: &Path, chain_list_path: &Path) { + let depsets: Vec = read_json(target); + let chain_list: ChainList = read_json(chain_list_path); + let known: BTreeSet = chain_list.chains.iter().map(|c| c.chain_id).collect(); + for ds in &depsets { + for id in ds.dependencies.keys() { + assert!( + known.contains(id), + "Depset references chain id {id} which is not in {}", + chain_list_path.display() + ); + } + } +} diff --git a/rust/kona/crates/protocol/registry/etc/chainList.json b/rust/kona/crates/protocol/registry/etc/chainList.json index a3e69f36caa..19bf60643b2 100755 --- a/rust/kona/crates/protocol/registry/etc/chainList.json +++ b/rust/kona/crates/protocol/registry/etc/chainList.json @@ -39,28 +39,7 @@ "chain": "mainnet" }, "faultProofs": { - "status": "permissionless" - } - }, - { - "name": "Base", - "identifier": "mainnet/base", - "chainId": 8453, - "rpc": [ - "https://mainnet.base.org" - ], - "explorers": [ - "https://explorer.base.org" - ], - "superchainLevel": 1, - "governedByOptimism": false, - "dataAvailabilityType": "eth-da", - "parent": { - "type": "L2", - "chain": "mainnet" - }, - "faultProofs": { - "status": "permissionless" + "status": "permissioned" } }, { @@ -103,7 +82,7 @@ "chain": "mainnet" }, "faultProofs": { - "status": "none" + "status": "permissioned" } }, { @@ -166,7 +145,7 @@ "chain": "mainnet" }, "faultProofs": { - "status": "none" + "status": "permissioned" } }, { @@ -464,27 +443,6 @@ "status": "permissioned" } }, - { - "name": "SnaxChain", - "identifier": "mainnet/snax", - "chainId": 2192, - "rpc": [ - "https://mainnet.snaxchain.io" - ], - "explorers": [ - "https://explorer.snaxchain.io" - ], - "superchainLevel": 0, - "governedByOptimism": false, - "dataAvailabilityType": "eth-da", - "parent": { - "type": "L2", - "chain": "mainnet" - }, - "faultProofs": { - "status": "permissioned" - } - }, { "name": "Soneium", "identifier": "mainnet/soneium", @@ -674,27 +632,6 @@ "status": "permissioned" } }, - { - "name": "Base Sepolia Testnet", - "identifier": "sepolia/base", - "chainId": 84532, - "rpc": [ - "https://sepolia.base.org" - ], - "explorers": [ - "https://sepolia-explorer.base.org" - ], - "superchainLevel": 1, - "governedByOptimism": false, - "dataAvailabilityType": "eth-da", - "parent": { - "type": "L2", - "chain": "sepolia" - }, - "faultProofs": { - "status": "permissionless" - } - }, { "name": "Binary Sepolia", "identifier": "sepolia/tbn", @@ -1051,7 +988,7 @@ "chain": "sepolia" }, "faultProofs": { - "status": "permissioned" + "status": "permissionless" } }, { @@ -1117,27 +1054,6 @@ "status": "permissioned" } }, - { - "name": "Base devnet 0", - "identifier": "sepolia-dev-0/base-devnet-0", - "chainId": 11763072, - "rpc": [ - "" - ], - "explorers": [ - "" - ], - "superchainLevel": 0, - "governedByOptimism": false, - "dataAvailabilityType": "eth-da", - "parent": { - "type": "L2", - "chain": "sepolia-dev-0" - }, - "faultProofs": { - "status": "permissionless" - } - }, { "name": "OP Labs Sepolia devnet 0", "identifier": "sepolia-dev-0/oplabs-devnet-0", @@ -1262,7 +1178,7 @@ "chain": "rehearsal-0-bn" }, "faultProofs": { - "status": "permissionless" + "status": "permissioned" } }, { @@ -1283,7 +1199,7 @@ "chain": "rehearsal-0-bn" }, "faultProofs": { - "status": "permissionless" + "status": "permissioned" } }, { @@ -1329,45 +1245,25 @@ } }, { - "name": "test1", - "identifier": "test1/testnet", - "chainId": 123999119, + "name": "Celo Sepolia Testnet", + "identifier": "sepolia/celo-sep", + "chainId": 11142220, "rpc": [ - "https://rpc.test1.com" + "https://forno.celo-sepolia.celo-testnet.org" ], "explorers": [ - "https://explorer.test1.com" + "https://celo-sepolia.blockscout.com" ], - "superchainLevel": 0, - "governedByOptimism": false, - "dataAvailabilityType": "eth-da", - "parent": { - "type": "L2", - "chain": "testnet" - }, - "faultProofs": { - "status": "none" - } - }, - { - "name": "test2", - "identifier": "test2/testnet", - "chainId": 223999119, - "rpc": [ - "https://rpc.test2.com" - ], - "explorers": [ - "https://explorer.test2.com" - ], - "superchainLevel": 0, + "superchainLevel": 2, "governedByOptimism": false, - "dataAvailabilityType": "eth-da", + "dataAvailabilityType": "alt-da", "parent": { "type": "L2", - "chain": "testnet" + "chain": "sepolia" }, + "gasPayingToken": "0x3C7011fD5e6Aed460cAa4985cF8d8Caba435b092", "faultProofs": { - "status": "none" + "status": "permissioned" } } ] \ No newline at end of file diff --git a/rust/kona/crates/protocol/registry/etc/configs.json b/rust/kona/crates/protocol/registry/etc/configs.json index 1100b4484bd..57110372e6b 100644 --- a/rust/kona/crates/protocol/registry/etc/configs.json +++ b/rust/kona/crates/protocol/registry/etc/configs.json @@ -19,7 +19,6 @@ "isthmus_time": 1746806401, "jovian_time": 1764691201 }, - "protocol_versions_addr": "0x8062abc286f5e7d9428a0ccb9abd71e50d93b935", "superchain_config_addr": "0x95703e0982140d16f8eba6d158fccede42f04a4c", "op_contracts_manager_proxy_addr": null }, @@ -887,7 +886,8 @@ "fjord_time": 1721826000, "granite_time": 1727780400, "holocene_time": 1738238400, - "isthmus_time": 1764072000 + "isthmus_time": 1764072000, + "jovian_time": 1777593600 }, "optimism": { "eip1559Elasticity": 10, @@ -1551,91 +1551,6 @@ "DataAvailabilityChallenge": null } }, - { - "Name": "SnaxChain", - "PublicRPC": "https://mainnet.snaxchain.io", - "SequencerRPC": "https://mainnet.snaxchain.io", - "Explorer": "https://explorer.snaxchain.io", - "SuperchainLevel": 0, - "GovernedByOptimism": false, - "SuperchainTime": null, - "DataAvailabilityType": "eth-da", - "l2_chain_id": 2192, - "batch_inbox_address": "0xfec57bd3729a5f930d4ee8ac5992fdc8988426e4", - "block_time": 2, - "seq_window_size": 3600, - "max_sequencer_drift": 600, - "GasPayingToken": null, - "hardfork_configuration": { - "canyon_time": 0, - "delta_time": 0, - "ecotone_time": 0, - "fjord_time": 0, - "granite_time": 1726070401, - "holocene_time": 1736445601, - "isthmus_time": 1746806401 - }, - "optimism": { - "eip1559Elasticity": 6, - "eip1559Denominator": 50, - "eip1559DenominatorCanyon": 250 - }, - "alt_da": null, - "genesis": { - "l1": { - "number": 20520542, - "hash": "0x920dc48c1f037d444cb4dee5c69f41853f469dd9e7751398458126a6f76ecea6" - }, - "l2": { - "number": 0, - "hash": "0x518aadbc56e4ca8b03aa141c13b2fc246a9eae88edea09ee477f3d620b00d5ae" - }, - "l2_time": 1723562231, - "system_config": { - "batcherAddr": "0x060b915ca4904b56ada63565626b9c97f6cad212", - "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc", - "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0", - "gasLimit": 30000000, - "baseFeeScalar": null, - "blobBaseFeeScalar": null, - "eip1559Denominator": null, - "eip1559Elasticity": null, - "operatorFeeScalar": null, - "operatorFeeConstant": null, - "minBaseFee": null, - "daFootprintGasScalar": null - } - }, - "Roles": { - "SystemConfigOwner": null, - "ProxyAdminOwner": "0x4a4962275df8c60a80d3a25faec5aa7de116a746", - "Guardian": null, - "Challenger": null, - "Proposer": null, - "UnsafeBlockSigner": null, - "BatchSubmitter": null - }, - "Addresses": { - "AddressManager": null, - "L1CrossDomainMessengerProxy": null, - "L1Erc721BridgeProxy": null, - "L1StandardBridgeProxy": "0xa5fb68c24b02852e8b514e98a1014faf12547fa5", - "L2OutputOracleProxy": null, - "OptimismMintableErc20FactoryProxy": null, - "OptimismPortalProxy": "0x936d881b4760d5e9b6d55b774f65c509236b4743", - "SystemConfigProxy": "0x9c9b78f798f821c2f6398f603825fd175e2427f9", - "ProxyAdmin": null, - "SuperchainConfig": null, - "AnchorStateRegistryProxy": null, - "DelayedWethProxy": null, - "DisputeGameFactoryProxy": "0x0fd13c7f11d95070ce5cf31baf1acf9355bf4578", - "FaultDisputeGame": null, - "Mips": null, - "PermissionedDisputeGame": null, - "PreimageOracle": null, - "DataAvailabilityChallenge": null - } - }, { "Name": "Superseed", "PublicRPC": "https://mainnet.superseed.xyz", @@ -1904,7 +1819,10 @@ "hardfork_configuration": { "canyon_time": 0, "delta_time": 0, - "ecotone_time": 0 + "ecotone_time": 0, + "fjord_time": 1767754800, + "granite_time": 1767841200, + "holocene_time": 1767927600 }, "optimism": { "eip1559Elasticity": 6, @@ -2143,92 +2061,6 @@ "DataAvailabilityChallenge": null } }, - { - "Name": "Base", - "PublicRPC": "https://mainnet.base.org", - "SequencerRPC": "https://mainnet-sequencer.base.org", - "Explorer": "https://explorer.base.org", - "SuperchainLevel": 1, - "GovernedByOptimism": false, - "SuperchainTime": 0, - "DataAvailabilityType": "eth-da", - "l2_chain_id": 8453, - "batch_inbox_address": "0xff00000000000000000000000000000000008453", - "block_time": 2, - "seq_window_size": 3600, - "max_sequencer_drift": 600, - "GasPayingToken": null, - "hardfork_configuration": { - "canyon_time": 1704992401, - "delta_time": 1708560000, - "ecotone_time": 1710374401, - "fjord_time": 1720627201, - "granite_time": 1726070401, - "holocene_time": 1736445601, - "isthmus_time": 1746806401, - "jovian_time": 1764691201 - }, - "optimism": { - "eip1559Elasticity": 6, - "eip1559Denominator": 50, - "eip1559DenominatorCanyon": 250 - }, - "alt_da": null, - "genesis": { - "l1": { - "number": 17481768, - "hash": "0x5c13d307623a926cd31415036c8b7fa14572f9dac64528e857a470511fc30771" - }, - "l2": { - "number": 0, - "hash": "0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd" - }, - "l2_time": 1686789347, - "system_config": { - "batcherAddr": "0x5050f69a9786f081509234f1a7f4684b5e5b76c9", - "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc", - "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0", - "gasLimit": 30000000, - "baseFeeScalar": null, - "blobBaseFeeScalar": null, - "eip1559Denominator": null, - "eip1559Elasticity": null, - "operatorFeeScalar": null, - "operatorFeeConstant": null, - "minBaseFee": null, - "daFootprintGasScalar": null - } - }, - "Roles": { - "SystemConfigOwner": null, - "ProxyAdminOwner": "0x7bb41c3008b3f03fe483b28b8db90e19cf07595c", - "Guardian": null, - "Challenger": null, - "Proposer": null, - "UnsafeBlockSigner": null, - "BatchSubmitter": null - }, - "Addresses": { - "AddressManager": null, - "L1CrossDomainMessengerProxy": null, - "L1Erc721BridgeProxy": null, - "L1StandardBridgeProxy": "0x3154cf16ccdb4c6d922629664174b904d80f2c35", - "L2OutputOracleProxy": null, - "OptimismMintableErc20FactoryProxy": null, - "OptimismPortalProxy": "0x49048044d57e1c92a77f79988d21fa8faf74e97e", - "SystemConfigProxy": "0x73a79fab69143498ed3712e519a88a918e1f4072", - "ProxyAdmin": null, - "SuperchainConfig": null, - "AnchorStateRegistryProxy": null, - "DelayedWethProxy": null, - "DisputeGameFactoryProxy": "0x43edb88c4b80fdd2adff2412a7bebf9df42cb40e", - "FaultDisputeGame": null, - "Mips": null, - "PermissionedDisputeGame": null, - "PreimageOracle": null, - "DataAvailabilityChallenge": null - } - }, { "Name": "Funki", "PublicRPC": "https://rpc-mainnet.funkichain.com", @@ -2248,7 +2080,9 @@ "canyon_time": 0, "delta_time": 0, "ecotone_time": 0, - "fjord_time": 0 + "fjord_time": 0, + "granite_time": 1767582000, + "holocene_time": 1767668400 }, "optimism": { "eip1559Elasticity": 10, @@ -3020,7 +2854,6 @@ "hardforks": { "interop_time": 1749150000 }, - "protocol_versions_addr": "0x72b9c5a159b0dee8331a268791aa432619693c06", "superchain_config_addr": "0x672f11f34b7be67c13a6fcba819c339bc3b0a585", "op_contracts_manager_proxy_addr": null }, @@ -3109,6 +2942,12 @@ "PermissionedDisputeGame": null, "PreimageOracle": null, "DataAvailabilityChallenge": null + }, + "interop": { + "dependencies": { + "420120009": {}, + "420120010": {} + } } }, { @@ -3195,6 +3034,12 @@ "PermissionedDisputeGame": null, "PreimageOracle": null, "DataAvailabilityChallenge": null + }, + "interop": { + "dependencies": { + "420120009": {}, + "420120010": {} + } } } ] @@ -3219,7 +3064,6 @@ "isthmus_time": 1744905600, "jovian_time": 1763568001 }, - "protocol_versions_addr": "0x79add5713b383daa0a138d3c4780c7a1804a8090", "superchain_config_addr": "0xc2be75506d5724086deb7245bd260cc9753911be", "op_contracts_manager_proxy_addr": null }, @@ -3936,7 +3780,8 @@ "granite_time": 1726570800, "holocene_time": 1737633600, "pectra_blob_schedule_time": 1742486400, - "isthmus_time": 1761825600 + "isthmus_time": 1761825600, + "jovian_time": 1777161600 }, "optimism": { "eip1559Elasticity": 10, @@ -4590,93 +4435,6 @@ "DataAvailabilityChallenge": null } }, - { - "Name": "Base Sepolia Testnet", - "PublicRPC": "https://sepolia.base.org", - "SequencerRPC": "https://sepolia-sequencer.base.org", - "Explorer": "https://sepolia-explorer.base.org", - "SuperchainLevel": 1, - "GovernedByOptimism": false, - "SuperchainTime": 0, - "DataAvailabilityType": "eth-da", - "l2_chain_id": 84532, - "batch_inbox_address": "0xff00000000000000000000000000000000084532", - "block_time": 2, - "seq_window_size": 3600, - "max_sequencer_drift": 600, - "GasPayingToken": null, - "hardfork_configuration": { - "canyon_time": 1699981200, - "delta_time": 1703203200, - "ecotone_time": 1708534800, - "fjord_time": 1716998400, - "granite_time": 1723478400, - "holocene_time": 1732633200, - "pectra_blob_schedule_time": 1742486400, - "isthmus_time": 1744905600, - "jovian_time": 1763568001 - }, - "optimism": { - "eip1559Elasticity": 10, - "eip1559Denominator": 50, - "eip1559DenominatorCanyon": 250 - }, - "alt_da": null, - "genesis": { - "l1": { - "number": 4370868, - "hash": "0xcac9a83291d4dec146d6f7f69ab2304f23f5be87b1789119a0c5b1e4482444ed" - }, - "l2": { - "number": 0, - "hash": "0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4" - }, - "l2_time": 1695768288, - "system_config": { - "batcherAddr": "0x6cdebe940bc0f26850285caca097c11c33103e47", - "overhead": "0x0000000000000000000000000000000000000000000000000000000000000834", - "scalar": "0x00000000000000000000000000000000000000000000000000000000000f4240", - "gasLimit": 25000000, - "baseFeeScalar": null, - "blobBaseFeeScalar": null, - "eip1559Denominator": null, - "eip1559Elasticity": null, - "operatorFeeScalar": null, - "operatorFeeConstant": null, - "minBaseFee": null, - "daFootprintGasScalar": null - } - }, - "Roles": { - "SystemConfigOwner": null, - "ProxyAdminOwner": "0x0fe884546476ddd290ec46318785046ef68a0ba9", - "Guardian": null, - "Challenger": null, - "Proposer": null, - "UnsafeBlockSigner": null, - "BatchSubmitter": null - }, - "Addresses": { - "AddressManager": null, - "L1CrossDomainMessengerProxy": null, - "L1Erc721BridgeProxy": null, - "L1StandardBridgeProxy": "0xfd0bf71f60660e2f608ed56e1659c450eb113120", - "L2OutputOracleProxy": null, - "OptimismMintableErc20FactoryProxy": null, - "OptimismPortalProxy": "0x49f53e41452c74589e85ca1677426ba426459e85", - "SystemConfigProxy": "0xf272670eb55e895584501d564afeb048bed26194", - "ProxyAdmin": null, - "SuperchainConfig": null, - "AnchorStateRegistryProxy": null, - "DelayedWethProxy": null, - "DisputeGameFactoryProxy": "0xd6e6dbf4f7ea0ac412fd8b65ed297e64bb7a06e1", - "FaultDisputeGame": null, - "Mips": null, - "PermissionedDisputeGame": null, - "PreimageOracle": null, - "DataAvailabilityChallenge": null - } - }, { "Name": "Camp Network Testnet V2", "PublicRPC": "https://rpc.camp-network-testnet.gelato.digital", @@ -5017,6 +4775,95 @@ "DataAvailabilityChallenge": null } }, + { + "Name": "Celo Sepolia Testnet", + "PublicRPC": "https://forno.celo-sepolia.celo-testnet.org", + "SequencerRPC": "https://forno.celo-sepolia.celo-testnet.org", + "Explorer": "https://celo-sepolia.blockscout.com", + "SuperchainLevel": 2, + "GovernedByOptimism": false, + "SuperchainTime": null, + "DataAvailabilityType": "alt-da", + "l2_chain_id": 11142220, + "batch_inbox_address": "0x00076c1f80cdb1d670046465c5ccd04e9e78c1f0", + "block_time": 1, + "seq_window_size": 7200, + "max_sequencer_drift": 600, + "GasPayingToken": "0x3c7011fd5e6aed460caa4985cf8d8caba435b092", + "hardfork_configuration": { + "canyon_time": 0, + "delta_time": 0, + "ecotone_time": 0, + "fjord_time": 0, + "granite_time": 0, + "holocene_time": 0 + }, + "optimism": { + "eip1559Elasticity": 5, + "eip1559Denominator": 400, + "eip1559DenominatorCanyon": 250 + }, + "alt_da": { + "da_challenge_address": "0x0000000000000000000000000000000000000000", + "da_challenge_window": 1, + "da_resolve_window": 1, + "da_commitment_type": "GenericCommitment" + }, + "genesis": { + "l1": { + "number": 8825790, + "hash": "0xeed417004236aa692023c216e8f1bd2185ac87a70c6f4393099f9c3777f05675" + }, + "l2": { + "number": 0, + "hash": "0x1b65cd292881564a4ba788e0822ef07f6dd558c76466ee9d1f07b57065b392f4" + }, + "l2_time": 1753283988, + "system_config": { + "batcherAddr": "0xe80d3f16e4d44a5127e1bd6ad9ed4e17a96475f0", + "overhead": "0x0000000000000000000000000000000000000000000000000000000000000000", + "scalar": "0x0100000000000000000000000000000000000000000000000000000000000000", + "gasLimit": 60000000, + "baseFeeScalar": null, + "blobBaseFeeScalar": null, + "eip1559Denominator": null, + "eip1559Elasticity": null, + "operatorFeeScalar": null, + "operatorFeeConstant": null, + "minBaseFee": null, + "daFootprintGasScalar": null + } + }, + "Roles": { + "SystemConfigOwner": null, + "ProxyAdminOwner": "0x5e60d897cd62588291656b54655e98ee73f0aabf", + "Guardian": null, + "Challenger": null, + "Proposer": null, + "UnsafeBlockSigner": null, + "BatchSubmitter": null + }, + "Addresses": { + "AddressManager": null, + "L1CrossDomainMessengerProxy": null, + "L1Erc721BridgeProxy": null, + "L1StandardBridgeProxy": "0xec18a3c30131a0db4246e785355fbc16e2eaf408", + "L2OutputOracleProxy": null, + "OptimismMintableErc20FactoryProxy": null, + "OptimismPortalProxy": "0x44ae3d41a335a7d05eb533029917aad35662dcc2", + "SystemConfigProxy": "0x760a5f022c9940f4a074e0030be682f560d29818", + "ProxyAdmin": null, + "SuperchainConfig": null, + "AnchorStateRegistryProxy": null, + "DelayedWethProxy": null, + "DisputeGameFactoryProxy": "0x57c45d82d1a995f1e135b8d7edc0a6bb5211cfaa", + "FaultDisputeGame": null, + "Mips": null, + "PermissionedDisputeGame": null, + "PreimageOracle": null, + "DataAvailabilityChallenge": null + } + }, { "Name": "OP Sepolia Testnet", "PublicRPC": "https://sepolia.optimism.io", @@ -5186,178 +5033,6 @@ "DataAvailabilityChallenge": null } }, - { - "Name": "test1", - "PublicRPC": "https://rpc.test1.com", - "SequencerRPC": "https://test1-sequencer.com", - "Explorer": "https://explorer.test1.com", - "SuperchainLevel": 2, - "GovernedByOptimism": false, - "SuperchainTime": 0, - "DataAvailabilityType": "eth-da", - "l2_chain_id": 123999119, - "batch_inbox_address": "0xff00000000000000000000000000000000000099", - "block_time": 2, - "seq_window_size": 3600, - "max_sequencer_drift": 600, - "GasPayingToken": null, - "hardfork_configuration": { - "canyon_time": 1704992401, - "delta_time": 1708560000, - "ecotone_time": 1710374401, - "fjord_time": 1720627201, - "granite_time": 1726070401, - "holocene_time": 1736445601, - "isthmus_time": 1746806401, - "jovian_time": 1764691201 - }, - "optimism": { - "eip1559Elasticity": 6, - "eip1559Denominator": 50, - "eip1559DenominatorCanyon": 250 - }, - "alt_da": null, - "genesis": { - "l1": { - "number": 17422590, - "hash": "0x438335a20d98863a4c0c97999eb2481921ccd28553eac6f913af7c12aec04108" - }, - "l2": { - "number": 105235063, - "hash": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" - }, - "l2_time": 1686068903, - "system_config": { - "batcherAddr": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc", - "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0", - "gasLimit": 30000000, - "baseFeeScalar": null, - "blobBaseFeeScalar": null, - "eip1559Denominator": null, - "eip1559Elasticity": null, - "operatorFeeScalar": null, - "operatorFeeConstant": null, - "minBaseFee": null, - "daFootprintGasScalar": null - } - }, - "Roles": { - "SystemConfigOwner": null, - "ProxyAdminOwner": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "Guardian": null, - "Challenger": null, - "Proposer": null, - "UnsafeBlockSigner": null, - "BatchSubmitter": null - }, - "Addresses": { - "AddressManager": null, - "L1CrossDomainMessengerProxy": null, - "L1Erc721BridgeProxy": null, - "L1StandardBridgeProxy": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "L2OutputOracleProxy": null, - "OptimismMintableErc20FactoryProxy": null, - "OptimismPortalProxy": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "SystemConfigProxy": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "ProxyAdmin": null, - "SuperchainConfig": null, - "AnchorStateRegistryProxy": null, - "DelayedWethProxy": null, - "DisputeGameFactoryProxy": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "FaultDisputeGame": null, - "Mips": null, - "PermissionedDisputeGame": null, - "PreimageOracle": null, - "DataAvailabilityChallenge": null - } - }, - { - "Name": "test2", - "PublicRPC": "https://rpc.test2.com", - "SequencerRPC": "https://test2-sequencer.com", - "Explorer": "https://explorer.test2.com", - "SuperchainLevel": 2, - "GovernedByOptimism": false, - "SuperchainTime": 0, - "DataAvailabilityType": "eth-da", - "l2_chain_id": 223999119, - "batch_inbox_address": "0xff00000000000000000000000000000000000099", - "block_time": 2, - "seq_window_size": 3600, - "max_sequencer_drift": 600, - "GasPayingToken": null, - "hardfork_configuration": { - "canyon_time": 1704992401, - "delta_time": 1708560000, - "ecotone_time": 1710374401, - "fjord_time": 1720627201, - "granite_time": 1726070401, - "holocene_time": 1736445601, - "isthmus_time": 1746806401, - "jovian_time": 1764691201 - }, - "optimism": { - "eip1559Elasticity": 6, - "eip1559Denominator": 50, - "eip1559DenominatorCanyon": 250 - }, - "alt_da": null, - "genesis": { - "l1": { - "number": 17422590, - "hash": "0x438335a20d98863a4c0c97999eb2481921ccd28553eac6f913af7c12aec04108" - }, - "l2": { - "number": 105235063, - "hash": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" - }, - "l2_time": 1686068903, - "system_config": { - "batcherAddr": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc", - "scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0", - "gasLimit": 30000000, - "baseFeeScalar": null, - "blobBaseFeeScalar": null, - "eip1559Denominator": null, - "eip1559Elasticity": null, - "operatorFeeScalar": null, - "operatorFeeConstant": null, - "minBaseFee": null, - "daFootprintGasScalar": null - } - }, - "Roles": { - "SystemConfigOwner": null, - "ProxyAdminOwner": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "Guardian": null, - "Challenger": null, - "Proposer": null, - "UnsafeBlockSigner": null, - "BatchSubmitter": null - }, - "Addresses": { - "AddressManager": null, - "L1CrossDomainMessengerProxy": null, - "L1Erc721BridgeProxy": null, - "L1StandardBridgeProxy": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "L2OutputOracleProxy": null, - "OptimismMintableErc20FactoryProxy": null, - "OptimismPortalProxy": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "SystemConfigProxy": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "ProxyAdmin": null, - "SuperchainConfig": null, - "AnchorStateRegistryProxy": null, - "DelayedWethProxy": null, - "DisputeGameFactoryProxy": "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "FaultDisputeGame": null, - "Mips": null, - "PermissionedDisputeGame": null, - "PreimageOracle": null, - "DataAvailabilityChallenge": null - } - }, { "Name": "Zora Sepolia Testnet", "PublicRPC": "https://sepolia.rpc.zora.energy", @@ -5467,7 +5142,6 @@ "isthmus_time": 1744300800, "jovian_time": 1762185600 }, - "protocol_versions_addr": "0x252cbe9517f731c618961d890d534183822dcc8d", "superchain_config_addr": "0x02d91cf852423640d93920be0cadcec0e7a00fa7", "op_contracts_manager_proxy_addr": null }, @@ -5558,93 +5232,6 @@ "PreimageOracle": null, "DataAvailabilityChallenge": null } - }, - { - "Name": "Base devnet 0", - "PublicRPC": "", - "SequencerRPC": "", - "Explorer": "", - "SuperchainLevel": 0, - "GovernedByOptimism": false, - "SuperchainTime": 1706634000, - "DataAvailabilityType": "eth-da", - "l2_chain_id": 11763072, - "batch_inbox_address": "0xff00000000000000000000000000000011763072", - "block_time": 2, - "seq_window_size": 3600, - "max_sequencer_drift": 600, - "GasPayingToken": null, - "hardfork_configuration": { - "canyon_time": 1698436800, - "delta_time": 1706555000, - "ecotone_time": 1706634000, - "fjord_time": 1715961600, - "granite_time": 1723046400, - "holocene_time": 1731682800, - "pectra_blob_schedule_time": 1742486400, - "isthmus_time": 1744300800, - "jovian_time": 1762185600 - }, - "optimism": { - "eip1559Elasticity": 6, - "eip1559Denominator": 50, - "eip1559DenominatorCanyon": 250 - }, - "alt_da": null, - "genesis": { - "l1": { - "number": 4344216, - "hash": "0x86252c512dc5bd7201d0532b31d50696ba84344a7cda545e04a98073a8e13d87" - }, - "l2": { - "number": 0, - "hash": "0x1ab91449a7c65b8cd6c06f13e2e7ea2d10b6f9cbf5def79f362f2e7e501d2928" - }, - "l2_time": 1695433056, - "system_config": { - "batcherAddr": "0x212dd524932bc43478688f91045f2682913ad8ee", - "overhead": "0x0000000000000000000000000000000000000000000000000000000000000834", - "scalar": "0x00000000000000000000000000000000000000000000000000000000000f4240", - "gasLimit": 25000000, - "baseFeeScalar": null, - "blobBaseFeeScalar": null, - "eip1559Denominator": null, - "eip1559Elasticity": null, - "operatorFeeScalar": null, - "operatorFeeConstant": null, - "minBaseFee": null, - "daFootprintGasScalar": null - } - }, - "Roles": { - "SystemConfigOwner": null, - "ProxyAdminOwner": "0x0fe884546476ddd290ec46318785046ef68a0ba9", - "Guardian": null, - "Challenger": null, - "Proposer": null, - "UnsafeBlockSigner": null, - "BatchSubmitter": null - }, - "Addresses": { - "AddressManager": null, - "L1CrossDomainMessengerProxy": null, - "L1Erc721BridgeProxy": null, - "L1StandardBridgeProxy": "0x5638e55db5fcf7a58df525f1098e8569c8dba80c", - "L2OutputOracleProxy": null, - "OptimismMintableErc20FactoryProxy": null, - "OptimismPortalProxy": "0x579c82a835b884336b632eebecc78fa08d3291ec", - "SystemConfigProxy": "0x7f67dc4959cb3e532b10a99f41bdd906c46fdfde", - "ProxyAdmin": null, - "SuperchainConfig": null, - "AnchorStateRegistryProxy": null, - "DelayedWethProxy": null, - "DisputeGameFactoryProxy": "0xe545ede9d1fadad12984c31467f56405884b9398", - "FaultDisputeGame": null, - "Mips": null, - "PermissionedDisputeGame": null, - "PreimageOracle": null, - "DataAvailabilityChallenge": null - } } ] } diff --git a/rust/kona/crates/protocol/registry/etc/depsets.json b/rust/kona/crates/protocol/registry/etc/depsets.json new file mode 100644 index 00000000000..1468697c060 --- /dev/null +++ b/rust/kona/crates/protocol/registry/etc/depsets.json @@ -0,0 +1,9 @@ +[ + { + "dependencies": { + "420120009": {}, + "420120010": {} + }, + "overrideMessageExpiryWindow": null + } +] \ No newline at end of file diff --git a/rust/kona/crates/protocol/registry/justfile b/rust/kona/crates/protocol/registry/justfile index 61dbed4eda8..7ebe957257e 100644 --- a/rust/kona/crates/protocol/registry/justfile +++ b/rust/kona/crates/protocol/registry/justfile @@ -10,4 +10,4 @@ bind: # Update the `superchain-registry` git submodule source source: - git submodule update --remote --init --recursive + cd "$(git rev-parse --show-toplevel)" && git submodule update --remote --init --recursive packages/contracts-bedrock/lib/superchain-registry diff --git a/rust/kona/crates/protocol/registry/src/l1/mod.rs b/rust/kona/crates/protocol/registry/src/l1/mod.rs index 44f6420837c..da89f903632 100644 --- a/rust/kona/crates/protocol/registry/src/l1/mod.rs +++ b/rust/kona/crates/protocol/registry/src/l1/mod.rs @@ -118,6 +118,7 @@ impl L1Config { cancun_time: alloy_hardforks::EthereumHardfork::Cancun.mainnet_activation_timestamp(), prague_time: alloy_hardforks::EthereumHardfork::Prague.mainnet_activation_timestamp(), osaka_time: alloy_hardforks::EthereumHardfork::Osaka.mainnet_activation_timestamp(), + amsterdam_time: None, bpo1_time: alloy_hardforks::EthereumHardfork::Bpo1.mainnet_activation_timestamp(), bpo2_time: alloy_hardforks::EthereumHardfork::Bpo2.mainnet_activation_timestamp(), bpo3_time: alloy_hardforks::EthereumHardfork::Bpo3.mainnet_activation_timestamp(), @@ -137,6 +138,7 @@ impl L1Config { parlia: None, extra_fields: Default::default(), terminal_total_difficulty_passed: false, + _non_exhaustive: (), }) } @@ -172,6 +174,7 @@ impl L1Config { cancun_time: alloy_hardforks::EthereumHardfork::Cancun.sepolia_activation_timestamp(), prague_time: alloy_hardforks::EthereumHardfork::Prague.sepolia_activation_timestamp(), osaka_time: alloy_hardforks::EthereumHardfork::Osaka.sepolia_activation_timestamp(), + amsterdam_time: None, bpo1_time: alloy_hardforks::EthereumHardfork::Bpo1.sepolia_activation_timestamp(), bpo2_time: alloy_hardforks::EthereumHardfork::Bpo2.sepolia_activation_timestamp(), bpo3_time: alloy_hardforks::EthereumHardfork::Bpo3.sepolia_activation_timestamp(), @@ -190,6 +193,7 @@ impl L1Config { parlia: None, extra_fields: Default::default(), terminal_total_difficulty_passed: false, + _non_exhaustive: (), }) } @@ -216,6 +220,7 @@ impl L1Config { cancun_time: alloy_hardforks::EthereumHardfork::Cancun.holesky_activation_timestamp(), prague_time: alloy_hardforks::EthereumHardfork::Prague.holesky_activation_timestamp(), osaka_time: alloy_hardforks::EthereumHardfork::Osaka.holesky_activation_timestamp(), + amsterdam_time: None, bpo1_time: alloy_hardforks::EthereumHardfork::Bpo1.holesky_activation_timestamp(), bpo2_time: alloy_hardforks::EthereumHardfork::Bpo2.holesky_activation_timestamp(), bpo3_time: alloy_hardforks::EthereumHardfork::Bpo3.holesky_activation_timestamp(), @@ -235,6 +240,7 @@ impl L1Config { parlia: None, extra_fields: Default::default(), terminal_total_difficulty_passed: false, + _non_exhaustive: (), }) } diff --git a/rust/kona/crates/protocol/registry/src/lib.rs b/rust/kona/crates/protocol/registry/src/lib.rs index dd51ece79ae..a6be0367d37 100644 --- a/rust/kona/crates/protocol/registry/src/lib.rs +++ b/rust/kona/crates/protocol/registry/src/lib.rs @@ -9,9 +9,14 @@ extern crate alloc; +use alloc::vec::Vec; + pub use alloy_primitives::map::HashMap; use kona_genesis::L1ChainConfig; -pub use kona_genesis::{Chain, ChainConfig, ChainList, RollupConfig}; +pub use kona_genesis::{ + Chain, ChainConfig, ChainDependency, ChainList, DependencySet, MESSAGE_EXPIRY_WINDOW, + RollupConfig, +}; pub mod superchain; pub use superchain::Registry; @@ -39,6 +44,28 @@ lazy_static::lazy_static! { /// L1 chain configurations exported from the registry /// Note: the l1 chain configurations are not exported from the superchain registry but rather from a genesis dump file. pub static ref L1_CONFIGS: HashMap = _INIT.l1_configs.clone(); + + /// All interop dependency sets embedded into this binary, keyed by L2 chain id. + /// Each chain that belongs to an interop cluster maps to that cluster's + /// [`DependencySet`]; chains in disjoint clusters map to **different** values. + /// Cross-cluster proofs must be rejected by the consumer (see `BootInfo::load`). + pub static ref DEPENDENCY_SETS: HashMap = { + let raw = include_str!("../etc/depsets.json"); + let depsets: Vec = serde_json::from_str(raw) + .expect("parse embedded etc/depsets.json"); + let mut by_chain: HashMap = HashMap::default(); + for ds in depsets { + for chain_id in ds.dependencies.keys().copied() { + if let Some(existing) = by_chain.insert(chain_id, ds.clone()) { + assert_eq!( + existing, ds, + "embedded depsets contain overlapping clusters; build script bug" + ); + } + } + } + by_chain + }; } /// Returns a [`RollupConfig`] by its identifier. @@ -60,20 +87,13 @@ mod tests { holesky::{HOLESKY_BPO1_TIMESTAMP, HOLESKY_BPO2_TIMESTAMP}, sepolia::{SEPOLIA_BPO1_TIMESTAMP, SEPOLIA_BPO2_TIMESTAMP}, }; - use alloy_op_hardforks::{ - BASE_MAINNET_JOVIAN_TIMESTAMP, BASE_SEPOLIA_JOVIAN_TIMESTAMP, OP_MAINNET_JOVIAN_TIMESTAMP, - OP_SEPOLIA_JOVIAN_TIMESTAMP, - }; + use alloy_op_hardforks::{OP_MAINNET_JOVIAN_TIMESTAMP, OP_SEPOLIA_JOVIAN_TIMESTAMP}; #[test] fn test_hardcoded_rollup_configs() { - let test_cases = [ - (10, test_utils::OP_MAINNET_CONFIG), - (8453, test_utils::BASE_MAINNET_CONFIG), - (11155420, test_utils::OP_SEPOLIA_CONFIG), - (84532, test_utils::BASE_SEPOLIA_CONFIG), - ] - .to_vec(); + let test_cases = + [(10, test_utils::OP_MAINNET_CONFIG), (11155420, test_utils::OP_SEPOLIA_CONFIG)] + .to_vec(); for (chain_id, expected) in test_cases { let derived = super::ROLLUP_CONFIGS.get(&chain_id).unwrap(); @@ -83,11 +103,11 @@ mod tests { #[test] fn test_chain_by_ident() { - const ALLOY_BASE: AlloyChain = AlloyChain::base_mainnet(); + const ALLOY_OP: AlloyChain = AlloyChain::optimism_mainnet(); - let chain_by_ident = CHAINS.get_chain_by_ident("mainnet/base").unwrap(); - let chain_by_alloy_ident = CHAINS.get_chain_by_alloy_ident(&ALLOY_BASE).unwrap(); - let chain_by_id = CHAINS.get_chain_by_id(8453).unwrap(); + let chain_by_ident = CHAINS.get_chain_by_ident("mainnet/op").unwrap(); + let chain_by_alloy_ident = CHAINS.get_chain_by_alloy_ident(&ALLOY_OP).unwrap(); + let chain_by_id = CHAINS.get_chain_by_id(10).unwrap(); assert_eq!(chain_by_ident, chain_by_id); assert_eq!(chain_by_alloy_ident, chain_by_id); @@ -95,11 +115,11 @@ mod tests { #[test] fn test_rollup_config_by_ident() { - const ALLOY_BASE: AlloyChain = AlloyChain::base_mainnet(); + const ALLOY_OP: AlloyChain = AlloyChain::optimism_mainnet(); - let rollup_config_by_ident = scr_rollup_config_by_ident("mainnet/base").unwrap(); - let rollup_config_by_alloy_ident = scr_rollup_config_by_alloy_ident(&ALLOY_BASE).unwrap(); - let rollup_config_by_id = ROLLUP_CONFIGS.get(&8453).unwrap(); + let rollup_config_by_ident = scr_rollup_config_by_ident("mainnet/op").unwrap(); + let rollup_config_by_alloy_ident = scr_rollup_config_by_alloy_ident(&ALLOY_OP).unwrap(); + let rollup_config_by_id = ROLLUP_CONFIGS.get(&10).unwrap(); assert_eq!(rollup_config_by_ident, rollup_config_by_id); assert_eq!(rollup_config_by_alloy_ident, rollup_config_by_id); @@ -107,18 +127,6 @@ mod tests { #[test] fn test_jovian_timestamps() { - let base_mainnet_config_by_ident = scr_rollup_config_by_ident("mainnet/base").unwrap(); - assert_eq!( - base_mainnet_config_by_ident.hardforks.jovian_time, - Some(BASE_MAINNET_JOVIAN_TIMESTAMP) - ); - - let base_sepolia_config_by_ident = scr_rollup_config_by_ident("sepolia/base").unwrap(); - assert_eq!( - base_sepolia_config_by_ident.hardforks.jovian_time, - Some(BASE_SEPOLIA_JOVIAN_TIMESTAMP) - ); - let op_mainnet_config_by_ident = scr_rollup_config_by_ident("mainnet/op").unwrap(); assert_eq!( op_mainnet_config_by_ident.hardforks.jovian_time, @@ -191,5 +199,62 @@ mod tests { ROLLUP_CONFIGS.contains_key(&test2_chain_id), "rollup config missing for {test2_chain_id}" ); + + let depset = DEPENDENCY_SETS + .get(&test1_chain_id) + .expect("test1 chain id present in embedded depsets"); + assert!(depset.dependencies.contains_key(&test1_chain_id)); + assert!(depset.dependencies.contains_key(&test2_chain_id)); + // Both chain ids must map to the SAME depset value (cluster identity). + assert_eq!(DEPENDENCY_SETS.get(&test1_chain_id), DEPENDENCY_SETS.get(&test2_chain_id)); + } + + /// Pins the registry-derived interop cluster against the committed + /// `etc/depsets.json` snapshot. The snapshot is regenerated from + /// `packages/contracts-bedrock/lib/superchain-registry` via + /// `KONA_BIND=true cargo build -p kona-registry`. + /// + /// Today the registry defines a single `[interop]` cluster — `rehearsal-0-bn` + /// (chain ids 420120009, 420120010). If the rehearsal TOMLs change upstream, + /// regenerate the snapshot and update the expected set below. + #[test] + fn embedded_depset_for_rehearsal_0_bn_cluster() { + const REHEARSAL_0: u64 = 420120009; + const REHEARSAL_1: u64 = 420120010; + + let depset = DEPENDENCY_SETS.get(&REHEARSAL_0).unwrap_or_else(|| { + panic!( + "rehearsal-0-bn-0 (chain id {REHEARSAL_0}) missing from embedded DEPENDENCY_SETS", + ) + }); + + // Both peers must resolve to the SAME cluster value (cluster identity). + assert_eq!( + DEPENDENCY_SETS.get(&REHEARSAL_0), + DEPENDENCY_SETS.get(&REHEARSAL_1), + "rehearsal-0-bn peers must map to the same DependencySet", + ); + + // The cluster's dependency map must contain exactly the two rehearsal chain ids. + let expected: alloc::collections::BTreeSet = + [REHEARSAL_0, REHEARSAL_1].into_iter().collect(); + let actual: alloc::collections::BTreeSet = + depset.dependencies.keys().copied().collect(); + assert_eq!( + actual, expected, + "rehearsal-0-bn cluster membership mismatch (got {actual:?}, want {expected:?})", + ); + + // Registry-derived clusters never carry an expiry-window override (the chain + // TOML schema has no field for it; see `aggregate_clusters`). + assert!( + depset.override_message_expiry_window.is_none(), + "registry-derived depset must not carry an expiry-window override", + ); + assert_eq!( + depset.get_message_expiry_window(), + MESSAGE_EXPIRY_WINDOW, + "registry-derived depset must use the default message expiry window", + ); } } diff --git a/rust/kona/crates/protocol/registry/src/superchain.rs b/rust/kona/crates/protocol/registry/src/superchain.rs index f9c859dc0a3..aa53bd06ebd 100644 --- a/rust/kona/crates/protocol/registry/src/superchain.rs +++ b/rust/kona/crates/protocol/registry/src/superchain.rs @@ -46,10 +46,6 @@ impl Registry { a.zero_proof_addresses(); } let mut rollup = chain_config.as_rollup_config(); - rollup.protocol_versions_address = superchain - .config - .protocol_versions_addr - .expect("Missing protocol versions address"); rollup.superchain_config_address = superchain.config.superchain_config_addr; rollup_configs.insert(chain_config.chain_id, rollup); op_chains.insert(chain_config.chain_id, chain_config); @@ -63,59 +59,25 @@ impl Registry { #[cfg(test)] mod tests { use super::*; - use alloc::string::{String, ToString}; use alloy_op_hardforks::{ - BASE_MAINNET_ISTHMUS_TIMESTAMP, BASE_MAINNET_JOVIAN_TIMESTAMP, - BASE_SEPOLIA_ISTHMUS_TIMESTAMP, BASE_SEPOLIA_JOVIAN_TIMESTAMP, OP_MAINNET_ISTHMUS_TIMESTAMP, OP_MAINNET_JOVIAN_TIMESTAMP, OP_SEPOLIA_ISTHMUS_TIMESTAMP, OP_SEPOLIA_JOVIAN_TIMESTAMP, }; use alloy_primitives::address; - use kona_genesis::{AddressList, OP_MAINNET_BASE_FEE_CONFIG, Roles, SuperchainLevel}; #[test] fn test_read_chain_configs() { let superchains = Registry::from_chain_list(); assert!(superchains.chain_list.len() > 1); - let base_config = ChainConfig { - name: String::from("Base"), - chain_id: 8453, - l1_chain_id: 1, - public_rpc: String::from("https://mainnet.base.org"), - sequencer_rpc: String::from("https://mainnet-sequencer.base.org"), - explorer: String::from("https://explorer.base.org"), - superchain_level: SuperchainLevel::StandardCandidate, - governed_by_optimism: false, - superchain_time: Some(0), - batch_inbox_addr: address!("ff00000000000000000000000000000000008453"), - hardfork_config: crate::test_utils::BASE_MAINNET_CONFIG.hardforks, - block_time: 2, - seq_window_size: 3600, - max_sequencer_drift: 600, - data_availability_type: "eth-da".to_string(), - optimism: Some(OP_MAINNET_BASE_FEE_CONFIG), - alt_da: None, - genesis: crate::test_utils::BASE_MAINNET_CONFIG.genesis, - roles: Some(Roles { - proxy_admin_owner: Some( - "7bB41C3008B3f03FE483B28b8DB90e19Cf07595c".parse().unwrap(), - ), - ..Default::default() - }), - addresses: Some(AddressList { - l1_standard_bridge_proxy: Some(address!( - "3154Cf16ccdb4C6d922629664174b904d80F2C35" - )), - optimism_portal_proxy: Some(address!("49048044D57e1C92A77f79988d21Fa8fAF74E97e")), - system_config_proxy: Some(address!("73a79Fab69143498Ed3712e519A88a918e1f4072")), - dispute_game_factory_proxy: Some(address!( - "43edb88c4b80fdd2adff2412a7bebf9df42cb40e" - )), - ..Default::default() - }), - gas_paying_token: None, - }; - assert_eq!(*superchains.op_chains.get(&8453).unwrap(), base_config); + let op_mainnet = superchains.op_chains.get(&10).expect("OP Mainnet config missing"); + assert_eq!(op_mainnet.name, "OP Mainnet"); + assert_eq!(op_mainnet.chain_id, 10); + assert_eq!(op_mainnet.l1_chain_id, 1); + assert_eq!( + op_mainnet.batch_inbox_addr, + address!("ff00000000000000000000000000000000000010") + ); + assert!(op_mainnet.governed_by_optimism); } #[test] @@ -135,18 +97,6 @@ mod tests { let op_sepolia_config = superchains.rollup_configs.get(&11155420).unwrap(); assert_eq!(op_sepolia_config.hardforks.isthmus_time, Some(OP_SEPOLIA_ISTHMUS_TIMESTAMP)); - - let base_mainnet_config = superchains.rollup_configs.get(&8453).unwrap(); - assert_eq!( - base_mainnet_config.hardforks.isthmus_time, - Some(BASE_MAINNET_ISTHMUS_TIMESTAMP) - ); - - let base_sepolia_config = superchains.rollup_configs.get(&84532).unwrap(); - assert_eq!( - base_sepolia_config.hardforks.isthmus_time, - Some(BASE_SEPOLIA_ISTHMUS_TIMESTAMP) - ); } #[test] @@ -157,11 +107,5 @@ mod tests { let op_sepolia_config = superchains.rollup_configs.get(&11155420).unwrap(); assert_eq!(op_sepolia_config.hardforks.jovian_time, Some(OP_SEPOLIA_JOVIAN_TIMESTAMP)); - - let base_mainnet_config = superchains.rollup_configs.get(&8453).unwrap(); - assert_eq!(base_mainnet_config.hardforks.jovian_time, Some(BASE_MAINNET_JOVIAN_TIMESTAMP)); - - let base_sepolia_config = superchains.rollup_configs.get(&84532).unwrap(); - assert_eq!(base_sepolia_config.hardforks.jovian_time, Some(BASE_SEPOLIA_JOVIAN_TIMESTAMP)); } } diff --git a/rust/kona/crates/protocol/registry/src/test_utils/base_mainnet.rs b/rust/kona/crates/protocol/registry/src/test_utils/base_mainnet.rs deleted file mode 100644 index 08e04ef4288..00000000000 --- a/rust/kona/crates/protocol/registry/src/test_utils/base_mainnet.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Base Mainnet Rollup Config. - -use alloy_chains::Chain; -use alloy_eips::BlockNumHash; -use alloy_op_hardforks::{ - BASE_MAINNET_CANYON_TIMESTAMP, BASE_MAINNET_ECOTONE_TIMESTAMP, BASE_MAINNET_FJORD_TIMESTAMP, - BASE_MAINNET_GRANITE_TIMESTAMP, BASE_MAINNET_HOLOCENE_TIMESTAMP, - BASE_MAINNET_ISTHMUS_TIMESTAMP, BASE_MAINNET_JOVIAN_TIMESTAMP, -}; -use alloy_primitives::{address, b256, uint}; -use kona_genesis::{ - BASE_MAINNET_BASE_FEE_CONFIG, ChainGenesis, DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, - HardForkConfig, RollupConfig, SystemConfig, -}; - -/// The [`RollupConfig`] for Base Mainnet. -pub const BASE_MAINNET_CONFIG: RollupConfig = RollupConfig { - genesis: ChainGenesis { - l1: BlockNumHash { - hash: b256!("5c13d307623a926cd31415036c8b7fa14572f9dac64528e857a470511fc30771"), - number: 17_481_768_u64, - }, - l2: BlockNumHash { - hash: b256!("f712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd"), - number: 0_u64, - }, - l2_time: 1686789347_u64, - system_config: Some(SystemConfig { - batcher_address: address!("5050f69a9786f081509234f1a7f4684b5e5b76c9"), - overhead: uint!(0xbc_U256), - scalar: uint!(0xa6fe0_U256), - gas_limit: 30_000_000_u64, - base_fee_scalar: None, - blob_base_fee_scalar: None, - eip1559_denominator: None, - eip1559_elasticity: None, - operator_fee_scalar: None, - operator_fee_constant: None, - min_base_fee: None, - da_footprint_gas_scalar: None, - }), - }, - block_time: 2, - max_sequencer_drift: 600, - seq_window_size: 3600, - channel_timeout: 300, - granite_channel_timeout: 50, - l1_chain_id: 1, - l2_chain_id: Chain::base_mainnet(), - hardforks: HardForkConfig { - regolith_time: None, - canyon_time: Some(BASE_MAINNET_CANYON_TIMESTAMP), - delta_time: Some(1708560000), - ecotone_time: Some(BASE_MAINNET_ECOTONE_TIMESTAMP), - fjord_time: Some(BASE_MAINNET_FJORD_TIMESTAMP), - granite_time: Some(BASE_MAINNET_GRANITE_TIMESTAMP), - holocene_time: Some(BASE_MAINNET_HOLOCENE_TIMESTAMP), - pectra_blob_schedule_time: None, - isthmus_time: Some(BASE_MAINNET_ISTHMUS_TIMESTAMP), - jovian_time: Some(BASE_MAINNET_JOVIAN_TIMESTAMP), - karst_time: None, - interop_time: None, - }, - batch_inbox_address: address!("ff00000000000000000000000000000000008453"), - deposit_contract_address: address!("49048044d57e1c92a77f79988d21fa8faf74e97e"), - l1_system_config_address: address!("73a79fab69143498ed3712e519a88a918e1f4072"), - protocol_versions_address: address!("8062abc286f5e7d9428a0ccb9abd71e50d93b935"), - superchain_config_address: Some(address!("95703e0982140D16f8ebA6d158FccEde42f04a4C")), - da_challenge_address: None, - blobs_enabled_l1_timestamp: None, - interop_message_expiry_window: DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, - alt_da_config: None, - chain_op_config: BASE_MAINNET_BASE_FEE_CONFIG, -}; diff --git a/rust/kona/crates/protocol/registry/src/test_utils/base_sepolia.rs b/rust/kona/crates/protocol/registry/src/test_utils/base_sepolia.rs deleted file mode 100644 index 81e19b9358a..00000000000 --- a/rust/kona/crates/protocol/registry/src/test_utils/base_sepolia.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Base Sepolia Rollup Config. - -use alloy_chains::Chain; -use alloy_eips::BlockNumHash; -use alloy_op_hardforks::{ - BASE_SEPOLIA_CANYON_TIMESTAMP, BASE_SEPOLIA_ECOTONE_TIMESTAMP, BASE_SEPOLIA_FJORD_TIMESTAMP, - BASE_SEPOLIA_GRANITE_TIMESTAMP, BASE_SEPOLIA_HOLOCENE_TIMESTAMP, - BASE_SEPOLIA_ISTHMUS_TIMESTAMP, BASE_SEPOLIA_JOVIAN_TIMESTAMP, -}; -use alloy_primitives::{address, b256, uint}; -use kona_genesis::{ - BASE_SEPOLIA_BASE_FEE_CONFIG, ChainGenesis, DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, - HardForkConfig, RollupConfig, SystemConfig, -}; - -/// The [`RollupConfig`] for Base Sepolia. -pub const BASE_SEPOLIA_CONFIG: RollupConfig = RollupConfig { - genesis: ChainGenesis { - l1: BlockNumHash { - hash: b256!("cac9a83291d4dec146d6f7f69ab2304f23f5be87b1789119a0c5b1e4482444ed"), - number: 4370868, - }, - l2: BlockNumHash { - hash: b256!("0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4"), - number: 0, - }, - l2_time: 1695768288, - system_config: Some(SystemConfig { - batcher_address: address!("6cdebe940bc0f26850285caca097c11c33103e47"), - overhead: uint!(0x834_U256), - scalar: uint!(0xf4240_U256), - gas_limit: 25000000, - base_fee_scalar: None, - blob_base_fee_scalar: None, - eip1559_denominator: None, - eip1559_elasticity: None, - operator_fee_scalar: None, - operator_fee_constant: None, - min_base_fee: None, - da_footprint_gas_scalar: None, - }), - }, - block_time: 2, - max_sequencer_drift: 600, - seq_window_size: 3600, - channel_timeout: 300, - granite_channel_timeout: 50, - l1_chain_id: 11155111, - l2_chain_id: Chain::base_sepolia(), - chain_op_config: BASE_SEPOLIA_BASE_FEE_CONFIG, - alt_da_config: None, - hardforks: HardForkConfig { - regolith_time: None, - canyon_time: Some(BASE_SEPOLIA_CANYON_TIMESTAMP), - delta_time: Some(1703203200), - ecotone_time: Some(BASE_SEPOLIA_ECOTONE_TIMESTAMP), - fjord_time: Some(BASE_SEPOLIA_FJORD_TIMESTAMP), - granite_time: Some(BASE_SEPOLIA_GRANITE_TIMESTAMP), - holocene_time: Some(BASE_SEPOLIA_HOLOCENE_TIMESTAMP), - pectra_blob_schedule_time: Some(1742486400), - isthmus_time: Some(BASE_SEPOLIA_ISTHMUS_TIMESTAMP), - jovian_time: Some(BASE_SEPOLIA_JOVIAN_TIMESTAMP), - karst_time: None, - interop_time: None, - }, - batch_inbox_address: address!("ff00000000000000000000000000000000084532"), - deposit_contract_address: address!("49f53e41452c74589e85ca1677426ba426459e85"), - l1_system_config_address: address!("f272670eb55e895584501d564afeb048bed26194"), - protocol_versions_address: address!("79add5713b383daa0a138d3c4780c7a1804a8090"), - superchain_config_address: Some(address!("C2Be75506d5724086DEB7245bd260Cc9753911Be")), - da_challenge_address: None, - blobs_enabled_l1_timestamp: None, - interop_message_expiry_window: DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, -}; diff --git a/rust/kona/crates/protocol/registry/src/test_utils/mod.rs b/rust/kona/crates/protocol/registry/src/test_utils/mod.rs index 3f18b58e306..79ff7dcb90e 100644 --- a/rust/kona/crates/protocol/registry/src/test_utils/mod.rs +++ b/rust/kona/crates/protocol/registry/src/test_utils/mod.rs @@ -3,11 +3,5 @@ mod op_mainnet; pub use op_mainnet::OP_MAINNET_CONFIG; -mod base_mainnet; -pub use base_mainnet::BASE_MAINNET_CONFIG; - mod op_sepolia; pub use op_sepolia::OP_SEPOLIA_CONFIG; - -mod base_sepolia; -pub use base_sepolia::BASE_SEPOLIA_CONFIG; diff --git a/rust/kona/crates/protocol/registry/src/test_utils/op_mainnet.rs b/rust/kona/crates/protocol/registry/src/test_utils/op_mainnet.rs index 809c005e203..82039f6a07d 100644 --- a/rust/kona/crates/protocol/registry/src/test_utils/op_mainnet.rs +++ b/rust/kona/crates/protocol/registry/src/test_utils/op_mainnet.rs @@ -8,9 +8,10 @@ use alloy_op_hardforks::{ OP_MAINNET_JOVIAN_TIMESTAMP, }; use alloy_primitives::{address, b256, uint}; +#[cfg(feature = "rollup_config_override")] +use kona_genesis::FJORD_MAX_SEQUENCER_DRIFT; use kona_genesis::{ - ChainGenesis, DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, HardForkConfig, - OP_MAINNET_BASE_FEE_CONFIG, RollupConfig, SystemConfig, + ChainGenesis, HardForkConfig, OP_MAINNET_BASE_FEE_CONFIG, RollupConfig, SystemConfig, }; /// The [`RollupConfig`] for OP Mainnet. @@ -45,6 +46,8 @@ pub const OP_MAINNET_CONFIG: RollupConfig = RollupConfig { seq_window_size: 3600_u64, channel_timeout: 300_u64, granite_channel_timeout: 50, + #[cfg(feature = "rollup_config_override")] + fjord_max_sequencer_drift: FJORD_MAX_SEQUENCER_DRIFT, l1_chain_id: 1_u64, l2_chain_id: Chain::optimism_mainnet(), chain_op_config: OP_MAINNET_BASE_FEE_CONFIG, @@ -66,9 +69,7 @@ pub const OP_MAINNET_CONFIG: RollupConfig = RollupConfig { batch_inbox_address: address!("ff00000000000000000000000000000000000010"), deposit_contract_address: address!("beb5fc579115071764c7423a4f12edde41f106ed"), l1_system_config_address: address!("229047fed2591dbec1ef1118d64f7af3db9eb290"), - protocol_versions_address: address!("8062abc286f5e7d9428a0ccb9abd71e50d93b935"), superchain_config_address: Some(address!("95703e0982140D16f8ebA6d158FccEde42f04a4C")), da_challenge_address: None, blobs_enabled_l1_timestamp: None, - interop_message_expiry_window: DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, }; diff --git a/rust/kona/crates/protocol/registry/src/test_utils/op_sepolia.rs b/rust/kona/crates/protocol/registry/src/test_utils/op_sepolia.rs index efbc8d4a9ca..93b533c5dce 100644 --- a/rust/kona/crates/protocol/registry/src/test_utils/op_sepolia.rs +++ b/rust/kona/crates/protocol/registry/src/test_utils/op_sepolia.rs @@ -8,9 +8,10 @@ use alloy_op_hardforks::{ OP_SEPOLIA_JOVIAN_TIMESTAMP, }; use alloy_primitives::{address, b256, uint}; +#[cfg(feature = "rollup_config_override")] +use kona_genesis::FJORD_MAX_SEQUENCER_DRIFT; use kona_genesis::{ - ChainGenesis, DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, HardForkConfig, - OP_SEPOLIA_BASE_FEE_CONFIG, RollupConfig, SystemConfig, + ChainGenesis, HardForkConfig, OP_SEPOLIA_BASE_FEE_CONFIG, RollupConfig, SystemConfig, }; /// The [`RollupConfig`] for OP Sepolia. @@ -45,6 +46,8 @@ pub const OP_SEPOLIA_CONFIG: RollupConfig = RollupConfig { seq_window_size: 3600, channel_timeout: 300, granite_channel_timeout: 50, + #[cfg(feature = "rollup_config_override")] + fjord_max_sequencer_drift: FJORD_MAX_SEQUENCER_DRIFT, l1_chain_id: 11155111, l2_chain_id: Chain::optimism_sepolia(), chain_op_config: OP_SEPOLIA_BASE_FEE_CONFIG, @@ -66,9 +69,7 @@ pub const OP_SEPOLIA_CONFIG: RollupConfig = RollupConfig { batch_inbox_address: address!("ff00000000000000000000000000000011155420"), deposit_contract_address: address!("16fc5058f25648194471939df75cf27a2fdc48bc"), l1_system_config_address: address!("034edd2a225f7f429a63e0f1d2084b9e0a93b538"), - protocol_versions_address: address!("79add5713b383daa0a138d3c4780c7a1804a8090"), superchain_config_address: Some(address!("C2Be75506d5724086DEB7245bd260Cc9753911Be")), da_challenge_address: None, blobs_enabled_l1_timestamp: None, - interop_message_expiry_window: DEFAULT_INTEROP_MESSAGE_EXPIRY_WINDOW, }; diff --git a/rust/kona/crates/protocol/registry/tests/fixtures/custom/configs.json b/rust/kona/crates/protocol/registry/tests/fixtures/custom/configs.json index ad3aa5dfedf..09226f91fcf 100644 --- a/rust/kona/crates/protocol/registry/tests/fixtures/custom/configs.json +++ b/rust/kona/crates/protocol/registry/tests/fixtures/custom/configs.json @@ -19,7 +19,6 @@ "isthmus_time": 1746806401, "jovian_time": 1764691201 }, - "protocol_versions_addr": "0xdedaddeaddeaddeaddeaddeaddeaddeaddeadded", "superchain_config_addr": "0xdedaddeaddeaddeaddeaddeaddeaddeaddeadded", "op_contracts_manager_proxy_addr": null }, diff --git a/rust/kona/crates/protocol/registry/tests/fixtures/custom/depsets.json b/rust/kona/crates/protocol/registry/tests/fixtures/custom/depsets.json new file mode 100644 index 00000000000..c4f09f12d83 --- /dev/null +++ b/rust/kona/crates/protocol/registry/tests/fixtures/custom/depsets.json @@ -0,0 +1,9 @@ +[ + { + "dependencies": { + "123999119": {}, + "223999119": {} + }, + "overrideMessageExpiryWindow": null + } +] diff --git a/rust/kona/crates/providers/providers-alloy/Cargo.toml b/rust/kona/crates/providers/providers-alloy/Cargo.toml index cde04410f4d..9e3dee5e8de 100644 --- a/rust/kona/crates/providers/providers-alloy/Cargo.toml +++ b/rust/kona/crates/providers/providers-alloy/Cargo.toml @@ -21,12 +21,14 @@ kona-macros.workspace = true kona-genesis.workspace = true kona-protocol.workspace = true kona-derive.workspace = true +kona-interop.workspace = true # Alloy alloy-serde.workspace = true alloy-eips = { workspace = true, features = ["kzg"] } alloy-transport.workspace = true alloy-transport-http = { workspace = true, features = ["reqwest", "reqwest-rustls-tls", "hyper", "hyper-tls", "jwt-auth"] } +reqwest = { workspace = true, features = ["query"] } alloy-consensus.workspace = true alloy-rpc-types-beacon.workspace = true alloy-rpc-types-engine.workspace = true @@ -38,6 +40,7 @@ alloy-primitives = { workspace = true, features = ["map"] } op-alloy-consensus.workspace = true op-alloy-network.workspace = true + # Misc lru.workspace = true serde.workspace = true diff --git a/rust/kona/crates/providers/providers-alloy/src/beacon_client.rs b/rust/kona/crates/providers/providers-alloy/src/beacon_client.rs index 9c47546dfd9..ef363ddff2e 100644 --- a/rust/kona/crates/providers/providers-alloy/src/beacon_client.rs +++ b/rust/kona/crates/providers/providers-alloy/src/beacon_client.rs @@ -6,9 +6,9 @@ use crate::blobs::BoxedBlob; use alloy_eips::eip4844::{env_settings::EnvKzgSettings, kzg_to_versioned_hash}; use alloy_primitives::{B256, FixedBytes}; use alloy_rpc_types_beacon::sidecar::GetBlobsResponse; -use alloy_transport_http::reqwest::{self, Client}; use async_trait::async_trait; use c_kzg::Blob; +use reqwest::{self, Client}; use std::{boxed::Box, collections::HashMap, format, string::String, vec::Vec}; use thiserror::Error; diff --git a/rust/kona/crates/providers/providers-alloy/src/pipeline.rs b/rust/kona/crates/providers/providers-alloy/src/pipeline.rs index 66d0dbed902..8772aeddeaf 100644 --- a/rust/kona/crates/providers/providers-alloy/src/pipeline.rs +++ b/rust/kona/crates/providers/providers-alloy/src/pipeline.rs @@ -4,12 +4,12 @@ use crate::{AlloyChainProvider, AlloyL2ChainProvider, OnlineBeaconClient, Online use async_trait::async_trait; use core::fmt::Debug; use kona_derive::{ - DerivationPipeline, EthereumDataSource, IndexedAttributesQueueStage, L2ChainProvider, - OriginProvider, Pipeline, PipelineBuilder, PipelineErrorKind, PipelineResult, - PolledAttributesQueueStage, ResetSignal, Signal, SignalReceiver, StatefulAttributesBuilder, - StepResult, + DerivationPipeline, EthereumDataSource, IndexedAttributesQueueStage, OriginProvider, Pipeline, + PipelineBuilder, PipelineErrorKind, PipelineResult, PolledAttributesQueueStage, ResetSignal, + Signal, SignalReceiver, StatefulAttributesBuilder, StepResult, }; use kona_genesis::{L1ChainConfig, RollupConfig, SystemConfig}; +use kona_interop::DependencySet; use kona_protocol::{BlockInfo, L2BlockInfo, OpAttributesWithParent}; use std::sync::Arc; @@ -54,14 +54,21 @@ pub enum OnlinePipeline { impl OnlinePipeline { /// Constructs a new polled derivation pipeline that is initialized. + /// + /// `dependency_set` must be `Some` when the rollup config schedules the + /// Interop hardfork. The inner [`StatefulAttributesBuilder`] constructor + /// panics otherwise; turning a silent state-divergence bug into a + /// startup crash. + #[allow(clippy::too_many_arguments)] pub async fn new( cfg: Arc, l1_cfg: Arc, l2_safe_head: L2BlockInfo, - l1_origin: BlockInfo, + _l1_origin: BlockInfo, blob_provider: OnlineBlobProvider, chain_provider: AlloyChainProvider, - mut l2_chain_provider: AlloyL2ChainProvider, + l2_chain_provider: AlloyL2ChainProvider, + dependency_set: Option>, ) -> PipelineResult { let mut pipeline = Self::new_polled( cfg.clone(), @@ -69,23 +76,12 @@ impl OnlinePipeline { blob_provider, chain_provider, l2_chain_provider.clone(), + dependency_set, ); // Reset the pipeline to populate the initial L1/L2 cursor and system configuration in L1 // Traversal. - pipeline - .signal( - ResetSignal { - l2_safe_head, - l1_origin, - system_config: l2_chain_provider - .system_config_by_number(l2_safe_head.block_info.number, cfg.clone()) - .await - .ok(), - } - .signal(), - ) - .await?; + pipeline.signal(Signal::Reset(ResetSignal { l2_safe_head })).await?; Ok(pipeline) } @@ -103,12 +99,14 @@ impl OnlinePipeline { blob_provider: OnlineBlobProvider, chain_provider: AlloyChainProvider, l2_chain_provider: AlloyL2ChainProvider, + dependency_set: Option>, ) -> Self { let attributes = StatefulAttributesBuilder::new( cfg.clone(), l1_cfg, l2_chain_provider.clone(), chain_provider.clone(), + dependency_set, ); let dap = EthereumDataSource::new_from_parts(chain_provider.clone(), blob_provider, &cfg); @@ -137,12 +135,14 @@ impl OnlinePipeline { blob_provider: OnlineBlobProvider, chain_provider: AlloyChainProvider, l2_chain_provider: AlloyL2ChainProvider, + dependency_set: Option>, ) -> Self { let attributes = StatefulAttributesBuilder::new( cfg.clone(), l1_cfg, l2_chain_provider.clone(), chain_provider.clone(), + dependency_set, ); let dap = EthereumDataSource::new_from_parts(chain_provider.clone(), blob_provider, &cfg); diff --git a/rust/kona/docker/README.md b/rust/kona/docker/README.md index 63a8223508a..9cc1becfd70 100644 --- a/rust/kona/docker/README.md +++ b/rust/kona/docker/README.md @@ -69,6 +69,55 @@ To manually trigger a nightly build: gh workflow run "Build and Publish Nightly Docker Images" ``` +## Building Kona Prestates + +### Reproducible Build (Docker — recommended for releases) + +```bash +# From repo root +just reproducible-prestate-kona +``` + +### Native Build (Linux — for development) + +#### Prerequisites + +Managed by mise (`mise install` from repo root): rustup, stable Rust, the pinned +dated nightly, Go, just, jq. Both toolchains come from `mise.toml`. + +`just install-nightly` then adds the `rust-src` component to the nightly (needed +for `-Zbuild-std`); `mise.toml` only pulls `rustfmt`. + +**MIPS64 cross-compilation toolchain (manual, apt only):** + +```bash +sudo apt install g++-mips64-linux-gnuabi64 libc6-dev-mips64-cross binutils-mips64-linux-gnuabi64 +``` + +macOS: use the Docker path (`just reproducible-prestate-kona`). + +#### Build + +```bash +cd rust +just build-kona-prestates +``` + +#### Custom Configs + +```bash +export KONA_CUSTOM_CONFIGS_DIR=/path/to/custom/configs +cd rust +just build-kona-prestates +``` + +### cannon-builder Image + +The `cannon-builder` Docker image contains only apt-level MIPS64 cross-compilation +packages. The prestate Dockerfile installs mise on top, and mise pulls Rust (stable ++ nightly), Go, just, and jq from `mise.toml`. The image only needs to be rebuilt +when the cross-compilation toolchain packages change (rare). + ## Cutting a Release (for maintainers / forks) To cut a release of the docker image for any of the targets, cut a new annotated tag for the target like so: diff --git a/rust/kona/docker/apps/README.md b/rust/kona/docker/apps/README.md index 12ad4a0b840..0c0c72c03a1 100644 --- a/rust/kona/docker/apps/README.md +++ b/rust/kona/docker/apps/README.md @@ -57,7 +57,7 @@ target "" { } ``` -The [docker release workflow](../../.github/workflows/docker.yaml) will **first** check if a target is available, +The docker release workflow will **first** check if a target is available, using that target for the docker build. If the workflow can't find the target, it will fallback to the "generic" target specified in the [`docker-bake.hcl`](../docker-bake.hcl). diff --git a/rust/kona/docker/apps/kona_app_generic.dockerfile b/rust/kona/docker/apps/kona_app_generic.dockerfile index fe744381d2b..7ceb3b9b076 100644 --- a/rust/kona/docker/apps/kona_app_generic.dockerfile +++ b/rust/kona/docker/apps/kona_app_generic.dockerfile @@ -18,14 +18,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pkg-config # Install rust -ENV RUST_VERSION=1.92 +ENV RUST_VERSION=1.94 RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --default-toolchain ${RUST_VERSION} --profile minimal ENV PATH="/root/.cargo/bin:${PATH}" # Install cargo-binstall RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash -RUN cargo binstall cargo-chef -y +RUN cargo binstall cargo-chef cargo-auditable -y ################################ # Local Repo Setup Stage # @@ -35,6 +35,12 @@ FROM dep-setup-stage AS app-local-setup-stage # Copy in the local workspace repository COPY . /workspace +# Pull in the NUT bundle JSONs from an additional named build context. The +# kona-hardforks build.rs walks ancestors of CARGO_MANIFEST_DIR looking for an +# op-core/ sibling; placing the bundles at /workspace/op-core/nuts/bundles +# satisfies that walk without widening the primary rust/ context. +COPY --from=nuts-bundles / /workspace/op-core/nuts/bundles + ################################ # Remote Repo Setup Stage # ################################ @@ -44,11 +50,13 @@ SHELL ["/bin/bash", "-c"] ARG TAG ARG REPOSITORY -# Clone kona at the specified tag +# Clone kona at the specified tag. op-core is preserved alongside rust so the +# kona-hardforks build.rs ancestor walk finds the NUT bundles. RUN git clone https://github.com/${REPOSITORY} repo && \ cd repo && \ git checkout "${TAG}" && \ - mv rust /workspace + mv rust /workspace && \ + mv op-core /workspace/op-core ################################ # App Build Stage # @@ -79,11 +87,10 @@ RUN RUSTFLAGS="-C target-cpu=generic" cargo chef cook --bin "${BIN_TARGET}" --pr COPY --from=app-setup /workspace . # Build the application binary on the selected tag. Since we build the external dependencies in the previous step, # this step will reuse the target directory from the previous step. -RUN RUSTFLAGS="-C target-cpu=generic" cargo build --bin "${BIN_TARGET}" --profile "${BUILD_PROFILE}" +RUN RUSTFLAGS="-C target-cpu=generic" cargo auditable build --bin "${BIN_TARGET}" --profile "${BUILD_PROFILE}" # Export stage -FROM ubuntu:22.04 AS export-stage -SHELL ["/bin/bash", "-c"] +FROM chainguard/wolfi-base:latest AS export-stage ARG BIN_TARGET ARG BUILD_PROFILE @@ -92,11 +99,15 @@ ARG BUILD_PROFILE ARG UID=10001 ARG GID=10001 -# Install ca-certificates and libssl-dev for TLS support. -RUN apt-get update && apt-get install -y --no-install-recommends \ +# Install ca-certificates, openssl, libstdc++ for TLS + C++ runtime support. +RUN apk add --no-cache \ ca-certificates \ - libssl-dev \ - && rm -rf /var/lib/apt/lists/* + openssl \ + libstdc++ \ + bash \ + shadow + +RUN update-ca-certificates # Create non-root runtime user RUN groupadd --gid ${GID} app \ diff --git a/rust/kona/docker/cannon/cannon.dockerfile b/rust/kona/docker/cannon/cannon.dockerfile index 14e6135d45b..9e1129f3758 100644 --- a/rust/kona/docker/cannon/cannon.dockerfile +++ b/rust/kona/docker/cannon/cannon.dockerfile @@ -3,8 +3,9 @@ FROM ubuntu:22.04 ENV SHELL=/bin/bash ENV DEBIAN_FRONTEND=noninteractive -ENV RUST_VERSION=nightly-2026-02-20 - +# MIPS64 cross-compilation toolchain — the only thing that must be frozen +# in a Docker image because apt package versions are not deterministically pinnable. +# Everything else (Rust, Go, mise, just) is installed on top from pinned version sources. RUN apt-get update && apt-get install --assume-yes --no-install-recommends \ ca-certificates \ build-essential \ @@ -17,19 +18,3 @@ RUN apt-get update && apt-get install --assume-yes --no-install-recommends \ make \ cmake \ git - -# Install Rustup and Rust -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y --default-toolchain ${RUST_VERSION} --component rust-src -ENV PATH="/root/.cargo/bin:${PATH}" - -# Add the special cannon build target -COPY ./mips64-unknown-none.json . - -# Set up the env vars to instruct rustc to use the correct compiler and linker -# and to build correctly to support the Cannon processor -ENV CC_mips64_unknown_none=mips64-linux-gnuabi64-gcc \ - CXX_mips64_unknown_none=mips64-linux-gnuabi64-g++ \ - CARGO_TARGET_MIPS64_UNKNOWN_NONE_LINKER=mips64-linux-gnuabi64-gcc \ - RUSTFLAGS="-Clink-arg=-e_start -Cllvm-args=-mno-check-zero-division" \ - CARGO_BUILD_TARGET="/mips64-unknown-none.json" \ - RUSTUP_TOOLCHAIN=${RUST_VERSION} diff --git a/rust/kona/docker/cannon/mips64-unknown-none.json b/rust/kona/docker/cannon/mips64-unknown-none.json index 1f8a116149e..5aaf4950357 100644 --- a/rust/kona/docker/cannon/mips64-unknown-none.json +++ b/rust/kona/docker/cannon/mips64-unknown-none.json @@ -5,7 +5,7 @@ "data-layout": "E-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", "target-endian": "big", "target-pointer-width": 64, - "target-c-int-width": 64, + "target-c-int-width": 32, "os": "none", "llvm-abiname": "n64", "features": "+soft-float,+mips64", diff --git a/rust/kona/docker/docker-bake.hcl b/rust/kona/docker/docker-bake.hcl index 90b5cde12a9..b736e4b21df 100644 --- a/rust/kona/docker/docker-bake.hcl +++ b/rust/kona/docker/docker-bake.hcl @@ -78,41 +78,12 @@ target "generic" { // Proof Images // //////////////////////////////////////////////////////////////// -// The path to the monorepo root, used to build cannon from local source. -variable "MONOREPO_CONTEXT" { - default = ".." -} - -// The `kona-client` binary to use in the `kona-cannon-prestate` target. -// -// You can override this if you'd like to use a different `kona-client` binary to generate -// the prestate. -// -// Valid options: -// - `kona` (single-chain) -// - `kona-int` (interop) -variable "CLIENT_BIN" { - default = "kona" - -} - -// Enables custom chain configurations to be built into kona artifacts -variable "KONA_CUSTOM_CONFIGS" { - default = "false" - -} - -// The build context for custom chain configurations to add to the prestate build -variable "CUSTOM_CONFIGS_CONTEXT" { - default = "" - -} - - -// Rust build environment for bare-metal MIPS64r1 (Cannon FPVM ISA) +// Rust build environment for bare-metal MIPS64r1 (Cannon FPVM ISA). +// Contains only apt-level MIPS64 cross-compilation packages. +// Rust, Go, mise, and just are installed on top from pinned version sources. target "cannon-builder" { inherits = ["docker-metadata-action"] - context = "docker/cannon" + context = "kona/docker/cannon" dockerfile = "cannon.dockerfile" args = { HOST_UID = "${HOST_UID}" @@ -120,20 +91,3 @@ target "cannon-builder" { } platforms = split(",", PLATFORMS) } - -// Prestate builder for kona-client with Cannon FPVM -target "kona-cannon-prestate" { - inherits = ["docker-metadata-action"] - context = "." - dockerfile = "kona/docker/fpvm-prestates/cannon-repro.dockerfile" - contexts = { - custom_configs = "${CUSTOM_CONFIGS_CONTEXT}" - monorepo = "${MONOREPO_CONTEXT}" - } - args = { - CLIENT_BIN = "${CLIENT_BIN}" - KONA_CUSTOM_CONFIGS = "${KONA_CUSTOM_CONFIGS}" - } - # Only build on linux/amd64 for a single source of reproducibility. - platforms = ["linux/amd64"] -} diff --git a/rust/kona/docker/fpvm-prestates/README.md b/rust/kona/docker/fpvm-prestates/README.md index 3725488944c..dc2eb27c1c0 100644 --- a/rust/kona/docker/fpvm-prestates/README.md +++ b/rust/kona/docker/fpvm-prestates/README.md @@ -6,23 +6,24 @@ Cannon is built from the local monorepo source. ## Usage -### `kona-client` + `cannon` prestate artifacts +### Build all reproducible `kona-client` + `cannon` prestate artifacts ```sh -# Produce the prestate artifacts for `kona-client` running on `cannon` (built from local monorepo source) -just cannon +# From the monorepo root +just reproducible-prestate-kona ``` -### `kona-client` + `cannon` prestate artifacts with custom output directory +This builds both supported variants: -```sh -just cannon -``` +- `kona-client` into `rust/kona/prestate-artifacts-cannon` +- `kona-client-int` into `rust/kona/prestate-artifacts-cannon-interop` -### `kona-client` + `cannon` prestate artifacts for custom chains +### Build reproducible prestate artifacts for custom chains To create a reproducible kona-client prestate build that supports custom or devnet chain configurations that are not in the superchain-registry: ```sh -just cannon +cd rust +KONA_CUSTOM_CONFIGS_DIR= \ + just build-kona-reproducible-prestate-variant ``` diff --git a/rust/kona/docker/fpvm-prestates/cannon-repro.dockerfile b/rust/kona/docker/fpvm-prestates/cannon-repro.dockerfile index 3540282292a..bf06680fcfd 100644 --- a/rust/kona/docker/fpvm-prestates/cannon-repro.dockerfile +++ b/rust/kona/docker/fpvm-prestates/cannon-repro.dockerfile @@ -1,92 +1,128 @@ +################################################################ +# Reproducible kona prestate build — thin environment wrapper # +# # +# Build logic lives in rust/justfile. # +# This Dockerfile provides a fixed environment and calls it. # +################################################################ + ################################################################ # Build Cannon from local monorepo # ################################################################ -FROM golang:1.24.10-alpine3.21 AS cannon-build +FROM golang:1.24.13-alpine3.22 AS cannon-build RUN apk add --no-cache bash just -# Copy monorepo source needed for the cannon build -COPY --from=monorepo go.mod go.sum /optimism/ -COPY --from=monorepo cannon/ /optimism/cannon/ -COPY --from=monorepo op-service/ /optimism/op-service/ -COPY --from=monorepo op-preimage/ /optimism/op-preimage/ -COPY --from=monorepo justfiles/ /optimism/justfiles/ +COPY go.mod go.sum /app/ +COPY cannon/ /app/cannon/ +COPY op-service/ /app/op-service/ +COPY op-preimage/ /app/op-preimage/ +COPY justfiles/ /app/justfiles/ -# Build cannon from local source -RUN cd /optimism/cannon && \ - just cannon && \ - cp bin/cannon /cannon-bin +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + cd /app/cannon && just cannon ################################################################ -# Build kona-client from local source # +# Build kona-client ELF + generate prestate # ################################################################ -FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon-builder:v1.0.0 AS client-build +FROM us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon-builder:v2.0.0 AS kona-build-env SHELL ["/bin/bash", "-c"] -ARG CLIENT_BIN -ARG KONA_CUSTOM_CONFIGS - -COPY --from=custom_configs / /usr/local/kona-custom-configs - -# Copy kona source from build context -COPY . /kona - -ENV KONA_CUSTOM_CONFIGS=$KONA_CUSTOM_CONFIGS -ENV KONA_CUSTOM_CONFIGS_DIR=/usr/local/kona-custom-configs - -# Build kona-client -RUN cd kona && \ - cargo build -Zbuild-std=core,alloc -Zjson-target-spec -p kona-client --bin $CLIENT_BIN --locked --profile release-client-lto && \ - mv ./target/mips64-unknown-none/release-client-lto/$CLIENT_BIN /kona-client-elf +ARG VARIANT=kona-client + +# --- Layer 1: mise + pinned toolchains --- +# mise's rust plugin bootstraps rustup at a pinned version and installs +# every rust entry from mise.toml (stable 1.94 + the dated nightly). +# All artifacts come from pinned sources — no `curl | sh` from an +# unpinned URL in the reproducible build path. +COPY ops/scripts/install_mise.sh /tmp/install_mise.sh +RUN chmod +x /tmp/install_mise.sh && /tmp/install_mise.sh +ENV PATH="/root/.local/bin:${PATH}" + +# Pin rustup's CARGO_HOME and RUSTUP_HOME to standard locations so the +# cargo registry cache mount below lands where cargo actually reads it. +ENV CARGO_HOME=/root/.cargo +ENV RUSTUP_HOME=/root/.rustup + +COPY mise.toml /app/mise.toml +COPY rust/rust-toolchain.toml /app/rust/rust-toolchain.toml +WORKDIR /app +RUN mise trust && mise install rust go just jq + +# Rustup's cargo/rustc proxies go ahead of mise shims on PATH. Any +# cargo call then resolves to the rustup proxy directly and respects +# RUSTUP_TOOLCHAIN (set by build-kona-client-elf). If it went through +# the mise shim instead, mise would re-set RUSTUP_TOOLCHAIN to the +# active rust from mise.toml (stable 1.94) and build the wrong +# toolchain into the prestate. +ENV PATH="/root/.cargo/bin:/root/.local/share/mise/shims:${PATH}" +ENV MISE_GLOBAL_CONFIG_FILE="/app/mise.toml" + +# --- Layer 2: add rust-src to the dated nightly (needed for -Zbuild-std) --- +# mise.toml's nightly entry only lists rustfmt; just install-nightly adds +# rust-src on top via the rustup that mise just bootstrapped. +COPY rust/justfile /app/rust/justfile +RUN cd /app/rust && just install-nightly + +# --- Layer 3: Rust workspace source --- +COPY rust/Cargo.toml rust/Cargo.lock /app/rust/ +COPY rust/.cargo/ /app/rust/.cargo/ +COPY rust/kona/ /app/rust/kona/ +COPY rust/op-alloy/ /app/rust/op-alloy/ +COPY rust/alloy-op-evm/ /app/rust/alloy-op-evm/ +COPY rust/alloy-op-hardforks/ /app/rust/alloy-op-hardforks/ +COPY rust/op-revm/ /app/rust/op-revm/ +# op-reth and revm-ee-tests are workspace members but not kona-client +# dependencies. We need their Cargo.toml files so the workspace resolves. +COPY rust/op-reth/ /app/rust/op-reth/ +COPY rust/revm-ee-tests/ /app/rust/revm-ee-tests/ + +# kona-hardforks build.rs walks ancestors of CARGO_MANIFEST_DIR for +# op-core/nuts/bundles. Stage the bundles at /app/op-core so the walk +# succeeds inside the rust/-scoped build. +COPY op-core/nuts/bundles /app/op-core/nuts/bundles + +# --- Layer 4: Custom chain configs --- +# Always copy from the named build context. The justfile stages an empty +# temp dir when KONA_CUSTOM_CONFIGS_DIR is unset, so this COPY succeeds in +# both modes without conditional Dockerfile logic. +ARG KONA_CUSTOM_CONFIGS=false +COPY --from=kona-custom-configs / /usr/local/kona-custom-configs +ENV KONA_CUSTOM_CONFIGS=${KONA_CUSTOM_CONFIGS} + +# --- Layer 5: Build kona-client ELF --- +# build-kona-client-elf sets CARGO_BUILD_TARGET to the corrected target spec +# from the source tree, overriding cannon-builder's baked-in spec. +RUN --mount=type=cache,target=/root/.cargo/registry \ + --mount=type=cache,target=/app/rust/target \ + cd /app/rust && \ + if [ "$KONA_CUSTOM_CONFIGS" = "true" ]; then \ + export KONA_CUSTOM_CONFIGS_DIR=/usr/local/kona-custom-configs; \ + fi && \ + just build-kona-client-elf ${VARIANT} && \ + cp /app/rust/target/mips64-unknown-none/release-client-lto/${VARIANT} /app/kona-elf ################################################################ -# Create `prestate.bin.gz` + `prestate-proof.json` # +# Generate prestate # ################################################################ -FROM ubuntu:22.04 AS prestate-build -SHELL ["/bin/bash", "-c"] - -ARG UID=10001 -ARG GID=10001 - -RUN groupadd --gid ${GID} app \ - && useradd --uid ${UID} --gid ${GID} \ - --home-dir /home/app --create-home \ - --shell /usr/sbin/nologin \ - app - -# Use a writable workspace owned by the non-root user -WORKDIR /work -RUN chown ${UID}:${GID} /work - -# Copy cannon binary -COPY --from=cannon-build /cannon-bin /work/cannon - -# Copy kona-client binary -COPY --from=client-build /kona-client-elf /work/kona-client-elf - -# Make the binaries executable -RUN chmod 0555 /work/cannon /work/kona-client-elf - -USER ${UID}:${GID} - -# Create `prestate.bin.gz` -RUN /work/cannon load-elf \ - --path=/work/kona-client-elf \ - --out=/work/prestate.bin.gz \ - --type multithreaded64-5 - -# Create `prestate-proof.json` -RUN /work/cannon run \ - --proof-at "=0" \ - --stop-at "=1" \ - --input /work/prestate.bin.gz \ - --meta /work/meta.json \ - --proof-fmt "/work/%d.json" \ - --output "" && \ - mv /work/0.json /work/prestate-proof.json +FROM kona-build-env AS prestate-build + +COPY --from=cannon-build /app/cannon/bin/cannon /app/cannon +RUN /app/cannon load-elf \ + --path=/app/kona-elf \ + --out=/app/prestate.bin.gz \ + --type multithreaded64-5 && \ + /app/cannon run \ + --proof-at "=0" \ + --stop-at "=1" \ + --input /app/prestate.bin.gz \ + --meta /app/meta.json \ + --proof-fmt "/app/%d.json" \ + --output "" && \ + mv /app/0.json /app/prestate-proof.json ################################################################ # Export Artifacts # @@ -94,8 +130,6 @@ RUN /work/cannon run \ FROM scratch AS export-stage -COPY --from=prestate-build /work/cannon . -COPY --from=prestate-build /work/kona-client-elf . -COPY --from=prestate-build /work/prestate.bin.gz . -COPY --from=prestate-build /work/prestate-proof.json . -COPY --from=prestate-build /work/meta.json . +COPY --from=prestate-build /app/prestate.bin.gz . +COPY --from=prestate-build /app/prestate-proof.json . +COPY --from=prestate-build /app/meta.json . diff --git a/rust/kona/docker/fpvm-prestates/justfile b/rust/kona/docker/fpvm-prestates/justfile deleted file mode 100644 index e31f55c8c0a..00000000000 --- a/rust/kona/docker/fpvm-prestates/justfile +++ /dev/null @@ -1,63 +0,0 @@ -set positional-arguments -alias cannon := build-client-prestate-cannon-artifacts - -# default recipe to display help information -default: - @just --list - -# Build the `kona-client` prestate artifacts from local source (cannon). -build-client-prestate-cannon-artifacts \ - kona_client_variant \ - out='./prestate-artifacts-cannon' \ - custom_config_dir='': - #!/bin/bash - # Resolve output directory to an absolute path before changing directories - OUTPUT_DIR="{{out}}" - if [[ "$OUTPUT_DIR" != /* ]]; then - OUTPUT_DIR="$(pwd)/$OUTPUT_DIR" - fi - - # Docker bake env - export CLIENT_BIN="{{kona_client_variant}}" - export DEFAULT_TAG="kona-cannon-prestate:local" - - # Navigate to rust workspace root - cd ../../.. - - # Set monorepo context (parent of rust workspace root) - export MONOREPO_CONTEXT="$(cd .. && pwd)" - - if [[ -n "{{custom_config_dir}}" ]]; then - export KONA_CUSTOM_CONFIGS="true" - export CUSTOM_CONFIGS_CONTEXT="{{custom_config_dir}}" - if [ ! -d "{{custom_config_dir}}" ]; then - echo "Invalid custom config directory: {{custom_config_dir}}" - exit 1 - fi - echo "Using custom config directory: {{custom_config_dir}}" - else - # set to an empty directory to satisfy the docker build context requirement - TEMP_DIR=$(mktemp -d) - trap "rm -rf $TEMP_DIR" EXIT - export CUSTOM_CONFIGS_CONTEXT="$TEMP_DIR" - fi - - # Create the output directory - mkdir -p $OUTPUT_DIR - - echo "Building kona-client (variant: {{kona_client_variant}}) prestate artifacts for the cannon target (local monorepo cannon)." - - # Build the --allow flag conditionally (requires Docker Buildx v0.15.0+) - ALLOW_FLAG="" - BUILDX_VERSION=$(docker buildx version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "0.0.0") - MAJOR=$(echo "$BUILDX_VERSION" | cut -d. -f1) - MINOR=$(echo "$BUILDX_VERSION" | cut -d. -f2) - if [[ "$MAJOR" -gt 0 ]] || [[ "$MAJOR" -eq 0 && "$MINOR" -ge 15 ]]; then - ALLOW_FLAG="--allow fs=${CUSTOM_CONFIGS_CONTEXT} --allow fs=${MONOREPO_CONTEXT}" - fi - - docker buildx bake \ - --set "*.output=$OUTPUT_DIR" \ - $ALLOW_FLAG \ - -f kona/docker/docker-bake.hcl \ - kona-cannon-prestate diff --git a/rust/kona/docker/recipes/kona-node-dev/grafana/dashboards/overview.json b/rust/kona/docker/recipes/kona-node-dev/grafana/dashboards/overview.json index 13e480f04a8..8dc935ef061 100644 --- a/rust/kona/docker/recipes/kona-node-dev/grafana/dashboards/overview.json +++ b/rust/kona/docker/recipes/kona-node-dev/grafana/dashboards/overview.json @@ -1209,7 +1209,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -1299,7 +1299,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -1389,7 +1389,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -2429,7 +2429,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -3654,7 +3654,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -4410,7 +4410,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -4500,7 +4500,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -6423,154 +6423,6 @@ "title": "Unsafe Block Signer Address", "transparent": true, "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 8, - "x": 8, - "y": 126 - }, - "id": 29, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "name", - "wideLayout": true - }, - "pluginVersion": "11.2.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "kona_node_runtime_loader{instance=~\"$instance\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": true, - "legendFormat": "{{required_protocol_version}}", - "range": false, - "refId": "A", - "useBackend": false - } - ], - "title": "Required Protocol Version", - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 8, - "x": 16, - "y": 126 - }, - "id": 30, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "name", - "wideLayout": true - }, - "pluginVersion": "11.2.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "kona_node_runtime_loader{instance=~\"$instance\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": true, - "legendFormat": "{{recommended_protocol_version}}", - "range": false, - "refId": "A", - "useBackend": false - } - ], - "title": "Recommended Protocol Version", - "transparent": true, - "type": "stat" } ], "title": "Dynamic Protocol Configuration", @@ -7691,84 +7543,6 @@ "title": "L1 System Config Address", "transparent": true, "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Protocol Versions Address", - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-YlRd" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 2, - "w": 12, - "x": 12, - "y": 143 - }, - "id": 59, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": { - "valueSize": 20 - }, - "textMode": "name", - "wideLayout": true - }, - "pluginVersion": "11.2.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "kona_node_rollup_config{instance=~\"$instance\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": true, - "legendFormat": "{{protocol_versions_address}}", - "range": false, - "refId": "A", - "useBackend": false - } - ], - "title": "Protocol Versions Address", - "transparent": true, - "type": "stat" } ], "title": "Rollup Configuration", diff --git a/rust/kona/docker/recipes/kona-node/grafana/dashboards/overview.json b/rust/kona/docker/recipes/kona-node/grafana/dashboards/overview.json index 13e480f04a8..8dc935ef061 100644 --- a/rust/kona/docker/recipes/kona-node/grafana/dashboards/overview.json +++ b/rust/kona/docker/recipes/kona-node/grafana/dashboards/overview.json @@ -1209,7 +1209,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -1299,7 +1299,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -1389,7 +1389,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -2429,7 +2429,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -3654,7 +3654,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -4410,7 +4410,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -4500,7 +4500,7 @@ "color": "rgba(255,0,255,0.7)" }, "filterValues": { - "le": 1e-9 + "le": 1e-09 }, "legend": { "show": true @@ -6423,154 +6423,6 @@ "title": "Unsafe Block Signer Address", "transparent": true, "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 8, - "x": 8, - "y": 126 - }, - "id": 29, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "name", - "wideLayout": true - }, - "pluginVersion": "11.2.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "kona_node_runtime_loader{instance=~\"$instance\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": true, - "legendFormat": "{{required_protocol_version}}", - "range": false, - "refId": "A", - "useBackend": false - } - ], - "title": "Required Protocol Version", - "transparent": true, - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 4, - "w": 8, - "x": 16, - "y": 126 - }, - "id": 30, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "name", - "wideLayout": true - }, - "pluginVersion": "11.2.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "kona_node_runtime_loader{instance=~\"$instance\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": true, - "legendFormat": "{{recommended_protocol_version}}", - "range": false, - "refId": "A", - "useBackend": false - } - ], - "title": "Recommended Protocol Version", - "transparent": true, - "type": "stat" } ], "title": "Dynamic Protocol Configuration", @@ -7691,84 +7543,6 @@ "title": "L1 System Config Address", "transparent": true, "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Protocol Versions Address", - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-YlRd" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 2, - "w": 12, - "x": 12, - "y": 143 - }, - "id": 59, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": { - "valueSize": 20 - }, - "textMode": "name", - "wideLayout": true - }, - "pluginVersion": "11.2.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "exemplar": false, - "expr": "kona_node_rollup_config{instance=~\"$instance\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": true, - "legendFormat": "{{protocol_versions_address}}", - "range": false, - "refId": "A", - "useBackend": false - } - ], - "title": "Protocol Versions Address", - "transparent": true, - "type": "stat" } ], "title": "Rollup Configuration", diff --git a/rust/kona/examples/gossip/Cargo.toml b/rust/kona/examples/gossip/Cargo.toml index 2478bfb40c5..337a50a807d 100644 --- a/rust/kona/examples/gossip/Cargo.toml +++ b/rust/kona/examples/gossip/Cargo.toml @@ -15,7 +15,6 @@ kona-registry.workspace = true libp2p.workspace = true clap = { workspace = true, features = ["derive", "env"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tokio-util.workspace = true tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] } discv5.workspace = true op-alloy-rpc-types-engine = { workspace = true, features = ["std"] } diff --git a/rust/kona/examples/gossip/src/main.rs b/rust/kona/examples/gossip/src/main.rs index 4e67e2ca9ea..38e200c1b8e 100644 --- a/rust/kona/examples/gossip/src/main.rs +++ b/rust/kona/examples/gossip/src/main.rs @@ -33,7 +33,6 @@ use std::{ time::Duration, }; use tokio::sync::mpsc; -use tokio_util::sync::CancellationToken; use tracing::error; use tracing_subscriber::EnvFilter; @@ -106,36 +105,58 @@ impl GossipCommand { LocalNode::new(secret_key, IpAddr::V4(disc_ip), self.disc_port, self.disc_port); let (unsafe_blocks_tx, mut unsafe_blocks_rx) = mpsc::channel(1024); - let (_, network) = NetworkActor::new( + let (signer_tx, signer_rx) = mpsc::channel(16); + let (p2p_rpc_tx, p2p_rpc_rx) = mpsc::channel(1024); + let (admin_rpc_tx, admin_rpc_rx) = mpsc::channel(1024); + let (gossip_payload_tx, gossip_payload_rx) = mpsc::channel(256); + // This example only consumes inbound gossip blocks; the other channels exist solely to + // satisfy NetworkActor::new and are held to keep them open. + let _unused_senders = (signer_tx, p2p_rpc_tx, admin_rpc_tx, gossip_payload_tx); + + let network_config: kona_node_service::NetworkBuilder = NetworkConfig { + discovery_address: disc_addr, + gossip_address: gossip_addr, + unsafe_block_signer: signer, + discovery_config: discv5::ConfigBuilder::new(discv5::ListenConfig::Ipv4 { + ip: disc_ip, + port: self.disc_port, + }) + .build(), + discovery_interval: Duration::from_secs(self.interval), + discovery_randomize: None, + keypair: Keypair::generate_secp256k1(), + gossip_config: Default::default(), + scoring: Default::default(), + topic_scoring: Default::default(), + monitor_peers: Default::default(), + bootstore: None, + gater_config: Default::default(), + bootnodes: Default::default(), + rollup_config: rollup_config.clone(), + gossip_signer: None, + enr_update: true, + } + .into(); + + let handler = network_config.build()?.start().await?; + + let mut network = NetworkActor::new( ForwardingNetworkEngineClient { block_tx: unsafe_blocks_tx }, - CancellationToken::new(), - NetworkConfig { - discovery_address: disc_addr, - gossip_address: gossip_addr, - unsafe_block_signer: signer, - discovery_config: discv5::ConfigBuilder::new(discv5::ListenConfig::Ipv4 { - ip: disc_ip, - port: self.disc_port, - }) - .build(), - discovery_interval: Duration::from_secs(self.interval), - discovery_randomize: None, - keypair: Keypair::generate_secp256k1(), - gossip_config: Default::default(), - scoring: Default::default(), - topic_scoring: Default::default(), - monitor_peers: Default::default(), - bootstore: None, - gater_config: Default::default(), - bootnodes: Default::default(), - rollup_config: rollup_config.clone(), - gossip_signer: None, - enr_update: true, - } - .into(), + handler, + signer_rx, + p2p_rpc_rx, + admin_rpc_rx, + gossip_payload_rx, ); - network.start(()).await?; + tokio::spawn(async move { + loop { + if let Err(e) = network.step().await { + error!(target: "gossip", "Network actor error: {e:?}"); + return; + } + } + }); tracing::info!(target: "gossip", "Gossip driver started, receiving blocks."); loop { diff --git a/rust/kona/justfile b/rust/kona/justfile index aec749755c5..b6b33b03db1 100644 --- a/rust/kona/justfile +++ b/rust/kona/justfile @@ -89,13 +89,34 @@ fmt-native-check: lint-native: fmt-native-check lint-docs cargo clippy --workspace --all-features --all-targets -- -D warnings -# Lint the workspace (mips arch). Currently, only the `kona-std-fpvm` crate is linted for the `cannon` target, as it is the only crate with architecture-specific code. -lint-cannon: - docker run \ - --rm \ - -v {{KONA_ROOT}}/../:/workdir \ - -w="/workdir" \ - us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon-builder:v1.0.0 cargo clippy -p kona-std-fpvm --all-features -Zbuild-std=core,alloc -Zjson-target-spec -- -D warnings +# Build the kona reproducible build environment as a local Docker image. +# Reused by lint-cannon and build-cannon-client. +build-kona-env: + #!/usr/bin/env bash + set -euo pipefail + MONOREPO_ROOT="{{KONA_ROOT}}/../.." + # The Dockerfile unconditionally COPYs from the `kona-custom-configs` + # named context. Lint and build-cannon-client never bake custom configs, + # so point it at an empty temp dir. + EMPTY_CONFIGS_DIR=$(mktemp -d) + trap 'rm -rf "${EMPTY_CONFIGS_DIR}"' EXIT + docker build \ + --platform linux/amd64 \ + --target kona-build-env \ + --build-context kona-custom-configs="${EMPTY_CONFIGS_DIR}" \ + -t kona-cannon-env:local \ + -f "{{KONA_ROOT}}/docker/fpvm-prestates/cannon-repro.dockerfile" \ + "$MONOREPO_ROOT" + +# Lint the workspace (mips arch) inside the reproducible build environment. +# Uses the same Docker environment as the prestate build to validate against the release toolchain. +# Runs `just lint-kona-cannon` inside the container, which sets up the MIPS64 cross-compilation env. +lint-cannon: build-kona-env + docker run --rm \ + -v {{KONA_ROOT}}/../..:/workdir \ + -w /workdir/rust \ + kona-cannon-env:local \ + just lint-kona-cannon # Lint the Rust documentation @@ -111,13 +132,17 @@ build-native *args='': #!/usr/bin/env bash cargo build --workspace $@ -# Build `kona-client` for the `cannon` target. -build-cannon-client: - docker run \ - --rm \ - -v {{KONA_ROOT}}/../:/workdir \ - -w="/workdir" \ - us-docker.pkg.dev/oplabs-tools-artifacts/images/cannon-builder:v1.0.0 cargo build -Zbuild-std=core,alloc -Zjson-target-spec -p kona-client --bin kona-client --profile release-client-lto +# Build `kona-client` for the `cannon` target using the reproducible build environment. +# The bind mount of {{KONA_ROOT}}/../.. exposes the monorepo at /workdir, so the +# kona-hardforks build script's ancestor walk for op-core/nuts/bundles succeeds. +build-cannon-client: build-kona-env + #!/usr/bin/env bash + set -euo pipefail + docker run --rm \ + -v {{KONA_ROOT}}/../..:/workdir \ + -w /workdir/rust \ + kona-cannon-env:local \ + just build-kona-client-elf kona-client # Check for unused dependencies in the crate graph. diff --git a/rust/kona/release.toml b/rust/kona/release.toml index bce80bcdd34..d7039bfa10f 100644 --- a/rust/kona/release.toml +++ b/rust/kona/release.toml @@ -7,4 +7,4 @@ sign-tag = true shared-version = true pre-release-commit-message = "chore: release {{version}}" tag-prefix = "" # tag only once instead of per every crate -pre-release-hook = ["sh", "-c", "$WORKSPACE_ROOT/.config/changelog.sh --tag {{version}}"] +pre-release-hook = ["sh", "-c", "$WORKSPACE_ROOT/kona/.config/changelog.sh --tag {{version}}"] diff --git a/rust/kona/tests/README.md b/rust/kona/tests/README.md index 77b7711123f..308ff4f2106 100644 --- a/rust/kona/tests/README.md +++ b/rust/kona/tests/README.md @@ -46,13 +46,12 @@ just test-e2e-sysgo node node/common simple-kona To run acceptance tests for the Rust stack: ```bash -just acceptance-tests CL_TYPE EL_TYPE GATE +just acceptance-tests CL_TYPE EL_TYPE ``` Where: - `CL_TYPE`: Consensus layer type (`kona` or `op-node`) - `EL_TYPE`: Execution layer type (`op-reth` or `op-geth`) -- `GATE`: The gate to run (default: `jovian`) ### Other Recipes diff --git a/rust/kona/tests/justfile b/rust/kona/tests/justfile index 6139e49d132..7f0b3a4c545 100644 --- a/rust/kona/tests/justfile +++ b/rust/kona/tests/justfile @@ -15,15 +15,15 @@ build-devnet BINARY: build-reth: #!/bin/bash - cd {{SOURCE}}/../../reth && cargo build --bin op-reth -p op-reth + cd {{SOURCE}}/../../op-reth && cargo build --bin op-reth -p op-reth build-kona PROFILE="release": #!/bin/bash cd {{SOURCE}}/.. && cargo build --bin kona-node --profile {{PROFILE}} -acceptance-tests CL_TYPE="kona" EL_TYPE="op-reth" GATE="jovian": +acceptance-tests CL_TYPE="kona-node" EL_TYPE="op-reth": #!/bin/bash - if [ "{{CL_TYPE}}" = "kona" ] ; then + if [ "{{CL_TYPE}}" = "kona-node" ] ; then echo "Building kona-node..." just build-kona fi @@ -33,28 +33,27 @@ acceptance-tests CL_TYPE="kona" EL_TYPE="op-reth" GATE="jovian": just build-reth fi - just acceptance-tests-run {{CL_TYPE}} {{EL_TYPE}} {{GATE}} + just acceptance-tests-run {{CL_TYPE}} {{EL_TYPE}} # Run acceptance tests for the rust stack. By default runs the acceptance tests for kona-node with op-reth. -# Uses the jovian gate by default. -acceptance-tests-run CL_TYPE="kona" EL_TYPE="op-reth" GATE="jovian": +acceptance-tests-run CL_TYPE="kona-node" EL_TYPE="op-reth": #!/bin/bash - if [ "{{CL_TYPE}}" = "kona" ] ; then + if [ "{{CL_TYPE}}" = "kona-node" ] ; then echo "Running acceptance tests for kona-node" export KONA_NODE_EXEC_PATH="{{SOURCE}}/../target/debug/kona-node" fi if [ "{{EL_TYPE}}" = "op-reth" ] ; then echo "Running acceptance tests for op-reth" - export OP_RETH_EXEC_PATH="{{SOURCE}}/../../reth/target/debug/op-reth" + export OP_RETH_EXEC_PATH="{{SOURCE}}/../../target/debug/op-reth" fi - echo "Running acceptance tests for {{CL_TYPE}} with {{EL_TYPE}} on gate {{GATE}}..." + echo "Running acceptance tests for {{CL_TYPE}} with {{EL_TYPE}}..." export DEVSTACK_L2CL_KIND="{{CL_TYPE}}" export DEVSTACK_L2EL_KIND="{{EL_TYPE}}" export LOG_LEVEL="debug" - cd {{SOURCE}}/optimism/op-acceptance-tests && just acceptance-test "" "{{GATE}}" + cd {{SOURCE}}/optimism/op-acceptance-tests && just acceptance-test # Run the e2e tests for the sysgo orchestrator. # Builds the kona-node and op-reth binaries and runs the tests. @@ -83,7 +82,7 @@ test-e2e-sysgo-run BINARY="node" GO_PKG_NAME="node/common" DEVNET="simple-kona" export KONA_NODE_EXEC_PATH="{{SOURCE}}/../target/debug/kona-node" fi if [ $OP_RETH_EXEC_PATH == "" ]; then - export OP_RETH_EXEC_PATH="{{SOURCE}}/../../reth/target/debug/op-reth" + export OP_RETH_EXEC_PATH="{{SOURCE}}/../../target/debug/op-reth" fi else echo "Invalid BINARY specified. Must be 'node'." @@ -154,7 +153,7 @@ long-running-test FILTER="" OUTPUT_LOGS_DIR="": export OP_VALIDATOR_WITH_GETH=0 export KONA_NODE_EXEC_PATH="{{SOURCE}}/../target/debug/kona-node" - export OP_RETH_EXEC_PATH="{{SOURCE}}/../../reth/target/debug/op-reth" + export OP_RETH_EXEC_PATH="{{SOURCE}}/../../target/debug/op-reth" export DEVSTACK_ORCHESTRATOR=sysgo echo "Building kona-node..." just build-kona @@ -174,8 +173,13 @@ long-running-test FILTER="" OUTPUT_LOGS_DIR="": # Run the test with count=1 to avoid caching the test results. cd {{SOURCE}} && go test -count=1 -timeout 0 -v ./node/long-running $FILTER -# Run action tests for the single-chain client program on the native target -action-tests-single test_name='Test_ProgramAction' *args='': action-tests-build (action-tests-single-run test_name args) +# Run action tests for the single-chain client program on the native target. +# +# Runs every Test* in op-e2e/actions/proofs/. Tests that exercise op-program-only +# behavior (and aren't expected to pass under kona-host) opt themselves out by +# calling helpers.SkipIfKona(t) at the top — the runner sets KONA_HOST_PATH, +# which the helper detects. +action-tests-single test_name='.*' *args='': action-tests-build (action-tests-single-run test_name args) # Build action tests for host program action-tests-build: @@ -185,10 +189,11 @@ action-tests-build: just build-native --release --bin kona-host # Run action tests for the single-chain client program on the native target -action-tests-single-run test_name='Test_ProgramAction' parallel="0" *args='': +action-tests-single-run test_name='.*' parallel="0" *args='': #!/bin/bash + set -euo pipefail - if [ ! -n "$KONA_HOST_PATH" ]; then + if [ -z "${KONA_HOST_PATH:-}" ]; then export KONA_HOST_PATH="{{SOURCE}}/../target/release/kona-host" fi @@ -198,7 +203,12 @@ action-tests-single-run test_name='Test_ProgramAction' parallel="0" *args='': # https://github.com/gotestyourself/gotestsum/blob/b4b13345fee56744d80016a20b760d3599c13504/testjson/format.go#L442-L444 echo "Running action tests for the client program on the native target" - cd {{SOURCE}}/../../op-e2e/actions/proofs + PROOFS_DIR="{{SOURCE}}/../../../op-e2e/actions/proofs" + if [ ! -d "$PROOFS_DIR" ]; then + echo "Error: proofs directory not found: $PROOFS_DIR" >&2 + exit 1 + fi + cd "$PROOFS_DIR" # Set parallel to the number of cores available if {{parallel}} is not greater than 0 if [ {{parallel}} -gt 0 ]; then @@ -210,38 +220,59 @@ action-tests-single-run test_name='Test_ProgramAction' parallel="0" *args='': echo "Running tests with $PARALLEL cores" # CircleCI parallelism - if [ -n "$CIRCLE_NODE_TOTAL" ] && [ "$CIRCLE_NODE_TOTAL" -gt 1 ]; then - echo "Setting up test directories..." + if [ -n "${CIRCLE_NODE_TOTAL:-}" ] && [ "${CIRCLE_NODE_TOTAL:-0}" -gt 1 ]; then + echo "Setting up test directories..." mkdir -p ./tmp/test-results ./tmp/testlogs export NODE_INDEX=${CIRCLE_NODE_INDEX:-0} export NODE_TOTAL=${CIRCLE_NODE_TOTAL:-1} - go test -list '^{{test_name}}' > /tmp/tests.txt + + # List matching test names, filtering out the "ok" summary line from go test -list. + # Op-program-only tests opt themselves out at runtime via helpers.SkipIfKona(t), + # so no name-based exclusion is needed here. + go test -list '{{test_name}}' . | grep -E '^Test' > /tmp/tests.txt + + TOTAL_TESTS=$(wc -l < /tmp/tests.txt | tr -d ' ') + if [ "$TOTAL_TESTS" -eq 0 ]; then + echo "Error: no tests matched '{{test_name}}'" >&2 + exit 1 + fi + echo "Found $TOTAL_TESTS tests to split across $NODE_TOTAL nodes" + circleci tests split --split-by=name /tmp/tests.txt > /tmp/tests_shard.txt - echo "Node $NODE_INDEX/$NODE_TOTAL running tests: $(cat /tmp/tests_shard.txt)" - xargs -n 1 -I {} gotestsum --format=testname \ + SHARD_COUNT=$(wc -l < /tmp/tests_shard.txt | tr -d ' ') + echo "Node $NODE_INDEX/$NODE_TOTAL running $SHARD_COUNT tests: $(cat /tmp/tests_shard.txt)" + + if [ "$SHARD_COUNT" -eq 0 ]; then + echo "No tests assigned to this node, skipping." + exit 0 + fi + + xargs -I {} gotestsum --format=testname \ --junitfile=./tmp/test-results/results-$NODE_INDEX.xml \ --jsonfile=./tmp/testlogs/log-$NODE_INDEX.json \ - -- -count=1 -parallel=$PARALLEL -coverprofile=coverage-$NODE_INDEX.out -timeout=60m -run '^{}$' \ + -- -count=1 -parallel=$PARALLEL -timeout=60m -run '^{}$' \ < /tmp/tests_shard.txt + # xargs calls gotestsum multiple times appending to the same jsonfile, # so split once at the end rather than wrapping each call. {{SOURCE}}/../../../ops/scripts/split-test-logs.sh ./tmp/testlogs/log-$NODE_INDEX.json - exit 0 + exit fi {{SOURCE}}/../../../ops/scripts/gotestsum-split.sh --format=testname \ --junitfile=./tmp/test-results/results.xml \ --jsonfile=./tmp/testlogs/log.json \ - -- -count=1 -parallel=$PARALLEL -coverprofile=coverage.out -timeout=60m -run "{{test_name}}" + -- -count=1 -parallel=$PARALLEL -timeout=60m -run "{{test_name}}" # Run action tests for the interop client program on the native target action-tests-interop test_name='TestInteropFaultProofs' *args='': action-tests-build (action-tests-interop-run test_name args) action-tests-interop-run test_name='TestInteropFaultProofs' *args='': #!/bin/bash + set -euo pipefail echo "Building host program for the native target" just build-native --bin kona-host @@ -251,7 +282,7 @@ action-tests-interop-run test_name='TestInteropFaultProofs' *args='': # https://github.com/gotestyourself/gotestsum/blob/b4b13345fee56744d80016a20b760d3599c13504/testjson/format.go#L442-L444 echo "Running action tests for the client program on the native target" - cd {{SOURCE}}/../../op-e2e/actions/interop && GITHUB_ACTIONS=false {{SOURCE}}/../../../ops/scripts/gotestsum-split.sh --format=short-verbose -- -count=1 -timeout 60m -run "{{test_name}}" {{args}} + cd {{SOURCE}}/../../../op-e2e/actions/interop && GITHUB_ACTIONS=false {{SOURCE}}/../../../ops/scripts/gotestsum-split.sh --format=short-verbose -- -count=1 -timeout 60m -run "{{test_name}}" {{args}} update-packages: #!/bin/bash diff --git a/rust/kona/tests/node/common/engine_test.go b/rust/kona/tests/node/common/engine_test.go index 5852b3fc392..df4fbfec2ad 100644 --- a/rust/kona/tests/node/common/engine_test.go +++ b/rust/kona/tests/node/common/engine_test.go @@ -6,7 +6,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" "github.com/stretchr/testify/require" ) @@ -29,7 +30,7 @@ func TestEngine(gt *testing.T) { go func() { defer close(done) // Wait for 40 unsafe blocks to be produced. - node.Advanced(types.LocalUnsafe, 40, 100) + node.Advanced(safety.LocalUnsafe, 40, 100) }() queueLens := node_utils.GetDevWS(t, node, "engine_queue_size", done) diff --git a/rust/kona/tests/node/common/p2p_test.go b/rust/kona/tests/node/common/p2p_test.go index 562e9249061..3a7b4305930 100644 --- a/rust/kona/tests/node/common/p2p_test.go +++ b/rust/kona/tests/node/common/p2p_test.go @@ -7,7 +7,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/apis" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" ) @@ -67,7 +68,7 @@ func TestP2PMinimal(gt *testing.T) { konaNodeId := secondNode.PeerInfo().PeerID // Wait for a few blocks to be produced. - dsl.CheckAll(t, secondNode.ReachedFn(types.LocalUnsafe, 40, 80), firstNode.ReachedFn(types.LocalUnsafe, 40, 80)) + dsl.CheckAll(t, secondNode.ReachedFn(safety.LocalUnsafe, 40, 80), firstNode.ReachedFn(safety.LocalUnsafe, 40, 80)) // Check that the nodes are connected to each other. arePeers(t, &firstNode, konaNodeId) diff --git a/rust/kona/tests/node/common/rpc_test.go b/rust/kona/tests/node/common/rpc_test.go index 560067cbdf3..eb4f50d1df3 100644 --- a/rust/kona/tests/node/common/rpc_test.go +++ b/rust/kona/tests/node/common/rpc_test.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/apis" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" - "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -171,10 +170,6 @@ func rollupConfig(t devtest.T, node *dsl.L2CLNode) *rollup.Config { } func rollupConfigMatches(t devtest.T, configA *rollup.Config, configB *rollup.Config) { - // ProtocolVersionsAddress is deprecated in kona-node while not yet removed from the op-node. - configA.ProtocolVersionsAddress = common.Address{} - configB.ProtocolVersionsAddress = common.Address{} - require.Equal(t, configA, configB, "rollup config mismatch") } diff --git a/rust/kona/tests/node/common/sync_test.go b/rust/kona/tests/node/common/sync_test.go index 5dd8d53ff53..598f7cb8ea2 100644 --- a/rust/kona/tests/node/common/sync_test.go +++ b/rust/kona/tests/node/common/sync_test.go @@ -5,7 +5,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" ) @@ -21,8 +22,8 @@ func TestL2SafeSync(gt *testing.T) { checkFuns := make([]dsl.CheckFunc, 0, 2*len(nodes)) for _, node := range nodes { - checkFuns = append(checkFuns, node.ReachedFn(types.LocalSafe, 20, 40)) - checkFuns = append(checkFuns, node_utils.MatchedWithinRange(t, node, sequencer, 5, types.LocalSafe, 100)) + checkFuns = append(checkFuns, node.ReachedFn(safety.LocalSafe, 20, 40)) + checkFuns = append(checkFuns, node_utils.MatchedWithinRange(t, node, sequencer, 5, safety.LocalSafe, 100)) } dsl.CheckAll(t, checkFuns...) @@ -39,7 +40,7 @@ func TestL2UnsafeSync(gt *testing.T) { checkFuns := make([]dsl.CheckFunc, 0, len(nodes)) for _, node := range nodes { - checkFuns = append(checkFuns, node.ReachedFn(types.LocalUnsafe, 40, 80)) + checkFuns = append(checkFuns, node.ReachedFn(safety.LocalUnsafe, 40, 80)) } dsl.CheckAll(t, checkFuns...) @@ -57,7 +58,7 @@ func TestL2FinalizedSync(gt *testing.T) { checkFuns := make([]dsl.CheckFunc, 0, len(nodes)) for _, node := range nodes { - checkFuns = append(checkFuns, node.ReachedFn(types.Finalized, 10, 600)) + checkFuns = append(checkFuns, node.ReachedFn(safety.Finalized, 10, 600)) } dsl.CheckAll(t, checkFuns...) diff --git a/rust/kona/tests/node/common/sync_ws_test.go b/rust/kona/tests/node/common/sync_ws_test.go index 499d10a8966..8a442e95260 100644 --- a/rust/kona/tests/node/common/sync_ws_test.go +++ b/rust/kona/tests/node/common/sync_ws_test.go @@ -8,7 +8,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" "github.com/stretchr/testify/require" ) @@ -31,8 +32,8 @@ func TestSyncUnsafeBecomesSafe(gt *testing.T) { // Ensure that all the nodes advance the unsafe and safe head advancedFns := make([]dsl.CheckFunc, 0, len(nodes)) for _, node := range nodes { - advancedFns = append(advancedFns, node.AdvancedFn(types.LocalSafe, 20, 80)) - advancedFns = append(advancedFns, node.AdvancedFn(types.LocalUnsafe, 20, 80)) + advancedFns = append(advancedFns, node.AdvancedFn(safety.LocalSafe, 20, 80)) + advancedFns = append(advancedFns, node.AdvancedFn(safety.LocalUnsafe, 20, 80)) } dsl.CheckAll(t, advancedFns...) @@ -85,7 +86,7 @@ func TestSyncUnsafe(gt *testing.T) { // Ensure that all the nodes advance the unsafe head advancedFns := make([]dsl.CheckFunc, 0, len(nodes)) for _, node := range nodes { - advancedFns = append(advancedFns, node.AdvancedFn(types.LocalUnsafe, 20, 80)) + advancedFns = append(advancedFns, node.AdvancedFn(safety.LocalUnsafe, 20, 80)) } dsl.CheckAll(t, advancedFns...) @@ -103,7 +104,7 @@ func TestSyncUnsafe(gt *testing.T) { for _, block := range output { for _, node := range nodes { otherCLNode := node.Escape().Name() - otherCLSyncStatus := node.ChainSyncStatus(out.L2Chain.ChainID(), types.LocalUnsafe) + otherCLSyncStatus := node.ChainSyncStatus(out.L2Chain.ChainID(), safety.LocalUnsafe) if otherCLSyncStatus.Number < block.Number { t.Log("✗ peer too far behind!", otherCLNode, block.Number, otherCLSyncStatus.Number) @@ -136,7 +137,7 @@ func TestSyncSafe(gt *testing.T) { // Ensure that all the nodes advance the safe head advancedFns := make([]dsl.CheckFunc, 0, len(nodes)) for _, node := range nodes { - advancedFns = append(advancedFns, node.AdvancedFn(types.LocalSafe, 20, 80)) + advancedFns = append(advancedFns, node.AdvancedFn(safety.LocalSafe, 20, 80)) } dsl.CheckAll(t, advancedFns...) @@ -155,7 +156,7 @@ func TestSyncSafe(gt *testing.T) { for _, block := range output { for _, node := range nodes { otherCLNode := node.Escape().Name() - otherCLSyncStatus := node.ChainSyncStatus(out.L2Chain.ChainID(), types.LocalSafe) + otherCLSyncStatus := node.ChainSyncStatus(out.L2Chain.ChainID(), safety.LocalSafe) if otherCLSyncStatus.Number < block.Number { t.Log("✗ peer too far behind!", otherCLNode, block.Number, otherCLSyncStatus.Number) @@ -202,7 +203,7 @@ func TestSyncFinalized(gt *testing.T) { for _, block := range output { for _, node := range nodes { otherCLNode := node.Escape().Name() - otherCLSyncStatus := node.ChainSyncStatus(out.L2Chain.ChainID(), types.Finalized) + otherCLSyncStatus := node.ChainSyncStatus(out.L2Chain.ChainID(), safety.Finalized) if otherCLSyncStatus.Number < block.Number { t.Log("✗ peer too far behind!", otherCLNode, block.Number, otherCLSyncStatus.Number) diff --git a/rust/kona/tests/node/reorgs/l2_reorg_after_l1_reorgs_test.go b/rust/kona/tests/node/reorgs/l2_reorg_after_l1_reorgs_test.go index ebb65688cdf..4d0a89a809f 100644 --- a/rust/kona/tests/node/reorgs/l2_reorg_after_l1_reorgs_test.go +++ b/rust/kona/tests/node/reorgs/l2_reorg_after_l1_reorgs_test.go @@ -8,7 +8,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/apis" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" "github.com/ethereum/go-ethereum/common" @@ -106,7 +107,7 @@ func testL2ReorgAfterL1Reorg(gt *testing.T, n int, preChecks, postChecks checksF // wait until L2 chain cross-safe ref caught up to where it was before the reorg var waitFunc []dsl.CheckFunc for _, clNode := range sys.L2CLNodes() { - waitFunc = append(waitFunc, clNode.ReachedFn(types.CrossSafe, tipL2_preReorg.Number, 200)) + waitFunc = append(waitFunc, clNode.ReachedFn(safety.CrossSafe, tipL2_preReorg.Number, 200)) } dsl.CheckAll(t, waitFunc...) diff --git a/rust/kona/tests/node/reorgs/l2_reorg_test.go b/rust/kona/tests/node/reorgs/l2_reorg_test.go index 457c78a6287..b2342b2dacc 100644 --- a/rust/kona/tests/node/reorgs/l2_reorg_test.go +++ b/rust/kona/tests/node/reorgs/l2_reorg_test.go @@ -8,7 +8,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/txplan" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" "github.com/stretchr/testify/require" @@ -32,7 +33,7 @@ func TestL2Reorg(gt *testing.T) { // Wait for the nodes to advance a little bit for _, node := range out.L2CLNodes() { - advancedFnsPreReorg = append(advancedFnsPreReorg, node.AdvancedFn(types.LocalUnsafe, 20, 40)) + advancedFnsPreReorg = append(advancedFnsPreReorg, node.AdvancedFn(safety.LocalUnsafe, 20, 40)) } dsl.CheckAll(t, advancedFnsPreReorg...) @@ -42,7 +43,7 @@ func TestL2Reorg(gt *testing.T) { advancedFnsReorgedBlocks := make([]dsl.CheckFunc, 0, len(out.L2CLNodes())) // Wait for the nodes to advance a little bit more ahead the unsafe head for _, node := range out.L2CLNodes() { - advancedFnsReorgedBlocks = append(advancedFnsReorgedBlocks, node.AdvancedFn(types.LocalUnsafe, NUM_BLOCKS_TO_REORG, 2*NUM_BLOCKS_TO_REORG)) + advancedFnsReorgedBlocks = append(advancedFnsReorgedBlocks, node.AdvancedFn(safety.LocalUnsafe, NUM_BLOCKS_TO_REORG, 2*NUM_BLOCKS_TO_REORG)) } dsl.CheckAll(t, advancedFnsReorgedBlocks...) @@ -56,7 +57,7 @@ func TestL2Reorg(gt *testing.T) { // Ensure that all the nodes still advance even after the reorg for _, node := range out.L2CLNodes() { - checksPostReorg = append(checksPostReorg, node.AdvancedFn(types.LocalUnsafe, 20, 40)) + checksPostReorg = append(checksPostReorg, node.AdvancedFn(safety.LocalUnsafe, 20, 40)) } reorgFun := func() error { @@ -133,7 +134,7 @@ func TestL2Reorg(gt *testing.T) { // Ensure the current unsafe head is ahead of the reorg head for _, node := range out.L2CLNodes() { - require.Greater(t, node.HeadBlockRef(types.LocalUnsafe).Number, unsafeHead.Number) + require.Greater(t, node.HeadBlockRef(safety.LocalUnsafe).Number, unsafeHead.Number) } // Ensure that bob has the funds diff --git a/rust/kona/tests/node/restart/conn_drop_test.go b/rust/kona/tests/node/restart/conn_drop_test.go index 3cbf942333a..512a902ceec 100644 --- a/rust/kona/tests/node/restart/conn_drop_test.go +++ b/rust/kona/tests/node/restart/conn_drop_test.go @@ -6,7 +6,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" ) @@ -24,7 +25,7 @@ func TestConnDropSync(gt *testing.T) { // Ensure that the nodes are advancing. var preCheckFuns []dsl.CheckFunc for _, node := range out.L2CLNodes() { - preCheckFuns = append(preCheckFuns, node.AdvancedFn(types.LocalSafe, 20, 100), node.AdvancedFn(types.LocalUnsafe, 20, 100)) + preCheckFuns = append(preCheckFuns, node.AdvancedFn(safety.LocalSafe, 20, 100), node.AdvancedFn(safety.LocalUnsafe, 20, 100)) } dsl.CheckAll(t, preCheckFuns...) @@ -48,7 +49,7 @@ func TestConnDropSync(gt *testing.T) { t.Require().NotEqual(peer.PeerID, sequencer.PeerInfo().PeerID, "expected node %s to be disconnected from sequencer %s", clName, sequencer.Escape().Name()) } - currentUnsafeHead := node.ChainSyncStatus(node.ChainID(), types.LocalUnsafe) + currentUnsafeHead := node.ChainSyncStatus(node.ChainID(), safety.LocalUnsafe) endSignal := make(chan struct{}) @@ -83,10 +84,10 @@ func TestConnDropSync(gt *testing.T) { // - the node's unsafe head is advancing (through consolidation) // - the node's safe head's number is catching up with the unsafe head's number // - the node's unsafe head is strictly lagging behind the sequencer's unsafe head - postDisconnectCheckFuns = append(postDisconnectCheckFuns, node.AdvancedFn(types.LocalSafe, 50, 200), node.AdvancedFn(types.LocalUnsafe, 50, 200), check) + postDisconnectCheckFuns = append(postDisconnectCheckFuns, node.AdvancedFn(safety.LocalSafe, 50, 200), node.AdvancedFn(safety.LocalUnsafe, 50, 200), check) } - postDisconnectCheckFuns = append(postDisconnectCheckFuns, sequencer.AdvancedFn(types.LocalUnsafe, 50, 200)) + postDisconnectCheckFuns = append(postDisconnectCheckFuns, sequencer.AdvancedFn(safety.LocalUnsafe, 50, 200)) dsl.CheckAll(t, postDisconnectCheckFuns...) @@ -113,7 +114,7 @@ func TestConnDropSync(gt *testing.T) { t.Require().True(found, "expected node %s to be connected to reference node %s", clName, sequencer.Escape().Name()) // Check that the node is resyncing with the unsafe head network - postReconnectCheckFuns = append(postReconnectCheckFuns, node_utils.MatchedWithinRange(t, node, sequencer, 3, types.LocalSafe, 50), node.AdvancedFn(types.LocalUnsafe, 50, 100), node_utils.MatchedWithinRange(t, node, sequencer, 3, types.LocalUnsafe, 100)) + postReconnectCheckFuns = append(postReconnectCheckFuns, node_utils.MatchedWithinRange(t, node, sequencer, 3, safety.LocalSafe, 50), node.AdvancedFn(safety.LocalUnsafe, 50, 100), node_utils.MatchedWithinRange(t, node, sequencer, 3, safety.LocalUnsafe, 100)) } dsl.CheckAll(t, postReconnectCheckFuns...) diff --git a/rust/kona/tests/node/restart/restart_test.go b/rust/kona/tests/node/restart/restart_test.go index 239b1a71d06..d5567b0f44a 100644 --- a/rust/kona/tests/node/restart/restart_test.go +++ b/rust/kona/tests/node/restart/restart_test.go @@ -10,7 +10,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/retry" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" ) @@ -30,7 +31,7 @@ func TestRestartSync(gt *testing.T) { // Ensure that the nodes are advancing. var preCheckFuns []dsl.CheckFunc for _, node := range out.L2CLNodes() { - preCheckFuns = append(preCheckFuns, node.AdvancedFn(types.LocalSafe, 20, 100), node.AdvancedFn(types.LocalUnsafe, 20, 100)) + preCheckFuns = append(preCheckFuns, node.AdvancedFn(safety.LocalSafe, 20, 100), node.AdvancedFn(safety.LocalUnsafe, 20, 100)) } dsl.CheckAll(t, preCheckFuns...) @@ -64,7 +65,7 @@ func TestRestartSync(gt *testing.T) { t.Require().Error(err, "expected node %s to be stopped", clName) } - sequencer.Advanced(types.LocalUnsafe, 50, 200) + sequencer.Advanced(safety.LocalUnsafe, 50, 200) var postStartCheckFuns []dsl.CheckFunc for _, node := range nodes { @@ -75,7 +76,7 @@ func TestRestartSync(gt *testing.T) { node.ConnectPeer(&sequencer) // Check that the node is resyncing with the network - postStartCheckFuns = append(postStartCheckFuns, node_utils.MatchedWithinRange(t, node, sequencer, 3, types.LocalSafe, 100), node_utils.MatchedWithinRange(t, node, sequencer, 3, types.LocalUnsafe, 100)) + postStartCheckFuns = append(postStartCheckFuns, node_utils.MatchedWithinRange(t, node, sequencer, 3, safety.LocalSafe, 100), node_utils.MatchedWithinRange(t, node, sequencer, 3, safety.LocalUnsafe, 100)) // Check that the node is connected to the reference node peers := node.Peers() diff --git a/rust/kona/tests/node/restart/sequencer_restart_test.go b/rust/kona/tests/node/restart/sequencer_restart_test.go index 7ea4a93d8a8..0287a1c61f6 100644 --- a/rust/kona/tests/node/restart/sequencer_restart_test.go +++ b/rust/kona/tests/node/restart/sequencer_restart_test.go @@ -8,7 +8,8 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-service/retry" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) func TestSequencerRestart(gt *testing.T) { @@ -27,7 +28,7 @@ func TestSequencerRestart(gt *testing.T) { // Let's ensure that all the nodes are properly advancing. var preCheckFuns []dsl.CheckFunc for _, node := range nodes { - preCheckFuns = append(preCheckFuns, node.LaggedFn(&sequencer, types.CrossUnsafe, 20, true), node.AdvancedFn(types.LocalSafe, 20, 40)) + preCheckFuns = append(preCheckFuns, node.LaggedFn(&sequencer, safety.CrossUnsafe, 20, true), node.AdvancedFn(safety.LocalSafe, 20, 40)) } dsl.CheckAll(t, preCheckFuns...) @@ -51,7 +52,7 @@ func TestSequencerRestart(gt *testing.T) { // Ensure that the other nodes are not advancing. // The local safe head may advance (for the next l1 block to be processed), but the unsafe head should not. - stopCheckFuns = append(stopCheckFuns, node.NotAdvancedFn(types.LocalUnsafe, 50)) + stopCheckFuns = append(stopCheckFuns, node.NotAdvancedFn(safety.LocalUnsafe, 20)) } dsl.CheckAll(t, stopCheckFuns...) @@ -71,7 +72,7 @@ func TestSequencerRestart(gt *testing.T) { t.Logf("Waiting for nodes to advance") var postCheckFuns []dsl.CheckFunc for _, node := range nodes { - postCheckFuns = append(postCheckFuns, node.AdvancedFn(types.LocalSafe, 10, 100), node.AdvancedFn(types.LocalUnsafe, 10, 100)) + postCheckFuns = append(postCheckFuns, node.AdvancedFn(safety.LocalSafe, 10, 100), node.AdvancedFn(safety.LocalUnsafe, 10, 100)) } dsl.CheckAll(t, postCheckFuns...) diff --git a/rust/kona/tests/node/restart/setup_test.go b/rust/kona/tests/node/restart/setup_test.go index 984062e7b3d..91744cca689 100644 --- a/rust/kona/tests/node/restart/setup_test.go +++ b/rust/kona/tests/node/restart/setup_test.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/sysgo" + "github.com/ethereum-optimism/optimism/op-service/eth" oplog "github.com/ethereum-optimism/optimism/op-service/log" node_utils "github.com/ethereum-optimism/optimism/rust/kona/tests/node/utils" ) @@ -42,7 +43,9 @@ func TestMain(m *testing.M) { sharedRestartRuntime = node_utils.NewSharedMixedOpKonaRuntimeForConfig(pkg, node_utils.L2NodeConfig{ KonaSequencerNodesWithGeth: 1, KonaNodesWithGeth: 1, - }) + }, sysgo.WithL2BlockTimes(map[eth.ChainID]uint64{ + sysgo.DefaultL2AID: 1, + })) code = m.Run() }() diff --git a/rust/kona/tests/node/utils/mod.go b/rust/kona/tests/node/utils/mod.go index 3443119dd9a..bcb1ae7d795 100644 --- a/rust/kona/tests/node/utils/mod.go +++ b/rust/kona/tests/node/utils/mod.go @@ -10,7 +10,8 @@ import ( "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/retry" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + + safety "github.com/ethereum-optimism/optimism/op-service/eth/safety" ) const DefaultL1ID = 900 @@ -35,7 +36,7 @@ func SendRPCRequest[T any](clientRPC client.RPC, method string, resOutput *T, pa return clientRPC.CallContext(ctx, &resOutput, method, params...) } -func MatchedWithinRange(t devtest.T, baseNode, refNode dsl.L2CLNode, delta uint64, lvl types.SafetyLevel, attempts int) dsl.CheckFunc { +func MatchedWithinRange(t devtest.T, baseNode, refNode dsl.L2CLNode, delta uint64, lvl safety.Level, attempts int) dsl.CheckFunc { logger := t.Logger() chainID := baseNode.ChainID() diff --git a/rust/kona/tests/node/utils/package_scope.go b/rust/kona/tests/node/utils/package_scope.go index 1ceb94a463b..b2478081cef 100644 --- a/rust/kona/tests/node/utils/package_scope.go +++ b/rust/kona/tests/node/utils/package_scope.go @@ -162,8 +162,9 @@ func NewSharedMixedOpKonaRuntime(pkg devtest.P) *sysgo.MixedSingleChainRuntime { return NewSharedMixedOpKonaRuntimeForConfig(pkg, ParseL2NodeConfigFromEnv()) } -func NewSharedMixedOpKonaRuntimeForConfig(pkg devtest.P, cfg L2NodeConfig) *sysgo.MixedSingleChainRuntime { +func NewSharedMixedOpKonaRuntimeForConfig(pkg devtest.P, cfg L2NodeConfig, deployerOpts ...sysgo.DeployerOption) *sysgo.MixedSingleChainRuntime { return sysgo.NewMixedSingleChainRuntime(newPackageScopeT(pkg), sysgo.MixedSingleChainPresetConfig{ - NodeSpecs: mixedOpKonaNodeSpecs(cfg), + NodeSpecs: mixedOpKonaNodeSpecs(cfg), + DeployerOptions: deployerOpts, }) } diff --git a/rust/kona/tests/supervisor/l2reorg/init_exec_msg_test.go b/rust/kona/tests/supervisor/l2reorg/init_exec_msg_test.go deleted file mode 100644 index 235f3060235..00000000000 --- a/rust/kona/tests/supervisor/l2reorg/init_exec_msg_test.go +++ /dev/null @@ -1,247 +0,0 @@ -package l2reorg - -import ( - "math/rand" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" - "github.com/ethereum-optimism/optimism/op-core/predeploys" - "github.com/ethereum-optimism/optimism/op-devstack/devtest" - "github.com/ethereum-optimism/optimism/op-devstack/dsl" - "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - "github.com/ethereum-optimism/optimism/op-service/bigs" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/txintent" - "github.com/ethereum-optimism/optimism/op-service/txintent/bindings" - "github.com/ethereum-optimism/optimism/op-service/txplan" - "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/stretchr/testify/require" -) - -// Acceptance Test: https://github.com/ethereum-optimism/optimism/blob/develop/op-acceptance-tests/tests/interop/reorgs/init_exec_msg_test.go#L25 -func TestReorgInitExecMsg(gt *testing.T) { - t := devtest.SerialT(gt) - ctx := t.Ctx() - - sys := presets.NewSimpleInterop(t) - l := sys.Log - - ia := sys.TestSequencer.Escape().ControlAPI(sys.L2ChainA.ChainID()) - - // three EOAs for triggering the init and exec interop txs, as well as a simple transfer tx - alice := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) - bob := sys.FunderB.NewFundedEOA(eth.OneHundredthEther) - cathrine := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) - - sys.L1Network.WaitForBlock() - sys.L2ChainA.WaitForBlock() - - // stop batchers on chain A and on chain B - sys.L2BatcherA.Stop() - sys.L2BatcherB.Stop() - - // deploy event logger on chain A - var eventLoggerAddress common.Address - { - tx := txplan.NewPlannedTx(txplan.Combine( - alice.Plan(), - txplan.WithData(common.FromHex(bindings.EventloggerBin)), - )) - res, err := tx.Included.Eval(ctx) - require.NoError(t, err) - - eventLoggerAddress = res.ContractAddress - l.Info("deployed EventLogger", "chainID", tx.ChainID.Value(), "address", eventLoggerAddress) - } - - sys.L1Network.WaitForBlock() - - var initTrigger *txintent.InitTrigger - // prepare init trigger (i.e. what logs to emit on chain A) - { - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - nTopics := 3 - lenData := 10 - initTrigger = interop.RandomInitTrigger(rng, eventLoggerAddress, nTopics, lenData) - - l.Info("created init trigger", "address", eventLoggerAddress, "topics", nTopics, "lenData", lenData) - } - - // wait for chain B to catch up to chain A if necessary - sys.L2ChainB.CatchUpTo(sys.L2ChainA) - - var initTx *txintent.IntentTx[*txintent.InitTrigger, *txintent.InteropOutput] - var initReceipt *types.Receipt - // prepare and include initiating message on chain A - { - initTx = txintent.NewIntent[*txintent.InitTrigger, *txintent.InteropOutput](alice.Plan()) - initTx.Content.Set(initTrigger) - var err error - initReceipt, err = initTx.PlannedTx.Included.Eval(ctx) - require.NoError(t, err) - - l.Info("initiating message included", "chain", sys.L2ChainA.ChainID(), "block_number", initReceipt.BlockNumber, "block_hash", initReceipt.BlockHash, "now", time.Now().Unix()) - } - - // stop sequencer on chain A so that we later force a reorg/removal of the init msg - sys.L2CLA.StopSequencer() - - // at least one block between the init tx on chain A and the exec tx on chain B - sys.L2ChainB.WaitForBlock() - - var execTx *txintent.IntentTx[*txintent.ExecTrigger, *txintent.InteropOutput] - var execReceipt *types.Receipt - // prepare and include executing message on chain B - { - execTx = txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](bob.Plan()) - execTx.Content.DependOn(&initTx.Result) - // single event in tx so index is 0. ExecuteIndexed returns a lambda to transform InteropOutput to a new ExecTrigger - execTx.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initTx.Result, 0)) - var err error - execReceipt, err = execTx.PlannedTx.Included.Eval(ctx) - require.NoError(t, err) - require.Equal(t, 1, len(execReceipt.Logs)) - - l.Info("executing message included", "chain", sys.L2ChainB.ChainID(), "block_number", execReceipt.BlockNumber, "block_hash", execReceipt.BlockHash, "now", time.Now().Unix()) - } - - // record divergence block numbers and original refs for future validation checks - var divergenceBlockNumber_A, divergenceBlockNumber_B uint64 - var originalRef_A, originalRef_B eth.L2BlockRef - - // sequence a conflicting block with a simple transfer tx, based on the parent of the parent of the unsafe head - { - var err error - divergenceBlockNumber_B = bigs.Uint64Strict(execReceipt.BlockNumber) - originalRef_B, err = sys.L2ELB.Escape().L2EthClient().L2BlockRefByHash(ctx, execReceipt.BlockHash) - require.NoError(t, err, "Expected to be able to call L2BlockRefByHash API, but got error") - - headToReorgA := initReceipt.BlockHash - headToReorgARef, err := sys.L2ELA.Escape().L2EthClient().L2BlockRefByHash(ctx, headToReorgA) - require.NoError(t, err, "Expected to be able to call L2BlockRefByHash API, but got error") - - divergenceBlockNumber_A = headToReorgARef.Number - originalRef_A = headToReorgARef - - parentOfHeadToReorgA := headToReorgARef.ParentID() - parentsL1Origin, err := sys.L2ELA.Escape().L2EthClient().L2BlockRefByHash(ctx, parentOfHeadToReorgA.Hash) - require.NoError(t, err, "Expected to be able to call L2BlockRefByHash API, but got error") - - nextL1Origin := parentsL1Origin.L1Origin.Number + 1 - l1Origin, err := sys.L1EL.EthClient().InfoByNumber(ctx, nextL1Origin) - require.NoError(t, err, "Expected to get block number %v from L1 execution client", nextL1Origin) - l1OriginHash := l1Origin.Hash() - - l.Info("Sequencing a conflicting block", "chain", sys.L2ChainA.ChainID(), "newL1Origin", eth.ToBlockID(l1Origin), "headToReorgA", headToReorgARef, "parent", parentOfHeadToReorgA, "parent_l1_origin", parentsL1Origin.L1Origin) - - err = ia.New(ctx, seqtypes.BuildOpts{ - Parent: parentOfHeadToReorgA.Hash, - L1Origin: &l1OriginHash, - }) - require.NoError(t, err, "Expected to be able to create a new block job for sequencing on op-test-sequencer, but got error") - - // include simple transfer tx in opened block - { - to := cathrine.PlanTransfer(alice.Address(), eth.OneGWei) - opt := txplan.Combine(to) - ptx := txplan.NewPlannedTx(opt) - signed_tx, err := ptx.Signed.Eval(ctx) - require.NoError(t, err, "Expected to be able to evaluate a planned transaction on op-test-sequencer, but got error") - txdata, err := signed_tx.MarshalBinary() - require.NoError(t, err, "Expected to be able to marshal a signed transaction on op-test-sequencer, but got error") - - err = ia.IncludeTx(ctx, txdata) - require.NoError(t, err, "Expected to be able to include a signed transaction on op-test-sequencer, but got error") - } - - err = ia.Next(ctx) - require.NoError(t, err, "Expected to be able to call Next() after New() on op-test-sequencer, but got error") - } - - // sequence a second block with op-test-sequencer - { - unsafe := sys.L2ELA.BlockRefByLabel(eth.Unsafe) - l.Info("Current unsafe ref", "unsafeHead", unsafe) - err := ia.New(ctx, seqtypes.BuildOpts{ - Parent: unsafe.Hash, - L1Origin: nil, - }) - require.NoError(t, err, "Expected to be able to create a new block job for sequencing on op-test-sequencer, but got error") - - err = ia.Next(ctx) - require.NoError(t, err, "Expected to be able to call Next() after New() on op-test-sequencer, but got error") - } - - // continue sequencing with op-node - sys.L2CLA.StartSequencer() - - // start batchers on chain A and on chain B - sys.L2BatcherA.Start() - sys.L2BatcherB.Start() - - // wait and confirm reorgs on chain A and B - dsl.CheckAll(t, - sys.L2ELA.ReorgExactFn(eth.L2BlockRef{ - Number: divergenceBlockNumber_A, - Hash: originalRef_A.Hash, - ParentHash: originalRef_A.ParentID().Hash, - }, 30), - sys.L2ELB.ReorgExactFn(eth.L2BlockRef{ - Number: divergenceBlockNumber_B, - Hash: originalRef_B.Hash, - ParentHash: originalRef_B.ParentID().Hash, - }, 30), - ) - - // executing tx should eventually be no longer confirmed on chain B - require.Eventually(t, func() bool { - receipt, err := sys.L2ELB.Escape().EthClient().TransactionReceipt(ctx, execReceipt.TxHash) - if err == nil || err.Error() != "not found" { // want to get "not found" error - return false - } - if receipt != nil { // want to get nil receipt - return false - } - return true - }, 60*time.Second, 3*time.Second, "Expected for the executing tx to be removed from chain B") - - err := wait.For(ctx, 5*time.Second, func() (bool, error) { - safeL2Head_supervisor_A := sys.Supervisor.SafeBlockID(sys.L2ChainA.ChainID()).Hash - safeL2Head_supervisor_B := sys.Supervisor.SafeBlockID(sys.L2ChainB.ChainID()).Hash - safeL2Head_sequencer_A := sys.L2CLA.SafeL2BlockRef() - safeL2Head_sequencer_B := sys.L2CLB.SafeL2BlockRef() - - if safeL2Head_sequencer_A.Number < divergenceBlockNumber_A { - l.Info("Safe ref number is still behind divergence block A number", "divergence", divergenceBlockNumber_A, "safe", safeL2Head_sequencer_A.Number) - return false, nil - } - - if safeL2Head_sequencer_B.Number < divergenceBlockNumber_B { - l.Info("Safe ref number is still behind divergence block B number", "divergence", divergenceBlockNumber_B, "safe", safeL2Head_sequencer_B.Number) - return false, nil - } - - if safeL2Head_sequencer_A.Hash.Cmp(safeL2Head_supervisor_A) != 0 { - l.Info("Safe ref still not the same on supervisor and sequencer A", "supervisor", safeL2Head_supervisor_A, "sequencer", safeL2Head_sequencer_A.Hash) - return false, nil - } - - if safeL2Head_sequencer_B.Hash.Cmp(safeL2Head_supervisor_B) != 0 { - l.Info("Safe ref still not the same on supervisor and sequencer B", "supervisor", safeL2Head_supervisor_B, "sequencer", safeL2Head_sequencer_B.Hash) - return false, nil - } - - l.Info("Safe ref the same across supervisor and sequencers", - "supervisor_A", safeL2Head_supervisor_A, - "sequencer_A", safeL2Head_sequencer_A.Hash, - "supervisor_B", safeL2Head_supervisor_B, - "sequencer_B", safeL2Head_sequencer_B.Hash) - - return true, nil - }) - require.NoError(t, err, "Expected to get same safe ref on both supervisor and sequencer eventually") -} diff --git a/rust/kona/tests/supervisor/l2reorgAfterL1reorg/reorg_test.go b/rust/kona/tests/supervisor/l2reorgAfterL1reorg/reorg_test.go deleted file mode 100644 index f253f5c81c2..00000000000 --- a/rust/kona/tests/supervisor/l2reorgAfterL1reorg/reorg_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package sysgo - -import ( - "testing" - "time" - - "github.com/ethereum-optimism/optimism/op-devstack/devtest" - "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-service/apis" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" - spresets "github.com/ethereum-optimism/optimism/rust/kona/tests/supervisor/presets" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -type checksFunc func(t devtest.T, sys *presets.SimpleInterop) - -func TestL2ReorgAfterL1Reorg(gt *testing.T) { - gt.Run("unsafe reorg", func(gt *testing.T) { - var crossSafeRef, localSafeRef, unsafeRef eth.BlockID - pre := func(t devtest.T, sys *presets.SimpleInterop) { - ss := sys.Supervisor.FetchSyncStatus() - crossSafeRef = ss.Chains[sys.L2ChainA.ChainID()].CrossSafe - localSafeRef = ss.Chains[sys.L2ChainA.ChainID()].LocalSafe - unsafeRef = ss.Chains[sys.L2ChainA.ChainID()].LocalUnsafe.ID() - } - post := func(t devtest.T, sys *presets.SimpleInterop) { - require.True(t, sys.L2ELA.IsCanonical(crossSafeRef), "Previous cross-safe block should still be canonical") - require.True(t, sys.L2ELA.IsCanonical(localSafeRef), "Previous local-safe block should still be canonical") - require.False(t, sys.L2ELA.IsCanonical(unsafeRef), "Previous unsafe block should have been reorged") - } - testL2ReorgAfterL1Reorg(gt, 3, pre, post) - }) - - gt.Run("unsafe, local-safe, cross-unsafe, cross-safe reorgs", func(gt *testing.T) { - var crossSafeRef, crossUnsafeRef, localSafeRef, unsafeRef eth.BlockID - pre := func(t devtest.T, sys *presets.SimpleInterop) { - ss := sys.Supervisor.FetchSyncStatus() - crossUnsafeRef = ss.Chains[sys.L2ChainA.ChainID()].CrossUnsafe - crossSafeRef = ss.Chains[sys.L2ChainA.ChainID()].CrossSafe - localSafeRef = ss.Chains[sys.L2ChainA.ChainID()].LocalSafe - unsafeRef = ss.Chains[sys.L2ChainA.ChainID()].LocalUnsafe.ID() - } - post := func(t devtest.T, sys *presets.SimpleInterop) { - require.False(t, sys.L2ELA.IsCanonical(crossSafeRef), "Previous cross-safe block should have been reorged") - require.False(t, sys.L2ELA.IsCanonical(crossUnsafeRef), "Previous cross-unsafe block should have been reorged") - require.False(t, sys.L2ELA.IsCanonical(localSafeRef), "Previous local-safe block should have been reorged") - require.False(t, sys.L2ELA.IsCanonical(unsafeRef), "Previous unsafe block should have been reorged") - } - testL2ReorgAfterL1Reorg(gt, 10, pre, post) - }) -} - -// testL2ReorgAfterL1Reorg tests that the L2 chain reorgs after an L1 reorg, and takes n, number of blocks to reorg, as parameter -// for unsafe reorgs - n must be at least >= confDepth, which is 2 in our test deployments -// for cross-safe reorgs - n must be at least >= safe distance, which is 10 in our test deployments (set in -// op-e2e/e2eutils/geth/geth.go when initialising FakePoS) -// pre- and post-checks are sanity checks to ensure that the blocks we expected to be reorged were indeed reorged or not -func testL2ReorgAfterL1Reorg(gt *testing.T, n int, preChecks, postChecks checksFunc) { - t := devtest.SerialT(gt) - ctx := t.Ctx() - - sys := spresets.NewSimpleInteropMinimal(t) - ts := sys.TestSequencer.Escape().ControlAPI(sys.L1Network.ChainID()) - - sys.L1Network.WaitForBlock() - - sys.L1CL.Stop() - - // sequence a few L1 and L2 blocks - for range n + 1 { - sequenceL1Block(t, ts, common.Hash{}) - - sys.L2ChainA.WaitForBlock() - sys.L2ChainA.WaitForBlock() - } - - // select a divergence block to reorg from - var divergence eth.L1BlockRef - { - tip := sys.L1EL.BlockRefByLabel(eth.Unsafe) - require.Greater(t, tip.Number, uint64(n), "n is larger than L1 tip, cannot reorg out block number `tip-n`") - - divergence = sys.L1EL.BlockRefByNumber(tip.Number - uint64(n)) - } - - // print the chains before sequencing an alternative L1 block - sys.L2ChainA.PrintChain() - sys.L1Network.PrintChain() - - // pre reorg trigger validations and checks - preChecks(t, sys) - - tipL2_preReorg := sys.L2ELA.BlockRefByLabel(eth.Unsafe) - - // reorg the L1 chain -- sequence an alternative L1 block from divergence block parent - sequenceL1Block(t, ts, divergence.ParentHash) - - // continue building on the alternative L1 chain - sys.L1CL.Start() - - // confirm L1 reorged - sys.L1EL.ReorgTriggered(divergence, 5) - - // wait until L2 chain A cross-safe ref caught up to where it was before the reorg - sys.L2CLA.Reached(types.CrossSafe, tipL2_preReorg.Number, 50) - - // test that latest chain A unsafe is not referencing a reorged L1 block (through the L1Origin field) - require.Eventually(t, func() bool { - unsafe := sys.L2ELA.BlockRefByLabel(eth.Unsafe) - - block, err := sys.L1EL.Escape().EthClient().InfoByNumber(ctx, unsafe.L1Origin.Number) - if err != nil { - sys.Log.Warn("failed to get L1 block info by number", "number", unsafe.L1Origin.Number, "err", err) - return false - } - - sys.Log.Info("current unsafe ref", "tip", unsafe, "tip_origin", unsafe.L1Origin, "l1blk", eth.InfoToL1BlockRef(block)) - - // print the chains so we have information to debug if the test fails - sys.L2ChainA.PrintChain() - sys.L1Network.PrintChain() - - return block.Hash() == unsafe.L1Origin.Hash - }, 120*time.Second, 7*time.Second, "L1 block origin hash should match hash of block on L1 at that number. If not, it means there was a reorg, and L2 blocks L1Origin field is referencing a reorged block.") - - // confirm all L1Origin fields point to canonical blocks - require.Eventually(t, func() bool { - ref := sys.L2ELA.BlockRefByLabel(eth.Unsafe) - var err error - - // wait until L2 chains' L1Origin points to a L1 block after the one that was reorged - if ref.L1Origin.Number < divergence.Number { - return false - } - - sys.Log.Info("L2 chain progressed, pointing to newer L1 block", "ref", ref, "ref_origin", ref.L1Origin, "divergence", divergence) - - for i := ref.Number; i > 0 && ref.L1Origin.Number >= divergence.Number; i-- { - ref, err = sys.L2ELA.Escape().L2EthClient().L2BlockRefByNumber(ctx, i) - if err != nil { - return false - } - - if !sys.L1EL.IsCanonical(ref.L1Origin) { - return false - } - } - - return true - }, 120*time.Second, 5*time.Second, "all L1Origin fields should point to canonical L1 blocks") - - // post reorg test validations and checks - postChecks(t, sys) -} - -func sequenceL1Block(t devtest.T, ts apis.TestSequencerControlAPI, parent common.Hash) { - require.NoError(t, ts.New(t.Ctx(), seqtypes.BuildOpts{Parent: parent})) - require.NoError(t, ts.Next(t.Ctx())) -} diff --git a/rust/kona/tests/supervisor/pre_interop/helpers_test.go b/rust/kona/tests/supervisor/pre_interop/helpers_test.go deleted file mode 100644 index 9b93b79fb13..00000000000 --- a/rust/kona/tests/supervisor/pre_interop/helpers_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package preinterop - -import ( - "github.com/ethereum-optimism/optimism/op-devstack/devtest" - "github.com/ethereum-optimism/optimism/op-devstack/presets" - spresets "github.com/ethereum-optimism/optimism/rust/kona/tests/supervisor/presets" -) - -func newMinimalPreInterop(t devtest.T) *presets.SimpleInterop { - return spresets.NewSimpleInteropMinimal(t, - spresets.WithSuggestedInteropActivationOffset(30), - spresets.WithInteropNotAtGenesis(), - ) -} diff --git a/rust/kona/tests/supervisor/pre_interop/post_test.go b/rust/kona/tests/supervisor/pre_interop/post_test.go deleted file mode 100644 index 04424557756..00000000000 --- a/rust/kona/tests/supervisor/pre_interop/post_test.go +++ /dev/null @@ -1,208 +0,0 @@ -package preinterop - -import ( - "math/rand" - "testing" - "time" - - "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" - "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" - "github.com/ethereum-optimism/optimism/op-core/forks" - "github.com/ethereum-optimism/optimism/op-core/predeploys" - "github.com/ethereum-optimism/optimism/op-devstack/devtest" - "github.com/ethereum-optimism/optimism/op-devstack/dsl" - "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-service/eth" - stypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - "github.com/ethereum/go-ethereum/common" -) - -func testSupervisorActivationBlock(t devtest.T, sys *presets.SimpleInterop, net *dsl.L2Network, activationBlock eth.BlockID) { - require := t.Require() - - // wait for some time to ensure the interop activation block is become cross-safe - t.Logger().Info("Waiting for interop activation block to be cross-safe") - sys.Supervisor.WaitForL2HeadToAdvanceTo(net.ChainID(), stypes.CrossSafe, activationBlock) - - interopTime := net.Escape().ChainConfig().InteropTime - pre := net.LatestBlockBeforeTimestamp(t, *interopTime) - require.NotNil(pre, "Pre-interop block should be found before interop time") - - // make sure pre-interop block is parent of activation block - require.Equal(pre.Number, activationBlock.Number-1, "Activation block should be one after pre-interop block") - - // fetching the source for the pre-interop block should return the error - // this is to make sure that we only store the blocks after interop - _, err := sys.Supervisor.Escape().QueryAPI().CrossDerivedToSource(t.Ctx(), net.ChainID(), pre.ID()) - require.Error(err, "CrossDerivedToSource should error before interop") - - // fetch the source for the activation block - derivedFrom, err := sys.Supervisor.Escape().QueryAPI().CrossDerivedToSource(t.Ctx(), net.ChainID(), activationBlock) - require.NoError(err, "CrossDerivedToSource should not error after interop") - require.NotNil(derivedFrom, "CrossDerivedToSource should return a valid source block") -} - -// Acceptance Test: https://github.com/ethereum-optimism/optimism/blob/develop/op-acceptance-tests/tests/interop/upgrade/post_test.go -// test case modified to check the correctness of the supervisor activation block as well -func TestPostInbox(gt *testing.T) { - t := devtest.ParallelT(gt) - sys := newMinimalPreInterop(t) - devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { - require := t.Require() - activationBlock := net.AwaitActivation(t, forks.Interop) - - el := net.PrimaryEL() - implAddrBytes, err := el.EthClient().GetStorageAt(t.Ctx(), predeploys.CrossL2InboxAddr, - genesis.ImplementationSlot, activationBlock.Hash.String()) - require.NoError(err) - implAddr := common.BytesToAddress(implAddrBytes[:]) - require.NotEqual(common.Address{}, implAddr) - code, err := el.EthClient().CodeAtHash(t.Ctx(), implAddr, activationBlock.Hash) - require.NoError(err) - require.NotEmpty(code) - - testSupervisorActivationBlock(t, sys, net, activationBlock) - }) -} - -// Acceptance Test: https://github.com/ethereum-optimism/optimism/blob/develop/op-acceptance-tests/tests/interop/upgrade/post_test.go -func TestPostInteropUpgradeComprehensive(gt *testing.T) { - t := devtest.ParallelT(gt) - sys := newMinimalPreInterop(t) - require := t.Require() - logger := t.Logger() - - // Wait for networks to be online by waiting for blocks - sys.L1Network.WaitForBlock() - sys.L2ChainA.WaitForBlock() - sys.L2ChainB.WaitForBlock() - - // Get interop activation time - interopTime := sys.L2ChainA.Escape().ChainConfig().InteropTime - require.NotNil(interopTime, "InteropTime must be set") - - logger.Info("Starting comprehensive post-interop upgrade tests", "interopTime", *interopTime) - - // 1. Check that anchor block of supervisor matches the activation block - logger.Info("Checking supervisor anchor block matches activation block") - testSupervisorAnchorBlock(t, sys) - - // 2. Check that the supervisor has safety progression for each level - logger.Info("Checking supervisor safety progression") - testSupervisorSafetyProgression(t, sys) - - // 3. Confirms that interop message can be included - logger.Info("Testing interop message inclusion") - testInteropMessageInclusion(t, sys) - - logger.Info("All comprehensive post-interop upgrade tests completed successfully") -} - -// testSupervisorAnchorBlock checks that the supervisor's anchor block has been set and matches the upgrade timestamp -func testSupervisorAnchorBlock(t devtest.T, sys *presets.SimpleInterop) { - logger := t.Logger() - - // Use the DSL helper for anchor block validation - logger.Info("Testing supervisor anchor block functionality") - - // Phase 1: Wait for L2 chains to reach interop activation time - logger.Info("Phase 1: Waiting for L2 chains to reach interop activation time") - - devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { - - // Gate test to not time out before upgrade happens - forkTimestamp := net.Escape().ChainConfig().InteropTime - t.Gate().NotNil(forkTimestamp, "Must have fork configured") - t.Gate().Greater(*forkTimestamp, uint64(0), "Must not start fork at genesis") - upgradeTime := time.Unix(int64(*forkTimestamp), 0) - if deadline, hasDeadline := t.Deadline(); hasDeadline { - t.Gate().True(upgradeTime.Before(deadline), "test must not time out before upgrade happens") - } - - activationBlock := net.AwaitActivation(t, forks.Interop) - sys.Supervisor.WaitForL2HeadToAdvanceTo(net.ChainID(), stypes.CrossSafe, activationBlock) - - logger.Info("Validating anchor block timing", - "chainID", net.ChainID(), - "derivedBlockNumber", activationBlock.Number, - "interopTime", *forkTimestamp) - }) - - logger.Info("Supervisor anchor block validation completed successfully") -} - -// testSupervisorSafetyProgression checks that supervisor has safety progression for each level -func testSupervisorSafetyProgression(t devtest.T, sys *presets.SimpleInterop) { - logger := t.Logger() - logger.Info("Testing supervisor safety progression") - - delta := uint64(3) // Minimum blocks of progression expected - dsl.CheckAll(t, - sys.L2CLA.AdvancedFn(stypes.LocalUnsafe, delta, 30), - sys.L2CLB.AdvancedFn(stypes.LocalUnsafe, delta, 30), - - sys.L2CLA.AdvancedFn(stypes.LocalSafe, delta, 30), - sys.L2CLB.AdvancedFn(stypes.LocalSafe, delta, 30), - - sys.L2CLA.AdvancedFn(stypes.CrossUnsafe, delta, 30), - sys.L2CLB.AdvancedFn(stypes.CrossUnsafe, delta, 30), - - sys.L2CLA.AdvancedFn(stypes.CrossSafe, delta, 60), - sys.L2CLB.AdvancedFn(stypes.CrossSafe, delta, 60), - ) - - logger.Info("Supervisor safety progression validation completed successfully") -} - -// testInteropMessageInclusion confirms that interop messages can be included post-upgrade -func testInteropMessageInclusion(t devtest.T, sys *presets.SimpleInterop) { - logger := t.Logger() - logger.Info("Starting interop message inclusion test") - - // Phase 1: Setup test accounts and contracts - alice, bob, eventLoggerAddress := setupInteropTestEnvironment(sys) - - // Phase 2: Send init message on chain A - rng := rand.New(rand.NewSource(1234)) - initMsg := alice.SendInitMessage(interop.RandomInitTrigger(rng, eventLoggerAddress, rng.Intn(5), rng.Intn(30))) - - // Make sure supervisor indexes block which includes init message - sys.Supervisor.WaitForUnsafeHeadToAdvance(alice.ChainID(), 2) - - // Single event in tx so index is 0 - execMsg := bob.SendExecMessage(initMsg) - - // Phase 5: Verify cross-safe progression - verifyInteropMessagesProgression(t, sys, initMsg, execMsg) - - logger.Info("Interop message inclusion test completed successfully") -} - -// setupInteropTestEnvironment creates test accounts and deploys necessary contracts -func setupInteropTestEnvironment(sys *presets.SimpleInterop) (alice, bob *dsl.EOA, eventLoggerAddress common.Address) { - - // Create EOAs for interop messaging - alice = sys.FunderA.NewFundedEOA(eth.OneHundredthEther) - bob = sys.FunderB.NewFundedEOA(eth.OneHundredthEther) - - // Deploy event logger contract on chain A - eventLoggerAddress = alice.DeployEventLogger() - - // Wait for chains to catch up - sys.L2ChainB.CatchUpTo(sys.L2ChainA) - - return alice, bob, eventLoggerAddress -} - -// verifyInteropMessagesProgression verifies cross-safe progression for both init and exec messages -func verifyInteropMessagesProgression(t devtest.T, sys *presets.SimpleInterop, initMsg *dsl.InitMessage, execMsg *dsl.ExecMessage) { - logger := t.Logger() - - // Verify cross-safe progression for both messages - dsl.CheckAll(t, - sys.L2CLA.ReachedRefFn(stypes.CrossSafe, initMsg.BlockID(), 60), - sys.L2CLB.ReachedRefFn(stypes.CrossSafe, execMsg.BlockID(), 60), - ) - - logger.Info("Cross-safe progression verified for both init and exec messages") -} diff --git a/rust/kona/tests/supervisor/pre_interop/pre_test.go b/rust/kona/tests/supervisor/pre_interop/pre_test.go deleted file mode 100644 index 869c62e7279..00000000000 --- a/rust/kona/tests/supervisor/pre_interop/pre_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package preinterop - -import ( - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - - "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" - "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" - "github.com/ethereum-optimism/optimism/op-core/predeploys" - "github.com/ethereum-optimism/optimism/op-devstack/devtest" - "github.com/ethereum-optimism/optimism/op-devstack/dsl" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/txintent" - stypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" -) - -// Acceptance Test: https://github.com/ethereum-optimism/optimism/blob/develop/op-acceptance-tests/tests/interop/upgrade/pre_test.go -func TestPreNoInbox(gt *testing.T) { - t := devtest.ParallelT(gt) - sys := newMinimalPreInterop(t) - require := t.Require() - - t.Logger().Info("Starting") - - devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { - interopTime := net.Escape().ChainConfig().InteropTime - t.Require().NotNil(interopTime) - pre := net.LatestBlockBeforeTimestamp(t, *interopTime) - el := net.PrimaryEL() - codeAddr := common.HexToAddress("0xC0D3C0d3C0D3C0d3c0d3c0D3c0D3C0d3C0D30022") - implCode, err := el.EthClient().CodeAtHash(t.Ctx(), codeAddr, pre.Hash) - require.NoError(err) - require.Len(implCode, 0, "needs to be empty") - implAddrBytes, err := el.EthClient().GetStorageAt(t.Ctx(), predeploys.CrossL2InboxAddr, - genesis.ImplementationSlot, pre.Hash.String()) - require.NoError(err) - require.Equal(common.Address{}, common.BytesToAddress(implAddrBytes[:])) - }) - - // try access the sync-status of the supervisor, assert that the sync-status returns the expected error - devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { - interopTime := net.Escape().ChainConfig().InteropTime - - _, err := sys.Supervisor.Escape().QueryAPI().SyncStatus(t.Ctx()) - require.ErrorContains(err, "supervisor status tracker not ready") - - // confirm we are still pre-interop - require.False(net.IsActivated(*interopTime)) - t.Logger().Info("Timestamps", "interopTime", *interopTime, "now", time.Now().Unix()) - }) - - var initMsg *dsl.InitMessage - // try interop before the upgrade, confirm that messages do not get included - { - // two EOAs for triggering the init and exec interop txs - alice := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) - bob := sys.FunderB.NewFundedEOA(eth.OneHundredthEther) - - interopTimeA := sys.L2ChainA.Escape().ChainConfig().InteropTime - interopTimeB := sys.L2ChainB.Escape().ChainConfig().InteropTime - - eventLoggerAddress := alice.DeployEventLogger() - - // wait for chain B to catch up to chain A if necessary - sys.L2ChainB.CatchUpTo(sys.L2ChainA) - - // send initiating message on chain A - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - initMsg = alice.SendInitMessage(interop.RandomInitTrigger(rng, eventLoggerAddress, rng.Intn(3), rng.Intn(10))) - - // at least one block between the init tx on chain A and the exec tx on chain B - sys.L2ChainB.WaitForBlock() - - // send executing message on chain B and confirm we got an error - execTx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](bob.Plan()) - execTx.Content.DependOn(&initMsg.Tx.Result) - execTx.Content.Fn(txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initMsg.Tx.Result, 0)) - execReceipt, err := execTx.PlannedTx.Included.Eval(sys.T.Ctx()) - require.ErrorContains(err, "implementation not initialized", "error did not contain expected string") - require.Nil(execReceipt) - - t.Logger().Info("initReceipt", "blocknum", initMsg.Receipt.BlockNumber, "txhash", initMsg.Receipt.TxHash) - - // confirm we are still pre-interop - require.False(sys.L2ChainA.IsActivated(*interopTimeA)) - require.False(sys.L2ChainB.IsActivated(*interopTimeB)) - t.Logger().Info("Timestamps", "interopTimeA", *interopTimeA, "interopTimeB", *interopTimeB, "now", time.Now().Unix()) - } - - // check that log events from a block before activation, when converted into an access-list, fail the check-access-list RPC check - { - ctx := sys.T.Ctx() - - execTrigger, err := txintent.ExecuteIndexed(predeploys.CrossL2InboxAddr, &initMsg.Tx.Result, 0)(ctx) - require.NoError(err) - - ed := stypes.ExecutingDescriptor{Timestamp: uint64(time.Now().Unix())} - accessEntries := []stypes.Access{execTrigger.Msg.Access()} - accessList := stypes.EncodeAccessList(accessEntries) - - err = sys.Supervisor.Escape().QueryAPI().CheckAccessList(ctx, accessList, stypes.CrossSafe, ed) - require.ErrorContains(err, "conflicting data") - } - - t.Logger().Info("Done") -} diff --git a/rust/kona/tests/supervisor/presets/interop_minimal.go b/rust/kona/tests/supervisor/presets/interop_minimal.go deleted file mode 100644 index 0d5ad7e09fa..00000000000 --- a/rust/kona/tests/supervisor/presets/interop_minimal.go +++ /dev/null @@ -1,39 +0,0 @@ -package presets - -import ( - "os" - "path/filepath" - - "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" - "github.com/ethereum-optimism/optimism/op-devstack/devtest" - oppresets "github.com/ethereum-optimism/optimism/op-devstack/presets" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/intentbuilder" -) - -func NewSimpleInteropMinimal(t devtest.T, opts ...oppresets.Option) *oppresets.SimpleInterop { - artifactsPath := os.Getenv("OP_DEPLOYER_ARTIFACTS") - if artifactsPath == "" { - panic("OP_DEPLOYER_ARTIFACTS is not set") - } - contractArtifacts := artifacts.MustNewFileLocator(filepath.Join(artifactsPath, "src")) - - baseOpts := []oppresets.Option{ - oppresets.WithDeployerOptions( - func(_ devtest.T, _ devkeys.Keys, builder intentbuilder.Builder) { - builder.WithL1ContractsLocator(contractArtifacts) - builder.WithL2ContractsLocator(contractArtifacts) - }, - ), - } - baseOpts = append(baseOpts, opts...) - return oppresets.NewSimpleInterop(t, baseOpts...) -} - -func WithSuggestedInteropActivationOffset(offset uint64) oppresets.Option { - return oppresets.WithSuggestedInteropActivationOffset(offset) -} - -func WithInteropNotAtGenesis() oppresets.Option { - return oppresets.WithInteropNotAtGenesis() -} diff --git a/rust/kona/version.json b/rust/kona/version.json deleted file mode 100644 index 8d8e7807a20..00000000000 --- a/rust/kona/version.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "version": "1.2.7", - "prestateHash": "0x0323914d3050e80c3d09da528be54794fde60cd26849cd3410dde0da7cd7d4fa", - "interopPrestateHash": "0x03f03018773fae0603f7c110ef1defa8d19b601b32ee530f9951987baec435b0" -} \ No newline at end of file diff --git a/rust/op-alloy/crates/consensus/Cargo.toml b/rust/op-alloy/crates/consensus/Cargo.toml index 4fa43515e54..03fee40baea 100644 --- a/rust/op-alloy/crates/consensus/Cargo.toml +++ b/rust/op-alloy/crates/consensus/Cargo.toml @@ -2,7 +2,7 @@ name = "op-alloy-consensus" description = "Optimism alloy consensus types" -version = "0.23.1" +version = "2.0.0" edition.workspace = true rust-version.workspace = true authors = ["Alloy Contributors"] @@ -37,6 +37,13 @@ serde_with = { workspace = true, optional = true } alloy-serde = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } +# reth-core / reth-codec +reth-codecs = { workspace = true, optional = true } +reth-codecs-derive = { workspace = true, optional = true } +reth-zstd-compressors = { workspace = true, optional = true } +bytes = { workspace = true, optional = true } +modular-bitfield = { workspace = true, optional = true } + [dev-dependencies] rand.workspace = true bincode = { workspace = true } @@ -57,9 +64,20 @@ std = [ "alloy-serde?/std", "serde?/std", "serde_with?/std", - "thiserror/std" + "thiserror/std", + "bytes?/std", + "reth-codecs?/std", + "reth-zstd-compressors?/std" ] alloy-compat = ["serde", "dep:alloy-network", "dep:alloy-rpc-types-eth"] +reth-core = [] +reth-codec = [ + "dep:reth-codecs", + "dep:reth-codecs-derive", + "dep:reth-zstd-compressors", + "dep:bytes", + "dep:modular-bitfield", +] k256 = ["alloy-primitives/k256", "alloy-consensus/k256"] kzg = ["alloy-eips/kzg", "alloy-consensus/kzg", "std"] arbitrary = [ @@ -70,7 +88,8 @@ arbitrary = [ "alloy-primitives/rand", "alloy-primitives/arbitrary", "alloy-rpc-types-eth?/arbitrary", - "alloy-serde?/arbitrary" + "alloy-serde?/arbitrary", + "reth-codecs?/arbitrary", ] serde = [ "dep:serde", @@ -78,7 +97,9 @@ serde = [ "alloy-primitives/serde", "alloy-consensus/serde", "alloy-eips/serde", - "alloy-rpc-types-eth?/serde" + "alloy-rpc-types-eth?/serde", + "bytes?/serde", + "reth-codecs?/serde", ] serde-bincode-compat = [ "serde_with", diff --git a/rust/op-alloy/crates/consensus/src/eip1559.rs b/rust/op-alloy/crates/consensus/src/eip1559.rs index 6ce04f494cb..f1b46419a3e 100644 --- a/rust/op-alloy/crates/consensus/src/eip1559.rs +++ b/rust/op-alloy/crates/consensus/src/eip1559.rs @@ -138,6 +138,9 @@ pub enum EIP1559ParamError { /// Minimum base fee cannot be None after Jovian. #[error("Minimum base fee cannot be None after Jovian")] MinBaseFeeNotSet, + /// Invalid post-exec payload or post-exec transaction placement. + #[error("Invalid post-exec payload")] + InvalidPostExecPayload, } #[cfg(test)] diff --git a/rust/op-alloy/crates/consensus/src/lib.rs b/rust/op-alloy/crates/consensus/src/lib.rs index 455ed3e3c05..6459af25cec 100644 --- a/rust/op-alloy/crates/consensus/src/lib.rs +++ b/rust/op-alloy/crates/consensus/src/lib.rs @@ -9,6 +9,12 @@ extern crate alloc; +#[cfg(feature = "reth-core")] +mod reth_core; + +#[cfg(feature = "reth-codec")] +mod reth_codec; + #[cfg(feature = "alloy-compat")] mod alloy_compat; @@ -29,6 +35,9 @@ pub use eip1559::{ decode_jovian_extra_data, encode_holocene_extra_data, encode_jovian_extra_data, }; +pub mod post_exec; +pub use post_exec::*; + mod source; pub use source::*; @@ -37,6 +46,9 @@ pub use block::OpBlock; pub mod interop; +pub mod nuts; +pub use nuts::{NetworkUpgradeTransaction, NutBundle, NutBundleError}; + pub mod predeploys; pub use predeploys::L2_TO_L1_MESSAGE_PASSER_ADDRESS; diff --git a/rust/op-alloy/crates/consensus/src/nuts/mod.rs b/rust/op-alloy/crates/consensus/src/nuts/mod.rs new file mode 100644 index 00000000000..58a46ebe2e9 --- /dev/null +++ b/rust/op-alloy/crates/consensus/src/nuts/mod.rs @@ -0,0 +1,257 @@ +//! Network Upgrade Transaction (NUT) bundle types and conversion. +//! +//! NUT bundles define the L2 deposit transactions that activate a hardfork. Each bundle contains +//! ordered transactions (contract deployments, proxy upgrades, etc.) that the rollup node executes +//! at the fork activation block. +//! +//! Source hashes are derived from a qualified intent string that combines the fork name, +//! transaction index, and per-transaction intent field: +//! `"{fork_name} {index}: {intent}"`. + +use alloc::{string::String, vec::Vec}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Address, Bytes, TxKind, U256}; + +use crate::{TxDeposit, UpgradeDepositSource}; + +/// A single network upgrade transaction from a NUT bundle. +#[derive(Debug, Clone)] +pub struct NetworkUpgradeTransaction { + /// Human-readable intent string, used for source hash derivation. + pub intent: String, + /// Sender address. + pub from: Address, + /// Recipient address. `None` for contract creation. + pub to: Option
, + /// Calldata. + pub data: Bytes, + /// Gas limit for this transaction. + pub gas_limit: u64, +} + +/// A parsed NUT (Network Upgrade Transaction) bundle. +/// +/// Constructed either from JSON at build time (via `build.rs`) or directly in code. +/// Use [`NutBundle::to_deposit_transactions`] to convert to deposit transactions with +/// correctly derived source hashes. +#[derive(Debug, Clone)] +pub struct NutBundle { + /// The fork name, used to namespace intents for source hash derivation. + pub fork_name: String, + /// The ordered list of upgrade transactions. + pub transactions: Vec, +} + +impl NutBundle { + /// Returns the total gas required by all transactions in the bundle. + pub fn total_gas(&self) -> u64 { + self.transactions.iter().map(|tx| tx.gas_limit).sum() + } + + /// Converts the bundle into a list of [`TxDeposit`]s. + /// + /// Source hashes are derived from a qualified intent string: + /// `"{fork_name} {index}: {intent}"`. + /// + /// Returns an error if any transaction is missing an intent. + pub fn to_deposit_transactions(&self) -> Result, NutBundleError> { + self.transactions + .iter() + .enumerate() + .map(|(i, tx)| { + if tx.intent.is_empty() { + return Err(NutBundleError::MissingIntent(i)); + } + + let qualified_intent = alloc::format!("{} {}: {}", self.fork_name, i, tx.intent); + let source = UpgradeDepositSource { intent: qualified_intent }; + + Ok(TxDeposit { + source_hash: source.source_hash(), + from: tx.from, + to: tx.to.map_or(TxKind::Create, TxKind::Call), + mint: 0, + value: U256::ZERO, + gas_limit: tx.gas_limit, + is_system_transaction: false, + input: tx.data.clone(), + }) + }) + .collect() + } + + /// Converts the bundle into EIP-2718 encoded deposit transaction bytes. + /// + /// This is the format expected by [`crate::Hardfork::txs`][hardfork-txs]. + /// + /// [hardfork-txs]: https://docs.rs/kona-hardforks/latest/kona_hardforks/trait.Hardfork.html + pub fn to_encoded_transactions(&self) -> Result, NutBundleError> { + Ok(self + .to_deposit_transactions()? + .into_iter() + .map(|tx| { + let mut encoded = Vec::new(); + tx.encode_2718(&mut encoded); + Bytes::from(encoded) + }) + .collect()) + } +} + +/// Errors that can occur when processing a NUT bundle. +#[derive(Debug, thiserror::Error)] +pub enum NutBundleError { + /// A transaction is missing an intent string. + #[error("tx {0}: missing intent")] + MissingIntent(usize), +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::string::ToString; + use alloy_primitives::{address, b256, hex}; + + fn test_bundle() -> NutBundle { + NutBundle { + fork_name: "Test".to_string(), + transactions: alloc::vec![ + NetworkUpgradeTransaction { + intent: "First Transaction".to_string(), + from: Address::ZERO, + to: Some(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266")), + data: Bytes::from_static(&hex!("abcdef")), + gas_limit: 1_000_000, + }, + NetworkUpgradeTransaction { + intent: "Second Transaction".to_string(), + from: address!("000000000000000000000000000000000000abba"), + to: Some(address!("4200000000000000000000000000000000000015")), + data: Bytes::from_static(&hex!("feedface")), + gas_limit: 5_000_000, + }, + ], + } + } + + #[test] + fn test_total_gas() { + let bundle = test_bundle(); + assert_eq!(bundle.total_gas(), 6_000_000); + } + + #[test] + fn test_to_deposit_transactions() { + let bundle = test_bundle(); + let deposits = bundle.to_deposit_transactions().unwrap(); + + assert_eq!(deposits.len(), 2); + + // Verify first tx: qualified intent is "Test 0: First Transaction" + let expected_source_0 = + UpgradeDepositSource { intent: "Test 0: First Transaction".to_string() }; + assert_eq!(deposits[0].source_hash, expected_source_0.source_hash()); + assert_eq!( + deposits[0].source_hash, + b256!("c3ecdc70c81521aae81240518d3547f601bb33ec07b909e83544b2fe093c78bd") + ); + assert_eq!(deposits[0].from, Address::ZERO); + assert_eq!( + deposits[0].to, + TxKind::Call(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266")) + ); + assert_eq!(deposits[0].gas_limit, 1_000_000); + assert_eq!(deposits[0].value, U256::ZERO); + assert_eq!(deposits[0].mint, 0); + assert!(!deposits[0].is_system_transaction); + assert_eq!(deposits[0].input.as_ref(), hex!("abcdef").as_slice()); + + // Verify second tx: qualified intent is "Test 1: Second Transaction" + let expected_source_1 = + UpgradeDepositSource { intent: "Test 1: Second Transaction".to_string() }; + assert_eq!(deposits[1].source_hash, expected_source_1.source_hash()); + assert_eq!( + deposits[1].source_hash, + b256!("ac61fbb5e3d61e626f66c413c564d376e3b363b94a37aab323b860a2293b7c6c") + ); + assert_eq!(deposits[1].from, address!("000000000000000000000000000000000000abba")); + assert_eq!( + deposits[1].to, + TxKind::Call(address!("4200000000000000000000000000000000000015")) + ); + assert_eq!(deposits[1].input.as_ref(), hex!("feedface").as_slice()); + assert_eq!(deposits[1].gas_limit, 5_000_000); + + // Source hashes must be unique. + assert_ne!(deposits[0].source_hash, deposits[1].source_hash); + } + + #[test] + fn test_to_encoded_transactions() { + let bundle = test_bundle(); + let encoded = bundle.to_encoded_transactions().unwrap(); + + assert_eq!(encoded.len(), 2); + assert_eq!( + encoded[0].as_ref(), + hex!("7ef856a0c3ecdc70c81521aae81240518d3547f601bb33ec07b909e83544b2fe093c78bd94000000000000000000000000000000000000000094f39fd6e51aad88f6f4ce6ab8827279cfffb922668080830f42408083abcdef").as_slice() + ); + assert_eq!( + encoded[1].as_ref(), + hex!("7ef857a0ac61fbb5e3d61e626f66c413c564d376e3b363b94a37aab323b860a2293b7c6c94000000000000000000000000000000000000abba9442000000000000000000000000000000000000158080834c4b408084feedface").as_slice() + ); + } + + #[test] + fn test_null_to_produces_create() { + let bundle = NutBundle { + fork_name: "Test".to_string(), + transactions: alloc::vec![NetworkUpgradeTransaction { + intent: "Deploy Contract".to_string(), + from: address!("4210000000000000000000000000000000000006"), + to: None, + data: Bytes::from_static(&hex!("deadbeef")), + gas_limit: 500_000, + }], + }; + + let deposits = bundle.to_deposit_transactions().unwrap(); + assert_eq!(deposits[0].to, TxKind::Create); + } + + #[test] + fn test_missing_intent_error() { + let bundle = NutBundle { + fork_name: "Test".to_string(), + transactions: alloc::vec![NetworkUpgradeTransaction { + intent: String::new(), + from: Address::ZERO, + to: Some(Address::ZERO), + data: Bytes::new(), + gas_limit: 100_000, + }], + }; + + let err = bundle.to_deposit_transactions().unwrap_err(); + assert!(matches!(err, NutBundleError::MissingIntent(0))); + assert_eq!(err.to_string(), "tx 0: missing intent"); + } + + #[test] + fn test_empty_bundle() { + let bundle = NutBundle { fork_name: "Test".to_string(), transactions: alloc::vec![] }; + + assert_eq!(bundle.total_gas(), 0); + let deposits = bundle.to_deposit_transactions().unwrap(); + assert!(deposits.is_empty()); + } + + #[test] + fn test_gas_matches_deposit_gas() { + let bundle = test_bundle(); + let deposits = bundle.to_deposit_transactions().unwrap(); + + let sum_gas: u64 = deposits.iter().map(|d| d.gas_limit).sum(); + assert_eq!(bundle.total_gas(), sum_gas); + } +} diff --git a/rust/op-alloy/crates/consensus/src/post_exec.rs b/rust/op-alloy/crates/consensus/src/post_exec.rs new file mode 100644 index 00000000000..95368813df5 --- /dev/null +++ b/rust/op-alloy/crates/consensus/src/post_exec.rs @@ -0,0 +1,653 @@ +//! Post-execution transaction types. + +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +#[cfg(feature = "serde")] +use alloy_consensus::Sealed; +use alloy_consensus::{Sealable, Transaction, Typed2718, transaction::RlpEcdsaEncodableTx}; +use alloy_eips::{ + eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718}, + eip2930::AccessList, +}; +use alloy_primitives::{Address, B256, Bytes, ChainId, TxHash, TxKind, U256, keccak256}; +use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpDecodable, RlpEncodable}; + +use crate::OpTransaction; + +/// Type byte for the post-execution transaction. +pub const POST_EXEC_TX_TYPE_ID: u8 = 0x7D; + +/// Current format version for [`PostExecPayload`]. +pub const POST_EXEC_PAYLOAD_VERSION: u8 = 1; + +/// Per-transaction gas refund entry within a [`PostExecPayload`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, RlpEncodable, RlpDecodable)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct SDMGasEntry { + /// Transaction index within the block. + pub index: u64, + /// Gas refund from post-execution warming settlement. + pub gas_refund: u64, +} + +/// Payload for the post-execution transaction. +/// +/// Today this only carries the SDM gas refund data, but additional post-exec fields may +/// be added in the future. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, RlpEncodable, RlpDecodable)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct PostExecPayload { + /// Format version. + pub version: u8, + /// L2 block number this post-execution payload is anchored to. + /// + /// This is included in the encoded post-exec transaction so otherwise identical + /// payloads in different blocks produce distinct transaction hashes. + pub block_number: u64, + /// Initial SDM gas refund entries keyed by transaction index. + pub gas_refund_entries: Vec, +} + +// `version` is pinned rather than left arbitrary because `decode_checked` rejects any +// non-`POST_EXEC_PAYLOAD_VERSION` value, which would break encode/decode roundtrip +// property tests (and any downstream fuzzer using arbitrary-generated payloads). +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for PostExecPayload { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self { + version: POST_EXEC_PAYLOAD_VERSION, + block_number: u64::arbitrary(u)?, + gas_refund_entries: >::arbitrary(u)?, + }) + } +} + +/// Parsed post-exec transaction metadata for a block. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParsedPostExecPayload { + /// Transaction index of the post-exec transaction within the block. + pub tx_index: u64, + /// Decoded post-exec payload. + pub payload: PostExecPayload, +} + +/// Errors returned while validating a block's post-exec transaction structure. +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum PostExecPayloadValidationError { + /// The block contains a post-exec transaction before SDM is active. + #[error("unexpected post-exec transaction at index {tx_index}: SDM not active for this block")] + UnexpectedPostExecTx { + /// Transaction index. + tx_index: u64, + }, + /// The block contains more than one post-exec transaction. + #[error( + "multiple post-exec transactions: first at index {first_index}, duplicate at index {duplicate_index}" + )] + MultiplePostExecTxs { + /// First post-exec tx index. + first_index: u64, + /// Duplicate post-exec tx index. + duplicate_index: u64, + }, + /// The post-exec transaction is not the final transaction in the block. + #[error( + "post-exec transaction at index {tx_index} must be the final transaction; final index is {last_index}" + )] + PostExecTxNotLast { + /// Post-exec tx index. + tx_index: u64, + /// Final transaction index. + last_index: u64, + }, + /// The payload block number does not match the containing block. + #[error( + "payload block number {payload_block_number} does not match block number {block_number}" + )] + BlockNumberMismatch { + /// Block number encoded in the payload. + payload_block_number: u64, + /// Containing block number. + block_number: u64, + }, +} + +impl PostExecPayloadValidationError { + /// Returns this error as an owned string. + #[must_use] + pub fn into_string(self) -> String { + self.to_string() + } +} + +/// Parse and validate the block-level post-exec transaction, if present. +/// +/// This enforces the shared consensus structure rules: post-exec transactions are only valid after +/// activation, at most one may be present, and when present it must be the final transaction and be +/// anchored to the containing block number. +/// +/// # Errors +/// +/// Returns [`PostExecPayloadValidationError`] if the post-exec transaction is not valid for the +/// block or SDM activation state. +pub fn parse_post_exec_payload_from_transactions<'a, I, T>( + transactions: I, + block_number: u64, + sdm_active: bool, +) -> Result, PostExecPayloadValidationError> +where + I: IntoIterator, + T: OpTransaction + 'a, +{ + let mut parsed = None::; + let mut last_index = None::; + + for (idx, tx) in transactions.into_iter().enumerate() { + let tx_index = idx as u64; + last_index = Some(tx_index); + + let Some(post_exec) = tx.as_post_exec() else { + continue; + }; + + if !sdm_active { + return Err(PostExecPayloadValidationError::UnexpectedPostExecTx { tx_index }); + } + + if let Some(first) = &parsed { + return Err(PostExecPayloadValidationError::MultiplePostExecTxs { + first_index: first.tx_index, + duplicate_index: tx_index, + }); + } + + let payload = post_exec.inner().payload.clone(); + if payload.block_number != block_number { + return Err(PostExecPayloadValidationError::BlockNumberMismatch { + payload_block_number: payload.block_number, + block_number, + }); + } + + parsed = Some(ParsedPostExecPayload { tx_index, payload }); + } + + if let (Some(parsed), Some(last_index)) = (&parsed, last_index) && + parsed.tx_index != last_index + { + return Err(PostExecPayloadValidationError::PostExecTxNotLast { + tx_index: parsed.tx_index, + last_index, + }); + } + + Ok(parsed) +} + +impl PostExecPayload { + /// Look up refund for a given tx index. + pub fn gas_refund_for_idx(&self, index: u64) -> Option { + self.gas_refund_entries.iter().find(|e| e.index == index).map(|e| e.gas_refund) + } + + /// RLP-encode the payload into bytes. + pub fn to_rlp_bytes(&self) -> Bytes { + let mut buf = Vec::new(); + self.encode(&mut buf); + buf.into() + } + + /// Decode a payload from an RLP stream, validating the payload version. + /// + /// Advances `buf` past the consumed bytes. Unlike [`Self::from_rlp_bytes`], trailing bytes + /// are left in place for the caller to consume; this is the decoder to use on the EIP-2718 + /// path where the envelope already framed the packet exactly. + pub fn decode_checked(buf: &mut &[u8]) -> alloy_rlp::Result { + let payload = Self::decode(buf)?; + if payload.version != POST_EXEC_PAYLOAD_VERSION { + return Err(alloy_rlp::Error::Custom("unsupported post-exec payload version")); + } + Ok(payload) + } + + /// Decode a payload from RLP bytes. + /// + /// Rejects payloads whose `version` is not [`POST_EXEC_PAYLOAD_VERSION`] and rejects any + /// trailing bytes after the RLP structure. + pub fn from_rlp_bytes(data: &[u8]) -> alloy_rlp::Result { + let mut buf = data; + let payload = Self::decode_checked(&mut buf)?; + if !buf.is_empty() { + return Err(alloy_rlp::Error::UnexpectedLength); + } + Ok(payload) + } +} + +/// Post-execution transaction carrying a [`PostExecPayload`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(into = "PostExecPayload", from = "PostExecPayload"))] +pub struct TxPostExec { + /// Decoded payload. + pub payload: PostExecPayload, + /// RLP-encoded payload bytes used as the transaction input. + pub input: Bytes, +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for TxPostExec { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // Keep `payload` and the cached `input` bytes in sync for fuzzed values. + Ok(Self::new(PostExecPayload::arbitrary(u)?)) + } +} + +impl From for TxPostExec { + fn from(payload: PostExecPayload) -> Self { + Self::new(payload) + } +} + +impl From for PostExecPayload { + fn from(tx: TxPostExec) -> Self { + tx.payload + } +} + +impl TxPostExec { + /// Construct a post-exec transaction from its decoded payload. + pub fn new(payload: PostExecPayload) -> Self { + let input = payload.to_rlp_bytes(); + Self { payload, input } + } + + /// Returns the canonical signature for post-exec transactions, which don't include a + /// signature. + pub const fn signature() -> alloy_primitives::Signature { + alloy_primitives::Signature::new(U256::ZERO, U256::ZERO, false) + } + + /// Returns the canonical signer address for post-exec transactions. + pub const fn signer_address(&self) -> Address { + Address::ZERO + } + + /// Encoded length of the transaction body. + pub fn rlp_encoded_length(&self) -> usize { + self.input.len() + } + + /// Encoded length including the type byte. + pub fn eip2718_encoded_length(&self) -> usize { + self.rlp_encoded_length() + 1 + } + + fn network_header(&self) -> Header { + Header { list: false, payload_length: self.eip2718_encoded_length() } + } + + /// Encoded length including the outer network RLP header. + pub fn network_encoded_length(&self) -> usize { + self.network_header().length_with_payload() + } + + /// Network encode the transaction. + pub fn network_encode(&self, out: &mut dyn BufMut) { + self.network_header().encode(out); + self.encode_2718(out); + } + + /// Calculates a heuristic for the in-memory size of the transaction. + pub fn size(&self) -> usize { + core::mem::size_of::() + + self.input.len() + + self.payload.gas_refund_entries.len() * core::mem::size_of::() + } + + /// Calculate the transaction hash. + pub fn tx_hash(&self) -> TxHash { + let mut buf = Vec::with_capacity(self.eip2718_encoded_length()); + self.encode_2718(&mut buf); + keccak256(&buf) + } +} + +impl Typed2718 for TxPostExec { + fn ty(&self) -> u8 { + POST_EXEC_TX_TYPE_ID + } +} + +impl IsTyped2718 for TxPostExec { + fn is_type(ty: u8) -> bool { + ty == POST_EXEC_TX_TYPE_ID + } +} + +impl RlpEcdsaEncodableTx for TxPostExec { + fn rlp_encoded_fields_length(&self) -> usize { + self.input.len() + } + + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + // `input` already stores the canonical RLP-encoded payload, so the transaction fields are + // written as-is instead of being wrapped in an additional RLP list. + out.put_slice(self.input.as_ref()); + } +} + +impl Transaction for TxPostExec { + fn chain_id(&self) -> Option { + None + } + fn nonce(&self) -> u64 { + 0 + } + fn gas_limit(&self) -> u64 { + 0 + } + fn gas_price(&self) -> Option { + None + } + fn max_fee_per_gas(&self) -> u128 { + 0 + } + fn max_priority_fee_per_gas(&self) -> Option { + None + } + fn max_fee_per_blob_gas(&self) -> Option { + None + } + fn priority_fee_or_price(&self) -> u128 { + 0 + } + fn effective_gas_price(&self, _: Option) -> u128 { + 0 + } + fn is_dynamic_fee(&self) -> bool { + false + } + fn kind(&self) -> TxKind { + // Post-exec transactions do not carry a destination like deposits do, so expose them as a + // zero-address call placeholder. + TxKind::Call(Default::default()) + } + fn is_create(&self) -> bool { + false + } + fn value(&self) -> U256 { + U256::ZERO + } + fn input(&self) -> &Bytes { + &self.input + } + fn access_list(&self) -> Option<&AccessList> { + None + } + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + None + } + fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> { + None + } +} + +impl Encodable2718 for TxPostExec { + fn type_flag(&self) -> Option { + Some(POST_EXEC_TX_TYPE_ID) + } + fn encode_2718_len(&self) -> usize { + self.eip2718_encoded_length() + } + fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { + out.put_u8(POST_EXEC_TX_TYPE_ID); + out.put_slice(self.input.as_ref()); + } +} + +impl Decodable2718 for TxPostExec { + fn typed_decode(ty: u8, data: &mut &[u8]) -> Eip2718Result { + if ty != POST_EXEC_TX_TYPE_ID { + return Err(Eip2718Error::UnexpectedType(ty)); + } + Ok(Self::new(PostExecPayload::decode_checked(data)?)) + } + + fn fallback_decode(data: &mut &[u8]) -> Eip2718Result { + Ok(Self::new(PostExecPayload::decode_checked(data)?)) + } +} + +impl Encodable for TxPostExec { + fn encode(&self, out: &mut dyn BufMut) { + out.put_slice(self.input.as_ref()); + } + + fn length(&self) -> usize { + self.rlp_encoded_length() + } +} + +impl Decodable for TxPostExec { + fn decode(data: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self::new(PostExecPayload::decode_checked(data)?)) + } +} + +impl Sealable for TxPostExec { + fn hash_slow(&self) -> B256 { + self.tx_hash() + } +} + +#[cfg(feature = "alloy-compat")] +impl From for alloy_rpc_types_eth::TransactionRequest { + fn from(tx: TxPostExec) -> Self { + Self { + from: Some(tx.signer_address()), + transaction_type: Some(POST_EXEC_TX_TYPE_ID), + gas: Some(0), + nonce: Some(0), + value: Some(U256::ZERO), + input: tx.input.into(), + ..Default::default() + } + } +} + +/// Build a post-execution transaction from a block number and refund entries. +pub fn build_post_exec_tx(block_number: u64, gas_refund_entries: Vec) -> TxPostExec { + TxPostExec::new(PostExecPayload { + version: POST_EXEC_PAYLOAD_VERSION, + block_number, + gas_refund_entries, + }) +} + +/// Post-exec transactions serialize as full RPC transaction objects when embedded in a +/// [`crate::OpTxEnvelope`] response. +/// +/// Unlike the standalone [`TxPostExec`] serde form, RPC consumers such as op-node expect the +/// canonical `input` field to be present so the transaction can be decoded by go-ethereum types. +#[cfg(feature = "serde")] +pub fn serde_post_exec_tx_rpc( + value: &Sealed, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + use serde::Serialize; + + #[derive(Serialize)] + struct SerdeHelper<'a> { + hash: B256, + #[serde(rename = "type", with = "alloy_serde::quantity")] + tx_type: u8, + #[serde(with = "alloy_serde::quantity")] + gas: u64, + value: U256, + input: &'a Bytes, + } + + SerdeHelper { + hash: value.hash(), + tx_type: POST_EXEC_TX_TYPE_ID, + gas: 0, + value: U256::ZERO, + input: &value.inner().input, + } + .serialize(serializer) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn post_exec_payload_rlp_roundtrip_preserves_block_number() { + let payload = PostExecPayload { + version: 1, + block_number: 42, + gas_refund_entries: vec![SDMGasEntry { index: 3, gas_refund: 7 }], + }; + + let encoded = payload.to_rlp_bytes(); + let decoded = PostExecPayload::from_rlp_bytes(encoded.as_ref()).expect("decode payload"); + + assert_eq!(decoded, payload); + } + + #[test] + fn post_exec_payload_rlp_decode_rejects_unknown_version() { + let payload = PostExecPayload { + version: POST_EXEC_PAYLOAD_VERSION + 1, + block_number: 42, + gas_refund_entries: vec![SDMGasEntry { index: 3, gas_refund: 7 }], + }; + + let encoded = payload.to_rlp_bytes(); + let err = + PostExecPayload::from_rlp_bytes(encoded.as_ref()).expect_err("reject unknown version"); + assert_eq!(err, alloy_rlp::Error::Custom("unsupported post-exec payload version")); + } + + #[test] + fn post_exec_payload_rlp_decode_rejects_trailing_bytes() { + let payload = PostExecPayload { + version: 1, + block_number: 42, + gas_refund_entries: vec![SDMGasEntry { index: 3, gas_refund: 7 }], + }; + + let mut encoded = payload.to_rlp_bytes().to_vec(); + encoded.push(0); + + let err = PostExecPayload::from_rlp_bytes(&encoded).expect_err("reject trailing bytes"); + assert_eq!(err, alloy_rlp::Error::UnexpectedLength); + } + + #[test] + fn post_exec_tx_hash_depends_on_block_number() { + let entries = vec![SDMGasEntry { index: 3, gas_refund: 7 }]; + let tx_a = build_post_exec_tx(42, entries.clone()); + let tx_b = build_post_exec_tx(43, entries); + + assert_ne!(tx_a.tx_hash(), tx_b.tx_hash()); + } + + #[test] + fn post_exec_tx_eip2718_roundtrip() { + let tx = build_post_exec_tx( + 99, + vec![ + SDMGasEntry { index: 0, gas_refund: 100 }, + SDMGasEntry { index: 5, gas_refund: 200 }, + ], + ); + + let mut buf = Vec::new(); + tx.encode_2718(&mut buf); + + let decoded = TxPostExec::decode_2718(&mut buf.as_slice()).expect("decode 2718"); + assert_eq!(decoded, tx); + assert_eq!(decoded.tx_hash(), tx.tx_hash()); + } + + #[test] + fn post_exec_tx_eip2718_decode_rejects_unknown_version() { + let payload = PostExecPayload { + version: POST_EXEC_PAYLOAD_VERSION + 1, + block_number: 42, + gas_refund_entries: vec![SDMGasEntry { index: 3, gas_refund: 7 }], + }; + + let mut buf = Vec::new(); + buf.put_u8(POST_EXEC_TX_TYPE_ID); + payload.encode(&mut buf); + + let err = TxPostExec::decode_2718(&mut buf.as_slice()) + .expect_err("2718 decode must reject unknown version"); + assert!( + matches!( + err, + Eip2718Error::RlpError(alloy_rlp::Error::Custom( + "unsupported post-exec payload version" + )) + ), + "unexpected error: {err:?}" + ); + } + + #[test] + fn post_exec_tx_rlp_decode_rejects_unknown_version() { + let payload = PostExecPayload { + version: POST_EXEC_PAYLOAD_VERSION + 1, + block_number: 42, + gas_refund_entries: vec![SDMGasEntry { index: 3, gas_refund: 7 }], + }; + let mut buf = Vec::new(); + payload.encode(&mut buf); + + let err = TxPostExec::decode(&mut buf.as_slice()) + .expect_err("rlp decode must reject unknown version"); + assert_eq!(err, alloy_rlp::Error::Custom("unsupported post-exec payload version")); + } + + #[test] + fn post_exec_tx_eip2718_roundtrip_empty_refunds() { + let tx = build_post_exec_tx(1, vec![]); + + let mut buf = Vec::new(); + tx.encode_2718(&mut buf); + + let decoded = TxPostExec::decode_2718(&mut buf.as_slice()).expect("decode 2718"); + assert_eq!(decoded, tx); + } + + #[cfg(feature = "serde")] + #[test] + fn post_exec_tx_serde_serializes_as_payload() { + let tx = build_post_exec_tx(42, vec![SDMGasEntry { index: 3, gas_refund: 7 }]); + let value = serde_json::to_value(&tx).expect("serialize tx"); + + assert_eq!(value, serde_json::to_value(&tx.payload).expect("serialize payload")); + } + + #[cfg(feature = "serde")] + #[test] + fn post_exec_tx_serde_roundtrip_preserves_cached_input() { + let tx = build_post_exec_tx(42, vec![SDMGasEntry { index: 3, gas_refund: 7 }]); + let value = serde_json::to_value(&tx).expect("serialize tx"); + + let decoded: TxPostExec = serde_json::from_value(value).expect("deserialize tx"); + assert_eq!(decoded, tx); + assert_eq!(decoded.input, decoded.payload.to_rlp_bytes()); + } +} diff --git a/rust/op-alloy/crates/consensus/src/receipts/envelope.rs b/rust/op-alloy/crates/consensus/src/receipts/envelope.rs index d0efcea1583..e3c33b16694 100644 --- a/rust/op-alloy/crates/consensus/src/receipts/envelope.rs +++ b/rust/op-alloy/crates/consensus/src/receipts/envelope.rs @@ -42,6 +42,9 @@ pub enum OpReceiptEnvelope { /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702 #[cfg_attr(feature = "serde", serde(rename = "0x4", alias = "0x04"))] Eip7702(ReceiptWithBloom>), + /// Receipt envelope with type flag 125, containing a post-execution receipt. + #[cfg_attr(feature = "serde", serde(rename = "0x7d", alias = "0x7D"))] + PostExec(ReceiptWithBloom>), /// Receipt envelope with type flag 126, containing a [deposit] receipt. /// /// [deposit]: https://specs.optimism.io/protocol/deposits.html @@ -76,6 +79,9 @@ impl OpReceiptEnvelope { OpTxType::Eip7702 => { Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) } + OpTxType::PostExec => { + Self::PostExec(ReceiptWithBloom { receipt: inner_receipt, logs_bloom }) + } OpTxType::Deposit => { let inner = OpDepositReceiptWithBloom { receipt: OpDepositReceipt { @@ -99,6 +105,7 @@ impl OpReceiptEnvelope { Self::Eip2930(_) => OpTxType::Eip2930, Self::Eip1559(_) => OpTxType::Eip1559, Self::Eip7702(_) => OpTxType::Eip7702, + Self::PostExec(_) => OpTxType::PostExec, Self::Deposit(_) => OpTxType::Deposit, } } @@ -127,6 +134,7 @@ impl OpReceiptEnvelope { Self::Eip2930(r) => OpReceiptEnvelope::Eip2930(r.map_logs(f)), Self::Eip1559(r) => OpReceiptEnvelope::Eip1559(r.map_logs(f)), Self::Eip7702(r) => OpReceiptEnvelope::Eip7702(r.map_logs(f)), + Self::PostExec(r) => OpReceiptEnvelope::PostExec(r.map_logs(f)), Self::Deposit(r) => OpReceiptEnvelope::Deposit(r.map_receipt(|r| r.map_logs(f))), } } @@ -144,9 +152,11 @@ impl OpReceiptEnvelope { /// Return the receipt's bloom. pub const fn logs_bloom(&self) -> &Bloom { match self { - Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip7702(t) => { - &t.logs_bloom - } + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::PostExec(t) => &t.logs_bloom, Self::Deposit(t) => &t.logs_bloom, } } @@ -180,7 +190,11 @@ impl OpReceiptEnvelope { /// Consumes the type and returns the underlying [`Receipt`]. pub fn into_receipt(self) -> Receipt { match self { - Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip7702(t) => t.receipt, + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::PostExec(t) => t.receipt, Self::Deposit(t) => t.receipt.into_inner(), } } @@ -189,9 +203,11 @@ impl OpReceiptEnvelope { /// receipt types may be added. pub const fn as_receipt(&self) -> Option<&Receipt> { match self { - Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip7702(t) => { - Some(&t.receipt) - } + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::PostExec(t) => Some(&t.receipt), Self::Deposit(t) => Some(&t.receipt.inner), } } @@ -201,7 +217,11 @@ impl OpReceiptEnvelope { /// Get the length of the inner receipt in the 2718 encoding. pub fn inner_length(&self) -> usize { match self { - Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip7702(t) => t.length(), + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::PostExec(t) => t.length(), Self::Deposit(t) => t.length(), } } @@ -278,6 +298,7 @@ impl Typed2718 for OpReceiptEnvelope { Self::Eip2930(_) => OpTxType::Eip2930, Self::Eip1559(_) => OpTxType::Eip1559, Self::Eip7702(_) => OpTxType::Eip7702, + Self::PostExec(_) => OpTxType::PostExec, Self::Deposit(_) => OpTxType::Deposit, }; ty as u8 @@ -302,9 +323,11 @@ impl Encodable2718 for OpReceiptEnvelope { } match self { Self::Deposit(t) => t.encode(out), - Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip7702(t) => { - t.encode(out) - } + Self::Legacy(t) | + Self::Eip2930(t) | + Self::Eip1559(t) | + Self::Eip7702(t) | + Self::PostExec(t) => t.encode(out), } } } @@ -319,6 +342,7 @@ impl Decodable2718 for OpReceiptEnvelope { OpTxType::Eip1559 => Ok(Self::Eip1559(Decodable::decode(buf)?)), OpTxType::Eip7702 => Ok(Self::Eip7702(Decodable::decode(buf)?)), OpTxType::Eip2930 => Ok(Self::Eip2930(Decodable::decode(buf)?)), + OpTxType::PostExec => Ok(Self::PostExec(Decodable::decode(buf)?)), OpTxType::Deposit => Ok(Self::Deposit(Decodable::decode(buf)?)), } } @@ -349,10 +373,12 @@ where T: arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - match u.int_in_range(0..=4)? { + match u.int_in_range(0..=5)? { 0 => Ok(Self::Legacy(ReceiptWithBloom::arbitrary(u)?)), 1 => Ok(Self::Eip2930(ReceiptWithBloom::arbitrary(u)?)), 2 => Ok(Self::Eip1559(ReceiptWithBloom::arbitrary(u)?)), + 3 => Ok(Self::Eip7702(ReceiptWithBloom::arbitrary(u)?)), + 4 => Ok(Self::PostExec(ReceiptWithBloom::arbitrary(u)?)), _ => Ok(Self::Deposit(OpDepositReceiptWithBloom::arbitrary(u)?)), } } @@ -427,4 +453,15 @@ mod tests { assert_eq!(receipt.deposit_nonce(), Some(1)); assert_eq!(receipt.deposit_receipt_version(), Some(2)); } + + #[test] + fn post_exec_receipt_from_parts() { + let receipt = + OpReceiptEnvelope::from_parts(true, 100, vec![], OpTxType::PostExec, None, None); + assert!(receipt.status()); + assert_eq!(receipt.cumulative_gas_used(), 100); + assert_eq!(receipt.logs().len(), 0); + assert_eq!(receipt.tx_type(), OpTxType::PostExec); + assert!(matches!(receipt, OpReceiptEnvelope::PostExec(_))); + } } diff --git a/rust/op-alloy/crates/consensus/src/receipts/receipt.rs b/rust/op-alloy/crates/consensus/src/receipts/receipt.rs index 21b2b2ad632..56b9e8f403b 100644 --- a/rust/op-alloy/crates/consensus/src/receipts/receipt.rs +++ b/rust/op-alloy/crates/consensus/src/receipts/receipt.rs @@ -33,6 +33,9 @@ pub enum OpReceipt { /// EIP-7702 receipt #[cfg_attr(feature = "serde", serde(rename = "0x4", alias = "0x04"))] Eip7702(Receipt), + /// Post-exec receipt + #[cfg_attr(feature = "serde", serde(rename = "0x7d", alias = "0x7D"))] + PostExec(Receipt), /// Deposit receipt #[cfg_attr(feature = "serde", serde(rename = "0x7e", alias = "0x7E"))] Deposit(OpDepositReceipt), @@ -46,6 +49,7 @@ impl OpReceipt { Self::Eip2930(_) => OpTxType::Eip2930, Self::Eip1559(_) => OpTxType::Eip1559, Self::Eip7702(_) => OpTxType::Eip7702, + Self::PostExec(_) => OpTxType::PostExec, Self::Deposit(_) => OpTxType::Deposit, } } @@ -56,7 +60,8 @@ impl OpReceipt { Self::Legacy(receipt) | Self::Eip2930(receipt) | Self::Eip1559(receipt) | - Self::Eip7702(receipt) => receipt, + Self::Eip7702(receipt) | + Self::PostExec(receipt) => receipt, Self::Deposit(receipt) => &receipt.inner, } } @@ -67,7 +72,8 @@ impl OpReceipt { Self::Legacy(receipt) | Self::Eip2930(receipt) | Self::Eip1559(receipt) | - Self::Eip7702(receipt) => receipt, + Self::Eip7702(receipt) | + Self::PostExec(receipt) => receipt, Self::Deposit(receipt) => &mut receipt.inner, } } @@ -78,7 +84,8 @@ impl OpReceipt { Self::Legacy(receipt) | Self::Eip2930(receipt) | Self::Eip1559(receipt) | - Self::Eip7702(receipt) => receipt, + Self::Eip7702(receipt) | + Self::PostExec(receipt) => receipt, Self::Deposit(receipt) => receipt.inner, } } @@ -92,6 +99,7 @@ impl OpReceipt { Self::Eip2930(receipt) => OpReceipt::Eip2930(receipt.map_logs(f)), Self::Eip1559(receipt) => OpReceipt::Eip1559(receipt.map_logs(f)), Self::Eip7702(receipt) => OpReceipt::Eip7702(receipt.map_logs(f)), + Self::PostExec(receipt) => OpReceipt::PostExec(receipt.map_logs(f)), Self::Deposit(receipt) => OpReceipt::Deposit(receipt.map_logs(f)), } } @@ -105,7 +113,8 @@ impl OpReceipt { Self::Legacy(receipt) | Self::Eip2930(receipt) | Self::Eip1559(receipt) | - Self::Eip7702(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom), + Self::Eip7702(receipt) | + Self::PostExec(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom), Self::Deposit(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom), } } @@ -119,7 +128,8 @@ impl OpReceipt { Self::Legacy(receipt) | Self::Eip2930(receipt) | Self::Eip1559(receipt) | - Self::Eip7702(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out), + Self::Eip7702(receipt) | + Self::PostExec(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out), Self::Deposit(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out), } } @@ -170,6 +180,11 @@ impl OpReceipt { RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; Ok(ReceiptWithBloom { receipt: Self::Eip7702(receipt), logs_bloom }) } + OpTxType::PostExec => { + let ReceiptWithBloom { receipt, logs_bloom } = + RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; + Ok(ReceiptWithBloom { receipt: Self::PostExec(receipt), logs_bloom }) + } OpTxType::Deposit => { let ReceiptWithBloom { receipt, logs_bloom } = RlpDecodableReceipt::rlp_decode_with_bloom(buf)?; @@ -188,7 +203,8 @@ impl OpReceipt { Self::Legacy(receipt) | Self::Eip2930(receipt) | Self::Eip1559(receipt) | - Self::Eip7702(receipt) => { + Self::Eip7702(receipt) | + Self::PostExec(receipt) => { receipt.status.encode(out); receipt.cumulative_gas_used.encode(out); receipt.logs.encode(out); @@ -217,7 +233,8 @@ impl OpReceipt { Self::Legacy(receipt) | Self::Eip2930(receipt) | Self::Eip1559(receipt) | - Self::Eip7702(receipt) => { + Self::Eip7702(receipt) | + Self::PostExec(receipt) => { receipt.status.length() + receipt.cumulative_gas_used.length() + receipt.logs.length() @@ -258,6 +275,7 @@ impl OpReceipt { OpTxType::Eip2930 => Ok(Self::Eip2930(Receipt { status, cumulative_gas_used, logs })), OpTxType::Eip1559 => Ok(Self::Eip1559(Receipt { status, cumulative_gas_used, logs })), OpTxType::Eip7702 => Ok(Self::Eip7702(Receipt { status, cumulative_gas_used, logs })), + OpTxType::PostExec => Ok(Self::PostExec(Receipt { status, cumulative_gas_used, logs })), OpTxType::Deposit => Ok(Self::Deposit(OpDepositReceipt { inner: Receipt { status, cumulative_gas_used, logs }, deposit_nonce, @@ -402,7 +420,8 @@ impl> TxReceipt for OpReceipt receipt.logs, + Self::Eip7702(receipt) | + Self::PostExec(receipt) => receipt.logs, Self::Deposit(receipt) => receipt.inner.logs, } } @@ -443,6 +462,7 @@ impl From for OpReceipt { super::OpReceiptEnvelope::Eip2930(receipt) => Self::Eip2930(receipt.receipt), super::OpReceiptEnvelope::Eip1559(receipt) => Self::Eip1559(receipt.receipt), super::OpReceiptEnvelope::Eip7702(receipt) => Self::Eip7702(receipt.receipt), + super::OpReceiptEnvelope::PostExec(receipt) => Self::PostExec(receipt.receipt), super::OpReceiptEnvelope::Deposit(receipt) => Self::Deposit(OpDepositReceipt { deposit_nonce: receipt.receipt.deposit_nonce, deposit_receipt_version: receipt.receipt.deposit_receipt_version, @@ -460,6 +480,9 @@ impl From>> for OpReceiptEnvelope { OpReceipt::Eip2930(receipt) => Self::Eip2930(ReceiptWithBloom { receipt, logs_bloom }), OpReceipt::Eip1559(receipt) => Self::Eip1559(ReceiptWithBloom { receipt, logs_bloom }), OpReceipt::Eip7702(receipt) => Self::Eip7702(ReceiptWithBloom { receipt, logs_bloom }), + OpReceipt::PostExec(receipt) => { + Self::PostExec(ReceiptWithBloom { receipt, logs_bloom }) + } OpReceipt::Deposit(receipt) => Self::Deposit(ReceiptWithBloom { receipt, logs_bloom }), } } @@ -496,6 +519,8 @@ pub(crate) mod serde_bincode_compat { Eip1559(alloy_consensus::serde_bincode_compat::Receipt<'a, alloy_primitives::Log>), /// EIP-7702 receipt Eip7702(alloy_consensus::serde_bincode_compat::Receipt<'a, alloy_primitives::Log>), + /// Post-exec receipt + PostExec(alloy_consensus::serde_bincode_compat::Receipt<'a, alloy_primitives::Log>), /// Deposit receipt Deposit(crate::serde_bincode_compat::OpDepositReceipt<'a, alloy_primitives::Log>), } @@ -507,6 +532,7 @@ pub(crate) mod serde_bincode_compat { super::OpReceipt::Eip2930(receipt) => Self::Eip2930(receipt.into()), super::OpReceipt::Eip1559(receipt) => Self::Eip1559(receipt.into()), super::OpReceipt::Eip7702(receipt) => Self::Eip7702(receipt.into()), + super::OpReceipt::PostExec(receipt) => Self::PostExec(receipt.into()), super::OpReceipt::Deposit(receipt) => Self::Deposit(receipt.into()), } } @@ -519,6 +545,7 @@ pub(crate) mod serde_bincode_compat { OpReceipt::Eip2930(receipt) => Self::Eip2930(receipt.into()), OpReceipt::Eip1559(receipt) => Self::Eip1559(receipt.into()), OpReceipt::Eip7702(receipt) => Self::Eip7702(receipt.into()), + OpReceipt::PostExec(receipt) => Self::PostExec(receipt.into()), OpReceipt::Deposit(receipt) => Self::Deposit(receipt.into()), } } @@ -581,7 +608,7 @@ pub(crate) mod serde_bincode_compat { mod tests { use super::*; use alloc::vec; - use alloy_eips::Encodable2718; + use alloy_eips::{Decodable2718, Encodable2718}; use alloy_primitives::{Bytes, address, b256, bytes, hex_literal::hex}; use alloy_rlp::Encodable; @@ -766,4 +793,22 @@ mod tests { "Encoded length for legacy receipt should match the actual encoded data length" ); } + + #[test] + fn post_exec_receipt_roundtrip() { + let receipt: ReceiptWithBloom = ReceiptWithBloom { + receipt: OpReceipt::PostExec(Receipt { + status: Eip658Value::Eip658(true), + cumulative_gas_used: 0, + logs: vec![], + }), + logs_bloom: Bloom::default(), + }; + + let encoded = receipt.encoded_2718(); + let decoded = ReceiptWithBloom::::decode_2718(&mut encoded.as_ref()).unwrap(); + + assert_eq!(decoded, receipt); + assert_eq!(decoded.receipt.tx_type(), OpTxType::PostExec); + } } diff --git a/rust/op-alloy/crates/consensus/src/reth_codec.rs b/rust/op-alloy/crates/consensus/src/reth_codec.rs new file mode 100644 index 00000000000..96fc0da8a47 --- /dev/null +++ b/rust/op-alloy/crates/consensus/src/reth_codec.rs @@ -0,0 +1,416 @@ +//! Compact codec implementations for OP Stack consensus types. +//! +//! Ported from reth v1.11.3 (`d6324d63e`), where they lived behind the `op` feature: +//! - Transaction codecs: `crates/storage/codecs/src/alloy/transaction/optimism.rs` +//! - Receipt codecs: `crates/storage/codecs/src/alloy/optimism.rs` +//! +//! Differences from upstream: +//! - `CompactOpReceipt` uses `Vec` instead of `Cow<'a, Vec>` because the crates.io +//! `reth-codecs-derive` macro doesn't support lifetime parameters. The wire format is identical; +//! only serialization performance differs (clone vs borrow). +//! - `Compress`/`Decompress` impls for `OpTxEnvelope` and `OpReceipt` are added here since they +//! were previously provided by reth's in-tree codecs crate. + +use crate::{ + OpReceipt, OpTxEnvelope, OpTxType, OpTypedTransaction, POST_EXEC_TX_TYPE_ID, TxDeposit, + TxPostExec, +}; +use alloc::vec::Vec; +use alloy_consensus::{Receipt, Signed, Transaction}; +use alloy_primitives::{Address, B256, Bytes, Log, Signature, TxKind, U256}; +use alloy_rlp::Decodable; +use reth_codecs::{ + Compact, + alloy::transaction::{CompactEnvelope, Envelope, FromTxCompact, ToTxCompact}, + txtype::*, +}; + +// --- OpTxType --- + +impl Compact for OpTxType { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + match self { + Self::Legacy => COMPACT_IDENTIFIER_LEGACY, + Self::Eip2930 => COMPACT_IDENTIFIER_EIP2930, + Self::Eip1559 => COMPACT_IDENTIFIER_EIP1559, + Self::Eip7702 => { + buf.put_u8(alloy_consensus::constants::EIP7702_TX_TYPE_ID); + COMPACT_EXTENDED_IDENTIFIER_FLAG + } + Self::Deposit => { + buf.put_u8(crate::DEPOSIT_TX_TYPE_ID); + COMPACT_EXTENDED_IDENTIFIER_FLAG + } + Self::PostExec => { + buf.put_u8(POST_EXEC_TX_TYPE_ID); + COMPACT_EXTENDED_IDENTIFIER_FLAG + } + } + } + + fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) { + use bytes::Buf; + match identifier { + COMPACT_IDENTIFIER_LEGACY => (Self::Legacy, buf), + COMPACT_IDENTIFIER_EIP2930 => (Self::Eip2930, buf), + COMPACT_IDENTIFIER_EIP1559 => (Self::Eip1559, buf), + COMPACT_EXTENDED_IDENTIFIER_FLAG => { + let extended_identifier = buf.get_u8(); + let ty = match extended_identifier { + alloy_consensus::constants::EIP7702_TX_TYPE_ID => Self::Eip7702, + crate::DEPOSIT_TX_TYPE_ID => Self::Deposit, + POST_EXEC_TX_TYPE_ID => Self::PostExec, + _ => panic!("Unsupported OpTxType identifier: {extended_identifier}"), + }; + (ty, buf) + } + _ => panic!("Unknown identifier for OpTxType: {identifier}"), + } + } +} + +// --- TxDeposit --- + +/// Mirror struct for compact encoding of [`TxDeposit`]. +#[derive(reth_codecs_derive::Compact)] +#[reth_codecs(crate = "reth_codecs")] +struct CompactTxDeposit { + source_hash: B256, + from: Address, + to: TxKind, + mint: Option, + value: U256, + gas_limit: u64, + is_system_transaction: bool, + input: Bytes, +} + +impl From<&TxDeposit> for CompactTxDeposit { + fn from(tx: &TxDeposit) -> Self { + Self { + source_hash: tx.source_hash, + from: tx.from, + to: tx.to, + mint: match tx.mint { + 0 => None, + v => Some(v), + }, + value: tx.value, + gas_limit: tx.gas_limit, + is_system_transaction: tx.is_system_transaction, + input: tx.input.clone(), + } + } +} + +impl From for TxDeposit { + fn from(tx: CompactTxDeposit) -> Self { + Self { + source_hash: tx.source_hash, + from: tx.from, + to: tx.to, + mint: tx.mint.unwrap_or_default(), + value: tx.value, + gas_limit: tx.gas_limit, + is_system_transaction: tx.is_system_transaction, + input: tx.input, + } + } +} + +impl Compact for TxDeposit { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + CompactTxDeposit::from(self).to_compact(buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + let (compact, buf) = CompactTxDeposit::from_compact(buf, len); + (compact.into(), buf) + } +} + +impl Compact for TxPostExec { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + self.input().to_compact(buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + let (input, buf) = Bytes::from_compact(buf, len); + let mut slice = input.as_ref(); + let tx = Self::decode(&mut slice).expect("valid compact post-exec tx"); + (tx, buf) + } +} + +// --- OpTypedTransaction --- + +impl Compact for OpTypedTransaction { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + let tx_type = self.tx_type(); + let identifier = tx_type.to_compact(buf); + match self { + Self::Legacy(tx) => { + tx.to_compact(buf); + } + Self::Eip2930(tx) => { + tx.to_compact(buf); + } + Self::Eip1559(tx) => { + tx.to_compact(buf); + } + Self::Eip7702(tx) => { + tx.to_compact(buf); + } + Self::Deposit(tx) => { + tx.to_compact(buf); + } + Self::PostExec(tx) => { + tx.to_compact(buf); + } + } + identifier + } + + fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) { + let (tx_type, buf) = OpTxType::from_compact(buf, identifier); + match tx_type { + OpTxType::Legacy => { + let (tx, buf) = alloy_consensus::TxLegacy::from_compact(buf, buf.len()); + (Self::Legacy(tx), buf) + } + OpTxType::Eip2930 => { + let (tx, buf) = alloy_consensus::TxEip2930::from_compact(buf, buf.len()); + (Self::Eip2930(tx), buf) + } + OpTxType::Eip1559 => { + let (tx, buf) = alloy_consensus::TxEip1559::from_compact(buf, buf.len()); + (Self::Eip1559(tx), buf) + } + OpTxType::Eip7702 => { + let (tx, buf) = alloy_consensus::TxEip7702::from_compact(buf, buf.len()); + (Self::Eip7702(tx), buf) + } + OpTxType::Deposit => { + let (tx, buf) = TxDeposit::from_compact(buf, buf.len()); + (Self::Deposit(tx), buf) + } + OpTxType::PostExec => { + let (tx, buf) = TxPostExec::from_compact(buf, buf.len()); + (Self::PostExec(tx), buf) + } + } + } +} + +// --- OpTxEnvelope --- + +impl Envelope for OpTxEnvelope { + fn signature(&self) -> &Signature { + match self { + Self::Legacy(tx) => tx.signature(), + Self::Eip2930(tx) => tx.signature(), + Self::Eip1559(tx) => tx.signature(), + Self::Eip7702(tx) => tx.signature(), + Self::Deposit(_) | Self::PostExec(_) => { + const DEPOSIT_SIG: Signature = Signature::new(U256::ZERO, U256::ZERO, false); + &DEPOSIT_SIG + } + } + } + + fn tx_type(&self) -> Self::TxType { + alloy_consensus::Typed2718::ty(self).try_into().expect("valid op tx type") + } +} + +impl ToTxCompact for OpTxEnvelope { + fn to_tx_compact(&self, buf: &mut (impl bytes::BufMut + AsMut<[u8]>)) { + // Only write the tx body without the type prefix. The type is serialized separately + // by CompactEnvelope. + match self { + Self::Legacy(tx) => { + tx.tx().to_compact(buf); + } + Self::Eip2930(tx) => { + tx.tx().to_compact(buf); + } + Self::Eip1559(tx) => { + tx.tx().to_compact(buf); + } + Self::Eip7702(tx) => { + tx.tx().to_compact(buf); + } + Self::Deposit(tx) => { + tx.inner().to_compact(buf); + } + Self::PostExec(tx) => { + tx.inner().to_compact(buf); + } + }; + } +} + +impl FromTxCompact for OpTxEnvelope { + type TxType = OpTxType; + + fn from_tx_compact(buf: &[u8], tx_type: Self::TxType, signature: Signature) -> (Self, &[u8]) + where + Self: Sized, + { + // Deserialize the tx body directly based on tx_type. The type prefix was already + // consumed by CompactEnvelope. + match tx_type { + OpTxType::Legacy => { + let (tx, buf) = alloy_consensus::TxLegacy::from_compact(buf, buf.len()); + (Self::Legacy(Signed::new_unhashed(tx, signature)), buf) + } + OpTxType::Eip2930 => { + let (tx, buf) = alloy_consensus::TxEip2930::from_compact(buf, buf.len()); + (Self::Eip2930(Signed::new_unhashed(tx, signature)), buf) + } + OpTxType::Eip1559 => { + let (tx, buf) = alloy_consensus::TxEip1559::from_compact(buf, buf.len()); + (Self::Eip1559(Signed::new_unhashed(tx, signature)), buf) + } + OpTxType::Eip7702 => { + let (tx, buf) = alloy_consensus::TxEip7702::from_compact(buf, buf.len()); + (Self::Eip7702(Signed::new_unhashed(tx, signature)), buf) + } + OpTxType::Deposit => { + let (tx, buf) = TxDeposit::from_compact(buf, buf.len()); + (Self::Deposit(alloy_consensus::Sealed::new(tx)), buf) + } + OpTxType::PostExec => { + let (tx, buf) = TxPostExec::from_compact(buf, buf.len()); + (Self::PostExec(alloy_consensus::Sealed::new(tx)), buf) + } + } + } +} + +impl Compact for OpTxEnvelope { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + ::to_compact(self, buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + ::from_compact(buf, len) + } +} + +impl reth_codecs::Compress for OpTxEnvelope { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + let _ = Compact::to_compact(self, buf); + } +} + +impl reth_codecs::Decompress for OpTxEnvelope { + fn decompress(value: &[u8]) -> Result { + let (obj, _) = Compact::from_compact(value, value.len()); + Ok(obj) + } +} + +// --- OpReceipt --- + +/// Mirror struct for compact encoding of [`crate::OpDepositReceipt`]. +#[derive(reth_codecs_derive::CompactZstd)] +#[reth_codecs(crate = "reth_codecs")] +#[reth_zstd( + compressor = reth_zstd_compressors::with_receipt_compressor, + decompressor = reth_zstd_compressors::with_receipt_decompressor +)] +struct CompactOpReceipt { + tx_type: OpTxType, + success: bool, + cumulative_gas_used: u64, + logs: Vec, + deposit_nonce: Option, + deposit_receipt_version: Option, +} + +impl From<&OpReceipt> for CompactOpReceipt { + fn from(receipt: &OpReceipt) -> Self { + use alloy_consensus::TxReceipt; + let (deposit_nonce, deposit_receipt_version) = match receipt { + OpReceipt::Deposit(deposit) => (deposit.deposit_nonce, deposit.deposit_receipt_version), + _ => (None, None), + }; + Self { + tx_type: receipt.tx_type(), + success: receipt.status(), + cumulative_gas_used: receipt.cumulative_gas_used(), + logs: receipt.as_receipt().logs.clone(), + deposit_nonce, + deposit_receipt_version, + } + } +} + +impl From for OpReceipt { + fn from(compact: CompactOpReceipt) -> Self { + let receipt = Receipt { + status: compact.success.into(), + cumulative_gas_used: compact.cumulative_gas_used, + logs: compact.logs, + }; + match compact.tx_type { + OpTxType::Legacy => Self::Legacy(receipt), + OpTxType::Eip2930 => Self::Eip2930(receipt), + OpTxType::Eip1559 => Self::Eip1559(receipt), + OpTxType::Eip7702 => Self::Eip7702(receipt), + OpTxType::PostExec => Self::PostExec(receipt), + OpTxType::Deposit => Self::Deposit(crate::OpDepositReceipt { + inner: receipt, + deposit_nonce: compact.deposit_nonce, + deposit_receipt_version: compact.deposit_receipt_version, + }), + } + } +} + +impl Compact for OpReceipt { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + CompactOpReceipt::from(self).to_compact(buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + let (compact, buf) = CompactOpReceipt::from_compact(buf, len); + (compact.into(), buf) + } +} + +impl reth_codecs::Compress for OpReceipt { + type Compressed = Vec; + + fn compress_to_buf>(&self, buf: &mut B) { + let _ = Compact::to_compact(self, buf); + } +} + +impl reth_codecs::Decompress for OpReceipt { + fn decompress(value: &[u8]) -> Result { + let (obj, _) = Compact::from_compact(value, value.len()); + Ok(obj) + } +} diff --git a/rust/op-alloy/crates/consensus/src/reth_core.rs b/rust/op-alloy/crates/consensus/src/reth_core.rs new file mode 100644 index 00000000000..030d2449d88 --- /dev/null +++ b/rust/op-alloy/crates/consensus/src/reth_core.rs @@ -0,0 +1,88 @@ +//! Implementations of `InMemorySize` for OP Stack consensus types. +//! +//! Ported from reth v1.11.3 (`d6324d63e`): +//! - `crates/primitives-traits/src/size.rs` (behind `cfg(feature = "op")`) +//! +//! Differences from upstream: +//! - `OpTxType` and `TxDeposit` impls are new (upstream only had them for the compound types, but +//! the `reth-core` crate now requires them standalone). +//! - `OpTxEnvelope::Deposit` explicitly sizes the seal hash + inner tx, whereas upstream delegated +//! to `Sealed::size()` which did the same internally. + +use crate::{ + OpDepositReceipt, OpPooledTransaction, OpReceipt, OpTxEnvelope, OpTxType, OpTypedTransaction, + TxDeposit, +}; +use alloy_consensus::InMemorySize; + +impl InMemorySize for OpTxType { + fn size(&self) -> usize { + core::mem::size_of::() + } +} + +impl InMemorySize for TxDeposit { + fn size(&self) -> usize { + core::mem::size_of::() + self.input.len() + } +} + +impl InMemorySize for OpDepositReceipt { + fn size(&self) -> usize { + self.inner.size() + + core::mem::size_of_val(&self.deposit_nonce) + + core::mem::size_of_val(&self.deposit_receipt_version) + } +} + +impl InMemorySize for OpReceipt { + fn size(&self) -> usize { + match self { + Self::Legacy(receipt) | + Self::Eip2930(receipt) | + Self::Eip1559(receipt) | + Self::Eip7702(receipt) | + Self::PostExec(receipt) => receipt.size(), + Self::Deposit(receipt) => receipt.size(), + } + } +} + +impl InMemorySize for OpTypedTransaction { + fn size(&self) -> usize { + match self { + Self::Legacy(tx) => tx.size(), + Self::Eip2930(tx) => tx.size(), + Self::Eip1559(tx) => tx.size(), + Self::Eip7702(tx) => tx.size(), + Self::Deposit(tx) => tx.size(), + Self::PostExec(tx) => tx.size(), + } + } +} + +impl InMemorySize for OpPooledTransaction { + fn size(&self) -> usize { + match self { + Self::Legacy(tx) => tx.size(), + Self::Eip2930(tx) => tx.size(), + Self::Eip1559(tx) => tx.size(), + Self::Eip7702(tx) => tx.size(), + } + } +} + +impl InMemorySize for OpTxEnvelope { + fn size(&self) -> usize { + match self { + Self::Legacy(tx) => tx.size(), + Self::Eip2930(tx) => tx.size(), + Self::Eip1559(tx) => tx.size(), + Self::Eip7702(tx) => tx.size(), + Self::Deposit(tx) => core::mem::size_of::() + tx.inner().size(), + Self::PostExec(tx) => { + core::mem::size_of::() + tx.inner().size() + } + } + } +} diff --git a/rust/op-alloy/crates/consensus/src/transaction/envelope.rs b/rust/op-alloy/crates/consensus/src/transaction/envelope.rs index afe73b2474a..5fa75738ec5 100644 --- a/rust/op-alloy/crates/consensus/src/transaction/envelope.rs +++ b/rust/op-alloy/crates/consensus/src/transaction/envelope.rs @@ -1,5 +1,5 @@ use crate::{ - OpPooledTransaction, TxDeposit, + OpPooledTransaction, TxDeposit, TxPostExec, transaction::{OpDepositInfo, OpTransactionInfo}, }; use alloy_consensus::{ @@ -41,17 +41,25 @@ pub enum OpTxEnvelope { #[envelope(ty = 126)] #[serde(serialize_with = "crate::serde_deposit_tx_rpc")] Deposit(Sealed), + /// A [`TxPostExec`] tagged with type 0x7D. + #[envelope(ty = 0x7D)] + #[serde(serialize_with = "crate::post_exec::serde_post_exec_tx_rpc")] + PostExec(Sealed), } /// Represents an Optimism transaction envelope. /// -/// Compared to Ethereum it can tell whether the transaction is a deposit. +/// Compared to Ethereum it can tell whether the transaction is a deposit or post-exec +/// transaction. pub trait OpTransaction { /// Returns `true` if the transaction is a deposit. fn is_deposit(&self) -> bool; /// Returns `Some` if the transaction is a deposit. fn as_deposit(&self) -> Option<&Sealed>; + + /// Returns `Some` if the transaction is a post-exec transaction. + fn as_post_exec(&self) -> Option<&Sealed>; } impl OpTransaction for OpTxEnvelope { @@ -62,6 +70,10 @@ impl OpTransaction for OpTxEnvelope { fn as_deposit(&self) -> Option<&Sealed> { self.as_deposit() } + + fn as_post_exec(&self) -> Option<&Sealed> { + self.as_post_exec() + } } impl OpTransaction for Extended @@ -82,6 +94,13 @@ where Self::Other(t) => t.as_deposit(), } } + + fn as_post_exec(&self) -> Option<&Sealed> { + match self { + Self::BuiltIn(b) => b.as_post_exec(), + Self::Other(t) => t.as_post_exec(), + } + } } impl AsRef for OpTxEnvelope { @@ -141,6 +160,7 @@ impl From> for OpTxEnvelope { Self::Eip7702(tx) } OpTypedTransaction::Deposit(tx) => Self::Deposit(Sealed::new_unchecked(tx, hash)), + OpTypedTransaction::PostExec(tx) => Self::PostExec(Sealed::new_unchecked(tx, hash)), } } } @@ -157,6 +177,18 @@ impl From> for OpTxEnvelope { } } +impl From for OpTxEnvelope { + fn from(v: TxPostExec) -> Self { + v.seal_slow().into() + } +} + +impl From> for OpTxEnvelope { + fn from(v: Sealed) -> Self { + Self::PostExec(v) + } +} + impl From for Extended { fn from(value: OpTxEnvelope) -> Self { Self::BuiltIn(value) @@ -187,6 +219,7 @@ impl From for alloy_rpc_types_eth::TransactionRequest { OpTxEnvelope::Eip1559(tx) => tx.into_parts().0.into(), OpTxEnvelope::Eip7702(tx) => tx.into_parts().0.into(), OpTxEnvelope::Deposit(tx) => tx.into_inner().into(), + OpTxEnvelope::PostExec(tx) => tx.into_inner().into(), OpTxEnvelope::Legacy(tx) => tx.into_parts().0.into(), } } @@ -242,15 +275,18 @@ impl OpTxEnvelope { /// Attempts to convert the envelope into the pooled variant. /// /// Returns an error if the envelope's variant is incompatible with the pooled format: - /// [`TxDeposit`]. + /// [`TxDeposit`] and [`TxPostExec`]. pub fn try_into_pooled(self) -> Result> { match self { Self::Legacy(tx) => Ok(tx.into()), Self::Eip2930(tx) => Ok(tx.into()), Self::Eip1559(tx) => Ok(tx.into()), Self::Eip7702(tx) => Ok(tx.into()), - Self::Deposit(tx) => { - Err(ValueError::new(tx.into(), "Deposit transactions cannot be pooled")) + tx @ Self::Deposit(_) => { + Err(ValueError::new(tx, "Deposit transactions cannot be pooled")) + } + tx @ Self::PostExec(_) => { + Err(ValueError::new(tx, "PostExec transactions cannot be pooled")) } } } @@ -258,7 +294,7 @@ impl OpTxEnvelope { /// Attempts to convert the envelope into the ethereum pooled variant. /// /// Returns an error if the envelope's variant is incompatible with the pooled format: - /// [`TxDeposit`]. + /// [`TxDeposit`] and [`TxPostExec`]. pub fn try_into_eth_pooled( self, ) -> Result> { @@ -278,6 +314,10 @@ impl OpTxEnvelope { tx, "Deposit transactions cannot be converted to ethereum transaction", )), + tx @ Self::PostExec(_) => Err(ValueError::new( + tx, + "PostExec transactions cannot be converted to ethereum transaction", + )), } } @@ -316,6 +356,10 @@ impl OpTxEnvelope { /// Returns mutable access to the input bytes. /// /// Caution: modifying this will cause side-effects on the hash. + /// + /// For [`TxPostExec`], this mutates the cached encoded payload bytes directly and may leave + /// them out of sync with [`TxPostExec::payload`]. Rebuild the transaction with + /// [`TxPostExec::new`] if you need to restore that invariant after mutating the input. #[doc(hidden)] pub const fn input_mut(&mut self) -> &mut Bytes { match self { @@ -324,6 +368,7 @@ impl OpTxEnvelope { Self::Legacy(tx) => &mut tx.tx_mut().input, Self::Eip7702(tx) => &mut tx.tx_mut().input, Self::Deposit(tx) => &mut tx.inner_mut().input, + Self::PostExec(tx) => &mut tx.inner_mut().input, } } @@ -357,6 +402,12 @@ impl OpTxEnvelope { matches!(self, Self::Deposit(_)) } + /// Returns true if the transaction is a post-exec transaction. + #[inline] + pub const fn is_post_exec(&self) -> bool { + matches!(self, Self::PostExec(_)) + } + /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction. pub const fn as_legacy(&self) -> Option<&Signed> { match self { @@ -381,7 +432,7 @@ impl OpTxEnvelope { } } - /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction. + /// Returns the [`TxDeposit`] variant if the transaction is a deposit transaction. pub const fn as_deposit(&self) -> Option<&Sealed> { match self { Self::Deposit(tx) => Some(tx), @@ -389,16 +440,24 @@ impl OpTxEnvelope { } } + /// Returns the [`TxPostExec`] variant if the transaction is a post-exec transaction. + pub const fn as_post_exec(&self) -> Option<&Sealed> { + match self { + Self::PostExec(tx) => Some(tx), + _ => None, + } + } + /// Return the reference to signature. /// - /// Returns `None` if this is a deposit variant. + /// Returns `None` for unsigned variants: [`TxDeposit`] and [`TxPostExec`]. pub const fn signature(&self) -> Option<&Signature> { match self { Self::Legacy(tx) => Some(tx.signature()), Self::Eip2930(tx) => Some(tx.signature()), Self::Eip1559(tx) => Some(tx.signature()), Self::Eip7702(tx) => Some(tx.signature()), - Self::Deposit(_) => None, + Self::Deposit(_) | Self::PostExec(_) => None, } } @@ -410,6 +469,7 @@ impl OpTxEnvelope { Self::Eip1559(_) => OpTxType::Eip1559, Self::Eip7702(_) => OpTxType::Eip7702, Self::Deposit(_) => OpTxType::Deposit, + Self::PostExec(_) => OpTxType::PostExec, } } @@ -421,6 +481,7 @@ impl OpTxEnvelope { Self::Eip2930(tx) => tx.hash(), Self::Eip7702(tx) => tx.hash(), Self::Deposit(tx) => tx.hash_ref(), + Self::PostExec(tx) => tx.hash_ref(), } } @@ -437,6 +498,7 @@ impl OpTxEnvelope { Self::Eip1559(t) => t.eip2718_encoded_length(), Self::Eip7702(t) => t.eip2718_encoded_length(), Self::Deposit(t) => t.eip2718_encoded_length(), + Self::PostExec(t) => t.eip2718_encoded_length(), } } } @@ -460,13 +522,19 @@ impl alloy_consensus::transaction::SignerRecoverable for OpTxEnvelope { // Optimism's Deposit transaction does not have a signature. Directly return the // `from` address. Self::Deposit(tx) => return Ok(tx.from), + // Post-exec transactions are system transactions. They use a + // canonical zero-address signer rather than a cryptographic signature. + Self::PostExec(tx) => return Ok(tx.inner().signer_address()), }; let signature = match self { Self::Legacy(tx) => tx.signature(), Self::Eip2930(tx) => tx.signature(), Self::Eip1559(tx) => tx.signature(), Self::Eip7702(tx) => tx.signature(), - Self::Deposit(_) => unreachable!("Deposit transactions should not be handled here"), + // Deposit and PostExec are unsigned and handled via early return above. + Self::Deposit(_) | Self::PostExec(_) => { + unreachable!("non-signed transactions should not be handled here") + } }; alloy_consensus::crypto::secp256k1::recover_signer(signature, signature_hash) } @@ -482,13 +550,17 @@ impl alloy_consensus::transaction::SignerRecoverable for OpTxEnvelope { // Optimism's Deposit transaction does not have a signature. Directly return the // `from` address. Self::Deposit(tx) => return Ok(tx.from), + // Post-exec transactions are unsigned system transactions. They use a + // canonical zero-address signer rather than a cryptographic signature. + Self::PostExec(tx) => return Ok(tx.inner().signer_address()), }; let signature = match self { Self::Legacy(tx) => tx.signature(), Self::Eip2930(tx) => tx.signature(), Self::Eip1559(tx) => tx.signature(), Self::Eip7702(tx) => tx.signature(), - Self::Deposit(_) => unreachable!("Deposit transactions should not be handled here"), + // Deposit and PostExec are unsigned and handled via early return above. + Self::Deposit(_) | Self::PostExec(_) => unreachable!(), }; alloy_consensus::crypto::secp256k1::recover_signer_unchecked(signature, signature_hash) } @@ -510,7 +582,9 @@ impl alloy_consensus::transaction::SignerRecoverable for OpTxEnvelope { Self::Eip7702(tx) => { alloy_consensus::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf) } + // Deposit and PostExec are unsigned; return their canonical signer directly. Self::Deposit(tx) => Ok(tx.from), + Self::PostExec(tx) => Ok(tx.inner().signer_address()), } } } @@ -518,7 +592,7 @@ impl alloy_consensus::transaction::SignerRecoverable for OpTxEnvelope { /// Bincode-compatible serde implementation for `OpTxEnvelope`. #[cfg(all(feature = "serde", feature = "serde-bincode-compat"))] pub mod serde_bincode_compat { - use crate::serde_bincode_compat::TxDeposit; + use crate::{TxPostExec, serde_bincode_compat::TxDeposit}; use alloy_consensus::{ Sealed, Signed, transaction::serde_bincode_compat::{TxEip1559, TxEip2930, TxEip7702, TxLegacy}, @@ -565,6 +639,13 @@ pub mod serde_bincode_compat { /// Borrowed deposit transaction data. transaction: TxDeposit<'a>, }, + /// Post-exec variant. + PostExec { + /// Precomputed hash. + hash: B256, + /// Owned post-exec transaction data. + transaction: TxPostExec, + }, } impl<'a> From<&'a super::OpTxEnvelope> for OpTxEnvelope<'a> { @@ -590,6 +671,10 @@ pub mod serde_bincode_compat { hash: sealed_deposit.seal(), transaction: sealed_deposit.inner().into(), }, + super::OpTxEnvelope::PostExec(sealed_post_exec) => Self::PostExec { + hash: sealed_post_exec.seal(), + transaction: sealed_post_exec.inner().clone(), + }, } } } @@ -612,6 +697,9 @@ pub mod serde_bincode_compat { OpTxEnvelope::Deposit { hash, transaction } => { Self::Deposit(Sealed::new_unchecked(transaction.into(), hash)) } + OpTxEnvelope::PostExec { hash, transaction } => { + Self::PostExec(Sealed::new_unchecked(transaction, hash)) + } } } } @@ -639,11 +727,43 @@ pub mod serde_bincode_compat { #[cfg(test)] mod tests { use super::*; + use alloy_consensus::{Sealed, Signed, TxEip1559, TxEip2930, TxEip7702, TxLegacy}; use arbitrary::Arbitrary; use rand::Rng; use serde::{Deserialize, Serialize}; use serde_with::serde_as; + fn arbitrary_op_tx_envelope( + u: &mut arbitrary::Unstructured<'_>, + ) -> arbitrary::Result { + Ok(match u.int_in_range(0..=5)? { + 0 => super::super::OpTxEnvelope::Legacy(Signed::new_unhashed( + TxLegacy::arbitrary(u)?, + Signature::arbitrary(u)?, + )), + 1 => super::super::OpTxEnvelope::Eip2930(Signed::new_unhashed( + TxEip2930::arbitrary(u)?, + Signature::arbitrary(u)?, + )), + 2 => super::super::OpTxEnvelope::Eip1559(Signed::new_unhashed( + TxEip1559::arbitrary(u)?, + Signature::arbitrary(u)?, + )), + 3 => super::super::OpTxEnvelope::Eip7702(Signed::new_unhashed( + TxEip7702::arbitrary(u)?, + Signature::arbitrary(u)?, + )), + 4 => super::super::OpTxEnvelope::Deposit(Sealed::new_unchecked( + crate::TxDeposit::arbitrary(u)?, + B256::arbitrary(u)?, + )), + _ => super::super::OpTxEnvelope::PostExec(Sealed::new_unchecked( + crate::TxPostExec::arbitrary(u)?, + B256::arbitrary(u)?, + )), + }) + } + /// Tests a bincode round-trip for `OpTxEnvelope` using an arbitrary instance. #[test] fn test_op_tx_envelope_bincode_roundtrip_arbitrary() { @@ -655,14 +775,16 @@ pub mod serde_bincode_compat { envelope: super::super::OpTxEnvelope, } - let mut bytes = [0u8; 1024]; - rand::rng().fill(bytes.as_mut_slice()); - let data = Data { - envelope: super::super::OpTxEnvelope::arbitrary(&mut arbitrary::Unstructured::new( - &bytes, - )) - .unwrap(), - }; + let mut rng = rand::rng(); + let data = (0..128) + .find_map(|_| { + let mut bytes = [0u8; 4096]; + rng.fill(bytes.as_mut_slice()); + arbitrary_op_tx_envelope(&mut arbitrary::Unstructured::new(&bytes)) + .ok() + .map(|envelope| Data { envelope }) + }) + .expect("failed to generate arbitrary OpTxEnvelope"); let encoded = bincode::serde::encode_to_vec(&data, bincode::config::legacy()).unwrap(); let (decoded, _) = diff --git a/rust/op-alloy/crates/consensus/src/transaction/tx_type.rs b/rust/op-alloy/crates/consensus/src/transaction/tx_type.rs index fb6d864b662..899466f2ec5 100644 --- a/rust/op-alloy/crates/consensus/src/transaction/tx_type.rs +++ b/rust/op-alloy/crates/consensus/src/transaction/tx_type.rs @@ -21,14 +21,15 @@ impl Display for OpTxType { Self::Eip1559 => write!(f, "eip1559"), Self::Eip7702 => write!(f, "eip7702"), Self::Deposit => write!(f, "deposit"), + Self::PostExec => write!(f, "post-exec"), } } } impl OpTxType { /// List of all variants. - pub const ALL: [Self; 5] = - [Self::Legacy, Self::Eip2930, Self::Eip1559, Self::Eip7702, Self::Deposit]; + pub const ALL: [Self; 6] = + [Self::Legacy, Self::Eip2930, Self::Eip1559, Self::Eip7702, Self::Deposit, Self::PostExec]; /// Returns `true` if the type is [`OpTxType::Deposit`]. pub const fn is_deposit(&self) -> bool { @@ -44,13 +45,14 @@ mod tests { #[test] fn test_all_tx_types() { - assert_eq!(OpTxType::ALL.len(), 5); + assert_eq!(OpTxType::ALL.len(), 6); let all = vec![ OpTxType::Legacy, OpTxType::Eip2930, OpTxType::Eip1559, OpTxType::Eip7702, OpTxType::Deposit, + OpTxType::PostExec, ]; assert_eq!(OpTxType::ALL.to_vec(), all); } diff --git a/rust/op-alloy/crates/consensus/src/transaction/typed.rs b/rust/op-alloy/crates/consensus/src/transaction/typed.rs index 4cf2feaf6e5..4b901c604c5 100644 --- a/rust/op-alloy/crates/consensus/src/transaction/typed.rs +++ b/rust/op-alloy/crates/consensus/src/transaction/typed.rs @@ -1,5 +1,5 @@ pub use crate::transaction::envelope::OpTypedTransaction; -use crate::{OpTxEnvelope, OpTxType, TxDeposit}; +use crate::{OpTxEnvelope, OpTxType, TxDeposit, TxPostExec}; use alloy_consensus::{ EthereumTypedTransaction, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip7702, TxLegacy, Typed2718, TypedTransaction, error::ValueError, transaction::RlpEcdsaEncodableTx, @@ -37,6 +37,12 @@ impl From for OpTypedTransaction { } } +impl From for OpTypedTransaction { + fn from(tx: TxPostExec) -> Self { + Self::PostExec(tx) + } +} + impl From for OpTypedTransaction { fn from(envelope: OpTxEnvelope) -> Self { match envelope { @@ -45,6 +51,7 @@ impl From for OpTypedTransaction { OpTxEnvelope::Eip1559(tx) => Self::Eip1559(tx.strip_signature()), OpTxEnvelope::Eip7702(tx) => Self::Eip7702(tx.strip_signature()), OpTxEnvelope::Deposit(tx) => Self::Deposit(tx.into_inner()), + OpTxEnvelope::PostExec(tx) => Self::PostExec(tx.into_inner()), } } } @@ -66,6 +73,7 @@ impl From for alloy_rpc_types_eth::TransactionRequest { OpTypedTransaction::Eip1559(tx) => tx.into(), OpTypedTransaction::Eip7702(tx) => tx.into(), OpTypedTransaction::Deposit(tx) => tx.into(), + OpTypedTransaction::PostExec(tx) => tx.into(), } } } @@ -79,19 +87,21 @@ impl OpTypedTransaction { Self::Eip1559(_) => OpTxType::Eip1559, Self::Eip7702(_) => OpTxType::Eip7702, Self::Deposit(_) => OpTxType::Deposit, + Self::PostExec(_) => OpTxType::PostExec, } } /// Calculates the signing hash for the transaction. /// - /// Returns `None` if the tx is a deposit transaction. + /// Returns `None` for unsigned transaction variants: [`TxDeposit`] and [`TxPostExec`]. pub fn checked_signature_hash(&self) -> Option { match self { Self::Legacy(tx) => Some(tx.signature_hash()), Self::Eip2930(tx) => Some(tx.signature_hash()), Self::Eip1559(tx) => Some(tx.signature_hash()), Self::Eip7702(tx) => Some(tx.signature_hash()), - Self::Deposit(_) => None, + // Deposit and PostExec are system transactions with no signature hash. + Self::Deposit(_) | Self::PostExec(_) => None, } } @@ -127,6 +137,14 @@ impl OpTypedTransaction { } } + /// Return the inner post-exec transaction if it exists. + pub const fn post_exec(&self) -> Option<&TxPostExec> { + match self { + Self::PostExec(tx) => Some(tx), + _ => None, + } + } + /// Returns `true` if transaction is deposit transaction. pub const fn is_deposit(&self) -> bool { matches!(self, Self::Deposit(_)) @@ -134,7 +152,8 @@ impl OpTypedTransaction { /// Calculate the transaction hash for the given signature. /// - /// Note: Returns the regular tx hash if this is a deposit variant + /// Note: returns the regular transaction hash for unsigned variants: [`TxDeposit`] and + /// [`TxPostExec`]. In those cases the provided signature is ignored. pub fn tx_hash(&self, signature: &Signature) -> TxHash { match self { Self::Legacy(tx) => tx.tx_hash(signature), @@ -142,28 +161,30 @@ impl OpTypedTransaction { Self::Eip1559(tx) => tx.tx_hash(signature), Self::Eip7702(tx) => tx.tx_hash(signature), Self::Deposit(tx) => tx.tx_hash(), + Self::PostExec(tx) => tx.tx_hash(), } } /// Convenience function to convert this typed transaction into an [`OpTxEnvelope`]. /// - /// Note: If this is a [`OpTypedTransaction::Deposit`] variant, the signature will be ignored. + /// Note: for unsigned variants ([`OpTypedTransaction::Deposit`] and + /// [`OpTypedTransaction::PostExec`]), the provided signature is ignored. pub fn into_envelope(self, signature: Signature) -> OpTxEnvelope { self.into_signed(signature).into() } /// Attempts to convert the optimism variant into an ethereum [`TypedTransaction`]. /// - /// Returns the typed transaction as error if it is a variant unsupported on ethereum: - /// [`TxDeposit`] + /// Returns the typed transaction as an error if it is a variant unsupported on ethereum: + /// [`TxDeposit`] or [`TxPostExec`]. pub fn try_into_eth(self) -> Result> { self.try_into_eth_variant() } /// Attempts to convert the optimism variant into an ethereum [`TypedTransaction`]. /// - /// Returns the typed transaction as error if it is a variant unsupported on ethereum: - /// [`TxDeposit`] + /// Returns the typed transaction as an error if it is a variant unsupported on ethereum: + /// [`TxDeposit`] or [`TxPostExec`]. pub fn try_into_eth_variant( self, ) -> Result, ValueError> { @@ -176,6 +197,10 @@ impl OpTypedTransaction { tx, "Deposit transactions cannot be converted to ethereum transaction", )), + tx @ Self::PostExec(_) => Err(ValueError::new( + tx, + "PostExec transactions cannot be converted to ethereum transaction", + )), } } } @@ -188,6 +213,7 @@ impl RlpEcdsaEncodableTx for OpTypedTransaction { Self::Eip1559(tx) => tx.rlp_encoded_fields_length(), Self::Eip7702(tx) => tx.rlp_encoded_fields_length(), Self::Deposit(tx) => tx.rlp_encoded_fields_length(), + Self::PostExec(tx) => tx.rlp_encoded_fields_length(), } } @@ -198,6 +224,7 @@ impl RlpEcdsaEncodableTx for OpTypedTransaction { Self::Eip1559(tx) => tx.rlp_encode_fields(out), Self::Eip7702(tx) => tx.rlp_encode_fields(out), Self::Deposit(tx) => tx.rlp_encode_fields(out), + Self::PostExec(tx) => tx.rlp_encode_fields(out), } } @@ -208,6 +235,7 @@ impl RlpEcdsaEncodableTx for OpTypedTransaction { Self::Eip1559(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out), Self::Eip7702(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out), Self::Deposit(tx) => tx.encode_2718(out), + Self::PostExec(tx) => tx.encode_2718(out), } } @@ -218,6 +246,7 @@ impl RlpEcdsaEncodableTx for OpTypedTransaction { Self::Eip1559(tx) => tx.eip2718_encode(signature, out), Self::Eip7702(tx) => tx.eip2718_encode(signature, out), Self::Deposit(tx) => tx.encode_2718(out), + Self::PostExec(tx) => tx.encode_2718(out), } } @@ -228,6 +257,7 @@ impl RlpEcdsaEncodableTx for OpTypedTransaction { Self::Eip1559(tx) => tx.network_encode_with_type(signature, tx.ty(), out), Self::Eip7702(tx) => tx.network_encode_with_type(signature, tx.ty(), out), Self::Deposit(tx) => tx.network_encode(out), + Self::PostExec(tx) => tx.network_encode(out), } } @@ -238,6 +268,7 @@ impl RlpEcdsaEncodableTx for OpTypedTransaction { Self::Eip1559(tx) => tx.network_encode(signature, out), Self::Eip7702(tx) => tx.network_encode(signature, out), Self::Deposit(tx) => tx.network_encode(out), + Self::PostExec(tx) => tx.network_encode(out), } } @@ -248,6 +279,7 @@ impl RlpEcdsaEncodableTx for OpTypedTransaction { Self::Eip1559(tx) => tx.tx_hash_with_type(signature, tx.ty()), Self::Eip7702(tx) => tx.tx_hash_with_type(signature, tx.ty()), Self::Deposit(tx) => tx.tx_hash(), + Self::PostExec(tx) => tx.tx_hash(), } } @@ -258,6 +290,7 @@ impl RlpEcdsaEncodableTx for OpTypedTransaction { Self::Eip1559(tx) => tx.tx_hash(signature), Self::Eip7702(tx) => tx.tx_hash(signature), Self::Deposit(tx) => tx.tx_hash(), + Self::PostExec(tx) => tx.tx_hash(), } } } @@ -269,7 +302,7 @@ impl SignableTransaction for OpTypedTransaction { Self::Eip2930(tx) => tx.set_chain_id(chain_id), Self::Eip1559(tx) => tx.set_chain_id(chain_id), Self::Eip7702(tx) => tx.set_chain_id(chain_id), - Self::Deposit(_) => {} + Self::Deposit(_) | Self::PostExec(_) => {} } } @@ -279,7 +312,7 @@ impl SignableTransaction for OpTypedTransaction { Self::Eip2930(tx) => tx.encode_for_signing(out), Self::Eip1559(tx) => tx.encode_for_signing(out), Self::Eip7702(tx) => tx.encode_for_signing(out), - Self::Deposit(_) => {} + Self::Deposit(_) | Self::PostExec(_) => {} } } @@ -289,7 +322,7 @@ impl SignableTransaction for OpTypedTransaction { Self::Eip2930(tx) => tx.payload_len_for_signature(), Self::Eip1559(tx) => tx.payload_len_for_signature(), Self::Eip7702(tx) => tx.payload_len_for_signature(), - Self::Deposit(_) => 0, + Self::Deposit(_) | Self::PostExec(_) => 0, } } diff --git a/rust/op-alloy/crates/network/Cargo.toml b/rust/op-alloy/crates/network/Cargo.toml index f9e1882b979..2dfd3d6f06c 100644 --- a/rust/op-alloy/crates/network/Cargo.toml +++ b/rust/op-alloy/crates/network/Cargo.toml @@ -2,7 +2,7 @@ name = "op-alloy-network" description = "Optimism blockchain RPC behavior abstraction" -version = "0.23.1" +version = "2.0.0" edition.workspace = true rust-version.workspace = true authors = ["Alloy Contributors"] @@ -17,28 +17,24 @@ workspace = true [dependencies] # Workspace op-alloy-consensus = { workspace = true, features = ["alloy-compat"] } -op-alloy-rpc-types.workspace = true +op-alloy-rpc-types = { workspace = true, features = ["std"] } # Alloy alloy-consensus.workspace = true alloy-network.workspace = true -alloy-primitives.workspace = true alloy-provider.workspace = true alloy-rpc-types-eth.workspace = true -alloy-signer.workspace = true [features] std = [ "op-alloy-consensus/std", "op-alloy-rpc-types/std", "alloy-consensus/std", - "alloy-primitives/std", "alloy-rpc-types-eth/std" ] serde = [ "op-alloy-consensus/serde", "op-alloy-rpc-types/serde", "alloy-consensus/serde", - "alloy-primitives/serde", "alloy-rpc-types-eth/serde" ] diff --git a/rust/op-alloy/crates/network/src/lib.rs b/rust/op-alloy/crates/network/src/lib.rs index 03d72dc31d3..3580be3481d 100644 --- a/rust/op-alloy/crates/network/src/lib.rs +++ b/rust/op-alloy/crates/network/src/lib.rs @@ -8,10 +8,8 @@ pub use alloy_network::*; -use alloy_consensus::{ReceiptWithBloom, TxEnvelope, TxType, TypedTransaction}; -use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256}; -use alloy_rpc_types_eth::AccessList; -use op_alloy_consensus::{OpReceipt, OpTxEnvelope, OpTxType, OpTypedTransaction}; +use alloy_consensus::{ReceiptWithBloom, TxType}; +use op_alloy_consensus::{OpReceipt, OpTxType, OpTypedTransaction}; use op_alloy_rpc_types::OpTransactionRequest; /// Types for an Op-stack network. @@ -43,124 +41,29 @@ impl Network for Optimism { alloy_rpc_types_eth::Block; } -impl TransactionBuilder for OpTransactionRequest { - fn chain_id(&self) -> Option { - self.as_ref().chain_id() - } - - fn set_chain_id(&mut self, chain_id: ChainId) { - self.as_mut().set_chain_id(chain_id); - } - - fn nonce(&self) -> Option { - self.as_ref().nonce() - } - - fn set_nonce(&mut self, nonce: u64) { - self.as_mut().set_nonce(nonce); - } - - fn take_nonce(&mut self) -> Option { - self.as_mut().nonce.take() - } - - fn input(&self) -> Option<&Bytes> { - self.as_ref().input() - } - - fn set_input>(&mut self, input: T) { - self.as_mut().set_input(input); - } - - fn from(&self) -> Option
{ - self.as_ref().from() - } - - fn set_from(&mut self, from: Address) { - self.as_mut().set_from(from); - } - - fn kind(&self) -> Option { - self.as_ref().kind() - } - - fn clear_kind(&mut self) { - self.as_mut().clear_kind(); - } - - fn set_kind(&mut self, kind: TxKind) { - self.as_mut().set_kind(kind); - } - - fn value(&self) -> Option { - self.as_ref().value() - } - - fn set_value(&mut self, value: U256) { - self.as_mut().set_value(value); - } - - fn gas_price(&self) -> Option { - self.as_ref().gas_price() - } - - fn set_gas_price(&mut self, gas_price: u128) { - self.as_mut().set_gas_price(gas_price); - } - - fn max_fee_per_gas(&self) -> Option { - self.as_ref().max_fee_per_gas() - } - - fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) { - self.as_mut().set_max_fee_per_gas(max_fee_per_gas); - } - - fn max_priority_fee_per_gas(&self) -> Option { - self.as_ref().max_priority_fee_per_gas() - } - - fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) { - self.as_mut().set_max_priority_fee_per_gas(max_priority_fee_per_gas); - } - - fn gas_limit(&self) -> Option { - self.as_ref().gas_limit() - } - - fn set_gas_limit(&mut self, gas_limit: u64) { - self.as_mut().set_gas_limit(gas_limit); - } - - fn access_list(&self) -> Option<&AccessList> { - self.as_ref().access_list() - } - - fn set_access_list(&mut self, access_list: AccessList) { - self.as_mut().set_access_list(access_list); - } - +impl NetworkTransactionBuilder for OpTransactionRequest { fn complete_type(&self, ty: OpTxType) -> Result<(), Vec<&'static str>> { match ty { OpTxType::Deposit => Err(vec!["not implemented for deposit tx"]), + OpTxType::PostExec => Err(vec!["not implemented for post-exec tx"]), _ => { let ty = TxType::try_from(ty as u8).unwrap(); - self.as_ref().complete_type(ty) + NetworkTransactionBuilder::::complete_type(self.as_ref(), ty) } } } fn can_submit(&self) -> bool { - self.as_ref().can_submit() + NetworkTransactionBuilder::::can_submit(self.as_ref()) } fn can_build(&self) -> bool { - self.as_ref().can_build() + NetworkTransactionBuilder::::can_build(self.as_ref()) } #[doc(alias = "output_transaction_type")] fn output_tx_type(&self) -> OpTxType { - match self.as_ref().preferred_type() { + match NetworkTransactionBuilder::::output_tx_type(self.as_ref()) { TxType::Eip1559 | TxType::Eip4844 => OpTxType::Eip1559, TxType::Eip2930 => OpTxType::Eip2930, TxType::Eip7702 => OpTxType::Eip7702, @@ -170,16 +73,18 @@ impl TransactionBuilder for OpTransactionRequest { #[doc(alias = "output_transaction_type_checked")] fn output_tx_type_checked(&self) -> Option { - self.as_ref().buildable_type().map(|tx_ty| match tx_ty { - TxType::Eip1559 | TxType::Eip4844 => OpTxType::Eip1559, - TxType::Eip2930 => OpTxType::Eip2930, - TxType::Eip7702 => OpTxType::Eip7702, - TxType::Legacy => OpTxType::Legacy, + NetworkTransactionBuilder::::output_tx_type_checked(self.as_ref()).map(|tx_ty| { + match tx_ty { + TxType::Eip1559 | TxType::Eip4844 => OpTxType::Eip1559, + TxType::Eip2930 => OpTxType::Eip2930, + TxType::Eip7702 => OpTxType::Eip7702, + TxType::Legacy => OpTxType::Legacy, + } }) } fn prep_for_submission(&mut self) { - self.as_mut().prep_for_submission(); + NetworkTransactionBuilder::::prep_for_submission(self.as_mut()); } fn build_unsigned(self) -> BuildResult { @@ -199,45 +104,6 @@ impl TransactionBuilder for OpTransactionRequest { } } -impl NetworkWallet for EthereumWallet { - fn default_signer_address(&self) -> Address { - NetworkWallet::::default_signer_address(self) - } - - fn has_signer_for(&self, address: &Address) -> bool { - NetworkWallet::::has_signer_for(self, address) - } - - fn signer_addresses(&self) -> impl Iterator { - NetworkWallet::::signer_addresses(self) - } - - async fn sign_transaction_from( - &self, - sender: Address, - tx: OpTypedTransaction, - ) -> alloy_signer::Result { - let tx = match tx { - OpTypedTransaction::Legacy(tx) => TypedTransaction::Legacy(tx), - OpTypedTransaction::Eip2930(tx) => TypedTransaction::Eip2930(tx), - OpTypedTransaction::Eip1559(tx) => TypedTransaction::Eip1559(tx), - OpTypedTransaction::Eip7702(tx) => TypedTransaction::Eip7702(tx), - OpTypedTransaction::Deposit(_) => { - return Err(alloy_signer::Error::other("not implemented for deposit tx")); - } - }; - let tx = NetworkWallet::::sign_transaction_from(self, sender, tx).await?; - - Ok(match tx { - TxEnvelope::Eip1559(tx) => OpTxEnvelope::Eip1559(tx), - TxEnvelope::Eip2930(tx) => OpTxEnvelope::Eip2930(tx), - TxEnvelope::Eip7702(tx) => OpTxEnvelope::Eip7702(tx), - TxEnvelope::Legacy(tx) => OpTxEnvelope::Legacy(tx), - _ => unreachable!(), - }) - } -} - use alloy_provider::fillers::{ ChainIdFiller, GasFiller, JoinFill, NonceFiller, RecommendedFillers, }; diff --git a/rust/op-alloy/crates/op-alloy/Cargo.toml b/rust/op-alloy/crates/op-alloy/Cargo.toml index 01f792166be..83b341c38de 100644 --- a/rust/op-alloy/crates/op-alloy/Cargo.toml +++ b/rust/op-alloy/crates/op-alloy/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "op-alloy" description = "Connect applications to the OP Stack" -version = "0.23.1" +version = "2.0.0" edition.workspace = true rust-version.workspace = true authors = ["Alloy Contributors"] @@ -68,6 +68,8 @@ consensus = ["dep:op-alloy-consensus"] rpc-types = ["dep:op-alloy-rpc-types"] rpc-types-engine = ["dep:op-alloy-rpc-types-engine"] +reth = ["rpc-types", "op-alloy-rpc-types/reth"] + # std features network = ["dep:op-alloy-network"] rpc-jsonrpsee = ["dep:op-alloy-rpc-jsonrpsee"] diff --git a/rust/op-alloy/crates/op-alloy/README.md b/rust/op-alloy/crates/op-alloy/README.md index 775f6d256f4..d7107ec8aa8 100644 --- a/rust/op-alloy/crates/op-alloy/README.md +++ b/rust/op-alloy/crates/op-alloy/README.md @@ -61,8 +61,8 @@ This would not be possible without the hard work from open source contributors. ### License -Licensed under either of Apache License, Version -2.0 or MIT license at your option. +Licensed under either of Apache License, Version +2.0 or MIT license at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in these crates by you, as defined in the Apache-2.0 license, diff --git a/rust/op-alloy/crates/provider/Cargo.toml b/rust/op-alloy/crates/provider/Cargo.toml index 6eea54cf581..3ddb7db14f3 100644 --- a/rust/op-alloy/crates/provider/Cargo.toml +++ b/rust/op-alloy/crates/provider/Cargo.toml @@ -2,7 +2,7 @@ name = "op-alloy-provider" description = "Interface with an OP Stack blockchain" -version = "0.23.1" +version = "2.0.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/rust/op-alloy/crates/provider/src/ext/engine.rs b/rust/op-alloy/crates/provider/src/ext/engine.rs index 4cfcd106932..14811728625 100644 --- a/rust/op-alloy/crates/provider/src/ext/engine.rs +++ b/rust/op-alloy/crates/provider/src/ext/engine.rs @@ -8,7 +8,7 @@ use alloy_rpc_types_engine::{ use alloy_transport::{Transport, TransportResult}; use op_alloy_rpc_types_engine::{ OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4, - OpPayloadAttributes, ProtocolVersion, + OpPayloadAttributes, }; /// Extension trait that gives access to Optimism engine API RPC methods. @@ -170,18 +170,6 @@ pub trait OpEngineApi { client_version: ClientVersionV1, ) -> TransportResult>; - /// Optional extension to the Engine API. - /// - /// Signals superchain information to the Engine: V1 signals which protocol version is - /// recommended and required. - /// - /// See : - async fn signal_superchain_v1( - &self, - recommended: ProtocolVersion, - required: ProtocolVersion, - ) -> TransportResult; - /// Returns the list of Engine API methods supported by the execution layer client software. /// /// See also @@ -300,15 +288,6 @@ where self.client().request("engine_getClientVersionV1", (client_version,)).await } - async fn signal_superchain_v1( - &self, - recommended: ProtocolVersion, - required: ProtocolVersion, - ) -> TransportResult { - let signal = op_alloy_rpc_types_engine::SuperchainSignal { recommended, required }; - self.client().request("engine_signalSuperchainV1", (signal,)).await - } - async fn exchange_capabilities( &self, capabilities: Vec, diff --git a/rust/op-alloy/crates/rpc-jsonrpsee/Cargo.toml b/rust/op-alloy/crates/rpc-jsonrpsee/Cargo.toml index 1e0eec62d0e..b0d4100ab4f 100644 --- a/rust/op-alloy/crates/rpc-jsonrpsee/Cargo.toml +++ b/rust/op-alloy/crates/rpc-jsonrpsee/Cargo.toml @@ -2,7 +2,7 @@ name = "op-alloy-rpc-jsonrpsee" description = "Optimism RPC Client" -version = "0.23.1" +version = "2.0.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/rust/op-alloy/crates/rpc-types-engine/Cargo.toml b/rust/op-alloy/crates/rpc-types-engine/Cargo.toml index 2b7a8d0b3ac..fa246ab79cb 100644 --- a/rust/op-alloy/crates/rpc-types-engine/Cargo.toml +++ b/rust/op-alloy/crates/rpc-types-engine/Cargo.toml @@ -2,7 +2,7 @@ name = "op-alloy-rpc-types-engine" description = "Optimism RPC types for the `engine` namespace" -version = "0.23.1" +version = "2.0.0" edition.workspace = true rust-version.workspace = true license.workspace = true @@ -41,8 +41,6 @@ arbitrary = { workspace = true, features = ["derive"], optional = true } # hashing sha2.workspace = true -derive_more = { workspace = true, features = ["as_ref", "deref_mut"] } - [dev-dependencies] arbtest.workspace = true serde_json.workspace = true @@ -63,7 +61,6 @@ std = [ "alloy-eips/std", "alloy-rlp/std", "alloy-serde?/std", - "derive_more/std", "serde?/std", "sha2/std", "thiserror/std" diff --git a/rust/op-alloy/crates/rpc-types-engine/src/attributes.rs b/rust/op-alloy/crates/rpc-types-engine/src/attributes.rs index 4480985c424..c21214d986c 100644 --- a/rust/op-alloy/crates/rpc-types-engine/src/attributes.rs +++ b/rust/op-alloy/crates/rpc-types-engine/src/attributes.rs @@ -241,6 +241,7 @@ mod test { suggested_fee_recipient: address!("0x4200000000000000000000000000000000000011"), withdrawals: Some([].into()), parent_beacon_block_root: b256!("0x8fe0193b9bf83cb7e5a08538e494fecc23046aab9a497af3704f4afdae3250ff").into(), + slot_number: Default::default(), }, transactions: Some([bytes!("7ef8f8a0dc19cfa777d90980e4875d0a548a881baaa3f83f14d1bc0d3038bc329350e54194deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e20000f424000000000000000000000000300000000670d6d890000000000000125000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000014bf9181db6e381d4384bbf69c48b0ee0eed23c6ca26143c6d2544f9d39997a590000000000000000000000007f83d659683caf2767fd3c720981d51f5bc365bc")].into()), no_tx_pool: None, @@ -274,6 +275,7 @@ mod test { suggested_fee_recipient: address!("0x4200000000000000000000000000000000000011"), withdrawals: Some([].into()), parent_beacon_block_root: b256!("0x8fe0193b9bf83cb7e5a08538e494fecc23046aab9a497af3704f4afdae3250ff").into(), + slot_number: Default::default(), }, transactions: Some([bytes!("7ef8f8a0dc19cfa777d90980e4875d0a548a881baaa3f83f14d1bc0d3038bc329350e54194deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e20000f424000000000000000000000000300000000670d6d890000000000000125000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000014bf9181db6e381d4384bbf69c48b0ee0eed23c6ca26143c6d2544f9d39997a590000000000000000000000007f83d659683caf2767fd3c720981d51f5bc365bc")].into()), no_tx_pool: None, @@ -302,6 +304,7 @@ mod test { suggested_fee_recipient: Address::ZERO, withdrawals: Default::default(), parent_beacon_block_root: Some(B256::ZERO), + slot_number: Default::default(), }, transactions: Some(vec![b"hello".to_vec().into()]), no_tx_pool: Some(true), @@ -325,6 +328,7 @@ mod test { suggested_fee_recipient: Address::ZERO, withdrawals: Default::default(), parent_beacon_block_root: Some(B256::ZERO), + slot_number: Default::default(), }, transactions: Some(vec![b"hello".to_vec().into()]), no_tx_pool: Some(true), @@ -366,6 +370,7 @@ mod test { suggested_fee_recipient: Address::ZERO, withdrawals: Default::default(), parent_beacon_block_root: Some(B256::ZERO), + slot_number: Default::default(), }, transactions: Some(vec![b"hello".to_vec().into()]), no_tx_pool: Some(true), @@ -389,6 +394,7 @@ mod test { suggested_fee_recipient: Address::ZERO, withdrawals: Default::default(), parent_beacon_block_root: Some(B256::ZERO), + slot_number: Default::default(), }, transactions: Some(vec![b"hello".to_vec().into()]), no_tx_pool: Some(true), diff --git a/rust/op-alloy/crates/rpc-types-engine/src/lib.rs b/rust/op-alloy/crates/rpc-types-engine/src/lib.rs index 4b45b7b6e90..7f640b8a81b 100644 --- a/rust/op-alloy/crates/rpc-types-engine/src/lib.rs +++ b/rust/op-alloy/crates/rpc-types-engine/src/lib.rs @@ -31,11 +31,6 @@ pub use payload::{ v4::{OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4}, }; -mod superchain; -pub use superchain::{ - ProtocolVersion, ProtocolVersionError, ProtocolVersionFormatV0, SuperchainSignal, -}; - pub mod flashblock; pub use flashblock::{ OpFlashblockError, OpFlashblockPayload, OpFlashblockPayloadBase, OpFlashblockPayloadDelta, diff --git a/rust/op-alloy/crates/rpc-types-engine/src/payload/mod.rs b/rust/op-alloy/crates/rpc-types-engine/src/payload/mod.rs index 613251ef6b4..cce476b2888 100644 --- a/rust/op-alloy/crates/rpc-types-engine/src/payload/mod.rs +++ b/rust/op-alloy/crates/rpc-types-engine/src/payload/mod.rs @@ -514,6 +514,7 @@ impl OpExecutionPayload { blob_gas_used: self.blob_gas_used(), difficulty: U256::ZERO, mix_hash: Some(self.prev_randao()), + slot_number: None, } } diff --git a/rust/op-alloy/crates/rpc-types-engine/src/superchain.rs b/rust/op-alloy/crates/rpc-types-engine/src/superchain.rs deleted file mode 100644 index 39b35054af2..00000000000 --- a/rust/op-alloy/crates/rpc-types-engine/src/superchain.rs +++ /dev/null @@ -1,470 +0,0 @@ -//! Superchain types - -use alloc::{ - format, - string::{String, ToString}, -}; -use core::array::TryFromSliceError; - -use alloy_primitives::{B64, B256}; -use derive_more::derive::{Display, From}; - -/// Superchain Signal information. -/// -/// The execution engine SHOULD warn the user when the recommended version is newer than the current -/// version supported by the execution engine. -/// -/// The execution engine SHOULD take safety precautions if it does not meet the required protocol -/// version. This may include halting the engine, with consent of the execution engine operator. -/// -/// See also: -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct SuperchainSignal { - /// The recommended Superchain Protocol Version. - pub recommended: ProtocolVersion, - /// The minimum Superchain Protocol Version required. - pub required: ProtocolVersion, -} - -/// Formatted Superchain Protocol Version. -/// -/// The Protocol Version documents the progression of the total set of canonical OP-Stack -/// specifications. Components of the OP-Stack implement the subset of their respective protocol -/// component domain, up to a given Protocol Version of the OP-Stack. -/// -/// The Protocol Version **is NOT a hardfork identifier**, but rather indicates software-support for -/// a well-defined set of features introduced in past and future hardforks, not the activation of -/// said hardforks. -/// -/// The Protocol Version is Semver-compatible. It is encoded as a single 32 bytes long -/// protocol version. The version must be encoded as 32 bytes of DATA in JSON RPC usage. -/// -/// See also: -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum ProtocolVersion { - /// Version-type 0. - V0(ProtocolVersionFormatV0), -} - -impl core::fmt::Display for ProtocolVersion { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::V0(value) => write!(f, "{value}"), - } - } -} - -/// An error that can occur when encoding or decoding a `ProtocolVersion`. -#[derive(Copy, Clone, thiserror::Error, Debug, Display, From)] -pub enum ProtocolVersionError { - /// An unsupported version was encountered. - #[display("Unsupported version: {_0}")] - UnsupportedVersion(u8), - /// An invalid length was encountered. - #[display("Invalid length: got {}, expected {}", got, expected)] - InvalidLength { - /// The length that was encountered. - got: usize, - /// The expected length. - expected: usize, - }, - /// Failed to convert slice to array. - #[display("Failed to convert slice to array")] - #[from(TryFromSliceError)] - TryFromSlice, -} - -impl ProtocolVersion { - /// Version-type 0 byte encoding: - /// - /// ```text - /// ::= - /// ::= - /// ::= <31 bytes> - /// ``` - pub fn encode(&self) -> B256 { - let mut bytes = [0u8; 32]; - - match self { - Self::V0(value) => { - bytes[0] = 0x00; // this is not necessary, but added for clarity - bytes[1..].copy_from_slice(&value.encode()); - B256::from_slice(&bytes) - } - } - } - - /// Version-type 0 byte decoding: - /// - /// ```text - /// ::= - /// ::= - /// ::= <31 bytes> - /// ``` - pub fn decode(value: B256) -> Result { - let version_type = value[0]; - let typed_payload = &value[1..]; - - match version_type { - 0 => Ok(Self::V0(ProtocolVersionFormatV0::decode(typed_payload)?)), - other => Err(ProtocolVersionError::UnsupportedVersion(other)), - } - } - - /// Returns the inner value of the `ProtocolVersion` enum - pub const fn inner(&self) -> ProtocolVersionFormatV0 { - match self { - Self::V0(value) => *value, - } - } - - /// Returns the inner value of the `ProtocolVersion` enum if it is V0, otherwise None - pub const fn as_v0(&self) -> Option { - match self { - Self::V0(value) => Some(*value), - } - } - - /// Differentiates forks and custom-builds of standard protocol - pub const fn build(&self) -> B64 { - match self { - Self::V0(value) => value.build, - } - } - - /// Incompatible API changes - pub const fn major(&self) -> u32 { - match self { - Self::V0(value) => value.major, - } - } - - /// Identifies additional functionality in backwards compatible manner - pub const fn minor(&self) -> u32 { - match self { - Self::V0(value) => value.minor, - } - } - - /// Identifies backward-compatible bug-fixes - pub const fn patch(&self) -> u32 { - match self { - Self::V0(value) => value.patch, - } - } - - /// Identifies unstable versions that may not satisfy the above - pub const fn pre_release(&self) -> u32 { - match self { - Self::V0(value) => value.pre_release, - } - } - - /// Returns a human-readable string representation of the `ProtocolVersion` - pub fn display(&self) -> String { - match self { - Self::V0(value) => format!("{value}"), - } - } -} - -#[cfg(feature = "serde")] -impl serde::Serialize for ProtocolVersion { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.encode().serialize(serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for ProtocolVersion { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = alloy_primitives::B256::deserialize(deserializer)?; - Self::decode(value).map_err(serde::de::Error::custom) - } -} - -/// The Protocol Version V0 format. -/// Encoded as 31 bytes with the following structure: -/// -/// ```text -/// -/// ::= <7 zeroed bytes> -/// ::= <8 bytes> -/// ::= -/// ::= -/// ::= -/// ::= -/// ``` -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] -pub struct ProtocolVersionFormatV0 { - /// Differentiates forks and custom-builds of standard protocol - pub build: B64, - /// Incompatible API changes - pub major: u32, - /// Identifies additional functionality in backwards compatible manner - pub minor: u32, - /// Identifies backward-compatible bug-fixes - pub patch: u32, - /// Identifies unstable versions that may not satisfy the above - pub pre_release: u32, -} - -impl core::fmt::Display for ProtocolVersionFormatV0 { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let build_tag = if self.build.0.iter().any(|&byte| byte != 0) { - if self.is_readable_build_tag() { - let full = format!("+{}", String::from_utf8_lossy(&self.build.0)); - full.trim_end_matches('\0').to_string() - } else { - format!("+{}", self.build) - } - } else { - String::new() - }; - - let pre_release_tag = - if self.pre_release != 0 { format!("-{}", self.pre_release) } else { String::new() }; - - write!(f, "v{}.{}.{}{}{}", self.major, self.minor, self.patch, pre_release_tag, build_tag) - } -} - -impl ProtocolVersionFormatV0 { - /// Returns true if the build tag is human-readable, false otherwise. - pub fn is_readable_build_tag(&self) -> bool { - for (i, &c) in self.build.iter().enumerate() { - if c == 0 { - // Trailing zeros are allowed - if self.build[i..].iter().any(|&d| d != 0) { - return false; - } - return true; - } - - // following semver.org advertised regex, alphanumeric with '-' and '.', except leading - // '.'. - if !(c.is_ascii_alphanumeric() || c == b'-' || (c == b'.' && i > 0)) { - return false; - } - } - true - } - - /// Version-type 0 byte encoding: - /// - /// ```text - /// - /// ::= <7 zeroed bytes> - /// ::= <8 bytes> - /// ::= - /// ::= - /// ::= - /// ::= - /// ``` - pub fn encode(&self) -> [u8; 31] { - let mut bytes = [0u8; 31]; - bytes[0..7].copy_from_slice(&[0u8; 7]); - bytes[7..15].copy_from_slice(&self.build.0); - bytes[15..19].copy_from_slice(&self.major.to_be_bytes()); - bytes[19..23].copy_from_slice(&self.minor.to_be_bytes()); - bytes[23..27].copy_from_slice(&self.patch.to_be_bytes()); - bytes[27..31].copy_from_slice(&self.pre_release.to_be_bytes()); - bytes - } - - /// Version-type 0 byte encoding: - /// - /// ```text - /// - /// ::= <7 zeroed bytes> - /// ::= <8 bytes> - /// ::= - /// ::= - /// ::= - /// ::= - /// ``` - fn decode(value: &[u8]) -> Result { - if value.len() != 31 { - return Err(ProtocolVersionError::InvalidLength { got: value.len(), expected: 31 }); - } - - Ok(Self { - build: B64::from_slice(&value[7..15]), - major: u32::from_be_bytes(value[15..19].try_into()?), - minor: u32::from_be_bytes(value[19..23].try_into()?), - patch: u32::from_be_bytes(value[23..27].try_into()?), - pre_release: u32::from_be_bytes(value[27..31].try_into()?), - }) - } -} - -#[cfg(test)] -mod tests { - use alloy_primitives::b256; - - use super::*; - - #[test] - fn test_protocol_version_display() { - assert_eq!( - ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), - major: 42, - minor: 0, - patch: 2, - pre_release: 0, - }) - .display(), - "v42.0.2+0x6162010000000000" - ); - } - - #[test] - fn test_protocol_version_accessors() { - let inner = ProtocolVersionFormatV0 { - build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), - major: 42, - minor: 0, - patch: 2, - pre_release: 0, - }; - let protocol_version = ProtocolVersion::V0(inner); - - assert_eq!( - protocol_version.build(), - B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]) - ); - assert_eq!(protocol_version.major(), 42); - assert_eq!(protocol_version.minor(), 0); - assert_eq!(protocol_version.patch(), 2); - assert_eq!(protocol_version.pre_release(), 0); - assert_eq!(protocol_version.inner(), inner); - assert_eq!(protocol_version.as_v0(), Some(inner)); - } - - #[test] - #[cfg(feature = "serde")] - fn test_protocol_version_serde() { - let raw_protocol_version = r#" - "0x000000000000000061620100000000000000002a000000000000000200000000" - "#; - let protocol_version = ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), - major: 42, - minor: 0, - patch: 2, - pre_release: 0, - }); - - let encoded = serde_json::to_string(&protocol_version).unwrap(); - assert_eq!(encoded, raw_protocol_version.trim()); - } - - #[test] - fn test_protocol_version_encode_decode() { - let test_cases = [ - ( - ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), - major: 42, - minor: 0, - patch: 2, - pre_release: 0, - }), - "v42.0.2+0x6162010000000000", - b256!("000000000000000061620100000000000000002a000000000000000200000000"), - ), - ( - ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), - major: 42, - minor: 0, - patch: 2, - pre_release: 1, - }), - "v42.0.2-1+0x6162010000000000", - b256!("000000000000000061620100000000000000002a000000000000000200000001"), - ), - ( - ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]), - major: 42, - minor: 0, - patch: 2, - pre_release: 0, - }), - "v42.0.2+0x0102030405060708", - b256!("000000000000000001020304050607080000002a000000000000000200000000"), - ), - ( - ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), - major: 0, - minor: 100, - patch: 2, - pre_release: 0, - }), - "v0.100.2", - b256!("0000000000000000000000000000000000000000000000640000000200000000"), - ), - ( - ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(&[b'O', b'P', b'-', b'm', b'o', b'd', 0x00, 0x00]), - major: 42, - minor: 0, - patch: 2, - pre_release: 1, - }), - "v42.0.2-1+OP-mod", - b256!("00000000000000004f502d6d6f6400000000002a000000000000000200000001"), - ), - ( - ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(&[b'a', b'b', 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]), - major: 42, - minor: 0, - patch: 2, - pre_release: 0, - }), - "v42.0.2+0x6162010000000000", // do not render invalid alpha numeric - b256!("000000000000000061620100000000000000002a000000000000000200000000"), - ), - ( - ProtocolVersion::V0(ProtocolVersionFormatV0 { - build: B64::from_slice(b"beta.123"), - major: 1, - minor: 0, - patch: 0, - pre_release: 0, - }), - "v1.0.0+beta.123", - b256!("0000000000000000626574612e31323300000001000000000000000000000000"), - ), - ] - .to_vec(); - - for (decoded_exp, formatted_exp, encoded_exp) in test_cases { - encode_decode_v0(encoded_exp, formatted_exp, decoded_exp); - } - } - - fn encode_decode_v0(encoded_exp: B256, formatted_exp: &str, decoded_exp: ProtocolVersion) { - let decoded = ProtocolVersion::decode(encoded_exp).unwrap(); - assert_eq!(decoded, decoded_exp); - - let encoded = decoded.encode(); - assert_eq!(encoded, encoded_exp); - - let formatted = decoded.display(); - assert_eq!(formatted, formatted_exp); - } -} diff --git a/rust/op-alloy/crates/rpc-types/Cargo.toml b/rust/op-alloy/crates/rpc-types/Cargo.toml index 58537af2a04..c3cd66e5123 100644 --- a/rust/op-alloy/crates/rpc-types/Cargo.toml +++ b/rust/op-alloy/crates/rpc-types/Cargo.toml @@ -2,7 +2,7 @@ name = "op-alloy-rpc-types" description = "Optimism RPC types" -version = "0.23.1" +version = "2.0.0" edition.workspace = true rust-version.workspace = true license.workspace = true @@ -22,6 +22,7 @@ op-alloy-consensus = { workspace = true, features = ["serde"] } alloy-serde.workspace = true alloy-consensus.workspace = true alloy-network-primitives.workspace = true +alloy-network = { workspace = true, optional = true } alloy-eips = { workspace = true, features = ["serde"] } alloy-rpc-types-eth = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["map", "rlp", "serde"] } @@ -32,6 +33,8 @@ serde = { workspace = true, features = ["derive"] } # RPC jsonrpsee = { workspace = true, optional = true } +reth-rpc-traits = { workspace = true, optional = true } +alloy-signer = { workspace = true, optional = true } # arbitrary arbitrary = { workspace = true, features = ["derive"], optional = true } @@ -51,6 +54,7 @@ similar-asserts.workspace = true [features] default = ["std"] std = [ + "dep:alloy-network", "alloy-network-primitives/std", "alloy-eips/std", "alloy-primitives/std", @@ -61,7 +65,8 @@ std = [ "derive_more/std", "serde/std", "serde_json/std", - "thiserror/std" + "thiserror/std", + "reth-rpc-traits?/std" ] arbitrary = [ "std", @@ -83,3 +88,4 @@ serde = [ "alloy-rpc-types-eth/serde" ] jsonrpsee = ["dep:jsonrpsee"] +reth = ["dep:reth-rpc-traits", "dep:alloy-signer", "dep:alloy-network"] diff --git a/rust/op-alloy/crates/rpc-types/src/genesis.rs b/rust/op-alloy/crates/rpc-types/src/genesis.rs index e49556be590..0bd74e63642 100644 --- a/rust/op-alloy/crates/rpc-types/src/genesis.rs +++ b/rust/op-alloy/crates/rpc-types/src/genesis.rs @@ -56,6 +56,8 @@ pub struct OpGenesisInfo { pub interop_time: Option, /// jovian hardfork timestamp pub jovian_time: Option, + /// karst hardfork timestamp + pub karst_time: Option, } impl OpGenesisInfo { @@ -136,6 +138,7 @@ mod tests { isthmus_time: None, interop_time: None, jovian_time: None, + karst_time: None, } ); } @@ -197,6 +200,7 @@ mod tests { isthmus_time: None, interop_time: None, jovian_time: None, + karst_time: None, }), base_fee_info: Some(OpBaseFeeInfo { eip1559_elasticity: None, @@ -222,6 +226,7 @@ mod tests { isthmus_time: None, interop_time: None, jovian_time: None, + karst_time: None, }), base_fee_info: Some(OpBaseFeeInfo { eip1559_elasticity: None, @@ -265,6 +270,7 @@ mod tests { isthmus_time: Some(0), interop_time: None, jovian_time: Some(0), + karst_time: None, }), base_fee_info: None, } diff --git a/rust/op-alloy/crates/rpc-types/src/lib.rs b/rust/op-alloy/crates/rpc-types/src/lib.rs index e34acc9726c..d6e984e651a 100644 --- a/rust/op-alloy/crates/rpc-types/src/lib.rs +++ b/rust/op-alloy/crates/rpc-types/src/lib.rs @@ -9,6 +9,9 @@ extern crate alloc; +#[cfg(feature = "arbitrary")] +use arbitrary as _; + mod genesis; pub use genesis::{OpBaseFeeInfo, OpChainInfo, OpGenesisInfo}; @@ -20,3 +23,6 @@ pub use transaction::{OpTransactionFields, OpTransactionRequest, Transaction}; pub mod error; pub use error::SuperchainDAError; + +#[cfg(feature = "reth")] +mod reth_compat; diff --git a/rust/op-alloy/crates/rpc-types/src/receipt.rs b/rust/op-alloy/crates/rpc-types/src/receipt.rs index 5d7be127446..8faae24fdbe 100644 --- a/rust/op-alloy/crates/rpc-types/src/receipt.rs +++ b/rust/op-alloy/crates/rpc-types/src/receipt.rs @@ -19,6 +19,9 @@ pub struct OpTransactionReceipt { /// L1 block info of the transaction. #[serde(flatten)] pub l1_block_info: L1BlockInfo, + /// Per-transaction gas refund from post-exec block-level warming. + #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + pub op_gas_refund: Option, } impl alloy_network_primitives::ReceiptResponse for OpTransactionReceipt { @@ -87,6 +90,9 @@ pub struct OpTransactionReceiptFields { /// L1 block info. #[serde(flatten)] pub l1_block_info: L1BlockInfo, + /// Per-transaction gas refund from post-exec block-level warming. + #[serde(default, skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt")] + pub op_gas_refund: Option, /* --------------------------------------- Regolith --------------------------------------- */ /// Deposit nonce for deposit transactions. /// @@ -229,6 +235,9 @@ impl From for OpReceiptEnvelope { OpReceiptEnvelope::Eip2930(receipt) => Self::Eip2930(convert_standard_receipt(receipt)), OpReceiptEnvelope::Eip1559(receipt) => Self::Eip1559(convert_standard_receipt(receipt)), OpReceiptEnvelope::Eip7702(receipt) => Self::Eip7702(convert_standard_receipt(receipt)), + OpReceiptEnvelope::PostExec(receipt) => { + Self::PostExec(convert_standard_receipt(receipt)) + } OpReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { logs_bloom, receipt }) => { let consensus_logs = receipt.inner.logs.into_iter().map(|log| log.inner).collect(); let consensus_receipt = OpDepositReceiptWithBloom { diff --git a/rust/op-alloy/crates/rpc-types/src/reth_compat.rs b/rust/op-alloy/crates/rpc-types/src/reth_compat.rs new file mode 100644 index 00000000000..a0153d63dc0 --- /dev/null +++ b/rust/op-alloy/crates/rpc-types/src/reth_compat.rs @@ -0,0 +1,65 @@ +//! Implementations of `reth-rpc-traits` for OP types. +//! +//! Ported from reth v1.11.3 (`d6324d63e`), where they lived behind `cfg(feature = "op")`: +//! - `FromConsensusTx`, `TryIntoSimTx`: `crates/rpc/rpc-convert/src/transaction.rs` +//! - `SignableTxRequest`: `crates/rpc/rpc-convert/src/rpc.rs` +//! +//! The traits themselves moved from `reth-rpc-convert` to the published `reth-rpc-traits` +//! crate (v0.1.0), so the impls now target `reth-rpc-traits` types. The logic is identical +//! to upstream. + +use alloy_consensus::SignableTransaction; +use alloy_primitives::Address; +use alloy_signer::Signature; +use core::convert::Infallible; +use op_alloy_consensus::{ + OpTxEnvelope, TxDeposit, + transaction::{OpTransaction, OpTransactionInfo}, +}; +use reth_rpc_traits::{FromConsensusTx, SignTxRequestError, SignableTxRequest, TryIntoSimTx}; + +use crate::OpTransactionRequest; + +impl FromConsensusTx for crate::Transaction { + type TxInfo = OpTransactionInfo; + type Err = Infallible; + + fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Result { + Ok(Self::from_transaction( + alloy_consensus::transaction::Recovered::new_unchecked(tx, signer), + tx_info, + )) + } +} + +impl TryIntoSimTx for OpTransactionRequest { + fn try_into_sim_tx(self) -> Result> { + let tx = self.build_typed_tx().map_err(|request| { + alloy_consensus::error::ValueError::new(request, "Required fields missing") + })?; + + // Create an empty signature for the transaction. + let signature = Signature::new(Default::default(), Default::default(), false); + + Ok(tx.into_signed(signature).into()) + } +} + +impl SignableTxRequest for OpTransactionRequest { + async fn try_build_and_sign( + self, + signer: impl alloy_network::TxSigner + Send, + ) -> Result { + let mut tx = + self.build_typed_tx().map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + + // Deposit transactions must not be signed by the user. + if matches!(tx, op_alloy_consensus::OpTypedTransaction::Deposit(TxDeposit { .. })) { + return Err(SignTxRequestError::InvalidTransactionRequest); + } + + let signature = signer.sign_transaction(&mut tx).await?; + + Ok(tx.into_signed(signature).into()) + } +} diff --git a/rust/op-alloy/crates/rpc-types/src/transaction.rs b/rust/op-alloy/crates/rpc-types/src/transaction.rs index ae936895d56..fedb6059964 100644 --- a/rust/op-alloy/crates/rpc-types/src/transaction.rs +++ b/rust/op-alloy/crates/rpc-types/src/transaction.rs @@ -57,6 +57,7 @@ impl Transaction { block_number: tx_info.inner.block_number, transaction_index: tx_info.inner.index, effective_gas_price: Some(effective_gas_price), + block_timestamp: tx_info.inner.block_timestamp, }, deposit_nonce: tx_info.deposit_meta.deposit_nonce, deposit_receipt_version: tx_info.deposit_meta.deposit_receipt_version, @@ -251,6 +252,12 @@ mod tx_serde { skip_serializing_if = "Option::is_none", with = "alloy_serde::quantity::opt" )] + block_timestamp: Option, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + )] deposit_receipt_version: Option, #[serde(flatten)] @@ -267,6 +274,7 @@ mod tx_serde { block_number, transaction_index, effective_gas_price, + block_timestamp, }, deposit_receipt_version, deposit_nonce, @@ -283,6 +291,7 @@ mod tx_serde { block_hash, block_number, transaction_index, + block_timestamp, deposit_receipt_version, other: OptionalFields { from, effective_gas_price, deposit_nonce }, } @@ -298,6 +307,7 @@ mod tx_serde { block_hash, block_number, transaction_index, + block_timestamp, deposit_receipt_version, other, } = value; @@ -325,6 +335,7 @@ mod tx_serde { block_number, transaction_index, effective_gas_price, + block_timestamp, }, deposit_receipt_version, deposit_nonce, @@ -336,6 +347,11 @@ mod tx_serde { #[cfg(test)] mod tests { use super::*; + use alloy_consensus::transaction::Recovered; + use alloy_primitives::Address; + use op_alloy_consensus::{ + OpTxEnvelope, SDMGasEntry, build_post_exec_tx, transaction::OpTransactionInfo, + }; #[test] fn can_deserialize_deposit() { @@ -356,4 +372,25 @@ mod tests { let expected = serde_json::from_str::(rpc_tx).unwrap(); similar_asserts::assert_eq!(deserialized, expected); } + + #[test] + fn can_serialize_post_exec_rpc_transaction() { + let post_exec = build_post_exec_tx(42, vec![SDMGasEntry { index: 3, gas_refund: 7 }]); + let expected_input = serde_json::to_value(post_exec.input.clone()).unwrap(); + let expected_hash = serde_json::to_value(post_exec.tx_hash()).unwrap(); + + let tx = Transaction::from_transaction( + Recovered::new_unchecked(OpTxEnvelope::from(post_exec), Address::ZERO), + OpTransactionInfo::default(), + ); + + let value = serde_json::to_value(&tx).unwrap(); + + assert_eq!(value.get("type").unwrap(), "0x7d"); + assert_eq!(value.get("input"), Some(&expected_input)); + assert_eq!(value.get("hash"), Some(&expected_hash)); + assert_eq!(value.get("from"), Some(&serde_json::to_value(Address::ZERO).unwrap())); + assert!(value.get("gasRefundEntries").is_none()); + assert!(value.get("version").is_none()); + } } diff --git a/rust/op-alloy/crates/rpc-types/src/transaction/request.rs b/rust/op-alloy/crates/rpc-types/src/transaction/request.rs index bf621ca5a40..5a414fc6cad 100644 --- a/rust/op-alloy/crates/rpc-types/src/transaction/request.rs +++ b/rust/op-alloy/crates/rpc-types/src/transaction/request.rs @@ -5,8 +5,12 @@ use alloy_consensus::{ use alloy_eips::eip7702::SignedAuthorization; use alloy_network_primitives::TransactionBuilder7702; use alloy_primitives::{Address, Signature, TxKind, U256}; +#[cfg(feature = "std")] +use alloy_primitives::{Bytes, ChainId}; use alloy_rpc_types_eth::{AccessList, TransactionInput, TransactionRequest}; -use op_alloy_consensus::{OpTxEnvelope, OpTypedTransaction, TxDeposit}; +use op_alloy_consensus::{ + OpTxEnvelope, OpTypedTransaction, POST_EXEC_TX_TYPE_ID, TxDeposit, TxPostExec, +}; use serde::{Deserialize, Serialize}; /// Builder for [`OpTypedTransaction`]. @@ -156,6 +160,20 @@ impl From> for OpTransactionRequest { } } +impl From for OpTransactionRequest { + fn from(tx: TxPostExec) -> Self { + Self(TransactionRequest { + from: Some(tx.signer_address()), + transaction_type: Some(POST_EXEC_TX_TYPE_ID), + gas: Some(0), + nonce: Some(0), + value: Some(U256::ZERO), + input: tx.input.into(), + ..Default::default() + }) + } +} + impl From> for OpTransactionRequest where T: SignableTransaction + Into, @@ -181,6 +199,7 @@ impl From for OpTransactionRequest { OpTypedTransaction::Eip1559(tx) => Self(tx.into()), OpTypedTransaction::Eip7702(tx) => Self(tx.into()), OpTypedTransaction::Deposit(tx) => tx.into(), + OpTypedTransaction::PostExec(tx) => tx.into(), } } } @@ -192,11 +211,22 @@ impl From for OpTransactionRequest { OpTxEnvelope::Eip1559(tx) => tx.into(), OpTxEnvelope::Eip7702(tx) => tx.into(), OpTxEnvelope::Deposit(tx) => tx.into(), + OpTxEnvelope::PostExec(tx) => tx.into_inner().into(), _ => Default::default(), } } } +impl From for OpTransactionRequest { + fn from(tx: super::Transaction) -> Self { + let recovered = tx.inner.into_recovered(); + let from = recovered.signer(); + let mut req: Self = recovered.into_inner().into(); + req.0.from = Some(from); + req + } +} + impl TransactionBuilder7702 for OpTransactionRequest { fn authorization_list(&self) -> Option<&Vec> { self.as_ref().authorization_list() @@ -206,3 +236,102 @@ impl TransactionBuilder7702 for OpTransactionRequest { self.as_mut().set_authorization_list(authorization_list); } } + +#[cfg(feature = "std")] +impl alloy_network::TransactionBuilder for OpTransactionRequest { + fn chain_id(&self) -> Option { + self.as_ref().chain_id() + } + + fn set_chain_id(&mut self, chain_id: ChainId) { + self.as_mut().set_chain_id(chain_id); + } + + fn nonce(&self) -> Option { + self.as_ref().nonce() + } + + fn set_nonce(&mut self, nonce: u64) { + self.as_mut().set_nonce(nonce); + } + + fn take_nonce(&mut self) -> Option { + self.as_mut().nonce.take() + } + + fn input(&self) -> Option<&Bytes> { + self.as_ref().input() + } + + fn set_input>(&mut self, input: T) { + self.as_mut().set_input(input); + } + + fn from(&self) -> Option
{ + self.as_ref().from() + } + + fn set_from(&mut self, from: Address) { + self.as_mut().set_from(from); + } + + fn kind(&self) -> Option { + self.as_ref().kind() + } + + fn clear_kind(&mut self) { + self.as_mut().clear_kind(); + } + + fn set_kind(&mut self, kind: TxKind) { + self.as_mut().set_kind(kind); + } + + fn value(&self) -> Option { + self.as_ref().value() + } + + fn set_value(&mut self, value: U256) { + self.as_mut().set_value(value); + } + + fn gas_price(&self) -> Option { + self.as_ref().gas_price() + } + + fn set_gas_price(&mut self, gas_price: u128) { + self.as_mut().set_gas_price(gas_price); + } + + fn max_fee_per_gas(&self) -> Option { + self.as_ref().max_fee_per_gas() + } + + fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) { + self.as_mut().set_max_fee_per_gas(max_fee_per_gas); + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.as_ref().max_priority_fee_per_gas() + } + + fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) { + self.as_mut().set_max_priority_fee_per_gas(max_priority_fee_per_gas); + } + + fn gas_limit(&self) -> Option { + self.as_ref().gas_limit() + } + + fn set_gas_limit(&mut self, gas_limit: u64) { + self.as_mut().set_gas_limit(gas_limit); + } + + fn access_list(&self) -> Option<&AccessList> { + self.as_ref().access_list() + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.as_mut().set_access_list(access_list); + } +} diff --git a/rust/op-alloy/release.toml b/rust/op-alloy/release.toml index 12bd6dc4929..7456074bc9f 100644 --- a/rust/op-alloy/release.toml +++ b/rust/op-alloy/release.toml @@ -7,4 +7,4 @@ sign-tag = true shared-version = true pre-release-commit-message = "chore: release {{version}}" tag-prefix = "" # tag only once instead of per every crate -pre-release-hook = ["sh", "-c", "$WORKSPACE_ROOT/scripts/changelog.sh --tag {{version}}"] +pre-release-hook = ["sh", "-c", "$WORKSPACE_ROOT/op-alloy/scripts/changelog.sh --tag {{version}}"] diff --git a/rust/op-rbuilder/.dockerignore b/rust/op-rbuilder/.dockerignore new file mode 100644 index 00000000000..9847736d31a --- /dev/null +++ b/rust/op-rbuilder/.dockerignore @@ -0,0 +1,14 @@ +/cargo +/data +/mev-test-contract/cache +/mev-test-contract/out +/target +/scripts/benchmark-results.* +/test/ +/integration_logs +Dockerfile + +# editors +.code +.idea +.vscode diff --git a/rust/op-rbuilder/.editorconfig b/rust/op-rbuilder/.editorconfig new file mode 100644 index 00000000000..19fe6f5ad6e --- /dev/null +++ b/rust/op-rbuilder/.editorconfig @@ -0,0 +1,29 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.rs] +max_line_length = 100 + +[*.{yml,yaml}] +indent_size = 2 + +[*.md] +# double whitespace at end of line +# denotes a line break in Markdown +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab + +[] diff --git a/rust/op-rbuilder/.gitignore b/rust/op-rbuilder/.gitignore new file mode 100644 index 00000000000..f01ed3a0abe --- /dev/null +++ b/rust/op-rbuilder/.gitignore @@ -0,0 +1,16 @@ +/cargo +/data +/mev-test-contract/cache +/mev-test-contract/out +/target +/scripts/benchmark-results.* +/test/ +/integration_logs +genesis.json +/op-reth +.env + +# editors +.code +.idea +.vscode diff --git a/rust/op-rbuilder/.gitmodules b/rust/op-rbuilder/.gitmodules new file mode 100644 index 00000000000..c5944599194 --- /dev/null +++ b/rust/op-rbuilder/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mev-test-contract/lib/forge-std"] + path = mev-test-contract/lib/forge-std + url = https://github.com/foundry-rs/forge-std.git diff --git a/rust/op-rbuilder/CONTRIBUTING.md b/rust/op-rbuilder/CONTRIBUTING.md new file mode 100644 index 00000000000..a67c45d38f2 --- /dev/null +++ b/rust/op-rbuilder/CONTRIBUTING.md @@ -0,0 +1,75 @@ +## Contributing to rbuilder + +Welcome to the Flashbots collective! + +Thanks for your help improving `rbuilder`! We are so happy to have you! We just ask you to be nice when you play with us. + +Please start by reading our [license agreement](#individual-contributor-license-agreement) below, and our [code of conduct](#code-of-conduct). + +The [dev Telegram](https://t.me/flashbots_rbuilder) is available for any questions and concerns you may have that are not covered in the documentation. + +If you contribute to this project, your contributions will be made to the project under both Apache 2.0 and the MIT +license. + +### Ways to contribute + +There are fundamentally three ways an individual can contribute: + +1. **By opening an issue:** For example, if you believe that you have uncovered a bug + in `rbuilder`, creating a new issue in the issue tracker is the way to report it. +2. **By adding context:** Providing additional context to existing issues, + such as screenshots and code snippets to help resolve issues. +3. **By resolving issues:** Typically this is done in the form of either + demonstrating that the issue reported is not a problem after all, or more often, + by opening a pull request that fixes the underlying problem, in a concrete and + reviewable manner. + +**Anybody can participate in any stage of contribution**. We urge you to participate in the discussion around bugs and +participate in reviewing PRs. + +### Contributions Related to Spelling and Grammar + +At the moment, we discourage contributions that only fix spelling or grammatical errors in documentation, code or +elsewhere. + +### Code style + +Start by making sure that your code is readable, consistent, and pretty. +Follow the [Rust Style Guide](https://doc.rust-lang.org/nightly/style-guide/) recommendations. + +### Send a pull request + +- Your proposed changes should be first described and discussed in an issue. +- Open the branch in a personal fork, not in the team repository. +- Every pull request should be small and represent a single change. If the problem is complicated, split it in multiple issues and pull requests. +- Every pull request should include one or more tests to ensure that rbuilder does not regress in the future. + +We appreciate you, friend <3. + +--- + +## Code of Conduct + +The `rbuilder` project adheres to the [Rust Code of Conduct](https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md). This code of conduct describes the _minimum_ behavior +expected from all contributors. + +Instances of violations of the Code of Conduct can be reported by contacting the team +at [rbuilder@flashbots.net](mailto:rbuilder@flashbots.net). + +--- + +## Individual Contributor License Agreement + +This text is adapted from Google's contributors license agreement: https://cla.developers.google.com/about/google-individual + +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to Flashbots. Except for the license granted herein to Flashbots and recipients of software distributed by Flashbots, You reserve all right, title, and interest in and to Your Contributions. +1. Definitions. + * "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Flashbots. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + * "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Flashbots for inclusion in, or documentation of, any of the products owned or managed by Flashbots (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to Flashbots or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Flashbots for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." +2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to Flashbots and to recipients of software distributed by Flashbots a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works, under the terms of the license which the project is using on the Submission Date. +3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to Flashbots and to recipients of software distributed by Flashbots a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. +4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to Flashbots, or that your employer has executed a separate Corporate CLA with Flashbots. +5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. +6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. +7. Should You wish to submit work that is not Your original creation, You may submit it to Flashbots separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". +8. You agree to notify Flashbots of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. \ No newline at end of file diff --git a/rust/op-rbuilder/Cargo.lock b/rust/op-rbuilder/Cargo.lock new file mode 100644 index 00000000000..00fbb1d1c15 --- /dev/null +++ b/rust/op-rbuilder/Cargo.lock @@ -0,0 +1,16260 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ab0cd8afe573d1f7dc2353698a51b1f93aec362c8211e28cfd3948c6adba39" +dependencies = [ + "alloy-consensus 1.8.3", + "alloy-contract 1.8.3", + "alloy-core", + "alloy-eips 1.8.3", + "alloy-genesis 1.8.3", + "alloy-network 1.8.3", + "alloy-provider 1.8.3", + "alloy-rpc-client 1.8.3", + "alloy-rpc-types 1.8.3", + "alloy-serde 1.8.3", + "alloy-signer 1.8.3", + "alloy-signer-local 1.8.3", + "alloy-transport 1.8.3", + "alloy-transport-http 1.8.3", + "alloy-trie", +] + +[[package]] +name = "alloy-chains" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "num_enum", + "serde", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f16daaf7e1f95f62c6c3bf8a3fc3d78b08ae9777810c0bb5e94966c7cd57ef0" +dependencies = [ + "alloy-eips 1.8.3", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 1.8.3", + "alloy-trie", + "alloy-tx-macros 1.8.3", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "rand 0.8.6", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-consensus" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83447eeb17816e172f1dfc0db1f9dc0b7c5d069bd1f7cecbecceb382bf931015" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 2.0.5", + "alloy-trie", + "alloy-tx-macros 2.0.5", + "arbitrary", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "rand 0.8.6", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "118998d9015332ab1b4720ae1f1e3009491966a0349938a1f43ff45a8a4c6299" +dependencies = [ + "alloy-consensus 1.8.3", + "alloy-eips 1.8.3", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 1.8.3", + "serde", +] + +[[package]] +name = "alloy-consensus-any" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5406343e306856dc2be762700e98a16904de45dee14a07f233e742ce68daff2f" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 2.0.5", + "arbitrary", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac9e0c34dc6bce643b182049cdfcca1b8ce7d9c260cbdd561f511873b7e26cd" +dependencies = [ + "alloy-consensus 1.8.3", + "alloy-dyn-abi", + "alloy-json-abi 1.6.0", + "alloy-network 1.8.3", + "alloy-network-primitives 1.8.3", + "alloy-primitives 1.6.0", + "alloy-provider 1.8.3", + "alloy-rpc-types-eth 1.8.3", + "alloy-sol-types 1.6.0", + "alloy-transport 1.8.3", + "futures", + "futures-util", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-contract" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8b60d71b92824e095b4003ff01fd2bc923017b7568997c5f459240e83499c" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-dyn-abi", + "alloy-json-abi 1.6.0", + "alloy-network 2.0.5", + "alloy-network-primitives 2.0.5", + "alloy-primitives 1.6.0", + "alloy-provider 2.0.5", + "alloy-rpc-types-eth 2.0.5", + "alloy-sol-types 1.6.0", + "alloy-transport 2.0.5", + "futures", + "futures-util", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-core" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ddde5968de6044d67af107ad835bc0069a7ca245870b94c5958a7d8712b184" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi 1.6.0", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-sol-types 1.6.0", +] + +[[package]] +name = "alloy-dyn-abi" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a475bb02d9cef2dbb99065c1664ab3fe1f9352e21d6d5ed3f02cdbfc06ed1abc" +dependencies = [ + "alloy-json-abi 1.6.0", + "alloy-primitives 1.6.0", + "alloy-sol-type-parser 1.6.0", + "alloy-sol-types 1.6.0", + "derive_more", + "itoa", + "serde", + "serde_json", + "winnow 1.0.3", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "arbitrary", + "crc", + "rand 0.8.6", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "arbitrary", + "borsh", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "arbitrary", + "borsh", + "k256", + "rand 0.8.6", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "arbitrary", + "borsh", + "once_cell", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eips" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 1.8.3", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "serde", + "serde_with", + "sha2", +] + +[[package]] +name = "alloy-eips" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dca4c89ace90684b4b77366d00631ed498c9af962079af2a5dbc593a0618a77" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 2.0.5", + "arbitrary", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "ethereum_ssz", + "ethereum_ssz_derive", + "serde", + "serde_with", + "sha2", +] + +[[package]] +name = "alloy-evm" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ceeea6dcbbcd4e546b27700763a6f6c3b3fee30054209884f521078b6fda4f" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-hardforks", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "alloy-sol-types 1.6.0", + "auto_impl", + "derive_more", + "revm", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-genesis" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf9480307b09d22876efb67d30cadd9013134c21f3a17ec9f93fd7536d38024" +dependencies = [ + "alloy-eips 1.8.3", + "alloy-primitives 1.6.0", + "alloy-serde 1.8.3", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-genesis" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0e0fe9e6d1120ad7bb9254c3fc2b9bc80a8df42a033fb626be6559c13d5153" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-serde 2.0.5", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-hardforks" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives 1.6.0", + "auto_impl", + "dyn-clone", + "serde", +] + +[[package]] +name = "alloy-json-abi" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4584e3641181ff073e9d5bec5b3b8f78f9749d9fb108a1cfbc4399a4a139c72a" +dependencies = [ + "alloy-primitives 0.8.26", + "alloy-sol-type-parser 0.8.26", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-abi" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c36c9d7f9021601b04bfef14a4b64849f6d73116a4e91e071d7fbfe10247901" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-sol-type-parser 1.6.0", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "422d110f1c40f1f8d0e5562b0b649c35f345fccb7093d9f02729943dcd1eef71" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-sol-types 1.6.0", + "http", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-json-rpc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0a82e56b1843bce483942d54fcadea92e676f1bde162e93c7d3b621fabc4e1" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-sol-types 1.6.0", + "http", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7197a66d94c4de1591cdc16a9bcea5f8cccd0da81b865b49aef97b1b4016e0fa" +dependencies = [ + "alloy-consensus 1.8.3", + "alloy-consensus-any 1.8.3", + "alloy-eips 1.8.3", + "alloy-json-rpc 1.8.3", + "alloy-network-primitives 1.8.3", + "alloy-primitives 1.6.0", + "alloy-rpc-types-any 1.8.3", + "alloy-rpc-types-eth 1.8.3", + "alloy-serde 1.8.3", + "alloy-signer 1.8.3", + "alloy-sol-types 1.6.0", + "async-trait", + "auto_impl", + "derive_more", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-network" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7db7b095b0b1db1d18ce7e91dcd2e82007f2d52bfb8125e6b64633a74a06bc3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-consensus-any 2.0.5", + "alloy-eips 2.0.5", + "alloy-json-rpc 2.0.5", + "alloy-network-primitives 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-any 2.0.5", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "alloy-signer 2.0.5", + "alloy-sol-types 1.6.0", + "async-trait", + "auto_impl", + "derive_more", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb82711d59a43fdfd79727c99f270b974c784ec4eb5728a0d0d22f26716c87ef" +dependencies = [ + "alloy-consensus 1.8.3", + "alloy-eips 1.8.3", + "alloy-primitives 1.6.0", + "alloy-serde 1.8.3", + "serde", +] + +[[package]] +name = "alloy-network-primitives" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd28d9bfd11729037d194f2b1d43db8642eb3f342032691f4ca96bb745479c3c" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-serde 2.0.5", + "serde", +] + +[[package]] +name = "alloy-op-evm" +version = "0.32.0" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-op-hardforks", + "alloy-primitives 1.6.0", + "auto_impl", + "op-alloy", + "op-revm", + "revm", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-op-hardforks" +version = "0.5.0" +dependencies = [ + "alloy-chains", + "alloy-hardforks", + "alloy-primitives 1.6.0", + "auto_impl", +] + +[[package]] +name = "alloy-primitives" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777d58b30eb9a4db0e5f59bc30e8c2caef877fee7dc8734cf242a51a60f22e05" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash 0.1.5", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.8.6", + "ruint", + "rustc-hash", + "serde", + "sha3 0.10.9", + "tiny-keccak", +] + +[[package]] +name = "alloy-primitives" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" +dependencies = [ + "alloy-rlp", + "arbitrary", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "fixed-cache", + "foldhash 0.2.0", + "getrandom 0.4.2", + "hashbrown 0.17.1", + "indexmap 2.14.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "proptest-derive 0.8.0", + "rand 0.9.4", + "rapidhash", + "ruint", + "rustc-hash", + "secp256k1 0.31.1", + "serde", + "sha3 0.11.0", +] + +[[package]] +name = "alloy-provider" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6b18b929ef1d078b834c3631e9c925177f3b23ddc6fa08a722d13047205876" +dependencies = [ + "alloy-chains", + "alloy-consensus 1.8.3", + "alloy-eips 1.8.3", + "alloy-json-rpc 1.8.3", + "alloy-network 1.8.3", + "alloy-network-primitives 1.8.3", + "alloy-primitives 1.6.0", + "alloy-rpc-client 1.8.3", + "alloy-rpc-types-eth 1.8.3", + "alloy-signer 1.8.3", + "alloy-sol-types 1.6.0", + "alloy-transport 1.8.3", + "alloy-transport-http 1.8.3", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "either", + "futures", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest 0.13.3", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-provider" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8955ab30418343de57b356de2ea60200f9fb8016a7ea3bc7f5c6176f01a8b1cf" +dependencies = [ + "alloy-chains", + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-json-rpc 2.0.5", + "alloy-network 2.0.5", + "alloy-network-primitives 2.0.5", + "alloy-primitives 1.6.0", + "alloy-pubsub", + "alloy-rpc-client 2.0.5", + "alloy-rpc-types-debug", + "alloy-rpc-types-eth 2.0.5", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-signer 2.0.5", + "alloy-sol-types 1.6.0", + "alloy-transport 2.0.5", + "alloy-transport-http 2.0.5", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "either", + "futures", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest 0.13.3", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-pubsub" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd85cfea1fa8ebd20d3475e961fe3a3624c0eb4659ea137715c0c83c8aeaff0" +dependencies = [ + "alloy-json-rpc 2.0.5", + "alloy-primitives 1.6.0", + "alloy-transport 2.0.5", + "auto_impl", + "bimap", + "futures", + "parking_lot", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.3", + "tracing", + "wasmtimer", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-rpc-client" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fcc9604042ca80bd37aa5e232ea1cd851f337e31e2babbbb345bc0b1c30de3" +dependencies = [ + "alloy-json-rpc 1.8.3", + "alloy-primitives 1.6.0", + "alloy-transport 1.8.3", + "alloy-transport-http 1.8.3", + "futures", + "pin-project", + "reqwest 0.13.3", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.3", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-client" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f461f091dc8f657e73b5dea18fd63d5c7049720cd252f1eade4a7ebed6a7e1" +dependencies = [ + "alloy-json-rpc 2.0.5", + "alloy-primitives 1.6.0", + "alloy-pubsub", + "alloy-transport 2.0.5", + "alloy-transport-http 2.0.5", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures", + "pin-project", + "reqwest 0.13.3", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.3", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4faad925d3a669ffc15f43b3deec7fbdf2adeb28a4d6f9cf4bc661698c0f8f4b" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 1.8.3", + "alloy-serde 1.8.3", + "serde", +] + +[[package]] +name = "alloy-rpc-types" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052c031d1f7c5611997056bbcb8814e5cbf20f7efeee8c3de690555172038cf2" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "serde", +] + +[[package]] +name = "alloy-rpc-types-admin" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef669b370940e7945a3a384cc4024038cd69ee658b71270d59c20b78dd8d20d4" +dependencies = [ + "alloy-genesis 2.0.5", + "alloy-primitives 1.6.0", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-rpc-types-anvil" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff111a54268dc0bbd3b17f98571a7e27cc661dc081ad2999d91888647eb2e11" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "serde", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3823026d1ed239a40f12364fac50726c8daf1b6ab8077a97212c5123910429ed" +dependencies = [ + "alloy-consensus-any 1.8.3", + "alloy-rpc-types-eth 1.8.3", + "alloy-serde 1.8.3", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6561ed4759c974d9c144500a59e3fb8c1d87327a12900d5ce455c0cae6dcb6" +dependencies = [ + "alloy-consensus-any 2.0.5", + "alloy-network-primitives 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-rpc-types-beacon" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a62f6ce2d95f59ed310bd90d5fd1566a29d1ec45cc219abbc5dcc807d31f136" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "derive_more", + "ethereum_ssz", + "ethereum_ssz_derive", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tree_hash", + "tree_hash_derive", +] + +[[package]] +name = "alloy-rpc-types-debug" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48b9ad6eee93dd35a9ec0a6c1c6b180892a900ee17a6ed6500921552dd71e846" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "derive_more", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eba59e1c069f168a01982f42a57797736923b76aa854194df4930be17867e1c" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 2.0.5", + "derive_more", + "ethereum_ssz", + "ethereum_ssz_derive", + "jsonwebtoken", + "rand 0.8.6", + "serde", + "strum", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c095f92c4e1ff4981d89e9aa02d5f98c762a1980ab66bec49c44be11349da2" +dependencies = [ + "alloy-consensus 1.8.3", + "alloy-consensus-any 1.8.3", + "alloy-eips 1.8.3", + "alloy-network-primitives 1.8.3", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 1.8.3", + "alloy-sol-types 1.6.0", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175a2a5b6017d7f61b5e4b800d21215fe8e94fe729d00828e13bb6d93dcf3492" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-consensus-any 2.0.5", + "alloy-eips 2.0.5", + "alloy-network-primitives 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-serde 2.0.5", + "alloy-sol-types 1.6.0", + "arbitrary", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-rpc-types-mev" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed1004c1d68bfaee001712f83356f88031ab74a727b8560fb7fc738d1281ebe5" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-rpc-types-trace" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514b4b1ce3354f65067b4fc7eb75358e0f2ec8be3340c96dea65d6894f9ca435" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-rpc-types-txpool" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e34a42ebb4a71ab0bfdebc6d2f3c7bf809f01edf154d08fed159d10d1ef1d4" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "serde", +] + +[[package]] +name = "alloy-serde" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" +dependencies = [ + "alloy-primitives 1.6.0", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-serde" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc21a8772af7d78bba286726aa245bd2ff81cd9abe230afea2e91578996831c9" +dependencies = [ + "alloy-primitives 1.6.0", + "arbitrary", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f447aefab0f1c0649f71edc33f590992d4e122bc35fb9cdbbf67d4421ace85" +dependencies = [ + "alloy-primitives 1.6.0", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-signer" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ffbce94c50dd9d4d1f83e044c5595bbd3ada981bd3057ce28b3a5470e77385d" +dependencies = [ + "alloy-primitives 1.6.0", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-signer-local" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f721f4bf2e4812e5505aaf5de16ef3065a8e26b9139ac885862d00b5a55a659a" +dependencies = [ + "alloy-consensus 1.8.3", + "alloy-network 1.8.3", + "alloy-primitives 1.6.0", + "alloy-signer 1.8.3", + "async-trait", + "k256", + "rand 0.8.6", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-signer-local" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48366d2c42b8d95ef951101bafa28486590f21b7a1e68b6b2d069746557bbe3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-signer 2.0.5", + "async-trait", + "coins-bip32", + "coins-bip39", + "k256", + "rand 0.8.6", + "thiserror 2.0.18", + "zeroize", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68b32b6fa0d09bb74b4cefe35ccc8269d711c26629bc7cd98a47eeb12fe353f" +dependencies = [ + "alloy-sol-macro-expander 0.8.26", + "alloy-sol-macro-input 0.8.26", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840128ed2b2971d6d4668a553fe403a82683d3acc646c73e75887e7157408033" +dependencies = [ + "alloy-sol-macro-expander 1.6.0", + "alloy-sol-macro-input 1.6.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2afe6879ac373e58fd53581636f2cce843998ae0b058ebe1e4f649195e2bd23c" +dependencies = [ + "alloy-sol-macro-input 0.8.26", + "const-hex", + "heck", + "indexmap 2.14.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", + "syn-solidity 0.8.26", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63ec265e5d65d725175f6ca7711c970824c90ef9c0d1f1973711d4150ee612dd" +dependencies = [ + "alloy-json-abi 1.6.0", + "alloy-sol-macro-input 1.6.0", + "const-hex", + "heck", + "indexmap 2.14.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3 0.11.0", + "syn 2.0.117", + "syn-solidity 1.6.0", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ba01aee235a8c699d07e5be97ba215607564e71be72f433665329bec307d28" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string 0.1.4", + "proc-macro2", + "quote", + "syn 2.0.117", + "syn-solidity 0.8.26", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89bf01077f18650876cfa682eb1f949967b5cde03f1a51c955c469d2c9b4aa67" +dependencies = [ + "alloy-json-abi 1.6.0", + "const-hex", + "dunce", + "heck", + "macro-string 0.2.0", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.117", + "syn-solidity 1.6.0", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c13fc168b97411e04465f03e632f31ef94cad1c7c8951bf799237fd7870d535" +dependencies = [ + "serde", + "winnow 0.7.15", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857b470ecdd2ed38beaf82ad1a38c516a8ff75266750f38b9eeed001d575241b" +dependencies = [ + "serde", + "winnow 1.0.3", +] + +[[package]] +name = "alloy-sol-types" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e960c4b52508ef2ae1e37cae5058e905e9ae099b107900067a503f8c454036f" +dependencies = [ + "alloy-json-abi 0.8.26", + "alloy-primitives 0.8.26", + "alloy-sol-macro 0.8.26", + "const-hex", + "serde", +] + +[[package]] +name = "alloy-sol-types" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384cf252de0db2dec52821eac037a7f57e2aa33fe5b900ce6fe39973402341f1" +dependencies = [ + "alloy-json-abi 1.6.0", + "alloy-primitives 1.6.0", + "alloy-sol-macro 1.6.0", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8098f965442a9feb620965ba4b4be5e2b320f4ec5a3fff6bfa9e1ff7ef42bed1" +dependencies = [ + "alloy-json-rpc 1.8.3", + "auto_impl", + "base64 0.22.1", + "derive_more", + "futures", + "futures-utils-wasm", + "parking_lot", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower 0.5.3", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86052fdcec72d37ca4aa4b66254601e7453c45a6e1c70aa4561033d002fb80cc" +dependencies = [ + "alloy-json-rpc 2.0.5", + "auto_impl", + "base64 0.22.1", + "derive_more", + "futures", + "futures-utils-wasm", + "parking_lot", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower 0.5.3", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8597d36d546e1dab822345ad563243ec3920e199322cb554ce56c8ef1a1e2e7" +dependencies = [ + "alloy-json-rpc 1.8.3", + "alloy-transport 1.8.3", + "itertools 0.14.0", + "reqwest 0.13.3", + "serde_json", + "tower 0.5.3", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-http" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b273587487921274f4f5d0ef2c7ef36944dcbb75a4e2318e69eae822bd263f91" +dependencies = [ + "alloy-json-rpc 2.0.5", + "alloy-transport 2.0.5", + "itertools 0.14.0", + "reqwest 0.13.3", + "serde_json", + "tower 0.5.3", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-ipc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb89df168b24773ef603af14f2449c05a7d3f27d05d3eceaea6bf96cccae168" +dependencies = [ + "alloy-json-rpc 2.0.5", + "alloy-pubsub", + "alloy-transport 2.0.5", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e32e0b47d3b3bf5770b7c132090c614b008d307c5e1544f1925f5b7e9e9af6" +dependencies = [ + "alloy-pubsub", + "alloy-transport 2.0.5", + "futures", + "http", + "rustls", + "serde_json", + "tokio", + "tokio-tungstenite 0.28.0", + "tracing", + "url", + "ws_stream_wasm", +] + +[[package]] +name = "alloy-trie" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "arbitrary", + "derive_arbitrary", + "derive_more", + "nybbles", + "proptest", + "proptest-derive 0.7.0", + "serde", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69722eddcdf1ce096c3ab66cf8116999363f734eb36fe94a148f4f71c85da84" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-tx-macros" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a0035943b75fe1e249f52e688492d7a1b1826bc2d19b8e1d5d3c24a2ad8f50" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-r1cs-std", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-relations", + "ark-std 0.5.0", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff 0.5.0", + "ark-std 0.5.0", + "tracing", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive 0.4.0", + "asn1-rs-impl 0.1.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl 0.2.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "asn1_der" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" + +[[package]] +name = "async-compression" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.1", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64 0.22.1", + "http", + "log", + "url", +] + +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core 0.5.6", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom 0.2.17", + "instant", + "rand 0.8.6", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "tokio", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-url" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b761ea69df72d0cc9591ea39adfb0e3b922a27dc535227d7ffbed5702e8809" +dependencies = [ + "base64 0.22.1", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.117", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + +[[package]] +name = "bitfield" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" +dependencies = [ + "bitfield-macros", +] + +[[package]] +name = "bitfield-macros" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "serde", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "boa_ast" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6339a700715bda376f5ea65c76e8fe8fc880930d8b0638cea68e7f3da6538e0a" +dependencies = [ + "bitflags 2.11.1", + "boa_interner", + "boa_macros", + "boa_string", + "indexmap 2.14.0", + "num-bigint", + "rustc-hash", +] + +[[package]] +name = "boa_engine" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1521be326f8a5c8887e95d4ce7f002917a002a23f7b93b9a6a2bf50ed4157824" +dependencies = [ + "aligned-vec", + "arrayvec", + "bitflags 2.11.1", + "boa_ast", + "boa_gc", + "boa_interner", + "boa_macros", + "boa_parser", + "boa_string", + "bytemuck", + "cfg-if", + "cow-utils", + "dashmap", + "dynify", + "fast-float2", + "float16", + "futures-channel", + "futures-concurrency", + "futures-lite", + "hashbrown 0.16.1", + "icu_normalizer", + "indexmap 2.14.0", + "intrusive-collections", + "itertools 0.14.0", + "num-bigint", + "num-integer", + "num-traits", + "num_enum", + "paste", + "portable-atomic", + "rand 0.9.4", + "regress", + "rustc-hash", + "ryu-js", + "serde", + "serde_json", + "small_btree", + "static_assertions", + "tag_ptr", + "tap", + "thin-vec", + "thiserror 2.0.18", + "time", + "xsum", +] + +[[package]] +name = "boa_gc" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17323a98cf2e631afacf1a6d659c1212c48a68bacfa85afab0a66ade80582e51" +dependencies = [ + "boa_macros", + "boa_string", + "hashbrown 0.16.1", + "thin-vec", +] + +[[package]] +name = "boa_interner" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20510b8b02bcde9b0a01cf34c0c308c56156503d1d91cdab4c8cfbd292b747ea" +dependencies = [ + "boa_gc", + "boa_macros", + "hashbrown 0.16.1", + "indexmap 2.14.0", + "once_cell", + "phf", + "rustc-hash", + "static_assertions", +] + +[[package]] +name = "boa_macros" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5822cb4f146d243060e588bc5a5f2e709683fdad3d7111f42c48e6b5c921d23d" +dependencies = [ + "cfg-if", + "cow-utils", + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "boa_parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd957fa9fa93e3a001a8aba5a5cd40c2bbfde486378be4c4b472fd304aaddb" +dependencies = [ + "bitflags 2.11.1", + "boa_ast", + "boa_interner", + "boa_macros", + "fast-float2", + "icu_properties", + "num-bigint", + "num-traits", + "regress", + "rustc-hash", +] + +[[package]] +name = "boa_string" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2da1d7f4a76fd9040788a122f0d807910800a7b86f5952e9244848c36511de" +dependencies = [ + "fast-float2", + "itoa", + "paste", + "rustc-hash", + "ryu-js", + "static_assertions", +] + +[[package]] +name = "bollard" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.47.1-rc.27.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" +dependencies = [ + "serde", + "serde_repr", + "serde_with", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "boyer-moore-magiclen" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7441b4796eb8a7107d4cd99d829810be75f5573e1081c37faa0e8094169ea0d6" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "c-kzg" +version = "2.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +dependencies = [ + "arbitrary", + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.28", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cbindgen" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799" +dependencies = [ + "clap", + "heck", + "indexmap 2.14.0", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.117", + "tempfile", + "toml", +] + +[[package]] +name = "cbor4ii" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "472931dd4dfcc785075b09be910147f9c6258883fc4591d0dac6116392b2daa6" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "coco-provider" +version = "0.3.0" +source = "git+https://github.com/automata-network/coco-provider-sdk#cc355bcb0a71fab5c0e94dd154d785577ce61421" +dependencies = [ + "bincode 1.3.3", + "bitfield", + "cbindgen", + "iocuddle", + "libc", + "log", + "rand 0.8.6", + "serde", + "serde-big-array", + "sysinfo 0.35.2", + "tss-esapi", + "uuid", +] + +[[package]] +name = "coins-bip32" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2073678591747aed4000dd468b97b14d7007f7936851d3f2f01846899f5ebf08" +dependencies = [ + "bs58", + "coins-core", + "digest 0.10.7", + "hmac", + "k256", + "serde", + "sha2", + "thiserror 1.0.69", +] + +[[package]] +name = "coins-bip39" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b169b26623ff17e9db37a539fe4f15342080df39f129ef7631df7683d6d9d4" +dependencies = [ + "bitvec", + "coins-bip32", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.8.6", + "sha2", + "thiserror 1.0.69", +] + +[[package]] +name = "coins-core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b962ad8545e43a28e14e87377812ba9ae748dd4fd963f4c10e9fcc6d13475b" +dependencies = [ + "base64 0.21.7", + "bech32", + "bs58", + "const-hex", + "digest 0.10.7", + "generic-array", + "ripemd", + "serde", + "sha2", + "sha3 0.10.9", + "thiserror 1.0.69", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[package]] +name = "concat-kdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d72c1252426a83be2092dd5884a5f6e3b8e7180f6891b6263d2c21b92ec8816" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-hex" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cow-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.11.1", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctor" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "arbitrary", + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-encoding-macro" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" +dependencies = [ + "data-encoding", + "syn 2.0.117", +] + +[[package]] +name = "dcap-rs" +version = "0.1.0" +source = "git+https://github.com/automata-network/dcap-rs.git?rev=d847b8f75a493640c4881bdf67775250b6baefab#d847b8f75a493640c4881bdf67775250b6baefab" +dependencies = [ + "alloy-sol-types 0.8.26", + "chrono", + "hex", + "p256", + "serde", + "serde_json", + "sha2", + "sha3 0.10.9", + "time", + "x509-parser 0.15.1", +] + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "delay_map" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88e365f083a5cb5972d50ce8b1b2c9f125dc5ec0f50c0248cfb568ae59efcf0b" +dependencies = [ + "futures", + "tokio", + "tokio-util", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs 0.5.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-where" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.2", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + +[[package]] +name = "discv5" +version = "0.10.4" +source = "git+https://github.com/sigp/discv5?rev=7663c00#7663c00ee0837ea98547caaedede95d9d6736f4d" +dependencies = [ + "aes", + "aes-gcm", + "alloy-rlp", + "arrayvec", + "ctr", + "delay_map", + "enr", + "fnv", + "futures", + "hashlink 0.11.0", + "hex", + "hkdf", + "lazy_static", + "libp2p-identity", + "more-asserts", + "multiaddr", + "parking_lot", + "rand 0.8.6", + "smallvec", + "socket2 0.6.3", + "tokio", + "tracing", + "uint 0.10.0", + "zeroize", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "docker_credential" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29547a1dc60885a552306986316bc9701ba120c1a8db6769fa68691529ad373d" +dependencies = [ + "base64 0.22.1", + "serde", + "serde_json", +] + +[[package]] +name = "doctest-file" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359" + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtor" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "dynify" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81acb15628a3e22358bf73de5e7e62360b8a777dbcb5fc9ac7dfa9ae73723747" +dependencies = [ + "dynify-macros", +] + +[[package]] +name = "dynify-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec431cd708430d5029356535259c5d645d60edd3d39c54e5eea9782d46caa7d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "enr" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851bd664a3d3a3c175cff92b2f0df02df3c541b4895d0ae307611827aae46152" +dependencies = [ + "alloy-rlp", + "base64 0.22.1", + "bytes", + "ed25519-dalek", + "hex", + "k256", + "log", + "rand 0.8.6", + "secp256k1 0.30.0", + "serde", + "sha3 0.10.9", + "zeroize", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "etcetera" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.59.0", +] + +[[package]] +name = "ethereum_hashing" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa93f58bb1eb3d1e556e4f408ef1dac130bad01ac37db4e7ade45de40d1c86a" +dependencies = [ + "cpufeatures 0.2.17", + "ring", + "sha2", +] + +[[package]] +name = "ethereum_serde_utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" +dependencies = [ + "alloy-primitives 1.6.0", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "ethereum_ssz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e462875ad8693755ea8913d6e905715c76ea4836e2254e18c9cf0f7a8f8c2a13" +dependencies = [ + "alloy-primitives 1.6.0", + "ethereum_serde_utils", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf022360bdbe9456eda5f35718a50476d5b2a0d51a97ed4eae27420737a6fba" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "evmap" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8" +dependencies = [ + "hashbag", + "left-right", + "smallvec", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fdlimit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" +dependencies = [ + "libc", + "thiserror 1.0.69", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-cache" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41c7aa69c00ebccf06c3fa7ffe2a6cf26a58b5fe4deabfe646285ff48136a8f" +dependencies = [ + "equivalent", + "rapidhash", + "typeid", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.6", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixed-map" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ed19add84e8cb9e8cc5f7074de0324247149ffef0b851e215fb0edc50c229b" +dependencies = [ + "fixed-map-derive", + "serde", +] + +[[package]] +name = "fixed-map-derive" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dc7a9cb3326bafb80642c5ce99b39a2c0702d4bfa8ee8a3e773791a6cbe2407" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "float16" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bffafbd079d520191c7c2779ae9cf757601266cf4167d3f659ff09617ff8483" +dependencies = [ + "cfg-if", + "rustc_version 0.2.3", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-bounded" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" +dependencies = [ + "futures-timer", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite", + "pin-project", + "smallvec", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls", + "rustls-pki-types", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +dependencies = [ + "gloo-timers", + "send_wrapper 0.4.0", +] + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.2.1", + "windows-result 0.4.1", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "git2" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +dependencies = [ + "bitflags 2.11.1", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hashbag" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "foldhash 0.1.5", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "hashlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hickory-net" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "futures-channel", + "futures-io", + "futures-util", + "hickory-proto 0.26.1", + "idna", + "ipnet", + "jni 0.22.4", + "rand 0.10.1", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.4", + "ring", + "socket2 0.5.10", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni 0.22.4", + "once_cell", + "prefix-trie", + "rand 0.10.1", + "ring", + "serde", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto 0.25.2", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.4", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hickory-resolver" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-net", + "hickory-proto 0.26.1", + "ipconfig", + "ipnet", + "jni 0.22.4", + "moka", + "ndk-context", + "once_cell", + "parking_lot", + "rand 0.10.1", + "resolv-conf", + "serde", + "smallvec", + "system-configuration", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "human_bytes" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.5.10", + "system-configuration", + "tokio", + "tower-layer", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b24a59706036ba941c9476a55cd57b82b77f38a3c667d637ee7cabbc85eaedc" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a97b8ac6235e69506e8dacfb2adf38461d2ce6d3e9bd9c94c4cbc3cd4400a4" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "serde", + "stable_deref_trait", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if-addrs" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf39cc0423ee66021dc5eccface85580e4a001e0c5288bae8bea7ecb69225e90" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "if-addrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "if-watch" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c02a5161c313f0cbdbadc511611893584a10a7b6153cb554bdf83ddce99ec2" +dependencies = [ + "async-io", + "core-foundation 0.9.4", + "fnv", + "futures", + "if-addrs 0.15.0", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration", + "tokio", + "windows 0.62.2", +] + +[[package]] +name = "igd-next" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.9.4", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "imbl" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e525189e5f603908d0c6e0d402cb5de9c4b2c8866151fabc4ebd771ed2630a2e" +dependencies = [ + "archery", + "bitmaps", + "imbl-sized-chunks", + "rand_core 0.9.5", + "rand_xoshiro", + "serde_core", + "version_check", + "wide", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "arbitrary", + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "inotify" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +dependencies = [ + "bitflags 2.11.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instability" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" +dependencies = [ + "darling 0.23.0", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "interprocess" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.61.2", +] + +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + +[[package]] +name = "iocuddle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8972d5be69940353d5347a1344cb375d9b457d6809b428b05bb1ca2fb9ce007" + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2 0.6.3", + "widestring", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +dependencies = [ + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jemalloc_pprof" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d44c349cfe2654897fadcb9de4f0bfbf48288ec344f700b2bd59f152dd209" +dependencies = [ + "anyhow", + "libc", + "mappings", + "once_cell", + "pprof_util", + "tempfile", + "tikv-jemalloc-ctl", + "tokio", + "tracing", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonrpsee" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "jsonrpsee-core 0.25.1", + "jsonrpsee-http-client 0.25.1", + "jsonrpsee-proc-macros 0.25.1", + "jsonrpsee-server 0.25.1", + "jsonrpsee-types 0.25.1", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a" +dependencies = [ + "jsonrpsee-client-transport", + "jsonrpsee-core 0.26.0", + "jsonrpsee-http-client 0.26.0", + "jsonrpsee-proc-macros 0.26.0", + "jsonrpsee-server 0.26.0", + "jsonrpsee-types 0.26.0", + "jsonrpsee-wasm-client", + "jsonrpsee-ws-client", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-client-transport" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf36eb27f8e13fa93dcb50ccb44c417e25b818cfa1a481b5470cd07b19c60b98" +dependencies = [ + "base64 0.22.1", + "futures-channel", + "futures-util", + "gloo-net", + "http", + "jsonrpsee-core 0.26.0", + "pin-project", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier 0.5.3", + "soketto", + "thiserror 2.0.18", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", + "url", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "jsonrpsee-types 0.25.1", + "parking_lot", + "pin-project", + "rand 0.9.4", + "rustc-hash", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "316c96719901f05d1137f19ba598b5fe9c9bc39f4335f67f6be8613921946480" +dependencies = [ + "async-trait", + "bytes", + "futures-timer", + "futures-util", + "http", + "http-body", + "http-body-util", + "jsonrpsee-types 0.26.0", + "parking_lot", + "pin-project", + "rand 0.9.4", + "rustc-hash", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tower 0.5.3", + "tracing", + "wasm-bindgen-futures", +] + +[[package]] +name = "jsonrpsee-http-client" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "base64 0.22.1", + "http-body", + "hyper", + "hyper-rustls", + "hyper-util", + "jsonrpsee-core 0.25.1", + "jsonrpsee-types 0.25.1", + "rustls", + "rustls-platform-verifier 0.5.3", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower 0.5.3", + "url", +] + +[[package]] +name = "jsonrpsee-http-client" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790bedefcec85321e007ff3af84b4e417540d5c87b3c9779b9e247d1bcc3dab8" +dependencies = [ + "base64 0.22.1", + "http-body", + "hyper", + "hyper-rustls", + "hyper-util", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "rustls", + "rustls-platform-verifier 0.5.3", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower 0.5.3", + "url", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da3f8ab5ce1bb124b6d082e62dffe997578ceaf0aeb9f3174a214589dc00f07" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee-core 0.25.1", + "jsonrpsee-types 0.25.1", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f" +dependencies = [ + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "http", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" +dependencies = [ + "http", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "jsonrpsee-wasm-client" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7902885de4779f711a95d82c8da2d7e5f9f3a7c7cfa44d51c067fd1c29d72a3c" +dependencies = [ + "jsonrpsee-client-transport", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "tower 0.5.3", +] + +[[package]] +name = "jsonrpsee-ws-client" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6fceceeb05301cc4c065ab3bd2fa990d41ff4eb44e4ca1b30fa99c057c3e79" +dependencies = [ + "http", + "jsonrpsee-client-transport", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "tower 0.5.3", + "url", +] + +[[package]] +name = "jsonwebtoken" +version = "10.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +dependencies = [ + "aws-lc-rs", + "base64 0.22.1", + "getrandom 0.2.17", + "js-sys", + "pem", + "serde", + "serde_json", + "signature", + "simple_asn1", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", + "signature", +] + +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "keccak-asm" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1766b89733097006f3a1388a02849865d6bc98c89273cb622e29fdd209922183" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.11.1", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "left-right" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0c21e4c8ff95f487fb34e6f9182875f42c84cef966d29216bf115d9bba835a" +dependencies = [ + "crossbeam-utils", + "loom", + "slab", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libgit2-sys" +version = "0.18.4+1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b26f66f35e1871b22efcf7191564123d2a446ca0538cde63c23adfefa9b15b7" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libp2p" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce71348bf5838e46449ae240631117b487073d5f347c06d434caddcb91dceb5a" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom 0.2.17", + "libp2p-allow-block-list", + "libp2p-autonat", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dns", + "libp2p-identify", + "libp2p-identity", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-ping", + "libp2p-quic", + "libp2p-request-response", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-upnp", + "libp2p-yamux", + "multiaddr", + "pin-project", + "rw-stream-sink", + "thiserror 2.0.18", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16ccf824ee859ca83df301e1c0205270206223fd4b1f2e512a693e1912a8f4a" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-autonat" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fab5e25c49a7d48dac83d95d8f3bac0a290d8a5df717012f6e34ce9886396c0b" +dependencies = [ + "async-trait", + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-request-response", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.6", + "rand_core 0.6.4", + "thiserror 2.0.18", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18b8b607cf3bfa2f8c57db9c7d8569a315d5cc0a282e6bfd5ebfc0a9840b2a0" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-core" +version = "0.43.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot", + "pin-project", + "quick-protobuf", + "rand 0.8.6", + "rw-stream-sink", + "thiserror 2.0.18", + "tracing", + "unsigned-varint 0.8.0", + "web-time", +] + +[[package]] +name = "libp2p-dns" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b770c1c8476736ca98c578cba4b505104ff8e842c2876b528925f9766379f9a" +dependencies = [ + "async-trait", + "futures", + "hickory-resolver 0.25.2", + "libp2p-core", + "libp2p-identity", + "parking_lot", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-identify" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ab792a8b68fdef443a62155b01970c81c3aadab5e659621b063ef252a8e65e8" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "libp2p-identity" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" +dependencies = [ + "asn1_der", + "bs58", + "ed25519-dalek", + "hkdf", + "k256", + "multihash", + "quick-protobuf", + "rand 0.8.6", + "sha2", + "thiserror 2.0.18", + "tracing", + "zeroize", +] + +[[package]] +name = "libp2p-mdns" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66872d0f1ffcded2788683f76931be1c52e27f343edb93bc6d0bcd8887be443" +dependencies = [ + "futures", + "hickory-proto 0.25.2", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.6", + "smallvec", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-metrics" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805a555148522cb3414493a5153451910cb1a146c53ffbf4385708349baf62b7" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-identify", + "libp2p-identity", + "libp2p-ping", + "libp2p-swarm", + "pin-project", + "prometheus-client", + "web-time", +] + +[[package]] +name = "libp2p-noise" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc73eacbe6462a0eb92a6527cac6e63f02026e5407f8831bde8293f19217bfbf" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "libp2p-identity", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.6", + "snow", + "static_assertions", + "thiserror 2.0.18", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-ping" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74bb7fcdfd9fead4144a3859da0b49576f171a8c8c7c0bfc7c541921d25e60d3" +dependencies = [ + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.6", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-quic" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc448b2de9f4745784e3751fe8bc6c473d01b8317edd5ababcb0dec803d843f" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "quinn", + "rand 0.8.6", + "ring", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-request-response" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9f1cca83488b90102abac7b67d5c36fc65bc02ed47620228af7ed002e6a1478" +dependencies = [ + "async-trait", + "cbor4ii", + "futures", + "futures-bounded", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.6", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-stream" +version = "0.4.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6bd8025c80205ec2810cfb28b02f362ab48a01bee32c50ab5f12761e033464" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.6", + "tracing", +] + +[[package]] +name = "libp2p-swarm" +version = "0.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce88c6c4bf746c8482480345ea3edfd08301f49e026889d1cbccfa1808a9ed9e" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "hashlink 0.10.0", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "multistream-select", + "rand 0.8.6", + "smallvec", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd297cf53f0cb3dee4d2620bb319ae47ef27c702684309f682bdb7e55a18ae9c" +dependencies = [ + "heck", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "libp2p-tcp" +version = "0.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb6585b9309699f58704ec9ab0bb102eca7a3777170fa91a8678d73ca9cafa93" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "socket2 0.6.3", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-tls" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring", + "rustls", + "rustls-webpki", + "thiserror 2.0.18", + "x509-parser 0.17.0", + "yasna", +] + +[[package]] +name = "libp2p-upnp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4757e65fe69399c1a243bbb90ec1ae5a2114b907467bf09f3575e899815bb8d3" +dependencies = [ + "futures", + "futures-timer", + "igd-next", + "libp2p-core", + "libp2p-swarm", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-yamux" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8" +dependencies = [ + "either", + "futures", + "libp2p-core", + "thiserror 2.0.18", + "tracing", + "yamux 0.12.1", + "yamux 0.13.10", +] + +[[package]] +name = "libproc" +version = "0.14.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54ad7278b8bc5301d5ffd2a94251c004feb971feba96c971ea4063645990757" +dependencies = [ + "bindgen", + "errno", + "libc", +] + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "librocksdb-sys" +version = "0.17.3+10.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef2a00ee60fe526157c9023edab23943fae1ce2ab6f4abb2a807c1746835de9" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "libc", + "libz-sys", + "lz4-sys", + "tikv-jemalloc-sys", + "zstd-sys", +] + +[[package]] +name = "libz-sys" +version = "1.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984fb35d06508d1e69fc91050cceba9c0b748f983e6739fa2c7a9237154c52c8" +dependencies = [ + "linked-hash-map", + "serde_core", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", + "serde", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "lz4_flex" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90071f8077f8e40adfc4b7fe9cd495ce316263f19e75c2211eeff3fdf475a3d9" + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "macro-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "macros" +version = "0.1.0" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "mappings" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bab1e61a4b76757edb59cd81fcaa7f3ba9018d43b527d9abfad877b4c6c60f2" +dependencies = [ + "anyhow", + "libc", + "once_cell", + "pprof_util", + "tracing", +] + +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "mbox" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d142aeadbc4e8c679fc6d93fbe7efe1c021fa7d80629e615915b519e3bc6de" +dependencies = [ + "libc", + "stable_deref_trait", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metrics" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" +dependencies = [ + "portable-atomic", + "rapidhash", +] + +[[package]] +name = "metrics-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161ab904c2c62e7bda0f7562bf22f96440ca35ff79e66c800cbac298f2f4f5ec" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "indexmap 2.14.0", + "ipnet", + "metrics", + "metrics-util 0.19.1", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "indexmap 2.14.0", + "ipnet", + "metrics", + "metrics-util 0.20.4", + "quanta", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108" +dependencies = [ + "base64 0.22.1", + "evmap", + "indexmap 2.14.0", + "metrics", + "metrics-util 0.20.4", + "quanta", + "thiserror 2.0.18", +] + +[[package]] +name = "metrics-process" +version = "2.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4268d87f64a752f5a651314fc683f04da10be65701ea3e721ba4d74f79163cac" +dependencies = [ + "libc", + "libproc", + "mach2 0.6.0", + "metrics", + "once_cell", + "procfs", + "rlimit 0.11.0", + "windows 0.62.2", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "aho-corasick", + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "metrics", + "ordered-float", + "quanta", + "radix_trie", + "rand 0.9.4", + "rand_xoshiro", + "sketches-ddsketch", +] + +[[package]] +name = "metrics-util" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96f8722f8562635f92f8ed992f26df0532266eb03d5202607c20c0d7e9745e13" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.16.1", + "metrics", + "quanta", + "rand 0.9.4", + "rand_xoshiro", + "rapidhash", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "serde", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "modular-bitfield" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "moka" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "event-listener", + "futures-util", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "more-asserts" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint 0.8.0", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c63b00ad74d57e8c9aa870b5fccebf2fd64a308a5aee9f1bb88e4aea19447" +dependencies = [ + "unsigned-varint 0.8.0", +] + +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.6", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "netlink-packet-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" +dependencies = [ + "paste", +] + +[[package]] +name = "netlink-packet-route" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" +dependencies = [ + "bitflags 2.11.1", + "libc", + "log", + "netlink-packet-core", +] + +[[package]] +name = "netlink-proto" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.18", +] + +[[package]] +name = "netlink-sys" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" +dependencies = [ + "bytes", + "futures-util", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.11.1", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "nybbles" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" +dependencies = [ + "alloy-rlp", + "arbitrary", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "oid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" +dependencies = [ + "serde", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs 0.5.2", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs 0.7.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "op-alloy" +version = "2.0.0" +dependencies = [ + "op-alloy-consensus", + "op-alloy-network", + "op-alloy-provider", + "op-alloy-rpc-types", + "op-alloy-rpc-types-engine", +] + +[[package]] +name = "op-alloy-consensus" +version = "2.0.0" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "bytes", + "derive_more", + "modular-bitfield", + "reth-codecs", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "op-alloy-flz" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" + +[[package]] +name = "op-alloy-network" +version = "2.0.0" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-network 2.0.5", + "alloy-provider 2.0.5", + "alloy-rpc-types-eth 2.0.5", + "op-alloy-consensus", + "op-alloy-rpc-types", +] + +[[package]] +name = "op-alloy-provider" +version = "2.0.0" +dependencies = [ + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-provider 2.0.5", + "alloy-rpc-types-engine", + "alloy-transport 2.0.5", + "async-trait", + "op-alloy-rpc-types-engine", +] + +[[package]] +name = "op-alloy-rpc-jsonrpsee" +version = "2.0.0" +dependencies = [ + "alloy-primitives 1.6.0", + "jsonrpsee 0.26.0", +] + +[[package]] +name = "op-alloy-rpc-types" +version = "2.0.0" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-network 2.0.5", + "alloy-network-primitives 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "alloy-signer 2.0.5", + "derive_more", + "op-alloy-consensus", + "reth-rpc-traits", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "op-alloy-rpc-types-engine" +version = "2.0.0" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-engine", + "alloy-serde 2.0.5", + "ethereum_ssz", + "ethereum_ssz_derive", + "op-alloy-consensus", + "serde", + "sha2", + "snap", + "thiserror 2.0.18", +] + +[[package]] +name = "op-rbuilder" +version = "0.2.13" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-contract 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-json-rpc 2.0.5", + "alloy-network 2.0.5", + "alloy-op-evm", + "alloy-primitives 1.6.0", + "alloy-provider 2.0.5", + "alloy-rpc-client 2.0.5", + "alloy-rpc-types-beacon", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "alloy-signer-local 2.0.5", + "alloy-sol-types 1.6.0", + "alloy-transport 2.0.5", + "alloy-transport-http 2.0.5", + "anyhow", + "async-trait", + "chrono", + "clap", + "clap_builder", + "ctor", + "dashmap", + "derive_more", + "dirs-next", + "either", + "eyre", + "futures", + "futures-util", + "hex", + "http", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee 0.26.0", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "k256", + "macros", + "metrics", + "moka", + "nanoid", + "op-alloy-consensus", + "op-alloy-flz", + "op-alloy-network", + "op-alloy-rpc-types", + "op-alloy-rpc-types-engine", + "op-revm", + "opentelemetry 0.31.0", + "p2p", + "parking_lot", + "rand 0.9.4", + "reqwest 0.12.28", + "reth", + "reth-basic-payload-builder", + "reth-chain-state", + "reth-chainspec", + "reth-cli", + "reth-cli-commands", + "reth-cli-util", + "reth-db", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-exex", + "reth-ipc", + "reth-metrics", + "reth-network-peers", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-optimism-chainspec", + "reth-optimism-cli", + "reth-optimism-consensus", + "reth-optimism-evm", + "reth-optimism-forks", + "reth-optimism-node", + "reth-optimism-payload-builder", + "reth-optimism-primitives", + "reth-optimism-rpc", + "reth-optimism-txpool", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-payload-util", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc-api", + "reth-rpc-engine-api", + "reth-rpc-eth-types", + "reth-rpc-layer", + "reth-storage-api", + "reth-tasks", + "reth-testing-utils", + "reth-tracing-otlp", + "reth-transaction-pool", + "reth-trie", + "revm", + "rlimit 0.10.2", + "rollup-boost", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "serde_yaml", + "sha3 0.10.9", + "shellexpand", + "tar", + "tempfile", + "testcontainers", + "thiserror 1.0.69", + "tikv-jemallocator", + "time", + "tokio", + "tokio-tungstenite 0.26.2", + "tokio-util", + "tower 0.5.3", + "tracing", + "tracing-subscriber 0.3.23", + "url", + "uuid", + "vergen", + "vergen-git2", +] + +[[package]] +name = "op-revm" +version = "20.0.0" +dependencies = [ + "auto_impl", + "revm", + "serde", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236e667b670a5cdf90c258f5a55794ec5ac5027e960c224bff8367a59e1e6426" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +dependencies = [ + "opentelemetry 0.31.0", + "tracing", + "tracing-core", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "opentelemetry-http" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry 0.28.0", + "reqwest 0.12.28", + "tracing", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry 0.31.0", + "reqwest 0.12.28", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91" +dependencies = [ + "async-trait", + "futures-core", + "http", + "opentelemetry 0.28.0", + "opentelemetry-http 0.28.0", + "opentelemetry-proto 0.28.0", + "opentelemetry_sdk 0.28.0", + "prost 0.13.5", + "reqwest 0.12.28", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tonic 0.12.3", + "tracing", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +dependencies = [ + "http", + "opentelemetry 0.31.0", + "opentelemetry-http 0.31.0", + "opentelemetry-proto 0.31.0", + "opentelemetry_sdk 0.31.0", + "prost 0.14.3", + "reqwest 0.12.28", + "thiserror 2.0.18", + "tokio", + "tonic 0.14.6", + "tracing", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d" +dependencies = [ + "base64 0.22.1", + "hex", + "opentelemetry 0.28.0", + "opentelemetry_sdk 0.28.0", + "prost 0.13.5", + "serde", + "tonic 0.12.3", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +dependencies = [ + "opentelemetry 0.31.0", + "opentelemetry_sdk 0.31.0", + "prost 0.14.3", + "tonic 0.14.6", + "tonic-prost", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" + +[[package]] +name = "opentelemetry_sdk" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "opentelemetry 0.28.0", + "percent-encoding", + "rand 0.8.6", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry 0.31.0", + "percent-encoding", + "rand 0.9.4", + "thiserror 2.0.18", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p2p" +version = "0.2.13" +dependencies = [ + "derive_more", + "eyre", + "futures", + "futures-util", + "hex", + "libp2p", + "libp2p-stream", + "multiaddr", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "bytes", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.117", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.1", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "picky-asn1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff038f9360b934342fb3c0a1d6e82c438a2624b51c3c6e3e6d7cf252b6f3ee3" +dependencies = [ + "oid", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-der" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d413165e4bf7f808b9a27cbaba657657a2921f0965db833f488c4d4be96dcd2e" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d4117bd1b1dc5646359ee7243c50c5000c0920ea2d1fb120335a2f4c684b8" +dependencies = [ + "base64 0.22.1", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain_hasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e19e6491bdde87c2c43d70f4c194bc8a758f2eb732df00f61e43f7362e3b4cc" +dependencies = [ + "crunchy", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "pprof_util" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea0cc524de808a6d98d192a3d99fe95617031ad4a52ec0a0f987ef4432e8fe1" +dependencies = [ + "anyhow", + "flate2", + "num", + "paste", + "prost 0.14.3", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint 0.9.5", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "procfs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" +dependencies = [ + "bitflags 2.11.1", + "chrono", + "flate2", + "procfs-core", + "rustix", +] + +[[package]] +name = "procfs-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" +dependencies = [ + "bitflags 2.11.1", + "chrono", + "hex", +] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf41c1a7c32ed72abe5082fb19505b969095c12da9f5732a4bc9878757fd087c" +dependencies = [ + "dtoa", + "itoa", + "parking_lot", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.11.1", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "proptest-arbitrary-interop" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1981e49bd2432249da8b0e11e5557099a8e74690d6b94e721f7dc0bb7f3555f" +dependencies = [ + "arbitrary", + "proptest", +] + +[[package]] +name = "proptest-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proptest-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57924a81864dddafba92e1bf92f9bf82f97096c44489548a60e888e1547549b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive 0.14.3", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror 1.0.69", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", + "serde", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rand 0.9.4", + "rustversion", +] + +[[package]] +name = "ratatui" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +dependencies = [ + "bitflags 2.11.1", + "compact_str", + "hashbrown 0.16.1", + "indoc", + "itertools 0.14.0", + "kasuari", + "lru", + "strum", + "thiserror 2.0.18", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.16.1", + "indoc", + "instability", + "itertools 0.14.0", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "regress" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2057b2325e68a893284d1538021ab90279adac1139957ca2a74426c6f118fb48" +dependencies = [ + "hashbrown 0.16.1", + "memchr", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "reqwest" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier 0.7.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "reth" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rpc-types 2.0.5", + "aquamarine", + "clap", + "reth-chainspec", + "reth-cli-runner", + "reth-cli-util", + "reth-consensus", + "reth-consensus-common", + "reth-db", + "reth-ethereum-cli", + "reth-ethereum-payload-builder", + "reth-ethereum-primitives", + "reth-network", + "reth-network-api", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-node-metrics", + "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-convert", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-tasks", + "reth-transaction-pool", + "tracing", +] + +[[package]] +name = "reth-basic-payload-builder" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "futures-core", + "futures-util", + "metrics", + "reth-chain-state", + "reth-execution-cache", + "reth-metrics", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-revm", + "reth-storage-api", + "reth-tasks", + "reth-trie-parallel", + "serde", + "tokio", + "tracing", +] + +[[package]] +name = "reth-chain-state" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-signer 2.0.5", + "alloy-signer-local 2.0.5", + "derive_more", + "metrics", + "parking_lot", + "pin-project", + "rand 0.9.4", + "rayon", + "reth-chainspec", + "reth-errors", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-metrics", + "reth-primitives-traits", + "reth-storage-api", + "reth-tasks", + "reth-trie", + "revm-database", + "revm-state", + "serde", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-chainspec" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-chains", + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-genesis 2.0.5", + "alloy-primitives 1.6.0", + "alloy-trie", + "auto_impl", + "derive_more", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "serde_json", +] + +[[package]] +name = "reth-cli" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-genesis 2.0.5", + "clap", + "eyre", + "reth-cli-runner", + "reth-db", + "serde_json", +] + +[[package]] +name = "reth-cli-commands" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-chains", + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "backon", + "blake3", + "clap", + "comfy-table", + "crossterm", + "eyre", + "fdlimit", + "futures", + "human_bytes", + "humantime", + "itertools 0.14.0", + "lz4", + "metrics", + "parking_lot", + "ratatui", + "rayon", + "reqwest 0.13.3", + "reth-chainspec", + "reth-cli", + "reth-cli-runner", + "reth-cli-util", + "reth-codecs", + "reth-config", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-db-common", + "reth-discv4", + "reth-discv5", + "reth-downloaders", + "reth-ecies", + "reth-era", + "reth-era-downloader", + "reth-era-utils", + "reth-eth-wire", + "reth-etl", + "reth-evm", + "reth-exex", + "reth-fs-util", + "reth-net-nat", + "reth-network", + "reth-network-p2p", + "reth-network-peers", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-events", + "reth-node-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-prune-types", + "reth-revm", + "reth-stages", + "reth-stages-types", + "reth-static-file", + "reth-static-file-types", + "reth-storage-api", + "reth-tasks", + "reth-trie", + "reth-trie-db", + "secp256k1 0.30.0", + "serde", + "serde_json", + "tar", + "tokio", + "tokio-stream", + "toml", + "tracing", + "url", + "zstd", +] + +[[package]] +name = "reth-cli-runner" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "reth-tasks", + "tokio", + "tracing", +] + +[[package]] +name = "reth-cli-util" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "cfg-if", + "eyre", + "libc", + "rand 0.8.6", + "reth-fs-util", + "secp256k1 0.30.0", + "serde", + "thiserror 2.0.18", + "tikv-jemalloc-sys", + "tikv-jemallocator", +] + +[[package]] +name = "reth-codecs" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce542a96bf888f31854803e80b3340bc233927743aa580838014e8a88fe0d66" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-genesis 2.0.5", + "alloy-primitives 1.6.0", + "alloy-trie", + "arbitrary", + "bytes", + "modular-bitfield", + "parity-scale-codec", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", + "visibility", +] + +[[package]] +name = "reth-codecs-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c90f1cc0f9887680ca785b0b21aa961070b9465917bf65afaec56a6d005bb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "reth-config" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "eyre", + "humantime-serde", + "reth-network-types", + "reth-prune-types", + "reth-stages-types", + "reth-static-file-types", + "serde", + "toml", + "url", +] + +[[package]] +name = "reth-consensus" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eip7928", + "alloy-primitives 1.6.0", + "auto_impl", + "reth-execution-types", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-consensus-common" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "reth-chainspec", + "reth-consensus", + "reth-primitives-traits", +] + +[[package]] +name = "reth-consensus-debug-client" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-json-rpc 2.0.5", + "alloy-primitives 1.6.0", + "alloy-provider 2.0.5", + "alloy-rpc-types-engine", + "alloy-transport 2.0.5", + "auto_impl", + "derive_more", + "eyre", + "futures", + "reqwest 0.13.3", + "reth-node-api", + "reth-tracing", + "ringbuffer", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "reth-db" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "derive_more", + "eyre", + "libc", + "metrics", + "page_size", + "parking_lot", + "quanta", + "reth-db-api", + "reth-fs-util", + "reth-libmdbx", + "reth-metrics", + "reth-nippy-jar", + "reth-static-file-types", + "reth-storage-errors", + "reth-tracing", + "rustc-hash", + "strum", + "sysinfo 0.38.4", + "tempfile", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-db-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "arbitrary", + "arrayvec", + "bytes", + "derive_more", + "metrics", + "modular-bitfield", + "proptest", + "reth-codecs", + "reth-db-models", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "roaring", + "serde", +] + +[[package]] +name = "reth-db-common" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-genesis 2.0.5", + "alloy-primitives 1.6.0", + "boyer-moore-magiclen", + "eyre", + "reth-chainspec", + "reth-codecs", + "reth-config", + "reth-db-api", + "reth-etl", + "reth-execution-errors", + "reth-fs-util", + "reth-node-types", + "reth-primitives-traits", + "reth-provider", + "reth-stages-types", + "reth-static-file-types", + "reth-trie", + "reth-trie-db", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-db-models" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "arbitrary", + "bytes", + "modular-bitfield", + "reth-codecs", + "reth-primitives-traits", + "serde", +] + +[[package]] +name = "reth-discv4" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "discv5", + "enr", + "itertools 0.14.0", + "parking_lot", + "rand 0.8.6", + "reth-ethereum-forks", + "reth-net-banlist", + "reth-net-nat", + "reth-network-peers", + "schnellru", + "secp256k1 0.30.0", + "serde", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-discv5" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "derive_more", + "discv5", + "enr", + "futures", + "itertools 0.14.0", + "metrics", + "rand 0.9.4", + "reth-chainspec", + "reth-ethereum-forks", + "reth-metrics", + "reth-network-peers", + "secp256k1 0.30.0", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-dns-discovery" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "dashmap", + "data-encoding", + "enr", + "hickory-resolver 0.26.1", + "linked_hash_set", + "reth-ethereum-forks", + "reth-network-peers", + "reth-tokio-util", + "schnellru", + "secp256k1 0.30.0", + "serde", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-downloaders" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "async-compression", + "futures", + "futures-util", + "itertools 0.14.0", + "metrics", + "pin-project", + "rayon", + "reth-config", + "reth-consensus", + "reth-ethereum-primitives", + "reth-metrics", + "reth-network-p2p", + "reth-network-peers", + "reth-primitives-traits", + "reth-provider", + "reth-storage-api", + "reth-tasks", + "reth-testing-utils", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-ecies" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "aes", + "alloy-primitives 1.6.0", + "alloy-rlp", + "block-padding", + "byteorder", + "cipher", + "concat-kdf", + "ctr", + "digest 0.10.7", + "futures", + "hmac", + "pin-project", + "rand 0.8.6", + "reth-network-peers", + "secp256k1 0.30.0", + "sha2", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-engine-local" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "eyre", + "futures-util", + "reth-chainspec", + "reth-engine-primitives", + "reth-ethereum-engine-primitives", + "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-storage-api", + "reth-transaction-pool", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-engine-primitives" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "auto_impl", + "futures", + "reth-chain-state", + "reth-errors", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-trie-common", + "serde", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "reth-engine-tree" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eip7928", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-engine", + "crossbeam-channel", + "derive_more", + "futures", + "indexmap 2.14.0", + "metrics", + "moka", + "parking_lot", + "rayon", + "reth-chain-state", + "reth-chainspec", + "reth-consensus", + "reth-db", + "reth-engine-primitives", + "reth-errors", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-cache", + "reth-execution-types", + "reth-metrics", + "reth-network-p2p", + "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-prune-types", + "reth-revm", + "reth-stages", + "reth-stages-api", + "reth-static-file", + "reth-tasks", + "reth-tracing", + "reth-trie", + "reth-trie-common", + "reth-trie-db", + "reth-trie-parallel", + "reth-trie-sparse", + "revm", + "revm-primitives", + "revm-state", + "schnellru", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-engine-util" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-engine", + "eyre", + "futures", + "itertools 0.14.0", + "pin-project", + "reth-chainspec", + "reth-engine-primitives", + "reth-engine-tree", + "reth-errors", + "reth-evm", + "reth-fs-util", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-revm", + "reth-storage-api", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-era" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "ethereum_ssz", + "ethereum_ssz_derive", + "sha2", + "snap", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-era-downloader" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "bytes", + "eyre", + "futures-util", + "reqwest 0.13.3", + "reth-era", + "reth-fs-util", + "sha2", + "tokio", +] + +[[package]] +name = "reth-era-utils" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "eyre", + "futures-util", + "reth-db-api", + "reth-era", + "reth-era-downloader", + "reth-etl", + "reth-fs-util", + "reth-primitives-traits", + "reth-provider", + "reth-stages-types", + "reth-storage-api", + "tokio", + "tracing", +] + +[[package]] +name = "reth-errors" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "reth-consensus", + "reth-execution-errors", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-eth-wire" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-chains", + "alloy-primitives 1.6.0", + "alloy-rlp", + "bytes", + "derive_more", + "futures", + "pin-project", + "reth-codecs", + "reth-ecies", + "reth-eth-wire-types", + "reth-ethereum-forks", + "reth-metrics", + "reth-network-peers", + "reth-primitives-traits", + "serde", + "snap", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-eth-wire-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-chains", + "alloy-consensus 2.0.5", + "alloy-eip7928", + "alloy-eips 2.0.5", + "alloy-hardforks", + "alloy-primitives 1.6.0", + "alloy-rlp", + "bytes", + "derive_more", + "reth-chainspec", + "reth-codecs-derive", + "reth-ethereum-primitives", + "reth-primitives-traits", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-ethereum-cli" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "clap", + "eyre", + "reth-chainspec", + "reth-cli", + "reth-cli-commands", + "reth-cli-runner", + "reth-db", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-node-metrics", + "reth-rpc-server-types", + "reth-tasks", + "reth-tracing", + "tracing", +] + +[[package]] +name = "reth-ethereum-consensus" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-execution-types", + "reth-primitives-traits", + "tracing", +] + +[[package]] +name = "reth-ethereum-engine-primitives" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "reth-engine-primitives", + "reth-ethereum-primitives", + "reth-payload-primitives", + "reth-primitives-traits", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-ethereum-forks" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eip2124", + "alloy-hardforks", + "alloy-primitives 1.6.0", + "auto_impl", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "reth-ethereum-payload-builder" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-engine", + "reth-basic-payload-builder", + "reth-chainspec", + "reth-consensus-common", + "reth-errors", + "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", + "reth-execution-cache", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-payload-validator", + "reth-primitives-traits", + "reth-revm", + "reth-storage-api", + "reth-transaction-pool", + "revm", + "tracing", +] + +[[package]] +name = "reth-ethereum-primitives" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "reth-codecs", + "reth-primitives-traits", + "serde", +] + +[[package]] +name = "reth-etl" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "rayon", + "reth-db-api", + "tempfile", +] + +[[package]] +name = "reth-evm" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-primitives 1.6.0", + "auto_impl", + "derive_more", + "futures-util", + "metrics", + "rayon", + "reth-execution-errors", + "reth-execution-types", + "reth-metrics", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-evm-ethereum" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "reth-chainspec", + "reth-ethereum-forks", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-execution-cache" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "fixed-cache", + "metrics", + "parking_lot", + "reth-errors", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-trie", + "tracing", +] + +[[package]] +name = "reth-execution-errors" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-evm", + "alloy-primitives 1.6.0", + "alloy-rlp", + "nybbles", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-execution-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-primitives 1.6.0", + "alloy-rlp", + "derive_more", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-trie-common", + "revm", + "serde", + "serde_with", +] + +[[package]] +name = "reth-exex" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "eyre", + "futures", + "itertools 0.14.0", + "metrics", + "parking_lot", + "reth-chain-state", + "reth-chainspec", + "reth-config", + "reth-ethereum-primitives", + "reth-evm", + "reth-exex-types", + "reth-fs-util", + "reth-metrics", + "reth-node-api", + "reth-node-core", + "reth-payload-builder", + "reth-primitives-traits", + "reth-provider", + "reth-prune-types", + "reth-revm", + "reth-stages-api", + "reth-tasks", + "reth-tracing", + "rmp-serde", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-exex-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "reth-chain-state", + "reth-execution-types", + "reth-primitives-traits", + "serde", + "serde_with", +] + +[[package]] +name = "reth-fs-util" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-invalid-block-hooks" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-debug", + "eyre", + "futures", + "jsonrpsee 0.26.0", + "pretty_assertions", + "reth-engine-primitives", + "reth-evm", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc-api", + "reth-tracing", + "reth-trie", + "revm", + "revm-bytecode", + "revm-database", + "serde", + "serde_json", +] + +[[package]] +name = "reth-ipc" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "bytes", + "futures", + "futures-util", + "interprocess", + "jsonrpsee 0.26.0", + "pin-project", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "reth-libmdbx" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "bitflags 2.11.1", + "byteorder", + "crossbeam-queue", + "dashmap", + "derive_more", + "parking_lot", + "reth-mdbx-sys", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-mdbx-sys" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "bindgen", + "cc", +] + +[[package]] +name = "reth-metrics" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "futures", + "metrics", + "metrics-derive", + "reth-primitives-traits", + "tokio", + "tokio-util", +] + +[[package]] +name = "reth-net-banlist" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "ipnet", +] + +[[package]] +name = "reth-net-nat" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "futures-util", + "if-addrs 0.14.0", + "reqwest 0.13.3", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-network" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "aquamarine", + "auto_impl", + "derive_more", + "discv5", + "enr", + "futures", + "itertools 0.14.0", + "metrics", + "parking_lot", + "pin-project", + "rand 0.8.6", + "rand 0.9.4", + "rayon", + "reth-chainspec", + "reth-consensus", + "reth-discv4", + "reth-discv5", + "reth-dns-discovery", + "reth-ecies", + "reth-eth-wire", + "reth-eth-wire-types", + "reth-ethereum-forks", + "reth-ethereum-primitives", + "reth-evm-ethereum", + "reth-fs-util", + "reth-metrics", + "reth-net-banlist", + "reth-network-api", + "reth-network-p2p", + "reth-network-peers", + "reth-network-types", + "reth-primitives-traits", + "reth-storage-api", + "reth-tasks", + "reth-tokio-util", + "reth-transaction-pool", + "rustc-hash", + "schnellru", + "secp256k1 0.30.0", + "serde", + "smallvec", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-network-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-admin", + "alloy-rpc-types-eth 2.0.5", + "auto_impl", + "derive_more", + "enr", + "futures", + "reth-eth-wire-types", + "reth-ethereum-forks", + "reth-network-p2p", + "reth-network-peers", + "reth-network-types", + "reth-tokio-util", + "serde", + "thiserror 2.0.18", + "tokio", + "tokio-stream", +] + +[[package]] +name = "reth-network-p2p" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "auto_impl", + "derive_more", + "futures", + "parking_lot", + "reth-consensus", + "reth-eth-wire-types", + "reth-ethereum-primitives", + "reth-network-peers", + "reth-network-types", + "reth-primitives-traits", + "reth-storage-errors", + "tokio", + "tracing", +] + +[[package]] +name = "reth-network-peers" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "enr", + "secp256k1 0.30.0", + "serde_with", + "thiserror 2.0.18", + "tokio", + "url", +] + +[[package]] +name = "reth-network-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eip2124", + "humantime-serde", + "reth-net-banlist", + "reth-network-peers", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "reth-nippy-jar" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "anyhow", + "bincode 1.3.3", + "derive_more", + "lz4_flex", + "memmap2", + "reth-fs-util", + "serde", + "thiserror 2.0.18", + "tracing", + "zstd", +] + +[[package]] +name = "reth-node-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-rpc-types-engine", + "eyre", + "reth-basic-payload-builder", + "reth-consensus", + "reth-db-api", + "reth-engine-primitives", + "reth-evm", + "reth-network-api", + "reth-node-core", + "reth-node-types", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-provider", + "reth-tasks", + "reth-tokio-util", + "reth-transaction-pool", +] + +[[package]] +name = "reth-node-builder" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-provider 2.0.5", + "alloy-rpc-types 2.0.5", + "alloy-rpc-types-engine", + "aquamarine", + "eyre", + "fdlimit", + "futures", + "jsonrpsee 0.26.0", + "parking_lot", + "rayon", + "reth-basic-payload-builder", + "reth-chain-state", + "reth-chainspec", + "reth-config", + "reth-consensus", + "reth-consensus-debug-client", + "reth-db", + "reth-db-api", + "reth-db-common", + "reth-downloaders", + "reth-engine-local", + "reth-engine-primitives", + "reth-engine-tree", + "reth-engine-util", + "reth-evm", + "reth-exex", + "reth-fs-util", + "reth-invalid-block-hooks", + "reth-network", + "reth-network-api", + "reth-network-p2p", + "reth-node-api", + "reth-node-core", + "reth-node-ethstats", + "reth-node-events", + "reth-node-metrics", + "reth-payload-builder", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-engine-api", + "reth-rpc-eth-types", + "reth-rpc-layer", + "reth-stages", + "reth-static-file", + "reth-tasks", + "reth-tokio-util", + "reth-tracing", + "reth-transaction-pool", + "reth-trie-db", + "secp256k1 0.30.0", + "serde_json", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-node-core" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "clap", + "derive_more", + "dirs-next", + "eyre", + "futures", + "humantime", + "ipnet", + "rand 0.9.4", + "reth-chainspec", + "reth-cli-util", + "reth-config", + "reth-consensus", + "reth-db", + "reth-discv4", + "reth-discv5", + "reth-engine-local", + "reth-engine-primitives", + "reth-ethereum-forks", + "reth-net-banlist", + "reth-net-nat", + "reth-network", + "reth-network-p2p", + "reth-network-peers", + "reth-primitives-traits", + "reth-prune-types", + "reth-rpc-convert", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-stages-types", + "reth-storage-api", + "reth-storage-errors", + "reth-tasks", + "reth-tracing", + "reth-tracing-otlp", + "reth-transaction-pool", + "secp256k1 0.30.0", + "serde", + "strum", + "thiserror 2.0.18", + "toml", + "tracing", + "url", + "vergen", + "vergen-git2", +] + +[[package]] +name = "reth-node-ethereum" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "ethereum_ssz", + "eyre", + "http-body-util", + "jsonrpsee 0.26.0", + "reth-chainspec", + "reth-engine-local", + "reth-engine-primitives", + "reth-ethereum-consensus", + "reth-ethereum-engine-primitives", + "reth-ethereum-payload-builder", + "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", + "reth-network", + "reth-node-api", + "reth-node-builder", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-tracing", + "reth-transaction-pool", + "revm", + "tokio", + "tower 0.5.3", +] + +[[package]] +name = "reth-node-ethstats" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "chrono", + "futures-util", + "reth-chain-state", + "reth-network-api", + "reth-primitives-traits", + "reth-storage-api", + "reth-transaction-pool", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-tungstenite 0.28.0", + "tracing", + "url", +] + +[[package]] +name = "reth-node-events" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "derive_more", + "futures", + "humantime", + "pin-project", + "reth-engine-primitives", + "reth-network-api", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages", + "reth-static-file-types", + "reth-storage-api", + "tokio", + "tracing", +] + +[[package]] +name = "reth-node-metrics" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "bytes", + "eyre", + "http", + "http-body-util", + "jemalloc_pprof", + "jsonrpsee-server 0.26.0", + "mappings", + "metrics", + "metrics-exporter-prometheus 0.18.3", + "metrics-process", + "metrics-util 0.20.4", + "pprof_util", + "procfs", + "reqwest 0.13.3", + "reth-fs-util", + "reth-metrics", + "reth-tasks", + "tempfile", + "tikv-jemalloc-ctl", + "tokio", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "reth-node-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "reth-chainspec", + "reth-db-api", + "reth-engine-primitives", + "reth-payload-primitives", + "reth-primitives-traits", +] + +[[package]] +name = "reth-optimism-chainspec" +version = "1.11.3" +dependencies = [ + "alloy-chains", + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-genesis 2.0.5", + "alloy-hardforks", + "alloy-primitives 1.6.0", + "derive_more", + "miniz_oxide 0.9.1", + "op-alloy-consensus", + "op-alloy-rpc-types", + "paste", + "reth-chainspec", + "reth-ethereum-forks", + "reth-network-peers", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-primitives-traits", + "serde", + "serde_json", + "tar-no-std", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-optimism-cli" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "clap", + "derive_more", + "eyre", + "futures-util", + "op-alloy-consensus", + "reth-chainspec", + "reth-cli", + "reth-cli-commands", + "reth-cli-runner", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-db-common", + "reth-downloaders", + "reth-execution-types", + "reth-fs-util", + "reth-node-builder", + "reth-node-core", + "reth-node-events", + "reth-node-metrics", + "reth-optimism-chainspec", + "reth-optimism-consensus", + "reth-optimism-evm", + "reth-optimism-node", + "reth-optimism-primitives", + "reth-optimism-trie", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-rpc-server-types", + "reth-stages", + "reth-static-file", + "reth-static-file-types", + "reth-tasks", + "reth-tracing", + "serde", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-optimism-consensus" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-trie", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-execution-types", + "reth-optimism-chainspec", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-optimism-evm" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-op-evm", + "alloy-primitives 1.6.0", + "op-alloy-consensus", + "op-alloy-rpc-types", + "op-alloy-rpc-types-engine", + "op-revm", + "reth-chainspec", + "reth-evm", + "reth-execution-errors", + "reth-execution-types", + "reth-optimism-chainspec", + "reth-optimism-consensus", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-primitives-traits", + "reth-rpc-eth-api", + "reth-storage-errors", + "revm", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-optimism-exex" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "eyre", + "futures-util", + "reth-execution-types", + "reth-exex", + "reth-node-api", + "reth-optimism-trie", + "reth-provider", + "reth-trie", + "tracing", +] + +[[package]] +name = "reth-optimism-flashblocks" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types 2.0.5", + "alloy-rpc-types-engine", + "brotli", + "derive_more", + "eyre", + "futures-util", + "metrics", + "op-alloy-rpc-types-engine", + "reth-chain-state", + "reth-engine-primitives", + "reth-errors", + "reth-evm", + "reth-execution-types", + "reth-metrics", + "reth-optimism-payload-builder", + "reth-optimism-primitives", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-revm", + "reth-rpc-eth-types", + "reth-storage-api", + "reth-tasks", + "ringbuffer", + "serde_json", + "tokio", + "tokio-tungstenite 0.28.0", + "tracing", + "url", +] + +[[package]] +name = "reth-optimism-forks" +version = "1.11.3" +dependencies = [ + "alloy-op-hardforks", + "alloy-primitives 1.6.0", + "once_cell", + "reth-ethereum-forks", +] + +[[package]] +name = "reth-optimism-node" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "clap", + "eyre", + "futures-util", + "op-alloy-consensus", + "op-alloy-rpc-types-engine", + "op-revm", + "reth-chainspec", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-evm", + "reth-network", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-optimism-chainspec", + "reth-optimism-consensus", + "reth-optimism-evm", + "reth-optimism-exex", + "reth-optimism-forks", + "reth-optimism-payload-builder", + "reth-optimism-primitives", + "reth-optimism-rpc", + "reth-optimism-storage", + "reth-optimism-trie", + "reth-optimism-txpool", + "reth-payload-builder", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-api", + "reth-rpc-engine-api", + "reth-rpc-server-types", + "reth-tasks", + "reth-tracing", + "reth-transaction-pool", + "reth-trie-common", + "revm", + "serde", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "reth-optimism-payload-builder" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "derive_more", + "op-alloy-consensus", + "op-alloy-rpc-types-engine", + "op-revm", + "reth-basic-payload-builder", + "reth-chainspec", + "reth-evm", + "reth-execution-types", + "reth-optimism-evm", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-optimism-txpool", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-payload-util", + "reth-payload-validator", + "reth-primitives-traits", + "reth-revm", + "reth-storage-api", + "reth-transaction-pool", + "revm", + "serde", + "sha2", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-optimism-primitives" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "op-alloy-consensus", + "reth-primitives-traits", + "serde", + "serde_with", +] + +[[package]] +name = "reth-optimism-rpc" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-json-rpc 2.0.5", + "alloy-op-evm", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-client 2.0.5", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "alloy-transport 2.0.5", + "alloy-transport-http 2.0.5", + "async-trait", + "derive_more", + "eyre", + "futures", + "jsonrpsee 0.26.0", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "metrics", + "op-alloy-consensus", + "op-alloy-network", + "op-alloy-rpc-jsonrpsee", + "op-alloy-rpc-types", + "op-alloy-rpc-types-engine", + "op-revm", + "reqwest 0.13.3", + "reth-basic-payload-builder", + "reth-chain-state", + "reth-chainspec", + "reth-evm", + "reth-metrics", + "reth-node-api", + "reth-node-builder", + "reth-optimism-evm", + "reth-optimism-flashblocks", + "reth-optimism-forks", + "reth-optimism-payload-builder", + "reth-optimism-primitives", + "reth-optimism-trie", + "reth-optimism-txpool", + "reth-payload-util", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-engine-api", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "revm", + "serde", + "serde_json", + "strum", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "reth-optimism-storage" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "reth-optimism-primitives", + "reth-storage-api", +] + +[[package]] +name = "reth-optimism-trie" +version = "1.11.3" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "auto_impl", + "bincode 2.0.1", + "bytes", + "crossbeam-channel", + "derive_more", + "eyre", + "metrics", + "parking_lot", + "reth-codecs", + "reth-db", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-errors", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-tasks", + "reth-trie", + "reth-trie-common", + "serde", + "strum", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-optimism-txpool" +version = "1.11.3" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-json-rpc 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-client 2.0.5", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "c-kzg", + "derive_more", + "futures-util", + "metrics", + "op-alloy-consensus", + "op-alloy-flz", + "op-alloy-rpc-types", + "op-revm", + "parking_lot", + "reth-chain-state", + "reth-chainspec", + "reth-eth-wire-types", + "reth-evm", + "reth-execution-types", + "reth-metrics", + "reth-optimism-evm", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-primitives-traits", + "reth-storage-api", + "reth-transaction-pool", + "serde", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-payload-builder" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types 2.0.5", + "derive_more", + "futures-util", + "metrics", + "reth-chain-state", + "reth-ethereum-engine-primitives", + "reth-execution-cache", + "reth-metrics", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-trie-parallel", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-payload-builder-primitives" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "pin-project", + "reth-payload-primitives", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-payload-primitives" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-engine", + "auto_impl", + "either", + "reth-chainspec", + "reth-errors", + "reth-execution-types", + "reth-primitives-traits", + "reth-trie-common", + "serde", + "sha2", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "reth-payload-util" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "reth-transaction-pool", +] + +[[package]] +name = "reth-payload-validator" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-rpc-types-engine", + "reth-primitives-traits", +] + +[[package]] +name = "reth-primitives-traits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee12e304adbacbb32248c9806ebafbe1e2811fbfefe53c5e5b710a8438b7ec0" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-genesis 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-eth 2.0.5", + "alloy-trie", + "arbitrary", + "byteorder", + "bytes", + "dashmap", + "derive_more", + "modular-bitfield", + "once_cell", + "proptest", + "proptest-arbitrary-interop", + "quanta", + "rayon", + "reth-codecs", + "revm-bytecode", + "revm-primitives", + "revm-state", + "secp256k1 0.30.0", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-provider" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eip7928", + "alloy-eips 2.0.5", + "alloy-genesis 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "eyre", + "itertools 0.14.0", + "metrics", + "notify", + "parking_lot", + "rayon", + "reth-chain-state", + "reth-chainspec", + "reth-codecs", + "reth-db", + "reth-db-api", + "reth-errors", + "reth-ethereum-engine-primitives", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-fs-util", + "reth-metrics", + "reth-nippy-jar", + "reth-node-types", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-static-file-types", + "reth-storage-api", + "reth-storage-errors", + "reth-tasks", + "reth-tokio-util", + "reth-trie", + "reth-trie-db", + "revm-database", + "revm-state", + "rocksdb", + "smallvec", + "strum", + "tokio", + "tracing", +] + +[[package]] +name = "reth-prune" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "itertools 0.14.0", + "metrics", + "rayon", + "reth-config", + "reth-db-api", + "reth-errors", + "reth-exex-types", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-prune-types", + "reth-stages-types", + "reth-static-file-types", + "reth-storage-api", + "reth-tokio-util", + "rustc-hash", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-prune-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "arbitrary", + "derive_more", + "modular-bitfield", + "reth-codecs", + "serde", + "strum", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-revm" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-debug", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie", + "revm", +] + +[[package]] +name = "reth-rpc" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-dyn-abi", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-genesis 2.0.5", + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-client 2.0.5", + "alloy-rpc-types 2.0.5", + "alloy-rpc-types-admin", + "alloy-rpc-types-beacon", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "alloy-rpc-types-mev", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde 2.0.5", + "alloy-signer 2.0.5", + "alloy-signer-local 2.0.5", + "async-trait", + "derive_more", + "dyn-clone", + "futures", + "itertools 0.14.0", + "jsonrpsee 0.26.0", + "jsonrpsee-types 0.26.0", + "parking_lot", + "pin-project", + "reth-chain-state", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-engine-primitives", + "reth-errors", + "reth-ethereum-engine-primitives", + "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", + "reth-execution-types", + "reth-metrics", + "reth-network-api", + "reth-network-peers", + "reth-network-types", + "reth-node-api", + "reth-primitives-traits", + "reth-revm", + "reth-rpc-api", + "reth-rpc-convert", + "reth-rpc-engine-api", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "reth-trie-common", + "revm", + "revm-inspectors", + "revm-primitives", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "tracing-futures", +] + +[[package]] +name = "reth-rpc-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-genesis 2.0.5", + "alloy-json-rpc 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types 2.0.5", + "alloy-rpc-types-admin", + "alloy-rpc-types-anvil", + "alloy-rpc-types-beacon", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "alloy-rpc-types-mev", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde 2.0.5", + "jsonrpsee 0.26.0", + "reth-chain-state", + "reth-engine-primitives", + "reth-network-peers", + "reth-rpc-eth-api", + "reth-trie-common", + "serde", + "serde_json", +] + +[[package]] +name = "reth-rpc-builder" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-network 2.0.5", + "alloy-provider 2.0.5", + "dyn-clone", + "http", + "jsonrpsee 0.26.0", + "metrics", + "pin-project", + "reth-chain-state", + "reth-chainspec", + "reth-consensus", + "reth-engine-primitives", + "reth-evm", + "reth-ipc", + "reth-metrics", + "reth-network-api", + "reth-node-core", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-engine-api", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-layer", + "reth-rpc-server-types", + "reth-storage-api", + "reth-tasks", + "reth-tokio-util", + "reth-transaction-pool", + "serde", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tower 0.5.3", + "tower-http", + "tracing", +] + +[[package]] +name = "reth-rpc-convert" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-evm", + "alloy-json-rpc 2.0.5", + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "auto_impl", + "dyn-clone", + "jsonrpsee-types 0.26.0", + "reth-evm", + "reth-primitives-traits", + "reth-rpc-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-rpc-engine-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-engine", + "async-trait", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "metrics", + "reth-chainspec", + "reth-engine-primitives", + "reth-metrics", + "reth-network-api", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-rpc-api", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "serde", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-rpc-eth-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-dyn-abi", + "alloy-eip7928", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-json-rpc 2.0.5", + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-eth 2.0.5", + "alloy-rpc-types-mev", + "alloy-serde 2.0.5", + "async-trait", + "auto_impl", + "dyn-clone", + "futures", + "jsonrpsee 0.26.0", + "jsonrpsee-types 0.26.0", + "parking_lot", + "reth-chain-state", + "reth-chainspec", + "reth-errors", + "reth-evm", + "reth-network-api", + "reth-node-api", + "reth-primitives-traits", + "reth-revm", + "reth-rpc-convert", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "reth-trie-common", + "revm", + "revm-inspectors", + "serde_json", + "tokio", + "tracing", +] + +[[package]] +name = "reth-rpc-eth-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-evm", + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-client 2.0.5", + "alloy-rpc-types-eth 2.0.5", + "alloy-sol-types 1.6.0", + "alloy-transport 2.0.5", + "derive_more", + "futures", + "itertools 0.14.0", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "metrics", + "rand 0.9.4", + "reqwest 0.13.3", + "reth-chain-state", + "reth-chainspec", + "reth-errors", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-metrics", + "reth-primitives-traits", + "reth-revm", + "reth-rpc-convert", + "reth-rpc-server-types", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "reth-trie", + "revm", + "revm-inspectors", + "schnellru", + "serde", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "reth-rpc-layer" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-rpc-types-engine", + "http", + "jsonrpsee-http-client 0.26.0", + "pin-project", + "tower 0.5.3", + "tower-http", + "tracing", +] + +[[package]] +name = "reth-rpc-server-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "reth-errors", + "reth-network-api", + "serde", + "strum", +] + +[[package]] +name = "reth-rpc-traits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "860fe223501a76ff14aa3bf164f739f31008c2a2905ac85708bfd88f042e6151" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-network 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "alloy-signer 2.0.5", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-stages" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "eyre", + "futures-util", + "itertools 0.14.0", + "num-traits", + "page_size", + "rayon", + "reqwest 0.13.3", + "reth-chainspec", + "reth-codecs", + "reth-config", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-era", + "reth-era-downloader", + "reth-era-utils", + "reth-ethereum-primitives", + "reth-etl", + "reth-evm", + "reth-execution-types", + "reth-exex", + "reth-fs-util", + "reth-libmdbx", + "reth-network-p2p", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-prune-types", + "reth-revm", + "reth-stages-api", + "reth-static-file-types", + "reth-storage-api", + "reth-storage-errors", + "reth-tasks", + "reth-testing-utils", + "reth-trie", + "reth-trie-db", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-stages-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "aquamarine", + "auto_impl", + "futures-util", + "metrics", + "reth-codecs", + "reth-consensus", + "reth-errors", + "reth-metrics", + "reth-network-p2p", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-stages-types", + "reth-static-file", + "reth-static-file-types", + "reth-tokio-util", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "reth-stages-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "arbitrary", + "bytes", + "modular-bitfield", + "reth-codecs", + "reth-trie-common", + "serde", +] + +[[package]] +name = "reth-static-file" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "parking_lot", + "rayon", + "reth-codecs", + "reth-db-api", + "reth-primitives-traits", + "reth-provider", + "reth-prune-types", + "reth-stages-types", + "reth-static-file-types", + "reth-storage-errors", + "reth-tokio-util", + "tracing", +] + +[[package]] +name = "reth-static-file-types" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "clap", + "derive_more", + "fixed-map", + "reth-stages-types", + "serde", + "strum", + "tracing", +] + +[[package]] +name = "reth-storage-api" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "auto_impl", + "reth-chainspec", + "reth-db-api", + "reth-db-models", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-tokio-util", + "reth-trie-common", + "revm-database", + "serde_json", +] + +[[package]] +name = "reth-storage-errors" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "derive_more", + "reth-codecs", + "reth-primitives-traits", + "reth-prune-types", + "reth-static-file-types", + "revm-database-interface", + "revm-state", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-tasks" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "crossbeam-utils", + "dashmap", + "futures-util", + "libc", + "metrics", + "parking_lot", + "pin-project", + "rayon", + "reth-metrics", + "thiserror 2.0.18", + "thread-priority", + "tokio", + "tracing", + "tracing-futures", +] + +[[package]] +name = "reth-testing-utils" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-genesis 2.0.5", + "alloy-primitives 1.6.0", + "rand 0.8.6", + "rand 0.9.4", + "reth-ethereum-primitives", + "reth-primitives-traits", + "secp256k1 0.30.0", +] + +[[package]] +name = "reth-tokio-util" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-tracing" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "clap", + "eyre", + "reth-tracing-otlp", + "rolling-file", + "tracing", + "tracing-appender", + "tracing-journald", + "tracing-logfmt", + "tracing-samply", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "reth-tracing-otlp" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "clap", + "eyre", + "opentelemetry 0.31.0", + "opentelemetry-appender-tracing", + "opentelemetry-otlp 0.31.1", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk 0.31.0", + "tracing", + "tracing-opentelemetry 0.32.1", + "tracing-subscriber 0.3.23", + "url", +] + +[[package]] +name = "reth-transaction-pool" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "aquamarine", + "auto_impl", + "bitflags 2.11.1", + "futures-util", + "imbl", + "metrics", + "parking_lot", + "paste", + "pin-project", + "rand 0.9.4", + "reth-chain-state", + "reth-chainspec", + "reth-eth-wire-types", + "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", + "reth-execution-types", + "reth-fs-util", + "reth-metrics", + "reth-primitives-traits", + "reth-storage-api", + "reth-tasks", + "revm", + "revm-interpreter", + "revm-primitives", + "rustc-hash", + "schnellru", + "serde", + "serde_json", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-trie" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-eips 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-trie", + "auto_impl", + "itertools 0.14.0", + "metrics", + "parking_lot", + "reth-execution-errors", + "reth-metrics", + "reth-primitives-traits", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "reth-trie-sparse", + "revm-database", + "tracing", + "triehash", +] + +[[package]] +name = "reth-trie-common" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-consensus 2.0.5", + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "alloy-trie", + "arbitrary", + "arrayvec", + "bytes", + "derive_more", + "hash-db", + "itertools 0.14.0", + "nybbles", + "plain_hasher", + "rayon", + "reth-codecs", + "reth-primitives-traits", + "revm-database", + "serde", + "serde_with", +] + +[[package]] +name = "reth-trie-db" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "metrics", + "parking_lot", + "reth-db-api", + "reth-execution-errors", + "reth-metrics", + "reth-primitives-traits", + "reth-stages-types", + "reth-storage-api", + "reth-storage-errors", + "reth-trie", + "reth-trie-common", + "tracing", +] + +[[package]] +name = "reth-trie-parallel" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-eip7928", + "alloy-evm", + "alloy-primitives 1.6.0", + "alloy-rlp", + "crossbeam-channel", + "crossbeam-utils", + "derive_more", + "itertools 0.14.0", + "metrics", + "rayon", + "reth-execution-errors", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-storage-errors", + "reth-tasks", + "reth-trie", + "reth-trie-sparse", + "revm-state", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-trie-sparse" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?rev=81c026181e96ef33a823f3ef4d2a28940e9fa4fe#81c026181e96ef33a823f3ef4d2a28940e9fa4fe" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rlp", + "alloy-trie", + "either", + "metrics", + "rayon", + "reth-execution-errors", + "reth-metrics", + "reth-primitives-traits", + "reth-trie-common", + "serde", + "serde_json", + "slotmap", + "smallvec", + "tracing", +] + +[[package]] +name = "reth-zstd-compressors" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c12fafa33d2f420a9d39249a3e0357b1928d09429f30758b85280409092873b2" +dependencies = [ + "zstd", +] + +[[package]] +name = "revm" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91202d39dbe8e8d10e9e8f2b76c30da68ecd1d25be69ba6d853ad0d03a3a398a" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbb3a3d735efa94c91f2ef6bf20a35f99a77bc78f3e25bd758336901bdf9661" +dependencies = [ + "bitvec", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "16.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f68d928d8b228e0faeb1c6ed75c4fde7d124f1ddf9119b67e7a0ad4041237d" +dependencies = [ + "bitvec", + "cfg-if", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-context-interface" +version = "17.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3758e6167c4ba7a59a689c519a047edaefcd4c37d74f279b93ed87bc8aece4" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database" +version = "13.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c281a1f11d3bcb8c0bba1199ed6bcb001d1aeb3d4fb366819e14f88723989a4e" +dependencies = [ + "alloy-eips 1.8.3", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database-interface" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89efb9832a4e3742bb4ded5f7fe5bf905e8860e69427d4dfec153484fc6d304" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "revm-handler" +version = "18.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783e903d6922b7f5f9a940d1bb229530502d2924b1aed9d5ca5a94ebf065d460" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-inspector" +version = "19.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8216ad58422090d0daa9eb430e0a081f7ad07e7fd30681dee71f8420c99624e0" +dependencies = [ + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", + "serde", + "serde_json", +] + +[[package]] +name = "revm-inspectors" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731b682530a732ef9c189ef831589128e2ce34d4a306c956322ae2dffe009715" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rpc-types-eth 2.0.5", + "alloy-rpc-types-trace", + "alloy-sol-types 1.6.0", + "anstyle", + "boa_engine", + "boa_gc", + "colorchoice", + "revm", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "revm-interpreter" +version = "35.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ece9f41b69658c15d748288a4dbdfc06a63f3ce93d983af440de3f1631dce6a" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-precompile" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a346a8cc6c8c39bd65306641c692191299c0a7b63d38810e39e8fe9b92378660" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "aws-lc-rs", + "blst", + "c-kzg", + "cfg-if", + "k256", + "p256", + "revm-context-interface", + "revm-primitives", + "ripemd", + "secp256k1 0.31.1", + "sha2", +] + +[[package]] +name = "revm-primitives" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c99bda77d9661521ba0b4bc04558c6692074f01e65dd420fa3b893033d9b8a2" +dependencies = [ + "alloy-primitives 1.6.0", + "num_enum", + "once_cell", + "serde", +] + +[[package]] +name = "revm-state" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32490ed687dba31c3c882beb8c20408bdd30ef96690d8f145b0ee9a87040bfe" +dependencies = [ + "alloy-eip7928", + "bitflags 2.11.1", + "revm-bytecode", + "revm-primitives", + "serde", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "ringbuffer" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b0b88a509053cbfd535726dcaaceee631313cef981266119527a1d110f6d2b" + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + +[[package]] +name = "rlimit" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f35ee2729c56bb610f6dba436bf78135f728b7373bdffae2ec815b2d3eb98cc3" +dependencies = [ + "libc", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "roaring" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" +dependencies = [ + "bytemuck", + "byteorder", +] + +[[package]] +name = "rocksdb" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb7af00d2b17dbd07d82c0063e25411959748ff03e8d4f96134c2ff41fce34f" +dependencies = [ + "libc", + "librocksdb-sys", +] + +[[package]] +name = "rolling-file" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8395b4f860856b740f20a296ea2cd4d823e81a2658cf05ef61be22916026a906" +dependencies = [ + "chrono", +] + +[[package]] +name = "rollup-boost" +version = "0.1.0" +dependencies = [ + "alloy-primitives 1.6.0", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth 2.0.5", + "alloy-serde 2.0.5", + "backoff", + "bytes", + "clap", + "dashmap", + "dotenvy", + "eyre", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "jsonrpsee 0.25.1", + "lru", + "metrics", + "metrics-derive", + "metrics-exporter-prometheus 0.16.2", + "metrics-util 0.19.1", + "moka", + "op-alloy-rpc-types-engine", + "opentelemetry 0.28.0", + "opentelemetry-otlp 0.28.0", + "opentelemetry_sdk 0.28.0", + "parking_lot", + "paste", + "reth-optimism-payload-builder", + "reth-rpc-layer", + "rustls", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.26.2", + "tokio-util", + "tower 0.5.3", + "tower-http", + "tracing", + "tracing-opentelemetry 0.29.0", + "tracing-subscriber 0.3.23", + "url", + "uuid", + "vergen", + "vergen-git2", +] + +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + +[[package]] +name = "rtnetlink" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b960d5d873a75b5be9761b1e73b146f52dddcd27bac75263f40fba686d4d7b5" +dependencies = [ + "futures-channel", + "futures-util", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "ruint" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" +dependencies = [ + "alloy-rlp", + "arbitrary", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.6", + "rand 0.9.4", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +dependencies = [ + "rand 0.8.6", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.28", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.21.1", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs 0.26.11", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "ryu-js" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schnellru" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" +dependencies = [ + "ahash", + "cfg-if", + "hashbrown 0.13.2", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.6", + "secp256k1-sys 0.10.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.4", + "secp256k1-sys 0.11.0", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.3", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", +] + +[[package]] +name = "sha3-asm" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3f15d4e239ebe08413eed880e0f9b5af4b40ee0472543320efa91d488e96a7" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +dependencies = [ + "dirs", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "small_btree" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba60d2df92ba73864714808ca68c059734853e6ab722b40e1cf543ebb3a057a" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "arbitrary", + "serde", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek", + "rand_core 0.6.4", + "ring", + "rustc_version 0.4.1", + "sha2", + "subtle", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "soketto" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures", + "http", + "httparse", + "log", + "rand 0.8.6", + "sha1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.117", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4e6eed052a117409a1a744c8bda9c3ea6934597cf7419f791cb7d590871c4c" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "syn-solidity" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec005042c7d952febc1a3ef5b0f6674e9054aa836877a31c90b20e25b3d31744" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sysinfo" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.61.3", +] + +[[package]] +name = "sysinfo" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.62.2", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tag_ptr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e973b34477b7823833469eb0f5a3a60370fef7a453e02d751b59180d0a5a05" + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tar-no-std" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715f9a4586706a61c571cb5ee1c3ac2bbb2cf63e15bce772307b95befef5f5ee" +dependencies = [ + "bitflags 2.11.1", + "log", + "num-traits", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tdx" +version = "0.2.0" +source = "git+https://github.com/automata-network/tdx-attestation-sdk.git?branch=main#e9b478beaeebd90350b57b78847e0e827a3e6df0" +dependencies = [ + "alloy", + "anyhow", + "base64-url", + "cbindgen", + "chrono", + "clap", + "coco-provider", + "dcap-rs", + "hex", + "rand 0.8.6", + "serde", + "tokio", + "ureq", + "x509-parser 0.15.1", +] + +[[package]] +name = "tdx-quote-provider" +version = "0.1.0" +dependencies = [ + "axum 0.8.9", + "clap", + "dotenvy", + "eyre", + "hex", + "metrics", + "metrics-derive", + "metrics-exporter-prometheus 0.17.2", + "reqwest 0.12.28", + "serde", + "serde_json", + "tdx", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "testcontainers" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bb7577dca13ad86a78e8271ef5d322f37229ec83b8d98da6d996c588a1ddb1" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes", + "docker_credential", + "either", + "etcetera", + "futures", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "url", +] + +[[package]] +name = "thin-vec" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread-priority" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210811179577da3d54eb69ab0b50490ee40491a25d95b8c6011ba40771cb721" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.61.3", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tikv-jemalloc-ctl" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "661f1f6a57b3a36dc9174a2c10f19513b4866816e13425d3e418b11cc37bc24c" +dependencies = [ + "libc", + "paste", + "tikv-jemalloc-sys", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.26.2", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.28.0", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.7.9", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.5", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost 0.14.3", + "tonic 0.14.6", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.6", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "hdrhistogram", + "indexmap 2.14.0", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "async-compression", + "base64 0.22.1", + "bitflags 2.11.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.18", + "time", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-journald" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3a81ed245bfb62592b1e2bc153e77656d94ee6a0497683a65a12ccaf2438d0" +dependencies = [ + "libc", + "tracing-core", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-logfmt" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a250055a3518b5efba928a18ffac8d32d42ea607a9affff4532144cd5b2e378e" +dependencies = [ + "time", + "tracing", + "tracing-core", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721f2d2569dce9f3dfbbddee5906941e953bfcdf736a62da3377f5751650cc36" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry 0.28.0", + "opentelemetry_sdk 0.28.0", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber 0.3.23", + "web-time", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +dependencies = [ + "js-sys", + "opentelemetry 0.31.0", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber 0.3.23", + "web-time", +] + +[[package]] +name = "tracing-samply" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c175f7ecc002b6ef04776a39f440503e4e788790ddbdbfac8259b7a069526334" +dependencies = [ + "cfg-if", + "itoa", + "libc", + "mach2 0.5.0", + "memmap2", + "smallvec", + "tracing-core", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tree_hash" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" +dependencies = [ + "alloy-primitives 1.6.0", + "ethereum_hashing", + "ethereum_ssz", + "smallvec", + "typenum", +] + +[[package]] +name = "tree_hash_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "triehash" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +dependencies = [ + "hash-db", + "rlp", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tss-esapi" +version = "7.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f10b25a84912b894d0e6d68f4a3771c923e9c44ddaaed7920cde92ed28aa84e" +dependencies = [ + "bitfield", + "enumflags2", + "getrandom 0.4.2", + "hostname-validator", + "log", + "mbox", + "num-derive", + "num-traits", + "oid", + "picky-asn1", + "picky-asn1-x509", + "regex", + "serde", + "tss-esapi-sys", + "zeroize", +] + +[[package]] +name = "tss-esapi-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f972672926a3d3d18ecc04524720e4d20b7d1664a3fb73dbf7d4274196dbd9" +dependencies = [ + "pkg-config", + "target-lexicon", +] + +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.9.4", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.4", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-truncate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +dependencies = [ + "itertools 0.14.0", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" +dependencies = [ + "anyhow", + "cargo_metadata", + "derive_builder", + "regex", + "rustversion", + "time", + "vergen-lib", +] + +[[package]] +name = "vergen-git2" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51ab55ddf1188c8d679f349775362b0fa9e90bd7a4ac69838b2a087623f0d57" +dependencies = [ + "anyhow", + "derive_builder", + "git2", + "rustversion", + "time", + "vergen", + "vergen-lib", +] + +[[package]] +name = "vergen-lib" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver 1.0.28", +] + +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + +[[package]] +name = "web-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.7", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.7", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.1", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver 1.0.28", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.1", + "send_wrapper 0.6.0", + "thiserror 2.0.18", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs 0.5.2", + "data-encoding", + "der-parser 8.2.0", + "lazy_static", + "nom", + "oid-registry 0.6.1", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs 0.7.2", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + +[[package]] +name = "xsum" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0637d3a5566a82fa5214bae89087bc8c9fb94cd8e8a3c07feb691bb8d9c632db" + +[[package]] +name = "yamux" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.8.6", + "static_assertions", +] + +[[package]] +name = "yamux" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1991f6690292030e31b0144d73f5e8368936c58e45e7068254f7138b23b00672" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.9.4", + "static_assertions", + "web-time", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/rust/op-rbuilder/Cargo.toml b/rust/op-rbuilder/Cargo.toml new file mode 100644 index 00000000000..ef0049d1d8d --- /dev/null +++ b/rust/op-rbuilder/Cargo.toml @@ -0,0 +1,215 @@ +[workspace.package] +version = "0.2.13" +edition = "2024" +rust-version = "1.88" +license = "MIT OR Apache-2.0" +homepage = "https://github.com/flashbots/op-rbuilder" +repository = "https://github.com/flashbots/op-rbuilder" +exclude = [".github/"] + +[workspace] +members = [ "crates/op-rbuilder", "crates/p2p", "crates/tdx-quote-provider"] +default-members = ["crates/op-rbuilder"] +resolver = "2" + +# Speed up compilation time for dev builds by reducing emitted debug info. +# NOTE: Debuggers may provide less useful information with this setting. +# Uncomment this section if you're using a debugger. +[profile.dev] +# https://davidlattimore.github.io/posts/2024/02/04/speeding-up-the-rust-edit-build-run-cycle.html +debug = "line-tables-only" +split-debuginfo = "unpacked" + +# Speed up tests. +[profile.dev.package] +proptest.opt-level = 3 +rand_chacha.opt-level = 3 +rand_xorshift.opt-level = 3 +unarray.opt-level = 3 + +# Like release, but with full debug symbols. Useful for e.g. `perf`. +[profile.debug-fast] +inherits = "release" +debug = true +lto = "thin" + +[profile.maxperf] +inherits = "release" +lto = "fat" +codegen-units = 1 +incremental = false + +[workspace.lints.rust] +unreachable_pub = "deny" + +[workspace.lints.clippy] +unused_async = "warn" + +[workspace.dependencies] +reth = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-chain-state = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-cli = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-cli-util = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-db = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-db-common = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-errors = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-trie = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-trie-parallel = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-core = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-primitives = { package = "reth-ethereum-primitives", git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-primitives-traits = { version = "0.3.1", default-features = false } +reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe", features = [ + "test-utils", +] } + +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-evm = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-exex = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-metrics = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-trie-db = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-revm = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-payload-util = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-testing-utils = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-tracing-otlp = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } +reth-ipc = { git = "https://github.com/paradigmxyz/reth", rev = "81c026181e96ef33a823f3ef4d2a28940e9fa4fe" } + +# reth optimism +reth-optimism-primitives = { path = "../op-reth/crates/primitives/", default-features = false } +reth-optimism-consensus = { path = "../op-reth/crates/consensus/", default-features = false } +reth-optimism-cli = { path = "../op-reth/crates/cli/", default-features = false } +reth-optimism-forks = { path = "../op-reth/crates/hardforks/", default-features = false } +reth-optimism-evm = { path = "../op-reth/crates/evm/", default-features = false } +reth-optimism-node = { path = "../op-reth/crates/node/" } +reth-optimism-payload-builder = { path = "../op-reth/crates/payload/" } +reth-optimism-chainspec = { path = "../op-reth/crates/chainspec/", default-features = false } +reth-optimism-txpool = { path = "../op-reth/crates/txpool/" } +reth-optimism-rpc = { path = "../op-reth/crates/rpc/", features = [ + "client", +] } + +revm = { version = "38.0.0", default-features = false } +revm-inspectors = "0.39.0" +op-revm = { version = "20.0.0", path = "../op-revm/", default-features = false } +revm-bytecode = { version = "10.0.0", default-features = false } +revm-database = { version = "13.0.0", default-features = false } +revm-state = { version = "11.0.0", default-features = false } +revm-primitives = { version = "23.0.0", default-features = false } +revm-interpreter = { version = "35.0.0", default-features = false } +revm-inspector = { version = "14.1.0", default-features = false } +revm-context = { version = "16.0.0", default-features = false } +revm-context-interface = { version = "17.0.0", default-features = false } +revm-database-interface = { version = "11.0.0", default-features = false } + +ethereum_ssz_derive = "0.9.0" +ethereum_ssz = "0.9.0" + +alloy-primitives = { version = "1.5.6", default-features = false, features = [ + "map-foldhash", +] } +alloy-rlp = { version = "0.3.13", default-features = false, features = [ + "core-net", +] } +alloy-chains = { version = "0.2.33", default-features = false } +alloy-contract = { version = "2.0.4", default-features = false } +alloy-evm = { version = "0.34.0", default-features = false } +alloy-provider = { version = "2.0.4", features = [ + "reqwest", + "debug-api", +], default-features = false } +alloy-pubsub = { version = "2.0.4", default-features = false } +alloy-eips = { version = "2.0.4", default-features = false } +alloy-rpc-types = { version = "2.0.4", features = [ + "eth", +], default-features = false } +alloy-json-rpc = { version = "2.0.4", default-features = false } +alloy-transport-http = { version = "2.0.4", features = [ + "reqwest-rustls-tls", +], default-features = false } +alloy-network = { version = "2.0.4", default-features = false } +alloy-network-primitives = { version = "2.0.4", default-features = false } +alloy-transport = { version = "2.0.4" } +alloy-node-bindings = { version = "2.0.4", default-features = false } +alloy-consensus = { version = "2.0.4", default-features = false } +alloy-serde = { version = "2.0.4", default-features = false } +alloy-sol-types = { version = "1.5.6", default-features = false, features = ["json"] } +alloy-rpc-types-beacon = { version = "2.0.4", default-features = false } +alloy-rpc-types-engine = { version = "2.0.4", default-features = false } +alloy-rpc-types-eth = { version = "2.0.4", default-features = false } +alloy-signer-local = { version = "2.0.4", default-features = false } +alloy-rpc-client = { version = "2.0.4", default-features = false } +alloy-genesis = { version = "2.0.4", default-features = false } +alloy-trie = { version = "0.9.4", default-features = false } + +alloy-hardforks = { version = "0.4.7", default-features = false } + +# rollup-boost +rollup-boost = { path = "../rollup-boost/crates/rollup-boost" } + +# optimism +alloy-op-evm = { version = "0.32.0", path = "../alloy-op-evm/", default-features = false } +alloy-op-hardforks = { version = "0.5.0", path = "../alloy-op-hardforks/", default-features = false } +op-alloy-rpc-types = { version = "2.0.0", path = "../op-alloy/crates/rpc-types", default-features = false } +op-alloy-rpc-types-engine = { version = "2.0.0", path = "../op-alloy/crates/rpc-types-engine", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.22.0", default-features = false } +op-alloy-network = { version = "2.0.0", path = "../op-alloy/crates/network", default-features = false } +op-alloy-consensus = { version = "2.0.0", path = "../op-alloy/crates/consensus", default-features = false } +op-alloy-flz = { version = "0.13.1", default-features = false } + +async-trait = { version = "0.1.83" } +clap = { version = "4.4.3", features = ["derive", "env", "string"] } +clap_builder = { version = "4.5.19" } +thiserror = { version = "1.0.64" } +eyre = { version = "0.6.12" } +jsonrpsee = { version = "0.26.0" } +jsonrpsee-types = { version = "0.26.0" } +jsonrpsee-core = { version = "0.26.0" } +parking_lot = { version = "0.12.3" } +tokio = { version = "1.44.2", default-features = false } +auto_impl = { version = "1.2.0" } +reqwest = { version = "0.12.8" } +serde = { version = "1.0.210" } +serde_json = { version = "1.0.128" } +serde_with = { version = "3.8.1" } +secp256k1 = { version = "0.30" } +derive_more = { version = "2" } +either = { version = "1.15.0", default-features = false } +tokio-stream = "0.1.11" +tokio-util = { version = "0.7.4", features = ["codec"] } +url = "2.5.2" +warp = "0.3.7" +flate2 = "1.0.37" +prometheus = "0.13.4" +ctor = "0.2" +dashmap = "6.1" +hex = "0.4" +futures = "0.3" +futures-util = "0.3.31" + +lazy_static = "1.4.0" +tikv-jemallocator = { version = "0.6" } +tracing = "0.1.37" +metrics = { version = "0.24.1" } +ahash = "0.8.6" +time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } +vergen = "9.1.0" +vergen-git2 = "9.1.0" +opentelemetry = { version = "0.31", features = ["trace"] } diff --git a/rust/op-rbuilder/Dockerfile b/rust/op-rbuilder/Dockerfile new file mode 100644 index 00000000000..5742c7bc438 --- /dev/null +++ b/rust/op-rbuilder/Dockerfile @@ -0,0 +1,145 @@ +# +# Base container (with sccache and cargo-chef) +# +# - https://github.com/mozilla/sccache +# - https://github.com/LukeMathWalker/cargo-chef +# +# Based on https://depot.dev/blog/rust-dockerfile-best-practices +# +ARG FEATURES +ARG RBUILDER_BIN="op-rbuilder" + +FROM rust:1.94 AS base +ARG TARGETPLATFORM + +RUN apt-get update \ + && apt-get install -y clang libclang-dev libtss2-dev + +RUN rustup component add clippy rustfmt + + +RUN set -eux; \ + case "$TARGETPLATFORM" in \ + "linux/amd64") ARCH_TAG="x86_64-unknown-linux-musl" ;; \ + "linux/arm64") ARCH_TAG="aarch64-unknown-linux-musl" ;; \ + *) \ + echo "Unsupported platform: $TARGETPLATFORM"; \ + exit 1 \ + ;; \ + esac; \ + wget -O /tmp/sccache.tar.gz \ + "https://github.com/mozilla/sccache/releases/download/v0.8.2/sccache-v0.8.2-${ARCH_TAG}.tar.gz"; \ + tar -xf /tmp/sccache.tar.gz -C /tmp; \ + mv /tmp/sccache-v0.8.2-${ARCH_TAG}/sccache /usr/local/bin/sccache; \ + chmod +x /usr/local/bin/sccache; \ + rm -rf /tmp/sccache.tar.gz /tmp/sccache-v0.8.2-${ARCH_TAG} + +RUN cargo install cargo-chef --version ^0.1 --locked + + +ENV CARGO_HOME=/usr/local/cargo +ENV RUSTC_WRAPPER=sccache +ENV SCCACHE_DIR=/sccache + +# +# op-rbuilder/Cargo.toml has path dependencies to sibling crates inside the +# parent rust/ workspace (../op-reth/..., ../op-revm/, ../op-alloy/..., +# ../alloy-op-evm/, ../alloy-op-hardforks/, ../rollup-boost/...). We pull +# those siblings in as a named BuildKit context so `docker buildx build` +# from inside rust/op-rbuilder/ still resolves them. From rust/op-rbuilder +# the parent dir `..` is the rust/ workspace, so: +# +# docker buildx build \ +# --build-context monorepo-rust=.. \ +# ... +# +# Inside the image the final layout is flat under /workspace/: +# /workspace/op-rbuilder/ <-- the build context (".") +# /workspace/op-reth/ <-- from --build-context monorepo-rust +# /workspace/op-revm/ <-- from --build-context monorepo-rust +# /workspace/op-alloy/ <-- from --build-context monorepo-rust +# /workspace/alloy-op-evm/ <-- from --build-context monorepo-rust +# /workspace/rollup-boost/ <-- from --build-context monorepo-rust +# ... +# +# which matches `..//...` in op-rbuilder/Cargo.toml. + +# +# Planner container (running "cargo chef prepare") +# +FROM base AS planner +WORKDIR /workspace/op-rbuilder +COPY --from=monorepo-rust . /workspace/ +COPY . . +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \ + cargo chef prepare --recipe-path recipe.json + +# +# Builder container (running "cargo chef cook" and "cargo build --release") +# +FROM base AS builder +WORKDIR /workspace/op-rbuilder +COPY --from=monorepo-rust . /workspace/ +COPY --from=planner /workspace/op-rbuilder/recipe.json recipe.json +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \ + cargo chef cook --release --recipe-path recipe.json +COPY . . + + +FROM builder AS rbuilder +ARG RBUILDER_BIN +ARG FEATURES +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=$SCCACHE_DIR,sharing=locked \ + cargo build --release --features="$FEATURES" --package=${RBUILDER_BIN} + +# +# Reproducible builder container (deterministic source-date-epoch, no caching, no incremental builds) +# +FROM base AS rbuilder-reproducible +ARG RBUILDER_BIN +ARG FEATURES +ARG TARGETPLATFORM + +ARG CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-static-libgcc -C link-arg=-Wl,--build-id=none -C metadata=target --remap-path-prefix=/workspace=." + +ARG CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-static-libgcc -C link-arg=-Wl,--build-id=none -C metadata=target --remap-path-prefix=/workspace=." + +WORKDIR /workspace/op-rbuilder +COPY --from=monorepo-rust . /workspace/ +COPY . . +RUN case "$TARGETPLATFORM" in \ + "linux/amd64") ARCH_TAG="x86_64-unknown-linux-gnu" ;; \ + "linux/arm64") ARCH_TAG="aarch64-unknown-linux-gnu" ;; \ + *) \ + echo "Unsupported platform: $TARGETPLATFORM"; \ + exit 1 \ + ;; \ + esac; \ + SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) \ + RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-static-libgcc -C link-arg=-Wl,--build-id=none -C metadata='' --remap-path-prefix=/workspace=." \ + CARGO_INCREMENTAL=0 \ + LC_ALL=C \ + TZ=UTC \ + CFLAGS="-D__TIME__=\"\" -D__DATE__=\"\"" \ + CXXFLAGS="-D__TIME__=\"\" -D__DATE__=\"\"" \ + cargo build --release --locked --features="$FEATURES" --package=${RBUILDER_BIN} --target "${ARCH_TAG}" + +# Runtime container for rbuilder +FROM gcr.io/distroless/cc-debian13:latest@sha256:8b5d1db6d2253036a53cb8362d3e3fa82a7caf84c247772c46a023166c64e977 AS rbuilder-runtime +ARG RBUILDER_BIN +WORKDIR /app +COPY --from=rbuilder /workspace/op-rbuilder/target/release/${RBUILDER_BIN} /app/rbuilder +ENTRYPOINT ["/app/rbuilder"] + +# Reproducible runtime container for rbuilder +FROM gcr.io/distroless/cc-debian13:latest@sha256:8b5d1db6d2253036a53cb8362d3e3fa82a7caf84c247772c46a023166c64e977 AS rbuilder-reproducible-runtime +ARG RBUILDER_BIN +WORKDIR /app +COPY --from=rbuilder-reproducible /workspace/op-rbuilder/target/*/release/${RBUILDER_BIN} /app/rbuilder +ENTRYPOINT ["/app/rbuilder"] diff --git a/rust/op-rbuilder/LICENSE-APACHE b/rust/op-rbuilder/LICENSE-APACHE new file mode 100644 index 00000000000..129cf663340 --- /dev/null +++ b/rust/op-rbuilder/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023-2024 rbuilder Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/rust/op-rbuilder/LICENSE-MIT b/rust/op-rbuilder/LICENSE-MIT new file mode 100644 index 00000000000..8b32a4d09e7 --- /dev/null +++ b/rust/op-rbuilder/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-2024 rbuilder Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/rust/op-rbuilder/Makefile b/rust/op-rbuilder/Makefile new file mode 100644 index 00000000000..5e9219de01e --- /dev/null +++ b/rust/op-rbuilder/Makefile @@ -0,0 +1,130 @@ +# Heavily inspired by Lighthouse: https://github.com/sigp/lighthouse/blob/stable/Makefile +# and Reth: https://github.com/paradigmxyz/reth/blob/main/Makefile +.DEFAULT_GOAL := help + +GIT_VER ?= $(shell git describe --tags --always --dirty="-dev") +GIT_TAG ?= $(shell git describe --tags --abbrev=0) + +FEATURES ?= + +# Environment variables for reproducible builds +# Initialize RUSTFLAGS +RUST_BUILD_FLAGS = +# Enable static linking to ensure reproducibility across builds +RUST_BUILD_FLAGS += --C target-feature=+crt-static +# Set the linker to use static libgcc to ensure reproducibility across builds +RUST_BUILD_FLAGS += -C link-arg=-static-libgcc +# Remove build ID from the binary to ensure reproducibility across builds +RUST_BUILD_FLAGS += -C link-arg=-Wl,--build-id=none +# Remove metadata hash from symbol names to ensure reproducible builds +RUST_BUILD_FLAGS += -C metadata='' +# Set timestamp from last git commit for reproducible builds +SOURCE_DATE ?= $(shell git log -1 --pretty=%ct) +# Disable incremental compilation to avoid non-deterministic artifacts +CARGO_INCREMENTAL_VAL = 0 +# Set C locale for consistent string handling and sorting +LOCALE_VAL = C +# Set UTC timezone for consistent time handling across builds +TZ_VAL = UTC + +##@ Help + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +.PHONY: v +v: ## Show the current version + @echo "Version: ${GIT_VER}" + +##@ Build + +.PHONY: clean +clean: ## Clean up + cargo clean + +.PHONY: build +build: ## Build (debug version) + cargo build --features "$(FEATURES)" + +.PHONY: op-rbuilder +op-rbuilder: ## Build op-rbuilder (debug version) + cargo build -p op-rbuilder --bin op-rbuilder --features "$(FEATURES)" + +.PHONY: build-reproducible +build-reproducible: ## Build the reth binary into `target` directory with reproducible builds + SOURCE_DATE_EPOCH=$(SOURCE_DATE) \ + RUSTFLAGS="${RUST_BUILD_FLAGS} --remap-path-prefix $$(pwd)=." \ + CARGO_INCREMENTAL=${CARGO_INCREMENTAL_VAL} \ + LC_ALL=${LOCALE_VAL} \ + TZ=${TZ_VAL} \ + cargo build -p op-rbuilder --bin op-rbuilder --features "$(FEATURES)" --profile "release" --locked --features "$(FEATURES)" --target x86_64-unknown-linux-gnu + +.PHONY: tdx-quote-provider +tdx-quote-provider: ## Build tdx-quote-provider (debug version) + cargo build -p tdx-quote-provider --bin tdx-quote-provider --features "$(FEATURES)" + +.PHONY: tester +tester: ## Build tester (debug version) + cargo build -p op-rbuilder --bin tester --features "testing,$(FEATURES)" + +.PHONY: docker-image-rbuilder +docker-image-rbuilder: ## Build a rbuilder Docker image + # The Dockerfile references the parent rust/ workspace via the named + # `monorepo-rust` BuildKit context to resolve sibling path deps + # (../op-reth/..., ../rollup-boost/..., etc.). Requires buildx. + docker buildx build --load \ + --platform linux/amd64 \ + --target rbuilder-runtime \ + --build-arg FEATURES="$(FEATURES)" \ + --build-context monorepo-rust=.. \ + -t rbuilder \ + . + +##@ Dev + +.PHONY: lint +lint: ## Run the linters + # Pinned to the monorepo's mise.toml nightly so local `make lint` + # reproduces CI exactly (plain `+nightly` drifts as new lints land). + cargo +nightly-2026-02-20 fmt -- --check + cargo +nightly-2026-02-20 clippy --all-features -- -D warnings + +.PHONY: test +test: ## Run the tests for rbuilder and op-rbuilder + cargo test --verbose --features "$(FEATURES)" + cargo test -p op-rbuilder --verbose --features "$(FEATURES)" + cargo test -p tdx-quote-provider --verbose --features "$(FEATURES)" + +.PHONY: lt +lt: lint test ## Run "lint" and "test" + +.PHONY: fmt +fmt: ## Format the code + cargo +nightly fmt + cargo +nightly clippy --all-features --fix --allow-staged --allow-dirty + cargo +nightly fix --allow-staged --allow-dirty + +.PHONY: bench +bench: ## Run benchmarks + cargo bench --features "$(FEATURES)" --workspace + +.PHONY: bench-report-open +bench-report-open: ## Open last benchmark report in the browser + open "target/criterion/report/index.html" + +.PHONY: bench-in-ci +bench-in-ci: ## Run benchmarks in CI (adds timestamp and version to the report, customizes Criterion output) + ./scripts/ci/benchmark-in-ci.sh + +.PHONY: bench-clean +bench-clean: ## Remove previous benchmark data + rm -rf target/criterion + rm -rf target/benchmark-in-ci + rm -rf target/benchmark-html-dev + +.PHONY: bench-prettify +bench-prettify: ## Prettifies the latest Criterion report + rm -rf target/benchmark-html-dev + ./scripts/ci/criterion-prettify-report.sh target/criterion target/benchmark-html-dev + @echo "\nopen target/benchmark-html-dev/report/index.html" diff --git a/rust/op-rbuilder/README.md b/rust/op-rbuilder/README.md new file mode 100644 index 00000000000..2b3092433ea --- /dev/null +++ b/rust/op-rbuilder/README.md @@ -0,0 +1,214 @@ +# op-rbuilder + +[![CI status](https://github.com/flashbots/op-rbuilder/actions/workflows/op_rbuilder_checks.yaml/badge.svg)](https://github.com/flashbots/op-rbuilder/actions) + +`op-rbuilder` is a Rust-based block builder designed to build blocks for the Optimism stack. + +## Running op-rbuilder + +To run op-rbuilder with the op-stack, you need: + +- CL node to sync the op-rbuilder with the canonical chain +- Sequencer with the [rollup-boost](https://github.com/flashbots/rollup-boost) setup + +To run the op-rbuilder, run: + +```bash +cargo run -p op-rbuilder --bin op-rbuilder -- node \ + --chain /path/to/chain-config.json \ + --http \ + --authrpc.port 9551 \ + --authrpc.jwtsecret /path/to/jwt.hex +``` + +To build the op-rbuilder, run: + +```bash +cargo build -p op-rbuilder --bin op-rbuilder +``` + +### Flashblocks + +To run op-rbuilder with flashblocks: + +```bash +cargo run -p op-rbuilder --bin op-rbuilder -- node \ + --chain /path/to/chain-config.json \ + --http \ + --authrpc.port 9551 \ + --authrpc.jwtsecret /path/to/jwt.hex \ + --flashblocks.enabled \ + --flashblocks.port 1111 \ # port to bind ws that provides flashblocks + --flashblocks.addr 127.0.0.1 # address to bind the ws that provides flashblocks +``` + +#### Flashblocks Number Contract + +To enable builder tranctions to the [flashblocks number contract](https://github.com/Uniswap/flashblocks_number_contract) for contracts to integrate with flashblocks onchain, specify the address in the CLI args: + +```bash +cargo run -p op-rbuilder --bin op-rbuilder -- node \ + --chain /path/to/chain-config.json \ + --http \ + --authrpc.port 9551 \ + --authrpc.jwtsecret /path/to/jwt.hex \ + --flashblocks.enabled \ + --flashblocks.number-contract-address 0xFlashblocksNumberAddress +``` + +This will increment the flashblock number before the start of every flashblock and replace the builder tx at the end of the block. + +### Flashtestations + +To run op-rbuilder with flashtestations: + +```bash +cargo run -p op-rbuilder --bin op-rbuilder -- node \ + --chain /path/to/chain-config.json \ + --http \ + --authrpc.port 9551 \ + --authrpc.jwtsecret /path/to/jwt.hex \ + --flashtestations.enabled \ + --flashtestations.rpc-url your-rpc-url \ # rpc to submit the attestation transaction to + --flashtestations.funding-amount 0.01 \ # amount in ETH to fund the TEE generated key + --flashtestations.funding-key secret-key \ # funding key for the TEE key + --flashtestations.registry-address 0xFlashtestationsRegistryAddress \ + --flashtestations.builder-policy-address 0xBuilderPolicyAddress +``` + +Note that `--rollup.builder-secret-key` must be set and funded in order for the flashtestations key to be funded and submit the attestation on-chain. + +## Observability + +To verify whether a builder block has landed on-chain, you can add the `--rollup.builder-secret-key` flag or `BUILDER_SECRET_KEY` environment variable. +This will add an additional transaction to the end of the block from the builder key. The transaction will have `Block Number: {}` in the input data as a transfer to the zero address. Ensure that the key has sufficient balance to pay for the transaction at the end of the block. + +To enable metrics, set the `--metrics` flag like in [reth](https://reth.rs/run/monitoring) which will expose reth metrics in addition to op-rbuilder metrics. op-rbuilder exposes on-chain metrics via [reth execution extensions](https://reth.rs/exex/overview) such as the number of blocks landed and builder balance. Note that the accuracy of the on-chain metrics will be dependent on the sync status of the builder node. There are also additional block building metrics such as: + +- Block building latency +- State root calculation latency +- Transaction fetch latency +- Transaction simulation latency +- Number of transactions included in the built block + +To see the full list of op-rbuilder metrics, see [`src/metrics.rs`](./crates/op-rbuilder/src/metrics.rs). + +Default `debug` level trace logs can be found at: + +- `~/.cache/op-rbuilder/logs` on Linux +- `~/Library/Caches/op-rbuilder/logs` on macOS +- `%localAppData%/op-rbuilder/logs` on Windows + +## Integration Testing + +op-rbuilder has an integration test framework that runs the builder against mock engine api payloads and ensures that the builder produces valid blocks. + +You can run the tests using the command + +```bash +just run-tests +``` + +or the following sequence: + +```bash +# Ensure you have op-reth installed in your path, +# you can download it with the command below and move it to a location in your path +./scripts/ci/download-op-reth.sh + +# Generate a genesis file +cargo run -p op-rbuilder --features="testing" --bin tester -- genesis --output genesis.json + +# Build the op-rbuilder binary +cargo build -p op-rbuilder --bin op-rbuilder + +# Run the integration tests +cargo test --package op-rbuilder --lib +``` + +## Local Devnet + +1. Clone [flashbots/builder-playground](https://github.com/flashbots/builder-playground) and start an OPStack chain. + +```bash +git clone https://github.com/flashbots/builder-playground.git +cd builder-playground +go run main.go cook opstack --external-builder http://host.docker.internal:4444 +``` + +2. Remove any existing `reth` chain db. The following are the default data directories: + +- Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` +- Windows: `{FOLDERID_RoamingAppData}/reth/` +- macOS: `$HOME/Library/Application Support/reth/` + +3. Run `op-rbuilder` in the `rbuilder` repo on port 4444: + +```bash +cargo run -p op-rbuilder --bin op-rbuilder -- node \ + --chain $HOME/.playground/devnet/l2-genesis.json \ + --http --http.port 2222 \ + --authrpc.addr 0.0.0.0 --authrpc.port 4444 --authrpc.jwtsecret $HOME/.playground/devnet/jwtsecret \ + --port 30333 --disable-discovery \ + --metrics 127.0.0.1:9011 \ + --rollup.builder-secret-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --trusted-peers enode://79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8@127.0.0.1:30304 +``` + +4. Run `contender`: + +```bash +cargo run -- spam --tps 10 -r http://localhost:2222 --optimism --min-balance 0.14 +``` + +And you should start to see blocks being built and landed on-chain with `contender` transactions. + +## Builder playground + +You can quickly spin up an op-stack devnet using [builder-playground](https://github.com/flashbots/builder-playground). The quickest workflow to get op-stack running against your local `op-rbuilder` instance is: + +1. Check out the builder playground repo + +``` +git clone git@github.com:flashbots/builder-playground.git +``` + +2. In the builder-playgound spin up an l2 opstack setup specifying that it should use an external block builder: + +``` +go run main.go cook opstack --external-builder http://host.docker.internal:4444 +``` + +3. Run rbuilder in playground mode: + +``` +cargo run --bin op-rbuilder -- node --builder.playground +``` + +You could also run it using: + +``` +just run-playground +``` + +This will automatically try to detect all settings and ports from the currently running playground. Sometimes you might need to clean up the builder-playground state between runs. This can be done using: + +``` +rm -rf ~/.local/share/reth +sudo rm -rf ~/.playground +``` + +## Running GitHub actions locally + +To verify that CI will allow your PR to be merged before sending it please make sure that our GitHub `checks.yaml` action passes locall by calling: + +``` +act -W .github/workflows/checks.yaml +``` + +More instructions on installing and configuring `act` can be found on [their website](https://nektosact.com). + +### Known issues + +- Running actions locally require a Github Token. You can generate one by following instructions on [Github Docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens). After generating a token you will need to pass it to `act` either through the command line using `-s GITHUB_TOKEN=` or by adding it to the `~/.config/act/actrc` file. +- You might get an error about missing or incompatible `warp-ubuntu-latest-x64-32x` platform. This can be mitigated by adding `-P warp-ubuntu-latest-x64-32x=ghcr.io/catthehacker/ubuntu:act-latest` on the command line when calling `act` or appending this flag to `~/.config/act/actrc` diff --git a/rust/op-rbuilder/SECURITY.md b/rust/op-rbuilder/SECURITY.md new file mode 100644 index 00000000000..ce4f80021ad --- /dev/null +++ b/rust/op-rbuilder/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Contact: security@flashbots.net \ No newline at end of file diff --git a/rust/op-rbuilder/clippy.toml b/rust/op-rbuilder/clippy.toml new file mode 100644 index 00000000000..f3322b5fd24 --- /dev/null +++ b/rust/op-rbuilder/clippy.toml @@ -0,0 +1 @@ +msrv = "1.88" diff --git a/rust/op-rbuilder/crates/op-rbuilder/Cargo.toml b/rust/op-rbuilder/crates/op-rbuilder/Cargo.toml new file mode 100644 index 00000000000..46778960ec5 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/Cargo.toml @@ -0,0 +1,222 @@ +[package] +name = "op-rbuilder" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +default-run = "op-rbuilder" + +[lints] +workspace = true + +[dependencies] +p2p = { path = "../p2p" } + +reth.workspace = true +reth-optimism-node.workspace = true +reth-optimism-cli.workspace = true +reth-optimism-chainspec.workspace = true +reth-optimism-payload-builder.workspace = true +reth-optimism-evm.workspace = true +reth-optimism-consensus.workspace = true +reth-optimism-primitives.workspace = true +reth-optimism-txpool.workspace = true +reth-cli.workspace = true +reth-cli-commands.workspace = true +reth-cli-util.workspace = true +reth-db.workspace = true +reth-payload-primitives.workspace = true +reth-evm.workspace = true +reth-exex.workspace = true +reth-chainspec.workspace = true +reth-primitives.workspace = true +reth-primitives-traits.workspace = true +reth-node-api.workspace = true +reth-rpc-engine-api.workspace = true +reth-node-core.workspace = true +reth-basic-payload-builder.workspace = true +reth-payload-builder.workspace = true +reth-node-ethereum.workspace = true +reth-chain-state.workspace = true +reth-execution-types.workspace = true +reth-metrics.workspace = true +reth-provider.workspace = true +reth-revm.workspace = true +reth-trie.workspace = true +reth-rpc-layer.workspace = true +reth-payload-builder-primitives.workspace = true +reth-payload-util.workspace = true +reth-transaction-pool.workspace = true +reth-network-peers.workspace = true +reth-testing-utils.workspace = true +reth-optimism-forks.workspace = true +reth-node-builder.workspace = true +reth-storage-api.workspace = true +reth-rpc-api.workspace = true +reth-rpc-eth-types.workspace = true +reth-optimism-rpc.workspace = true +reth-tasks.workspace = true +reth-tracing-otlp = { workspace = true, optional = true } + +alloy-primitives.workspace = true +alloy-consensus.workspace = true +alloy-contract.workspace = true +alloy-eips.workspace = true +alloy-evm.workspace = true +alloy-rpc-types-beacon.workspace = true +alloy-rpc-types-engine.workspace = true +alloy-transport-http.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-rpc-client.workspace = true +alloy-transport.workspace = true +alloy-network.workspace = true +alloy-provider.workspace = true +alloy-serde.workspace = true +alloy-json-rpc.workspace = true +alloy-signer-local.workspace = true +alloy-sol-types.workspace = true + +# op +alloy-op-evm.workspace = true +op-alloy-consensus.workspace = true +op-alloy-rpc-types-engine.workspace = true +op-alloy-rpc-types.workspace = true +op-alloy-network.workspace = true +op-alloy-flz.workspace = true + +revm.workspace = true +op-revm.workspace = true + +tracing.workspace = true +eyre.workspace = true +serde_with.workspace = true +serde.workspace = true +secp256k1.workspace = true +tokio.workspace = true +jsonrpsee = { workspace = true } +jsonrpsee-core = { workspace = true } +jsonrpsee-types = { workspace = true } +async-trait = { workspace = true } +clap_builder = { workspace = true } +clap.workspace = true +derive_more.workspace = true +either.workspace = true +metrics.workspace = true +serde_json.workspace = true +tokio-util.workspace = true +thiserror.workspace = true +parking_lot.workspace = true +url.workspace = true +anyhow = "1" +opentelemetry = { workspace = true, optional = true } +dashmap.workspace = true +hex = { workspace = true } +futures = { workspace = true } +futures-util = { workspace = true } + +tower = "0.5" +time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } +chrono = "0.4" +uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } +tokio-tungstenite = "0.26.2" +rand = "0.9.0" +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } +shellexpand = "3.1" +serde_yaml = { version = "0.9" } +moka = "0.12" +http = "1.0" +sha3 = "0.10" +reqwest = "0.12.23" +k256 = "0.13.4" + +rollup-boost.workspace = true + +nanoid = { version = "0.4", optional = true } +reth-ipc = { workspace = true, optional = true } +tar = { version = "0.4", optional = true } +ctor = { version = "0.4.2", optional = true } +rlimit = { version = "0.10", optional = true } +macros = { path = "src/tests/framework/macros", optional = true } +hyper = { version = "1.7.0", features = ["http1"], optional = true } +hyper-util = { version = "0.1.11", optional = true } +http-body-util = { version = "0.1.3", optional = true } +testcontainers = "0.24.0" +dirs-next = "2.0.0" + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { version = "0.6", optional = true } + +[build-dependencies] +vergen = { workspace = true, features = ["build", "cargo", "emit_and_set"] } +vergen-git2.workspace = true + +[dev-dependencies] +alloy-provider = { workspace = true, default-features = true, features = [ + "txpool-api", +] } +tempfile = "3.8" +macros = { path = "src/tests/framework/macros" } +nanoid = { version = "0.4" } +reth-ipc = { workspace = true } +reth-node-builder = { workspace = true, features = ["test-utils"] } +ctor = "0.4.2" +rlimit = { version = "0.10" } +hyper = { version = "1.7.0", features = ["http1"] } +hyper-util = { version = "0.1.11" } +http-body-util = { version = "0.1.3" } + +[features] +default = ["jemalloc", "docker-tests"] + +# Gates the smoke tests under `src/tests/smoke.rs` that cross-validate +# against an external op-reth via testcontainers (needs the host's +# `/var/run/docker.sock`). Disable with `--no-default-features` in +# environments (e.g. the CircleCI Docker executor) where the socket +# is not available. +docker-tests = [] + +jemalloc = [ + "dep:tikv-jemallocator", + "reth-cli-util/jemalloc", + "reth-optimism-cli/jemalloc", +] +jemalloc-prof = [ + "jemalloc", + "tikv-jemallocator?/profiling", + "reth/jemalloc-prof", + "reth-cli-util/jemalloc-prof", +] + +min-error-logs = ["tracing/release_max_level_error"] +min-warn-logs = ["tracing/release_max_level_warn"] +min-info-logs = ["tracing/release_max_level_info"] +min-debug-logs = ["tracing/release_max_level_debug"] +min-trace-logs = ["tracing/release_max_level_trace"] + +testing = [ + "nanoid", + "reth-ipc", + "reth-node-builder/test-utils", + "ctor", + "macros", + "rlimit", + "hyper", + "hyper-util", + "http-body-util", +] + +interop = [] + +telemetry = ["reth-tracing-otlp", "opentelemetry"] + +custom-engine-api = [] + +[[bin]] +name = "op-rbuilder" +path = "src/bin/op-rbuilder/main.rs" + +[[bin]] +name = "tester" +required-features = ["testing"] diff --git a/rust/op-rbuilder/crates/op-rbuilder/build.rs b/rust/op-rbuilder/crates/op-rbuilder/build.rs new file mode 100644 index 00000000000..97acc1fefce --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/build.rs @@ -0,0 +1,142 @@ +// Taken from reth [https://github.com/paradigmxyz/reth/blob/main/crates/node/core/build.rs] +// The MIT License (MIT) +// +// Copyright (c) 2022-2025 Reth Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +use std::{env, error::Error}; +use vergen::{BuildBuilder, CargoBuilder, Emitter}; +use vergen_git2::Git2Builder; + +fn main() -> Result<(), Box> { + let mut emitter = Emitter::default(); + + let build_builder = BuildBuilder::default().build_timestamp(true).build()?; + + emitter.add_instructions(&build_builder)?; + + let cargo_builder = CargoBuilder::default() + .features(true) + .target_triple(true) + .build()?; + + emitter.add_instructions(&cargo_builder)?; + + let git_builder = Git2Builder::default() + .describe(false, true, None) + .dirty(true) + .sha(false) + .commit_author_name(true) + .commit_author_email(true) + .commit_message(true) + .build()?; + + emitter.add_instructions(&git_builder)?; + + emitter.emit_and_set()?; + let sha = env::var("VERGEN_GIT_SHA")?; + let sha_short = &sha[0..7]; + + // Set short SHA + println!("cargo:rustc-env=VERGEN_GIT_SHA_SHORT={}", &sha[..8]); + + let author_name = env::var("VERGEN_GIT_COMMIT_AUTHOR_NAME")?; + let author_email = env::var("VERGEN_GIT_COMMIT_AUTHOR_EMAIL")?; + let author_full = format!("{author_name} <{author_email}>"); + + // Set author full name + println!("cargo:rustc-env=VERGEN_GIT_COMMIT_AUTHOR={author_full}"); + + let is_dirty = env::var("VERGEN_GIT_DIRTY")? == "true"; + // > git describe --always --tags + // if not on a tag: v0.2.0-beta.3-82-g1939939b + // if on a tag: v0.2.0-beta.3 + let not_on_tag = env::var("VERGEN_GIT_DESCRIBE")?.ends_with(&format!("-g{sha_short}")); + let version_suffix = if is_dirty || not_on_tag { "-dev" } else { "" }; + println!("cargo:rustc-env=OP_RBUILDER_VERSION_SUFFIX={version_suffix}"); + + // Set the build profile + let out_dir = env::var("OUT_DIR").unwrap(); + let profile = out_dir.rsplit(std::path::MAIN_SEPARATOR).nth(3).unwrap(); + println!("cargo:rustc-env=OP_RBUILDER_BUILD_PROFILE={profile}"); + + // Set formatted version strings + let pkg_version = env!("CARGO_PKG_VERSION"); + + // The short version information for op-rbuilder. + // - The latest version from Cargo.toml + // - The short SHA of the latest commit. + // Example: 0.1.0 (defa64b2) + println!( + "cargo:rustc-env=OP_RBUILDER_SHORT_VERSION={pkg_version}{version_suffix} ({sha_short})" + ); + + // LONG_VERSION + // The long version information for op-rbuilder. + // + // - The latest version from Cargo.toml + version suffix (if any) + // - The full SHA of the latest commit + // - The build datetime + // - The build features + // - The build profile + // - The latest commit message and author + // + // Example: + // + // ```text + // Version: 0.1.0 + // Commit SHA: defa64b2 + // Build Timestamp: 2023-05-19T01:47:19.815651705Z + // Build Features: jemalloc + // Build Profile: maxperf + // Latest Commit: 'message' by John Doe + // ``` + println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_0=Version: {pkg_version}{version_suffix}"); + println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_1=Commit SHA: {sha}"); + println!( + "cargo:rustc-env=OP_RBUILDER_LONG_VERSION_2=Build Timestamp: {}", + env::var("VERGEN_BUILD_TIMESTAMP")? + ); + println!( + "cargo:rustc-env=OP_RBUILDER_LONG_VERSION_3=Build Features: {}", + env::var("VERGEN_CARGO_FEATURES")? + ); + println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_4=Build Profile: {profile}"); + println!( + "cargo:rustc-env=OP_RBUILDER_LONG_VERSION_5=Latest Commit: '{}' by {}", + env::var("VERGEN_GIT_COMMIT_MESSAGE")?.trim_end(), + author_full + ); + + // The version information for op-rbuilder formatted for P2P (devp2p). + // - The latest version from Cargo.toml + // - The target triple + // + // Example: op-rbuilder/v0.1.0-alpha.1-428a6dc2f/aarch64-apple-darwin + println!( + "cargo:rustc-env=OP_RBUILDER_P2P_CLIENT_VERSION={}", + format_args!( + "op-rbuilder/v{pkg_version}-{sha_short}/{}", + env::var("VERGEN_CARGO_TARGET_TRIPLE")? + ) + ); + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/args/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/args/mod.rs new file mode 100644 index 00000000000..587cce8a77e --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/args/mod.rs @@ -0,0 +1,102 @@ +use crate::{ + builders::BuilderMode, + metrics::{LONG_VERSION, SHORT_VERSION}, +}; +use clap_builder::{CommandFactory, FromArgMatches}; +pub use op::{FlashblocksArgs, OpRbuilderArgs, TelemetryArgs}; +use playground::PlaygroundOptions; +use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; + +mod op; +mod playground; + +/// This trait is used to extend Reth's CLI with additional functionality that +/// are specific to the OP builder, such as populating default values for CLI arguments +/// when running in the playground mode or checking the builder mode. +/// +pub trait CliExt { + /// Populates the default values for the CLI arguments when the user specifies + /// the `--builder.playground` flag. + fn populate_defaults(self) -> Self; + + /// Returns the builder mode that the node is started with. + fn builder_mode(&self) -> BuilderMode; + + /// Returns the Cli instance with the parsed command line arguments + /// and defaults populated if applicable. + fn parsed() -> Self; + + /// Returns the Cli instance with the parsed command line arguments + /// and replaces version, name, author, and about + fn set_version() -> Self; +} + +pub type Cli = reth_optimism_cli::Cli; + +impl CliExt for Cli { + /// Checks if the node is started with the `--builder.playground` flag, + /// and if so, populates the default values for the CLI arguments from the + /// playground configuration. + /// + /// The `--builder.playground` flag is used to populate the CLI arguments with + /// default values for running the builder against the playground environment. + /// + /// The values are populated from the default directory of the playground + /// configuration, which is `$HOME/.playground/devnet/` by default. + /// + /// Any manually specified CLI arguments by the user will override the defaults. + fn populate_defaults(self) -> Self { + let Commands::Node(ref node_command) = self.command else { + // playground defaults are only relevant if running the node commands. + return self; + }; + + let Some(ref playground_dir) = node_command.ext.playground else { + // not running in playground mode. + return self; + }; + + let options = PlaygroundOptions::new(playground_dir).unwrap_or_else(|e| exit(e)); + + options.apply(self) + } + + fn parsed() -> Self { + Cli::set_version().populate_defaults() + } + + /// Returns the type of builder implementation that the node is started with. + /// Currently supports `Standard` and `Flashblocks` modes. + fn builder_mode(&self) -> BuilderMode { + if let Commands::Node(ref node_command) = self.command + && node_command.ext.flashblocks.enabled + { + return BuilderMode::Flashblocks; + } + BuilderMode::Standard + } + + /// Parses commands and overrides versions + fn set_version() -> Self { + let logs_dir = dirs_next::cache_dir() + .map(|root| root.join("op-rbuilder/logs")) + .unwrap() + .into_os_string(); + let matches = Cli::command() + .version(SHORT_VERSION) + .long_version(LONG_VERSION) + .about("Block builder designed for the Optimism stack") + .author("Flashbots") + .name("op-rbuilder") + .mut_arg("log_file_directory", |arg| arg.default_value(logs_dir)) + .get_matches(); + Cli::from_arg_matches(&matches).expect("Parsing args") + } +} + +/// Following clap's convention, a failure to parse the command line arguments +/// will result in terminating the program with a non-zero exit code. +fn exit(error: eyre::Report) -> ! { + eprintln!("{error}"); + std::process::exit(-1); +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/args/op.rs b/rust/op-rbuilder/crates/op-rbuilder/src/args/op.rs new file mode 100644 index 00000000000..4e3d0308e31 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/args/op.rs @@ -0,0 +1,255 @@ +//! Additional Node command arguments. +//! +//! Copied from OptimismNode to allow easy extension. + +//! clap [Args](clap::Args) for optimism rollup configuration + +use crate::{ + flashtestations::args::FlashtestationsArgs, gas_limiter::args::GasLimiterArgs, + tx_signer::Signer, +}; +use alloy_primitives::Address; +use anyhow::{Result, anyhow}; +use clap::Parser; +use reth_optimism_cli::commands::Commands; +use reth_optimism_node::args::RollupArgs; +use std::path::PathBuf; + +/// Parameters for rollup configuration +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] +#[command(next_help_heading = "Rollup")] +pub struct OpRbuilderArgs { + /// Rollup configuration + #[command(flatten)] + pub rollup_args: RollupArgs, + /// Builder secret key for signing last transaction in block + #[arg(long = "rollup.builder-secret-key", env = "BUILDER_SECRET_KEY")] + pub builder_signer: Option, + + /// chain block time in milliseconds + #[arg( + long = "rollup.chain-block-time", + default_value = "1000", + env = "CHAIN_BLOCK_TIME" + )] + pub chain_block_time: u64, + + /// max gas a transaction can use + #[arg(long = "builder.max_gas_per_txn")] + pub max_gas_per_txn: Option, + + /// Signals whether to log pool transaction events + #[arg(long = "builder.log-pool-transactions", default_value = "false")] + pub log_pool_transactions: bool, + + /// How much time extra to wait for the block building job to complete and not get garbage collected + #[arg(long = "builder.extra-block-deadline-secs", default_value = "20")] + pub extra_block_deadline_secs: u64, + /// Whether to enable revert protection by default + #[arg(long = "builder.enable-revert-protection", default_value = "false")] + pub enable_revert_protection: bool, + + /// Path to builder playgorund to automatically start up the node connected to it + #[arg( + long = "builder.playground", + num_args = 0..=1, + default_missing_value = "$HOME/.playground/devnet/", + value_parser = expand_path, + env = "PLAYGROUND_DIR", + )] + pub playground: Option, + #[command(flatten)] + pub flashblocks: FlashblocksArgs, + #[command(flatten)] + pub telemetry: TelemetryArgs, + #[command(flatten)] + pub flashtestations: FlashtestationsArgs, + #[command(flatten)] + pub gas_limiter: GasLimiterArgs, +} + +impl Default for OpRbuilderArgs { + fn default() -> Self { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext + } +} + +fn expand_path(s: &str) -> Result { + shellexpand::full(s) + .map_err(|e| anyhow!("expansion error for `{s}`: {e}"))? + .into_owned() + .parse() + .map_err(|e| anyhow!("invalid path after expansion: {e}")) +} + +/// Parameters for Flashblocks configuration +/// The names in the struct are prefixed with `flashblocks` to avoid conflicts +/// with the standard block building configuration since these args are flattened +/// into the main `OpRbuilderArgs` struct with the other rollup/node args. +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] +pub struct FlashblocksArgs { + /// When set to true, the builder will build flashblocks + /// and will build standard blocks at the chain block time. + /// + /// The default value will change in the future once the flashblocks + /// feature is stable. + #[arg( + long = "flashblocks.enabled", + default_value = "false", + env = "ENABLE_FLASHBLOCKS" + )] + pub enabled: bool, + + /// The port that we bind to for the websocket server that provides flashblocks + #[arg( + long = "flashblocks.port", + env = "FLASHBLOCKS_WS_PORT", + default_value = "1111" + )] + pub flashblocks_port: u16, + + /// The address that we bind to for the websocket server that provides flashblocks + #[arg( + long = "flashblocks.addr", + env = "FLASHBLOCKS_WS_ADDR", + default_value = "127.0.0.1" + )] + pub flashblocks_addr: String, + + /// flashblock block time in milliseconds + #[arg( + long = "flashblocks.block-time", + default_value = "250", + env = "FLASHBLOCK_BLOCK_TIME" + )] + pub flashblocks_block_time: u64, + + /// Builder would always thry to produce fixed number of flashblocks without regard to time of + /// FCU arrival. + /// In cases of late FCU it could lead to partially filled blocks. + #[arg( + long = "flashblocks.fixed", + default_value = "false", + env = "FLASHBLOCK_FIXED" + )] + pub flashblocks_fixed: bool, + + /// Time by which blocks would be completed earlier in milliseconds. + /// + /// This time used to account for latencies, this time would be deducted from total block + /// building time before calculating number of fbs. + #[arg( + long = "flashblocks.leeway-time", + default_value = "75", + env = "FLASHBLOCK_LEEWAY_TIME" + )] + pub flashblocks_leeway_time: u64, + + /// Whether to disable state root calculation for each flashblock + #[arg( + long = "flashblocks.disable-state-root", + default_value = "false", + env = "FLASHBLOCKS_DISABLE_STATE_ROOT" + )] + pub flashblocks_disable_state_root: bool, + + /// Flashblocks number contract address + /// + /// This is the address of the contract that will be used to increment the flashblock number. + /// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx. + #[arg( + long = "flashblocks.number-contract-address", + env = "FLASHBLOCK_NUMBER_CONTRACT_ADDRESS" + )] + pub flashblocks_number_contract_address: Option
, + + /// Use permit signatures if flashtestations is enabled with the flashtestation key + /// to increment the flashblocks number + #[arg( + long = "flashblocks.number-contract-use-permit", + env = "FLASHBLOCK_NUMBER_CONTRACT_USE_PERMIT", + default_value = "false" + )] + pub flashblocks_number_contract_use_permit: bool, + + /// Flashblocks p2p configuration + #[command(flatten)] + pub p2p: FlashblocksP2pArgs, +} + +impl Default for FlashblocksArgs { + fn default() -> Self { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext.flashblocks + } +} + +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] +pub struct FlashblocksP2pArgs { + /// Enable libp2p networking for flashblock propagation + #[arg( + long = "flashblocks.p2p_enabled", + env = "FLASHBLOCK_P2P_ENABLED", + default_value = "false" + )] + pub p2p_enabled: bool, + + /// Port for the flashblocks p2p node + #[arg( + long = "flashblocks.p2p_port", + env = "FLASHBLOCK_P2P_PORT", + default_value = "9009" + )] + pub p2p_port: u16, + + /// Path to the file containing a hex-encoded libp2p private key. + /// If the file does not exist, a new key will be generated. + #[arg( + long = "flashblocks.p2p_private_key_file", + env = "FLASHBLOCK_P2P_PRIVATE_KEY_FILE" + )] + pub p2p_private_key_file: Option, + + /// Comma-separated list of multiaddrs of known Flashblocks peers + /// Example: "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ,/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ" + #[arg( + long = "flashblocks.p2p_known_peers", + env = "FLASHBLOCK_P2P_KNOWN_PEERS" + )] + pub p2p_known_peers: Option, + + /// Maximum number of peers for the flashblocks p2p node + #[arg( + long = "flashblocks.p2p_max_peer_count", + env = "FLASHBLOCK_P2P_MAX_PEER_COUNT", + default_value = "50" + )] + pub p2p_max_peer_count: u32, +} + +/// Parameters for telemetry configuration +#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +pub struct TelemetryArgs { + /// OpenTelemetry endpoint for traces + #[arg(long = "telemetry.otlp-endpoint", env = "OTEL_EXPORTER_OTLP_ENDPOINT")] + pub otlp_endpoint: Option, + + /// OpenTelemetry headers for authentication + #[arg(long = "telemetry.otlp-headers", env = "OTEL_EXPORTER_OTLP_HEADERS")] + pub otlp_headers: Option, + + /// Inverted sampling frequency in blocks. 1 - each block, 100 - every 100th block. + #[arg( + long = "telemetry.sampling-ratio", + env = "SAMPLING_RATIO", + default_value = "100" + )] + pub sampling_ratio: u64, +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/args/playground.rs b/rust/op-rbuilder/crates/op-rbuilder/src/args/playground.rs new file mode 100644 index 00000000000..3ef24c1d015 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/args/playground.rs @@ -0,0 +1,341 @@ +//! Automatic builder playground configuration. +//! +//! This module is used mostly for testing purposes. It allows op-rbuilder to +//! automatically configure itself to run against a running op-builder playground. +//! +//! To setup the playground, checkout this repository: +//! +//! https://github.com/flashbots/builder-playground +//! +//! Then run the following command: +//! +//! go run main.go cook opstack --external-builder http://host.docker.internal:4444 +//! +//! Wait until the playground is up and running, then run the following command to build +//! op-rbuilder with flashblocks support: +//! +//! cargo build --bin op-rbuilder -p op-rbuilder +//! +//! then run the following command to start op-rbuilder against the playground: +//! +//! target/debug/op-rbuilder node --builder.playground +//! +//! This will automatically try to detect the playground configuration and apply +//! it to the op-rbuilder startup settings. +//! +//! Optionally you can specify the `--builder.playground` flag with a different +//! directory to use. This is useful for testing against different playground +//! configurations. + +use alloy_primitives::hex; +use clap::{CommandFactory, parser::ValueSource}; +use core::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + ops::Range, + time::Duration, +}; +use eyre::{Result, eyre}; +use reth_cli::chainspec::ChainSpecParser; +use reth_network_peers::TrustedPeer; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; +use secp256k1::SecretKey; +use serde_json::Value; +use std::{ + fs::read_to_string, + path::{Path, PathBuf}, + sync::Arc, +}; +use url::{Host, Url}; + +use super::Cli; + +pub(super) struct PlaygroundOptions { + /// Sets node.chain in NodeCommand + pub chain: Arc, + + /// Sets node.rpc.http_port in NodeCommand + pub http_port: u16, + + /// Sets node.rpc.auth_addr in NodeCommand + pub authrpc_addr: IpAddr, + + /// Sets node.rpc.authrpc_port in NodeCommand + pub authrpc_port: u16, + + /// Sets node.rpc.authrpc_jwtsecret in NodeCommand + pub authrpc_jwtsecret: PathBuf, + + /// Sets node.network.port in NodeCommand + pub port: u16, + + /// Sets the node.network.trusted_peers in NodeCommand + pub trusted_peer: TrustedPeer, + + /// Sets node.ext.flashblock_block_time in NodeCommand + pub chain_block_time: Duration, +} + +impl PlaygroundOptions { + /// Creates a new `PlaygroundOptions` instance with the specified genesis path. + pub(super) fn new(path: &Path) -> Result { + if !path.exists() { + return Err(eyre!( + "Playground data directory {} does not exist", + path.display() + )); + } + + let chain = OpChainSpecParser::parse(&existing_path(path, "l2-genesis.json")?)?; + + let authrpc_addr = Ipv4Addr::UNSPECIFIED.into(); + let http_port = pick_preferred_port(2222, 3000..9999); + let authrpc_jwtsecret = existing_path(path, "jwtsecret")?.into(); + let port = pick_preferred_port(30333, 30000..65535); + let chain_block_time = extract_chain_block_time(path)?; + let authrpc_port = extract_authrpc_port(path)?; + let trusted_peer = TrustedPeer::from_secret_key( + Host::Ipv4(Ipv4Addr::LOCALHOST), + extract_trusted_peer_port(path)?, + &extract_deterministic_p2p_key(path)?, + ); + + Ok(Self { + chain, + http_port, + authrpc_addr, + authrpc_port, + authrpc_jwtsecret, + port, + trusted_peer, + chain_block_time, + }) + } + + pub(super) fn apply(self, cli: Cli) -> Cli { + let mut cli = cli; + let Commands::Node(ref mut node) = cli.command else { + // playground defaults are only relevant if running the node commands. + return cli; + }; + + if !node.network.trusted_peers.contains(&self.trusted_peer) { + node.network.trusted_peers.push(self.trusted_peer); + } + + // populate the command line arguments only if they were never set by the user + // either via the command line or an environment variable. Otherwise, don't + // override the user provided values. + let matches = Cli::command().get_matches(); + let matches = matches + .subcommand_matches("node") + .expect("validated that we are in the node command"); + + if matches.value_source("chain").is_default() { + node.chain = self.chain; + } + + if matches.value_source("http").is_default() { + node.rpc.http = true; + } + + if matches.value_source("http_port").is_default() { + node.rpc.http_port = self.http_port; + } + + if matches.value_source("port").is_default() { + node.network.port = self.port; + } + + if matches.value_source("auth_addr").is_default() { + node.rpc.auth_addr = self.authrpc_addr; + } + + if matches.value_source("auth_port").is_default() { + node.rpc.auth_port = self.authrpc_port; + } + + if matches.value_source("auth_jwtsecret").is_default() { + node.rpc.auth_jwtsecret = Some(self.authrpc_jwtsecret); + } + + if matches.value_source("disable_discovery").is_default() { + node.network.discovery.disable_discovery = true; + } + + if matches.value_source("chain_block_time").is_default() { + node.ext.chain_block_time = self.chain_block_time.as_millis() as u64; + } + + cli + } +} + +fn existing_path(base: &Path, relative: &str) -> Result { + let path = base.join(relative); + if path.exists() { + Ok(path.to_string_lossy().to_string()) + } else { + Err(eyre::eyre!( + "Expected file {relative} is not present in playground directory {}", + base.display() + )) + } +} + +fn pick_random_port(range: Range) -> u16 { + use rand::Rng; + let mut rng = rand::rng(); + + loop { + // Generate a random port number between 30000 and 65535 + let port = rng.random_range(range.clone()); + + // Check if the port is already in use + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + if std::net::TcpListener::bind(socket).is_ok() { + return port; + } + } +} + +fn pick_preferred_port(preferred: u16, fallback_range: Range) -> u16 { + if !is_port_free(preferred) { + return pick_random_port(fallback_range); + } + + preferred +} + +fn is_port_free(port: u16) -> bool { + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + std::net::TcpListener::bind(socket).is_ok() +} + +fn extract_chain_block_time(basepath: &Path) -> Result { + Ok(Duration::from_secs( + serde_json::from_str::(&read_to_string(existing_path(basepath, "rollup.json")?)?)? + .get("block_time") + .and_then(|v| v.as_u64()) + .ok_or_else(|| eyre::eyre!("Missing chain_block_time in rollup.json"))?, + )) +} + +fn extract_deterministic_p2p_key(basepath: &Path) -> Result { + let key = read_to_string(existing_path(basepath, "enode-key-1.txt")?)?; + Ok(SecretKey::from_slice( + &hex::decode(key).map_err(|e| eyre!("Invalid hex key: {e}"))?, + )?) +} + +fn read_docker_compose(basepath: &Path) -> Result { + // this happens only once on statup so it's fine to read the file multiple times + let docker_compose = read_to_string(existing_path(basepath, "docker-compose.yaml")?)?; + serde_yaml::from_str(&docker_compose).map_err(|e| eyre!("Invalid docker-compose file: {e}")) +} + +fn extract_service_command_flag(basepath: &Path, service: &str, flag: &str) -> Result { + let docker_compose = read_docker_compose(basepath)?; + let args = docker_compose["services"][service]["command"] + .as_sequence() + .ok_or(eyre!( + "docker-compose.yaml is missing command line arguments for {service}" + ))? + .iter() + .map(|s| { + s.as_str().ok_or_else(|| { + eyre!("docker-compose.yaml service command line argument is not a string") + }) + }) + .collect::>>()?; + + let index = args + .iter() + .position(|arg| *arg == flag) + .ok_or_else(|| eyre!("docker_compose: {flag} not found on {service} service"))?; + + let value = args + .get(index + 1) + .ok_or_else(|| eyre!("docker_compose: {flag} value not found"))?; + + Ok(value.to_string()) +} + +fn extract_authrpc_port(basepath: &Path) -> Result { + let builder_url = extract_service_command_flag(basepath, "rollup-boost", "--builder-url")?; + let url = Url::parse(&builder_url).map_err(|e| eyre!("Invalid builder-url: {e}"))?; + url.port().ok_or_else(|| eyre!("missing builder-url port")) +} + +fn extract_trusted_peer_port(basepath: &Path) -> Result { + let docker_compose = read_docker_compose(basepath)?; + + // first we need to find the internal port of the op-geth service from the docker-compose.yaml + // command line arguments used to start the op-geth service + + let Some(opgeth_args) = docker_compose["services"]["op-geth"]["command"][1].as_str() else { + return Err(eyre!( + "docker-compose.yaml is missing command line arguments for op-geth" + )); + }; + + let opgeth_args = opgeth_args.split_whitespace().collect::>(); + let port_param_position = opgeth_args + .iter() + .position(|arg| *arg == "--port") + .ok_or_else(|| eyre!("docker_compose: --port param not found on op-geth service"))?; + + let port_value = opgeth_args + .get(port_param_position + 1) + .ok_or_else(|| eyre!("docker_compose: --port value not found"))?; + + let port_value = port_value + .parse::() + .map_err(|e| eyre!("Invalid port value: {e}"))?; + + // now we need to find the external port of the op-geth service from the docker-compose.yaml + // ports mapping used to start the op-geth service + let Some(opgeth_ports) = docker_compose["services"]["op-geth"]["ports"].as_sequence() else { + return Err(eyre!( + "docker-compose.yaml is missing ports mapping for op-geth" + )); + }; + let ports_mapping = opgeth_ports + .iter() + .map(|s| { + s.as_str().ok_or_else(|| { + eyre!("docker-compose.yaml service ports mapping in op-geth is not a string") + }) + }) + .collect::>>()?; + + // port mappings is in the format [..., "127.0.0.1:30304:30303", ...] + // we need to find the mapping that contains the port value we found earlier + // and extract the external port from it + let port_mapping = ports_mapping + .iter() + .find(|mapping| mapping.contains(&format!(":{port_value}"))) + .ok_or_else(|| { + eyre!("docker_compose: external port mapping not found for {port_value} for op-geth") + })?; + + // extract the external port from the mapping + let port_mapping = port_mapping + .split(':') + .nth(1) + .ok_or_else(|| eyre!("docker_compose: external port mapping for op-geth is not valid"))?; + + port_mapping + .parse::() + .map_err(|e| eyre!("Invalid external port mapping value for op-geth: {e}")) +} + +trait IsDefaultSource { + fn is_default(&self) -> bool; +} + +impl IsDefaultSource for Option { + fn is_default(&self) -> bool { + matches!(self, Some(ValueSource::DefaultValue)) || self.is_none() + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/bin/op-rbuilder/main.rs b/rust/op-rbuilder/crates/op-rbuilder/src/bin/op-rbuilder/main.rs new file mode 100644 index 00000000000..985b93c4e78 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/bin/op-rbuilder/main.rs @@ -0,0 +1,10 @@ +use op_rbuilder::launcher::launch; + +// Prefer jemalloc for performance reasons. +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +fn main() -> eyre::Result<()> { + launch() +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/bin/tester/main.rs b/rust/op-rbuilder/crates/op-rbuilder/src/bin/tester/main.rs new file mode 100644 index 00000000000..52c4812ef4d --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/bin/tester/main.rs @@ -0,0 +1,88 @@ +use alloy_primitives::Address; +use alloy_provider::{Identity, ProviderBuilder}; +use clap::Parser; +use op_alloy_network::Optimism; +use op_rbuilder::tests::*; + +/// CLI Commands +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Parser, Debug)] +enum Commands { + /// Generate genesis configuration + Genesis { + #[clap(long, help = "Output path for genesis files")] + output: Option, + }, + /// Run the testing system + Run { + #[clap(long, short, action)] + validation: bool, + + #[clap(long, short, action, default_value = "false")] + no_tx_pool: bool, + + #[clap(long, short, action, default_value = "1")] + block_time_secs: u64, + + #[clap(long, short, action)] + flashblocks_endpoint: Option, + + #[clap(long, action, default_value = "false")] + no_sleep: bool, + }, + /// Deposit funds to the system + Deposit { + #[clap(long, help = "Address to deposit funds to")] + address: Address, + #[clap(long, help = "Amount to deposit")] + amount: u128, + }, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::Genesis { output } => generate_genesis(output), + Commands::Run { validation, .. } => run_system(validation).await, + Commands::Deposit { address, amount } => { + let engine_api = EngineApi::with_http("http://localhost:4444"); + let provider = ProviderBuilder::::default() + .connect_http("http://localhost:2222".try_into()?); + let driver = ChainDriver::::remote(provider, engine_api); + let block_hash = driver.fund(address, amount).await?; + println!("Deposit transaction included in block: {block_hash}"); + Ok(()) + } + } +} + +#[allow(dead_code)] +pub async fn run_system(validation: bool) -> eyre::Result<()> { + println!("Validation node enabled: {validation}"); + + let engine_api = EngineApi::with_http("http://localhost:4444"); + let provider = ProviderBuilder::::default() + .connect_http("http://localhost:4444".try_into()?); + let mut driver = ChainDriver::::remote(provider, engine_api); + + if validation { + driver = driver + .with_validation_node(ExternalNode::reth().await?) + .await?; + } + + // Infinite loop generating blocks + loop { + println!("Generating new block..."); + let block = driver.build_new_block().await?; + println!("Generated block: {:?}", block.header.hash); + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/builder_tx.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/builder_tx.rs new file mode 100644 index 00000000000..66cf8bf45e6 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/builder_tx.rs @@ -0,0 +1,500 @@ +use alloy_consensus::{TxEip1559, transaction::Recovered}; +use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; +use alloy_evm::{ + Database, + block::{BlockExecutor as AlloyBlockExecutor, CommitChanges, TxResult}, + rpc::TryIntoTxEnv, +}; +use alloy_op_evm::{OpEvm, OpTx}; +use alloy_primitives::{ + Address, B256, Bytes, TxKind, U256, + map::{AddressMap, HashSet}, +}; +use alloy_sol_types::{ContractError, Revert, SolCall, SolError, SolInterface}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use op_alloy_rpc_types::OpTransactionRequest; +use op_revm::{OpHaltReason, OpTransactionError}; +use reth_evm::{ + ConfigureEvm, Evm, EvmError, + execute::{BlockBuilder, BlockExecutionError, BlockValidationError}, + precompiles::PrecompilesMap, +}; +use reth_node_api::PayloadBuilderError; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +use reth_provider::{ProviderError, StateProvider}; +use reth_revm::{State, database::StateProviderDatabase}; +use reth_rpc_api::eth::EthTxEnvError; +use revm::{ + DatabaseCommit, DatabaseRef, + context::result::{EVMError, ExecutionResult, ResultAndState}, + inspector::NoOpInspector, + state::Account, +}; +use tracing::{trace, warn}; + +use crate::{ + builders::context::{OpPayloadBuilderCtx, last_receipt_with_cumulative_gas}, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +#[derive(Debug, Default)] +pub struct SimulationSuccessResult { + pub gas_used: u64, + pub output: T::Return, + pub state_changes: AddressMap, +} + +#[derive(Debug, Clone)] +pub struct BuilderTransactionCtx { + pub gas_used: u64, + pub da_size: u64, + pub signed_tx: Recovered, + // whether the transaction should be a top of block or + // bottom of block transaction + pub is_top_of_block: bool, +} + +impl BuilderTransactionCtx { + pub fn set_top_of_block(mut self) -> Self { + self.is_top_of_block = true; + self + } + + pub fn set_bottom_of_block(mut self) -> Self { + self.is_top_of_block = false; + self + } +} + +#[derive(Debug, thiserror::Error)] +pub enum InvalidContractDataError { + #[error("did not find expected logs expected {0:?} but got {1:?}")] + InvalidLogs(Vec, Vec), + #[error("could not decode output from contract call")] + OutputAbiDecodeError, +} + +/// Possible error variants during construction of builder txs. +#[derive(Debug, thiserror::Error)] +pub enum BuilderTransactionError { + /// Builder account load fails to get builder nonce + #[error("failed to load account {0}")] + AccountLoadFailed(Address), + /// Signature signing fails + #[error("failed to sign transaction: {0}")] + SigningError(secp256k1::Error), + /// Invalid contract errors indicating the contract is incorrect + #[error("contract {0} may be incorrect, invalid contract data: {1}")] + InvalidContract(Address, InvalidContractDataError), + /// Transaction halted execution + #[error("transaction to {0} halted {1:?}")] + TransactionHalted(Address, OpHaltReason), + /// Transaction reverted + #[error("transaction to {0} reverted {1}")] + TransactionReverted(Address, Revert), + /// Invalid tx errors during evm execution. + #[error("invalid transaction error {0}")] + InvalidTransactionError(Box), + /// Unrecoverable error during evm execution. + #[error("evm execution error {0}")] + EvmExecutionError(Box), + /// Any other builder transaction errors. + #[error(transparent)] + Other(Box), +} + +impl From for BuilderTransactionError { + fn from(error: secp256k1::Error) -> Self { + BuilderTransactionError::SigningError(error) + } +} + +impl From> for BuilderTransactionError { + fn from(error: EVMError) -> Self { + BuilderTransactionError::EvmExecutionError(Box::new(error)) + } +} + +impl From for BuilderTransactionError { + fn from(error: EthTxEnvError) -> Self { + BuilderTransactionError::EvmExecutionError(Box::new(error)) + } +} + +impl From for PayloadBuilderError { + fn from(error: BuilderTransactionError) -> Self { + match error { + BuilderTransactionError::EvmExecutionError(e) => { + PayloadBuilderError::EvmExecutionError(e) + } + _ => PayloadBuilderError::other(error), + } + } +} + +impl BuilderTransactionError { + pub fn other(error: impl core::error::Error + Send + Sync + 'static) -> Self { + BuilderTransactionError::Other(Box::new(error)) + } + + pub fn msg(msg: impl core::fmt::Display) -> Self { + Self::Other(msg.to_string().into()) + } +} + +pub trait BuilderTransactions { + // Simulates and returns the signed builder transactions. The simulation modifies and commit + // changes to the db so call new_simulation_state to simulate on a new copy of the state + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + top_of_block: bool, + ) -> Result, BuilderTransactionError>; + + fn simulate_builder_txs_with_state_copy( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &State, + top_of_block: bool, + ) -> Result, BuilderTransactionError> { + let mut simulation_state = self.new_simulation_state(state_provider.clone(), db); + self.simulate_builder_txs( + state_provider, + info, + ctx, + &mut simulation_state, + top_of_block, + ) + } + + fn add_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + builder_ctx: &OpPayloadBuilderCtx, + builder: &mut Builder, + top_of_block: bool, + ) -> Result, BuilderTransactionError> + where + Builder: BlockBuilder, + Builder::Executor: AlloyBlockExecutor< + Transaction = OpTransactionSigned, + Receipt = OpReceipt, + Evm: alloy_evm::Evm>>, + >, + DB: Database, + { + let builder_txs = self.simulate_builder_txs_with_state_copy( + state_provider, + info, + builder_ctx, + builder.evm().db(), + top_of_block, + )?; + + let mut invalid = HashSet::new(); + + for builder_tx in builder_txs.iter() { + if builder_tx.is_top_of_block != top_of_block { + // don't commit tx if the builder tx is not being added in the intended + // position in the block + continue; + } + if invalid.contains(&builder_tx.signed_tx.signer()) { + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); + continue; + } + + let mut gas_used = 0; + let committed = match builder.execute_transaction_with_commit_condition( + builder_tx.signed_tx.clone(), + |result| { + gas_used = result.result().result.tx_gas_used(); + if result.result().result.is_success() { + CommitChanges::Yes + } else { + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted"); + invalid.insert(builder_tx.signed_tx.signer()); + CommitChanges::No + } + }, + ) { + Ok(committed) => committed, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + if error.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %error, ?builder_tx.signed_tx, "skipping nonce too low builder transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %error, ?builder_tx.signed_tx, "skipping invalid builder transaction and its descendants"); + invalid.insert(builder_tx.signed_tx.signer()); + } + + continue; + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + }; + + if committed.is_none() { + continue; + } + + info.cumulative_gas_used += gas_used; + info.cumulative_da_bytes_used += builder_tx.da_size; + info.receipts.push( + last_receipt_with_cumulative_gas(builder.executor(), info.cumulative_gas_used) + .expect("executor must record a receipt for committed tx"), + ); + + // Append sender and transaction to the respective lists + info.executed_senders.push(builder_tx.signed_tx.signer()); + info.executed_transactions + .push(builder_tx.signed_tx.clone().into_inner()); + } + + Ok(builder_txs) + } + + // Creates a copy of the state to simulate against + fn new_simulation_state( + &self, + state_provider: impl StateProvider, + db: &State, + ) -> State> { + let state = StateProviderDatabase::new(state_provider); + + State::builder() + .with_database(state) + .with_cached_prestate(db.cache.clone()) + .with_bundle_update() + .build() + } + + fn sign_tx( + &self, + to: Address, + from: Signer, + gas_used: u64, + calldata: Bytes, + ctx: &OpPayloadBuilderCtx, + db: impl DatabaseRef, + ) -> Result, BuilderTransactionError> { + let nonce = get_nonce(db, from.address)?; + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer + gas_limit: gas_used * 64 / 63, + max_fee_per_gas: ctx.base_fee().into(), + to: TxKind::Call(to), + input: calldata, + ..Default::default() + }); + Ok(from.sign_tx(tx)?) + } + + fn commit_txs( + &self, + signed_txs: Vec>, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result<(), BuilderTransactionError> { + let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); + for signed_tx in signed_txs { + let ResultAndState { state, .. } = evm + .transact(&signed_tx) + .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; + evm.db_mut().commit(state) + } + Ok(()) + } + + fn simulate_call( + &self, + tx: OpTransactionRequest, + expected_logs: Vec, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + let evm_env = alloy_evm::EvmEnv::new(evm.cfg_env().clone(), evm.block().clone()); + let tx_env: revm::context::TxEnv = tx.as_ref().clone().try_into_tx_env(&evm_env)?; + let to = tx_env.kind.into_to().unwrap_or_default(); + let op_tx = OpTx(op_revm::OpTransaction { + base: tx_env, + enveloped_tx: Some(Bytes::new()), + deposit: Default::default(), + }); + + let ResultAndState { result, state } = evm.transact(op_tx).map_err(|err| { + if err.is_invalid_tx_err() { + BuilderTransactionError::InvalidTransactionError(Box::new(err)) + } else { + BuilderTransactionError::EvmExecutionError(Box::new(err)) + } + })?; + let gas_used = result.tx_gas_used(); + + match result { + ExecutionResult::Success { output, logs, .. } => { + let topics: HashSet = logs + .into_iter() + .flat_map(|log| log.topics().to_vec()) + .collect(); + if !expected_logs + .iter() + .all(|expected_topic| topics.contains(expected_topic)) + { + return Err(BuilderTransactionError::InvalidContract( + to, + InvalidContractDataError::InvalidLogs( + expected_logs, + topics.into_iter().collect(), + ), + )); + } + let return_output = T::abi_decode_returns(&output.into_data()).map_err(|_| { + BuilderTransactionError::InvalidContract( + to, + InvalidContractDataError::OutputAbiDecodeError, + ) + })?; + Ok(SimulationSuccessResult:: { + gas_used, + output: return_output, + state_changes: state, + }) + } + ExecutionResult::Revert { output, .. } => { + let revert = ContractError::::abi_decode(&output) + .map(|reason| Revert::from(format!("{reason:?}"))) + .or_else(|_| Revert::abi_decode(&output)) + .unwrap_or_else(|_| { + Revert::from(format!("unknown revert: {}", hex::encode(&output))) + }); + Err(BuilderTransactionError::TransactionReverted(to, revert)) + } + ExecutionResult::Halt { reason, .. } => { + Err(BuilderTransactionError::TransactionHalted(to, reason)) + } + } + } +} + +#[derive(Debug, Clone)] +pub(super) struct BuilderTxBase { + pub signer: Option, + _marker: std::marker::PhantomData, +} + +impl BuilderTxBase { + pub(super) fn new(signer: Option) -> Self { + Self { + signer, + _marker: std::marker::PhantomData, + } + } + + pub(super) fn simulate_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: impl DatabaseRef, + ) -> Result, BuilderTransactionError> { + match self.signer { + Some(signer) => { + let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); + let gas_used = self.estimate_builder_tx_gas(&message); + let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + signed_tx.encoded_2718().as_slice(), + ); + Ok(Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + })) + } + None => Ok(None), + } + } + + fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 { + // Count zero and non-zero bytes + let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { + if byte == 0 { + (zeros + 1, nonzeros) + } else { + (zeros, nonzeros + 1) + } + }); + + // Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte) + let zero_cost = zero_bytes * 4; + let nonzero_cost = nonzero_bytes * 16; + + // Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623 + let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; + let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) + } + + fn signed_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: impl DatabaseRef, + signer: Signer, + gas_used: u64, + message: Vec, + ) -> Result, BuilderTransactionError> { + let nonce = get_nonce(db, signer.address)?; + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + gas_limit: gas_used, + max_fee_per_gas: ctx.base_fee().into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(Address::ZERO), + // Include the message as part of the transaction data + input: message.into(), + ..Default::default() + }); + // Sign the transaction + let builder_tx = signer + .sign_tx(tx) + .map_err(BuilderTransactionError::SigningError)?; + + Ok(builder_tx) + } +} + +pub fn get_nonce(db: impl DatabaseRef, address: Address) -> Result { + db.basic_ref(address) + .map(|acc| acc.unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) +} + +pub fn get_balance( + db: impl DatabaseRef, + address: Address, +) -> Result { + db.basic_ref(address) + .map(|acc| acc.unwrap_or_default().balance) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/context.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/context.rs new file mode 100644 index 00000000000..bb83e14589f --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/context.rs @@ -0,0 +1,607 @@ +use alloy_consensus::{Transaction, conditional::BlockConditionalAttributes}; +use alloy_eips::{Encodable2718, Typed2718}; +use alloy_evm::{ + Database, Evm as AlloyEvm, + block::{BlockExecutor as AlloyBlockExecutor, CommitChanges, TxResult}, +}; +use alloy_primitives::{BlockHash, Bytes, U256}; +use alloy_rpc_types_eth::Withdrawals; +use core::fmt::Debug; +use op_revm::{L1BlockInfo, OpSpecId}; +use reth_basic_payload_builder::PayloadConfig; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_evm::{ + ConfigureEvm, EvmEnv, + execute::{BlockBuilder, BlockExecutionError, BlockValidationError}, +}; +use reth_node_api::PayloadBuilderError; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_node::OpPayloadBuilderAttributes; +use reth_optimism_payload_builder::{ + config::{OpDAConfig, OpGasLimitConfig}, + error::OpPayloadBuilderError, +}; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +use reth_optimism_txpool::{ + conditional::MaybeConditionalTransaction, + estimated_da_size::DataAvailabilitySized, + interop::{MaybeInteropTransaction, is_valid_interop}, +}; +use reth_payload_builder::PayloadId; +use reth_primitives_traits::{InMemorySize, SealedHeader, SignedTransaction}; +use reth_revm::{State, context::Block}; +use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; +use revm::interpreter::as_u64_saturated; +use std::{sync::Arc, time::Instant}; +use tokio_util::sync::CancellationToken; +use tracing::{debug, info, trace}; + +use crate::{ + gas_limiter::AddressGasLimiter, + metrics::OpRBuilderMetrics, + primitives::reth::{ExecutionInfo, TxnExecutionResult}, + traits::PayloadTxsBounds, + tx::MaybeRevertingTransaction, + tx_signer::Signer, +}; + +pub(super) fn last_receipt_with_cumulative_gas( + executor: &Executor, + cumulative_gas_used: u64, +) -> Option +where + Executor: AlloyBlockExecutor, +{ + let mut receipt = executor.receipts().last().cloned()?; + receipt.as_receipt_mut().cumulative_gas_used = cumulative_gas_used; + Some(receipt) +} + +/// Container type that holds all necessities to build a new payload. +#[derive(Debug)] +pub struct OpPayloadBuilderCtx { + /// The type that knows how to perform system calls and configure the evm. + pub evm_config: OpEvmConfig, + /// The DA config for the payload builder + pub da_config: OpDAConfig, + // Gas limit configuration for the payload builder + pub gas_limit_config: OpGasLimitConfig, + /// The chainspec + pub chain_spec: Arc, + /// How to build the payload. + pub config: PayloadConfig>, + /// Evm Settings + pub evm_env: EvmEnv, + /// Block env attributes for the current block. + pub block_env_attributes: OpNextBlockEnvAttributes, + /// Marker to check whether the job has been cancelled. + pub cancel: CancellationToken, + /// The builder signer + pub builder_signer: Option, + /// The metrics for the builder + pub metrics: Arc, + /// Extra context for the payload builder + pub extra_ctx: ExtraCtx, + /// Max gas that can be used by a transaction. + pub max_gas_per_txn: Option, + /// Rate limiting based on gas. This is an optional feature. + pub address_gas_limiter: AddressGasLimiter, +} + +impl OpPayloadBuilderCtx { + pub(super) fn with_cancel(self, cancel: CancellationToken) -> Self { + Self { cancel, ..self } + } + + pub(super) fn with_extra_ctx(self, extra_ctx: ExtraCtx) -> Self { + Self { extra_ctx, ..self } + } + + /// Returns the parent block the payload will be build on. + pub fn parent(&self) -> &SealedHeader { + &self.config.parent_header + } + + /// Returns the parent hash + pub fn parent_hash(&self) -> BlockHash { + self.parent().hash() + } + + /// Returns the timestamp + pub fn timestamp(&self) -> u64 { + self.attributes().timestamp() + } + + /// Returns the builder attributes. + pub(super) const fn attributes(&self) -> &OpPayloadBuilderAttributes { + &self.config.attributes + } + + /// Returns the withdrawals if shanghai is active. + pub fn withdrawals(&self) -> Option<&Withdrawals> { + self.chain_spec + .is_shanghai_active_at_timestamp(self.attributes().timestamp()) + .then(|| self.attributes().withdrawals()) + } + + /// Returns the block gas limit to target. + pub fn block_gas_limit(&self) -> u64 { + match self.gas_limit_config.gas_limit() { + Some(gas_limit) => gas_limit, + None => self + .attributes() + .gas_limit + .unwrap_or(self.evm_env.block_env.gas_limit), + } + } + + /// Returns the block number for the block. + pub fn block_number(&self) -> u64 { + as_u64_saturated!(self.evm_env.block_env.number) + } + + /// Returns the current base fee + pub fn base_fee(&self) -> u64 { + self.evm_env.block_env.basefee + } + + /// Returns the current blob gas price. + pub fn get_blob_gasprice(&self) -> Option { + self.evm_env + .block_env + .blob_gasprice() + .map(|gasprice| gasprice as u64) + } + + /// Returns the blob fields for the header. + /// + /// This will return the culmative DA bytes * scalar after Jovian + /// after Ecotone, this will always return Some(0) as blobs aren't supported + /// pre Ecotone, these fields aren't used. + pub fn blob_fields( + &self, + info: &ExecutionInfo, + ) -> (Option, Option) { + if self.is_jovian_active() { + let scalar = info + .da_footprint_scalar + .expect("Scalar must be defined for Jovian blocks"); + let result = info.cumulative_da_bytes_used * scalar as u64; + (Some(0), Some(result)) + } else if self.is_ecotone_active() { + (Some(0), Some(0)) + } else { + (None, None) + } + } + + /// Returns the extra data for the block. + /// + /// After holocene this extracts the extradata from the payload + pub fn extra_data(&self) -> Result { + if self.is_jovian_active() { + self.attributes() + .get_jovian_extra_data( + self.chain_spec + .base_fee_params_at_timestamp(self.attributes().timestamp()), + ) + .map_err(PayloadBuilderError::other) + } else if self.is_holocene_active() { + self.attributes() + .get_holocene_extra_data( + self.chain_spec + .base_fee_params_at_timestamp(self.attributes().timestamp()), + ) + .map_err(PayloadBuilderError::other) + } else { + Ok(Default::default()) + } + } + + /// Returns the current fee settings for transactions from the mempool + pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { + BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) + } + + /// Returns the unique id for this payload job. + pub fn payload_id(&self) -> PayloadId { + self.attributes().payload_id() + } + + /// Returns true if regolith is active for the payload. + pub fn is_regolith_active(&self) -> bool { + self.chain_spec + .is_regolith_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if ecotone is active for the payload. + pub fn is_ecotone_active(&self) -> bool { + self.chain_spec + .is_ecotone_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if canyon is active for the payload. + pub fn is_canyon_active(&self) -> bool { + self.chain_spec + .is_canyon_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if holocene is active for the payload. + pub fn is_holocene_active(&self) -> bool { + self.chain_spec + .is_holocene_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if isthmus is active for the payload. + pub fn is_isthmus_active(&self) -> bool { + self.chain_spec + .is_isthmus_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if isthmus is active for the payload. + pub fn is_jovian_active(&self) -> bool { + self.chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns the chain id + pub fn chain_id(&self) -> u64 { + self.chain_spec.chain_id() + } +} + +impl OpPayloadBuilderCtx { + /// Returns a block builder for the next block in the payload. + pub(super) fn block_builder_for_next_block<'a, DB: Database + 'a>( + &'a self, + db: &'a mut State, + ) -> Result< + impl BlockBuilder< + Primitives = reth_optimism_primitives::OpPrimitives, + Executor: AlloyBlockExecutor< + Transaction = OpTransactionSigned, + Receipt = OpReceipt, + Evm: alloy_evm::Evm>>, + >, + > + 'a, + PayloadBuilderError, + > { + self.evm_config + .builder_for_next_block(db, self.parent(), self.block_env_attributes.clone()) + .map_err(PayloadBuilderError::other) + } + + /// Executes all sequencer transactions that are included in the payload attributes. + pub(super) fn execute_sequencer_transactions( + &self, + builder: &mut Builder, + ) -> Result, PayloadBuilderError> + where + Builder: BlockBuilder, + Builder::Executor: + AlloyBlockExecutor, + { + let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); + + for sequencer_tx in &self.attributes().transactions { + // A sequencer's block should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + OpPayloadBuilderError::BlobTransactionRejected, + )); + } + + // Convert the transaction to a [Recovered]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx + .value() + .try_clone_into_recovered() + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + let gas_used = match builder.execute_transaction(sequencer_tx.clone()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + trace!(target: "payload_builder", %error, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); + } + }; + + // add gas used by the transaction to cumulative gas used, before creating the receipt + info.cumulative_gas_used += gas_used.tx_gas_used(); + + if !sequencer_tx.is_deposit() { + info.cumulative_da_bytes_used += op_alloy_flz::tx_estimated_size_fjord_bytes( + sequencer_tx.encoded_2718().as_slice(), + ); + } + + info.receipts.push( + last_receipt_with_cumulative_gas(builder.executor(), info.cumulative_gas_used) + .expect("executor must record a receipt for committed tx"), + ); + + // append sender and transaction to the respective lists + info.executed_senders.push(sequencer_tx.signer()); + info.executed_transactions.push(sequencer_tx.into_inner()); + } + + let da_footprint_gas_scalar = self + .chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + .then(|| { + L1BlockInfo::fetch_da_footprint_gas_scalar(builder.evm_mut().db_mut()) + .expect("DA footprint should always be available from the database post jovian") + }); + + info.da_footprint_scalar = da_footprint_gas_scalar; + + Ok(info) + } + + /// Executes the given best transactions and updates the execution info. + /// + /// Returns `Ok(Some(())` if the job was cancelled. + pub(super) fn execute_best_transactions( + &self, + info: &mut ExecutionInfo, + builder: &mut Builder, + best_txs: &mut impl PayloadTxsBounds, + block_gas_limit: u64, + block_da_limit: Option, + block_da_footprint_limit: Option, + ) -> Result, PayloadBuilderError> + where + Builder: BlockBuilder, + Builder::Executor: + AlloyBlockExecutor, + { + let execute_txs_start_time = Instant::now(); + let mut num_txs_considered = 0; + let mut num_txs_simulated = 0; + let mut num_txs_simulated_success = 0; + let mut num_txs_simulated_fail = 0; + let mut num_bundles_reverted = 0; + let mut reverted_gas_used = 0; + let base_fee = self.base_fee(); + + let tx_da_limit = self.da_config.max_da_tx_size(); + + debug!( + target: "payload_builder", + message = "Executing best transactions", + block_da_limit = ?block_da_limit, + tx_da_limit = ?tx_da_limit, + block_gas_limit = ?block_gas_limit, + ); + + let block_attr = BlockConditionalAttributes { + number: self.block_number(), + timestamp: self.attributes().timestamp(), + }; + + while let Some(tx) = best_txs.next(()) { + let interop = tx.interop_deadline(); + let reverted_hashes = tx.reverted_hashes().clone(); + let conditional = tx.conditional().cloned(); + + let tx_da_size = tx.estimated_da_size(); + let tx = tx.into_consensus(); + let tx_hash = tx.tx_hash(); + + // exclude reverting transaction if: + // - the transaction comes from a bundle (is_some) and the hash **is not** in reverted hashes + // Note that we need to use the Option to signal whether the transaction comes from a bundle, + // otherwise, we would exclude all transactions that are not in the reverted hashes. + let is_bundle_tx = reverted_hashes.is_some(); + let exclude_reverting_txs = + is_bundle_tx && !reverted_hashes.unwrap().contains(&tx_hash); + + let log_txn = |result: TxnExecutionResult| { + debug!( + target: "payload_builder", + message = "Considering transaction", + tx_hash = ?tx_hash, + tx_da_size = ?tx_da_size, + exclude_reverting_txs = ?exclude_reverting_txs, + result = %result, + ); + }; + + num_txs_considered += 1; + + // TODO: ideally we should get this from the txpool stream + if let Some(conditional) = conditional + && !conditional.matches_block_attributes(&block_attr) + { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // TODO: remove this condition and feature once we are comfortable enabling interop for everything + if cfg!(feature = "interop") { + // We skip invalid cross chain txs, they would be removed on the next block update in + // the maintenance job + if let Some(interop) = interop + && !is_valid_interop(interop, self.config.attributes.timestamp()) + { + log_txn(TxnExecutionResult::InteropFailed); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + } + + // ensure we still have capacity for this transaction + if let Err(result) = info.is_tx_over_limits( + tx_da_size, + block_gas_limit, + tx_da_limit, + block_da_limit, + tx.gas_limit(), + info.da_footprint_scalar, + block_da_footprint_limit, + ) { + // we can't fit this transaction into the block, so we need to mark it as + // invalid which also removes all dependent transaction from + // the iterator before we can continue + log_txn(result); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // A sequencer's block should never contain blob or deposit transactions from the pool. + if tx.is_eip4844() || tx.is_deposit() { + log_txn(TxnExecutionResult::SequencerTransaction); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // check if the job was cancelled, if so we can exit early + if self.cancel.is_cancelled() { + return Ok(Some(())); + } + + let tx_simulation_start_time = Instant::now(); + let mut gas_used = 0; + let mut tx_succeeded = false; + let mut gas_limit_exceeded = false; + let mut address_limit_exceeded = false; + let committed = match builder.execute_transaction_with_commit_condition( + tx.clone(), + |result| { + gas_used = result.result().result.tx_gas_used(); + tx_succeeded = result.result().result.is_success(); + gas_limit_exceeded = self + .max_gas_per_txn + .is_some_and(|max_gas_per_txn| gas_used > max_gas_per_txn); + address_limit_exceeded = self + .address_gas_limiter + .consume_gas(tx.signer(), gas_used) + .is_err(); + + if gas_limit_exceeded + || address_limit_exceeded + || (!tx_succeeded && exclude_reverting_txs) + { + CommitChanges::No + } else { + CommitChanges::Yes + } + }, + ) { + Ok(committed) => committed, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + if error.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + log_txn(TxnExecutionResult::NonceTooLow); + trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + log_txn(TxnExecutionResult::EvmError); + trace!(target: "payload_builder", %error, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + } + + continue; + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + log_txn(TxnExecutionResult::EvmError); + return Err(PayloadBuilderError::evm(err)); + } + }; + + self.metrics + .tx_simulation_duration + .record(tx_simulation_start_time.elapsed()); + self.metrics.tx_byte_size.record(tx.inner().size() as f64); + num_txs_simulated += 1; + + if tx_succeeded { + log_txn(TxnExecutionResult::Success); + num_txs_simulated_success += 1; + self.metrics.successful_tx_gas_used.record(gas_used as f64); + } else { + num_txs_simulated_fail += 1; + reverted_gas_used += gas_used as i32; + self.metrics.reverted_tx_gas_used.record(gas_used as f64); + if is_bundle_tx { + num_bundles_reverted += 1; + } + if exclude_reverting_txs { + log_txn(TxnExecutionResult::RevertedAndExcluded); + info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } else { + log_txn(TxnExecutionResult::Reverted); + } + } + + if gas_limit_exceeded || address_limit_exceeded { + log_txn(TxnExecutionResult::MaxGasUsageExceeded); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + if committed.is_none() { + continue; + } + + info.cumulative_gas_used += gas_used; + info.cumulative_da_bytes_used += tx_da_size; + + info.receipts.push( + last_receipt_with_cumulative_gas(builder.executor(), info.cumulative_gas_used) + .expect("executor must record a receipt for committed tx"), + ); + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + + // append sender and transaction to the respective lists + info.executed_senders.push(tx.signer()); + info.executed_transactions.push(tx.into_inner()); + } + + let payload_transaction_simulation_time = execute_txs_start_time.elapsed(); + self.metrics.set_payload_builder_metrics( + payload_transaction_simulation_time, + num_txs_considered, + num_txs_simulated, + num_txs_simulated_success, + num_txs_simulated_fail, + num_bundles_reverted, + reverted_gas_used, + ); + + debug!( + target: "payload_builder", + message = "Completed executing best transactions", + txs_executed = num_txs_considered, + txs_applied = num_txs_simulated_success, + txs_rejected = num_txs_simulated_fail, + bundles_reverted = num_bundles_reverted, + ); + Ok(None) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/best_txs.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/best_txs.rs new file mode 100644 index 00000000000..ec2a45d1934 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/best_txs.rs @@ -0,0 +1,237 @@ +use alloy_primitives::{Address, TxHash}; +use reth_payload_util::PayloadTransactions; +use reth_transaction_pool::{PoolTransaction, ValidPoolTransaction}; +use std::{collections::HashSet, sync::Arc}; +use tracing::debug; + +use crate::tx::MaybeFlashblockFilter; + +pub(super) struct BestFlashblocksTxs +where + T: PoolTransaction, + I: Iterator>>, +{ + inner: reth_payload_util::BestPayloadTransactions, + current_flashblock_number: u64, + // Transactions that were already commited to the state. Using them again would cause NonceTooLow + // so we skip them + commited_transactions: HashSet, +} + +impl BestFlashblocksTxs +where + T: PoolTransaction, + I: Iterator>>, +{ + pub(super) fn new(inner: reth_payload_util::BestPayloadTransactions) -> Self { + Self { + inner, + current_flashblock_number: 0, + commited_transactions: Default::default(), + } + } + + /// Replaces current iterator with new one. We use it on new flashblock building, to refresh + /// priority boundaries + pub(super) fn refresh_iterator( + &mut self, + inner: reth_payload_util::BestPayloadTransactions, + current_flashblock_number: u64, + ) { + self.inner = inner; + self.current_flashblock_number = current_flashblock_number; + } + + /// Remove transaction from next iteration and it already in the state + pub(super) fn mark_commited(&mut self, txs: Vec) { + self.commited_transactions.extend(txs); + } +} + +impl PayloadTransactions for BestFlashblocksTxs +where + T: PoolTransaction + MaybeFlashblockFilter, + I: Iterator>>, +{ + type Transaction = T; + + fn next(&mut self, ctx: ()) -> Option { + loop { + let tx = self.inner.next(ctx)?; + // Skip transaction we already included + if self.commited_transactions.contains(tx.hash()) { + continue; + } + + let flashblock_number_min = tx.flashblock_number_min(); + let flashblock_number_max = tx.flashblock_number_max(); + + // Check min flashblock requirement + if let Some(min) = flashblock_number_min + && self.current_flashblock_number < min + { + continue; + } + + // Check max flashblock requirement + if let Some(max) = flashblock_number_max + && self.current_flashblock_number > max + { + debug!( + target: "payload_builder", + tx_hash = ?tx.hash(), + sender = ?tx.sender(), + nonce = tx.nonce(), + current_flashblock = self.current_flashblock_number, + max_flashblock = max, + "Bundle flashblock max exceeded" + ); + self.inner.mark_invalid(tx.sender(), tx.nonce()); + continue; + } + + return Some(tx); + } + } + + /// Proxy to inner iterator + fn mark_invalid(&mut self, sender: Address, nonce: u64) { + self.inner.mark_invalid(sender, nonce); + } +} + +#[cfg(test)] +mod tests { + use crate::{ + builders::flashblocks::best_txs::BestFlashblocksTxs, + mock_tx::{MockFbTransaction, MockFbTransactionFactory}, + }; + use alloy_consensus::Transaction; + use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; + use reth_transaction_pool::{CoinbaseTipOrdering, PoolTransaction, pool::PendingPool}; + use std::sync::Arc; + + #[test] + fn test_simple_case() { + let mut pool = PendingPool::new(CoinbaseTipOrdering::::default()); + let mut f = MockFbTransactionFactory::default(); + + // Add 3 regular transaction + let tx_1 = f.create_eip1559(); + let tx_2 = f.create_eip1559(); + let tx_3 = f.create_eip1559(); + pool.add_transaction(Arc::new(tx_1), 0); + pool.add_transaction(Arc::new(tx_2), 0); + pool.add_transaction(Arc::new(tx_3), 0); + + // Create iterator + let mut iterator = BestFlashblocksTxs::new(BestPayloadTransactions::new(pool.best())); + // ### First flashblock + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 0); + // Accept first tx + let tx1 = iterator.next(()).unwrap(); + // Invalidate second tx + let tx2 = iterator.next(()).unwrap(); + iterator.mark_invalid(tx2.sender(), tx2.nonce()); + // Accept third tx + let tx3 = iterator.next(()).unwrap(); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + // Mark transaction as commited + iterator.mark_commited(vec![*tx1.hash(), *tx3.hash()]); + + // ### Second flashblock + // It should not return txs 1 and 3, but should return 2 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 1); + let tx2 = iterator.next(()).unwrap(); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + // Mark transaction as commited + iterator.mark_commited(vec![*tx2.hash()]); + + // ### Third flashblock + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 2); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + } + + /// Test bundle cases + /// We won't mark transactions as commited to test that boundaries are respected + #[test] + fn test_bundle_case() { + let mut pool = PendingPool::new(CoinbaseTipOrdering::::default()); + let mut f = MockFbTransactionFactory::default(); + + // Add 4 fb transaction + let tx_1 = f.create_legacy_fb(None, None); + let tx_1_hash = *tx_1.hash(); + let tx_2 = f.create_legacy_fb(None, Some(1)); + let tx_2_hash = *tx_2.hash(); + let tx_3 = f.create_legacy_fb(Some(1), None); + let tx_3_hash = *tx_3.hash(); + let tx_4 = f.create_legacy_fb(Some(2), Some(3)); + let tx_4_hash = *tx_4.hash(); + pool.add_transaction(Arc::new(tx_1), 0); + pool.add_transaction(Arc::new(tx_2), 0); + pool.add_transaction(Arc::new(tx_3), 0); + pool.add_transaction(Arc::new(tx_4), 0); + + // Create iterator + let mut iterator = BestFlashblocksTxs::new(BestPayloadTransactions::new(pool.best())); + // ### First flashblock + // should contain txs 1 and 2 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 0); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx2 = iterator.next(()).unwrap(); + assert_eq!(tx2.hash(), &tx_2_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + + // ### Second flashblock + // should contain txs 1, 2, and 3 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 1); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx2 = iterator.next(()).unwrap(); + assert_eq!(tx2.hash(), &tx_2_hash); + let tx3 = iterator.next(()).unwrap(); + assert_eq!(tx3.hash(), &tx_3_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + + // ### Third flashblock + // should contain txs 1, 3, and 4 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 2); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx3 = iterator.next(()).unwrap(); + assert_eq!(tx3.hash(), &tx_3_hash); + let tx4 = iterator.next(()).unwrap(); + assert_eq!(tx4.hash(), &tx_4_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + + // ### Forth flashblock + // should contain txs 1, 3, and 4 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 3); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx3 = iterator.next(()).unwrap(); + assert_eq!(tx3.hash(), &tx_3_hash); + let tx4 = iterator.next(()).unwrap(); + assert_eq!(tx4.hash(), &tx_4_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + + // ### Fifth flashblock + // should contain txs 1 and 3 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 4); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx3 = iterator.next(()).unwrap(); + assert_eq!(tx3.hash(), &tx_3_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs new file mode 100644 index 00000000000..bc1063ac6e9 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -0,0 +1,341 @@ +use alloy_eips::Encodable2718; +use alloy_evm::{Database, Evm}; +use alloy_op_evm::OpEvm; +use alloy_primitives::{Address, B256, Signature, U256}; +use alloy_rpc_types_eth::TransactionInput; +use alloy_sol_types::{SolCall, SolEvent, sol}; +use core::fmt::Debug; +use op_alloy_rpc_types::OpTransactionRequest; +use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap}; +use reth_provider::StateProvider; +use reth_revm::State; +use revm::{DatabaseRef, inspector::NoOpInspector}; +use tracing::warn; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, + SimulationSuccessResult, + builder_tx::BuilderTxBase, + context::OpPayloadBuilderCtx, + flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, + get_nonce, + }, + flashtestations::builder_tx::FlashtestationsBuilderTx, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +sol!( + // From https://github.com/Uniswap/flashblocks_number_contract/blob/main/src/FlashblockNumber.sol + #[sol(rpc, abi)] + #[derive(Debug)] + interface IFlashblockNumber { + uint256 public flashblockNumber; + + function incrementFlashblockNumber() external; + + function permitIncrementFlashblockNumber(uint256 currentFlashblockNumber, bytes memory signature) external; + + function computeStructHash(uint256 currentFlashblockNumber) external pure returns (bytes32); + + function hashTypedDataV4(bytes32 structHash) external view returns (bytes32); + + + // @notice Emitted when flashblock index is incremented + // @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block) + event FlashblockIncremented(uint256 newFlashblockIndex); + + /// ----------------------------------------------------------------------- + /// Errors + /// ----------------------------------------------------------------------- + error NonBuilderAddress(address addr); + error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber); + } +); + +// This will be the end of block transaction of a regular block +#[derive(Debug, Clone)] +pub(super) struct FlashblocksBuilderTx { + pub base_builder_tx: BuilderTxBase, + pub flashtestations_builder_tx: + Option>, +} + +impl FlashblocksBuilderTx { + pub(super) fn new( + signer: Option, + flashtestations_builder_tx: Option< + FlashtestationsBuilderTx, + >, + ) -> Self { + let base_builder_tx = BuilderTxBase::new(signer); + Self { + base_builder_tx, + flashtestations_builder_tx, + } + } +} + +impl BuilderTransactions for FlashblocksBuilderTx { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + top_of_block: bool, + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + + if ctx.is_first_flashblock() { + let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; + builder_txs.extend(flashblocks_builder_tx.clone()); + } + + if ctx.is_last_flashblock() { + let base_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; + builder_txs.extend(base_tx.clone()); + + if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { + // Commit state that is included to get the correct nonce + if let Some(builder_tx) = base_tx { + self.commit_txs(vec![builder_tx.signed_tx], ctx, &mut *db)?; + } + // We only include flashtestations txs in the last flashblock + match flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + db, + top_of_block, + ) { + Ok(flashtestations_builder_txs) => { + builder_txs.extend(flashtestations_builder_txs) + } + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx") + } + } + } + } + Ok(builder_txs) + } +} + +// This will be the end of block transaction of a regular block +#[derive(Debug, Clone)] +pub(super) struct FlashblocksNumberBuilderTx { + pub signer: Signer, + pub flashblock_number_address: Address, + pub use_permit: bool, + pub base_builder_tx: BuilderTxBase, + pub flashtestations_builder_tx: + Option>, +} + +impl FlashblocksNumberBuilderTx { + pub(super) fn new( + signer: Signer, + flashblock_number_address: Address, + use_permit: bool, + flashtestations_builder_tx: Option< + FlashtestationsBuilderTx, + >, + ) -> Self { + let base_builder_tx = BuilderTxBase::new(Some(signer)); + Self { + signer, + flashblock_number_address, + use_permit, + base_builder_tx, + flashtestations_builder_tx, + } + } + + fn signed_increment_flashblocks_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}; + self.increment_flashblocks_tx(calldata, ctx, evm) + } + + fn increment_flashblocks_permit_signature( + &self, + flashtestations_signer: &Signer, + current_flashblock_number: U256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let struct_hash_calldata = IFlashblockNumber::computeStructHashCall { + currentFlashblockNumber: current_flashblock_number, + }; + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_readonly_call(struct_hash_calldata, ctx, evm)?; + let typed_data_hash_calldata = + IFlashblockNumber::hashTypedDataV4Call { structHash: output }; + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_readonly_call(typed_data_hash_calldata, ctx, evm)?; + let signature = flashtestations_signer.sign_message(output)?; + Ok(signature) + } + + fn signed_increment_flashblocks_permit_tx( + &self, + flashtestations_signer: &Signer, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let current_flashblock_calldata = IFlashblockNumber::flashblockNumberCall {}; + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_readonly_call(current_flashblock_calldata, ctx, evm)?; + let signature = + self.increment_flashblocks_permit_signature(flashtestations_signer, output, ctx, evm)?; + let calldata = IFlashblockNumber::permitIncrementFlashblockNumberCall { + currentFlashblockNumber: output, + signature: signature.as_bytes().into(), + }; + self.increment_flashblocks_tx(calldata, ctx, evm) + } + + fn increment_flashblocks_tx( + &self, + calldata: T, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call( + calldata.clone(), + vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH], + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.flashblock_number_address, + self.signer, + gas_used, + calldata.abi_encode().into(), + ctx, + evm.db_mut(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { + signed_tx, + gas_used, + da_size, + is_top_of_block: true, + }) + } + + fn simulate_flashblocks_readonly_call( + &self, + calldata: T, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + self.simulate_flashblocks_call(calldata, vec![], ctx, evm) + } + + fn simulate_flashblocks_call( + &self, + calldata: T, + expected_logs: Vec, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + let tx_req = OpTransactionRequest::default() + .gas_limit(ctx.block_gas_limit()) + .max_fee_per_gas(ctx.base_fee().into()) + .to(self.flashblock_number_address) + .from(self.signer.address) // use tee key as signer for simulations + .nonce(get_nonce(evm.db(), self.signer.address)?) + .input(TransactionInput::new(calldata.abi_encode().into())); + self.simulate_call::( + tx_req, + expected_logs, + evm, + ) + } +} + +impl BuilderTransactions + for FlashblocksNumberBuilderTx +{ + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + top_of_block: bool, + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + + if ctx.is_first_flashblock() { + // fallback block builder tx + builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?); + } else { + // we increment the flashblock number for the next flashblock so we don't increment in the last flashblock + let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + cfg.disable_block_gas_limit = true; + }); + + let flashblocks_num_tx = if let Some(flashtestations) = &self.flashtestations_builder_tx + && self.use_permit + { + self.signed_increment_flashblocks_permit_tx( + flashtestations.tee_signer(), + ctx, + &mut evm, + ) + } else { + self.signed_increment_flashblocks_tx(ctx, &mut evm) + }; + + let tx = match flashblocks_num_tx { + Ok(tx) => Some(tx), + Err(e) => { + warn!(target: "builder_tx", error = ?e, "flashblocks number contract tx simulation failed, defaulting to fallback builder tx"); + self.base_builder_tx + .simulate_builder_tx(ctx, &mut *db)? + .map(|tx| tx.set_top_of_block()) + } + }; + + builder_txs.extend(tx); + } + + if ctx.is_last_flashblock() + && let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx + { + // Commit state that should be included to compute the correct nonce + let flashblocks_builder_txs = builder_txs + .iter() + .filter(|tx| tx.is_top_of_block == top_of_block) + .map(|tx| tx.signed_tx.clone()) + .collect(); + self.commit_txs(flashblocks_builder_txs, ctx, &mut *db)?; + + // We only include flashtestations txs in the last flashblock + match flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + db, + top_of_block, + ) { + Ok(flashtestations_builder_txs) => builder_txs.extend(flashtestations_builder_txs), + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx") + } + } + } + + Ok(builder_txs) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/config.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/config.rs new file mode 100644 index 00000000000..cdc6ae915ce --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/config.rs @@ -0,0 +1,131 @@ +use alloy_primitives::Address; + +use crate::{args::OpRbuilderArgs, builders::BuilderConfig}; +use core::{ + net::{Ipv4Addr, SocketAddr}, + time::Duration, +}; + +/// Configuration values that are specific to the flashblocks builder. +#[derive(Debug, Clone)] +pub struct FlashblocksConfig { + /// The address of the websockets endpoint that listens for subscriptions to + /// new flashblocks updates. + pub ws_addr: SocketAddr, + + /// How often a flashblock is produced. This is independent of the block time of the chain. + /// Each block will contain one or more flashblocks. On average, the number of flashblocks + /// per block is equal to the block time divided by the flashblock interval. + pub interval: Duration, + + /// How much time would be deducted from block build time to account for latencies in + /// milliseconds. + /// + /// If dynamic_adjustment is false this value would be deducted from first flashblock and + /// it shouldn't be more than interval + /// + /// If dynamic_adjustment is true this value would be deducted from first flashblock and + /// it shouldn't be more than interval + pub leeway_time: Duration, + + /// Disables dynamic flashblocks number adjustment based on FCU arrival time + pub fixed: bool, + + /// Should we disable state root calculation for each flashblock + pub disable_state_root: bool, + + /// The address of the flashblocks number contract. + /// + /// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx. + pub flashblocks_number_contract_address: Option
, + + /// whether to use permit signatures for the contract calls + pub flashblocks_number_contract_use_permit: bool, + + /// Whether to enable the p2p node for flashblocks + pub p2p_enabled: bool, + + /// Port for the p2p node + pub p2p_port: u16, + + /// Optional hex-encoded private key file path for the p2p node + pub p2p_private_key_file: Option, + + /// Comma-separated list of multiaddresses of known peers to connect to + pub p2p_known_peers: Option, + + /// Maximum number of peers for the p2p node + pub p2p_max_peer_count: u32, +} + +impl Default for FlashblocksConfig { + fn default() -> Self { + Self { + ws_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 1111), + interval: Duration::from_millis(250), + leeway_time: Duration::from_millis(50), + fixed: false, + disable_state_root: false, + flashblocks_number_contract_address: None, + flashblocks_number_contract_use_permit: false, + p2p_enabled: false, + p2p_port: 9009, + p2p_private_key_file: None, + p2p_known_peers: None, + p2p_max_peer_count: 50, + } + } +} + +impl TryFrom for FlashblocksConfig { + type Error = eyre::Report; + + fn try_from(args: OpRbuilderArgs) -> Result { + let interval = Duration::from_millis(args.flashblocks.flashblocks_block_time); + + let ws_addr = SocketAddr::new( + args.flashblocks.flashblocks_addr.parse()?, + args.flashblocks.flashblocks_port, + ); + + let leeway_time = Duration::from_millis(args.flashblocks.flashblocks_leeway_time); + + let fixed = args.flashblocks.flashblocks_fixed; + + let disable_state_root = args.flashblocks.flashblocks_disable_state_root; + + let flashblocks_number_contract_address = + args.flashblocks.flashblocks_number_contract_address; + + let flashblocks_number_contract_use_permit = + args.flashblocks.flashblocks_number_contract_use_permit; + + Ok(Self { + ws_addr, + interval, + leeway_time, + fixed, + disable_state_root, + flashblocks_number_contract_address, + flashblocks_number_contract_use_permit, + p2p_enabled: args.flashblocks.p2p.p2p_enabled, + p2p_port: args.flashblocks.p2p.p2p_port, + p2p_private_key_file: args.flashblocks.p2p.p2p_private_key_file, + p2p_known_peers: args.flashblocks.p2p.p2p_known_peers, + p2p_max_peer_count: args.flashblocks.p2p.p2p_max_peer_count, + }) + } +} + +pub(super) trait FlashBlocksConfigExt { + fn flashblocks_per_block(&self) -> u64; +} + +impl FlashBlocksConfigExt for BuilderConfig { + fn flashblocks_per_block(&self) -> u64 { + if self.block_time.as_millis() == 0 { + return 0; + } + (self.block_time.as_millis() / self.specific.interval.as_millis()) as u64 + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/ctx.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/ctx.rs new file mode 100644 index 00000000000..16d1a71ce83 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -0,0 +1,85 @@ +use crate::{ + builders::{BuilderConfig, OpPayloadBuilderCtx, flashblocks::FlashblocksConfig}, + gas_limiter::{AddressGasLimiter, args::GasLimiterArgs}, + metrics::OpRBuilderMetrics, + traits::ClientBounds, +}; +use op_revm::OpSpecId; +use reth_basic_payload_builder::PayloadConfig; +use reth_evm::EvmEnv; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_payload_builder::{ + OpPayloadBuilderAttributes, + config::{OpDAConfig, OpGasLimitConfig}, +}; +use reth_optimism_primitives::OpTransactionSigned; +use std::sync::Arc; +use tokio_util::sync::CancellationToken; + +#[derive(Debug, Clone)] +pub(super) struct OpPayloadSyncerCtx { + /// The type that knows how to perform system calls and configure the evm. + evm_config: OpEvmConfig, + /// The DA config for the payload builder + da_config: OpDAConfig, + /// The chainspec + chain_spec: Arc, + /// Max gas that can be used by a transaction. + max_gas_per_txn: Option, + /// The metrics for the builder + metrics: Arc, +} + +impl OpPayloadSyncerCtx { + pub(super) fn new( + client: &Client, + builder_config: BuilderConfig, + evm_config: OpEvmConfig, + metrics: Arc, + ) -> eyre::Result + where + Client: ClientBounds, + { + let chain_spec = client.chain_spec(); + Ok(Self { + evm_config, + da_config: builder_config.da_config.clone(), + chain_spec, + max_gas_per_txn: builder_config.max_gas_per_txn, + metrics, + }) + } + + pub(super) fn evm_config(&self) -> &OpEvmConfig { + &self.evm_config + } + + pub(super) fn max_gas_per_txn(&self) -> Option { + self.max_gas_per_txn + } + + pub(super) fn into_op_payload_builder_ctx( + self, + payload_config: PayloadConfig>, + evm_env: EvmEnv, + block_env_attributes: OpNextBlockEnvAttributes, + cancel: CancellationToken, + ) -> OpPayloadBuilderCtx { + OpPayloadBuilderCtx { + evm_config: self.evm_config, + da_config: self.da_config, + gas_limit_config: OpGasLimitConfig::default(), + chain_spec: self.chain_spec, + config: payload_config, + evm_env, + block_env_attributes, + cancel, + builder_signer: None, + metrics: self.metrics, + extra_ctx: (), + max_gas_per_txn: self.max_gas_per_txn, + address_gas_limiter: AddressGasLimiter::new(GasLimiterArgs::default()), + } + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/mod.rs new file mode 100644 index 00000000000..87d4494cb8b --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/mod.rs @@ -0,0 +1,38 @@ +use super::BuilderConfig; +use crate::traits::{NodeBounds, PoolBounds}; +use config::FlashblocksConfig; +use service::FlashblocksServiceBuilder; + +mod best_txs; +mod builder_tx; +mod config; +mod ctx; +mod p2p; +mod payload; +mod payload_handler; +mod service; +mod wspub; + +/// Block building strategy that progressively builds chunks of a block and makes them available +/// through a websocket update, then merges them into a full block every chain block time. +pub struct FlashblocksBuilder; + +impl super::PayloadBuilder for FlashblocksBuilder { + type Config = FlashblocksConfig; + + type ServiceBuilder + = FlashblocksServiceBuilder + where + Node: NodeBounds, + Pool: PoolBounds; + + fn new_service( + config: BuilderConfig, + ) -> eyre::Result> + where + Node: NodeBounds, + Pool: PoolBounds, + { + Ok(FlashblocksServiceBuilder(config)) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/p2p.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/p2p.rs new file mode 100644 index 00000000000..9f947d959b0 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/p2p.rs @@ -0,0 +1,60 @@ +use alloy_primitives::U256; +use reth::{core::primitives::SealedBlock, payload::PayloadId}; +use reth_optimism_payload_builder::OpBuiltPayload as RethOpBuiltPayload; +use reth_optimism_primitives::OpBlock; +use serde::{Deserialize, Serialize}; + +pub(super) const AGENT_VERSION: &str = "op-rbuilder/1.0.0"; +pub(super) const FLASHBLOCKS_STREAM_PROTOCOL: p2p::StreamProtocol = + p2p::StreamProtocol::new("/flashblocks/1.0.0"); + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub(super) enum Message { + OpBuiltPayload(OpBuiltPayload), +} + +impl p2p::Message for Message { + fn protocol(&self) -> p2p::StreamProtocol { + FLASHBLOCKS_STREAM_PROTOCOL + } +} + +/// Internal type analogous to [`reth_optimism_payload_builder::OpBuiltPayload`] +/// which additionally implements `Serialize` and `Deserialize` for p2p transmission. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub(crate) struct OpBuiltPayload { + /// Identifier of the payload + pub(crate) id: PayloadId, + /// Sealed block + pub(crate) block: SealedBlock, + /// The fees of the block + pub(crate) fees: U256, +} + +impl From for Message { + fn from(value: RethOpBuiltPayload) -> Self { + Message::OpBuiltPayload(value.into()) + } +} + +impl From for Message { + fn from(value: OpBuiltPayload) -> Self { + Message::OpBuiltPayload(value) + } +} + +impl From for RethOpBuiltPayload { + fn from(value: OpBuiltPayload) -> Self { + RethOpBuiltPayload::new(value.id, value.block.into(), value.fees, None) + } +} + +impl From for OpBuiltPayload { + fn from(value: RethOpBuiltPayload) -> Self { + OpBuiltPayload { + id: value.id(), + block: value.block().clone(), + fees: value.fees(), + } + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/payload.rs new file mode 100644 index 00000000000..9d7a8728d44 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -0,0 +1,1222 @@ +use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; +use crate::{ + builders::{ + BuilderConfig, + builder_tx::BuilderTransactions, + context::OpPayloadBuilderCtx, + flashblocks::{best_txs::BestFlashblocksTxs, config::FlashBlocksConfigExt}, + generator::{BlockCell, BuildArguments, PayloadBuilder}, + }, + gas_limiter::AddressGasLimiter, + metrics::OpRBuilderMetrics, + primitives::reth::ExecutionInfo, + traits::{ClientBounds, PoolBounds}, +}; +use alloy_consensus::{ + BlockBody, EMPTY_OMMER_ROOT_HASH, Header, constants::EMPTY_WITHDRAWALS, proofs, +}; +use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; +use alloy_primitives::{Address, B256, U256, map::foldhash::HashMap}; +use core::time::Duration; +use eyre::WrapErr as _; +use reth_basic_payload_builder::{BuildOutcome, PayloadConfig}; +use reth_chainspec::EthChainSpec; +use reth_evm::{ConfigureEvm, execute::BlockBuilder}; +use reth_execution_types::{BlockExecutionOutput, BlockExecutionResult}; +use reth_node_api::{Block, NodePrimitives, PayloadBuilderError}; +use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_optimism_payload_builder::OpPayloadAttrs; +use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; +use reth_payload_primitives::BuiltPayloadExecutedBlock; +use reth_payload_util::BestPayloadTransactions; +use reth_primitives_traits::RecoveredBlock; +use reth_provider::{ + ExecutionOutcome, HashedPostStateProvider, ProviderError, StateRootProvider, + StorageRootProvider, +}; +use reth_revm::{ + State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, +}; +use reth_transaction_pool::TransactionPool; +use reth_trie::{HashedPostState, updates::TrieUpdates}; +use revm::Database; +use rollup_boost::{ + ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, +}; +use serde::{Deserialize, Serialize}; +use std::{ + ops::{Div, Rem}, + sync::Arc, + time::Instant, +}; +use tokio::sync::mpsc; +use tokio_util::sync::CancellationToken; +use tracing::{debug, error, info, metadata::Level, span, warn}; + +type NextBestFlashblocksTxs = BestFlashblocksTxs< + ::Transaction, + Box< + dyn reth_transaction_pool::BestTransactions< + Item = Arc< + reth_transaction_pool::ValidPoolTransaction< + ::Transaction, + >, + >, + >, + >, +>; + +#[derive(Debug, Default, Clone)] +pub(super) struct FlashblocksExecutionInfo { + /// Index of the last consumed flashblock, counting normal executed transactions only. + last_flashblock_index: usize, +} + +#[derive(Debug, Default, Clone)] +pub struct FlashblocksExtraCtx { + /// Current flashblock index + flashblock_index: u64, + /// Target flashblock count per block + target_flashblock_count: u64, + /// Total gas left for the current flashblock + target_gas_for_batch: u64, + /// Total DA bytes left for the current flashblock + target_da_for_batch: Option, + /// Total DA footprint left for the current flashblock + target_da_footprint_for_batch: Option, + /// Gas limit per flashblock + gas_per_batch: u64, + /// DA bytes limit per flashblock + da_per_batch: Option, + /// DA footprint limit per flashblock + da_footprint_per_batch: Option, + /// Whether to disable state root calculation for each flashblock + disable_state_root: bool, +} + +impl FlashblocksExtraCtx { + fn next( + self, + target_gas_for_batch: u64, + target_da_for_batch: Option, + target_da_footprint_for_batch: Option, + ) -> Self { + Self { + flashblock_index: self.flashblock_index + 1, + target_gas_for_batch, + target_da_for_batch, + target_da_footprint_for_batch, + ..self + } + } +} + +impl OpPayloadBuilderCtx { + /// Returns the current flashblock index + pub(crate) fn flashblock_index(&self) -> u64 { + self.extra_ctx.flashblock_index + } + + /// Returns the target flashblock count + pub(crate) fn target_flashblock_count(&self) -> u64 { + self.extra_ctx.target_flashblock_count + } + + /// Returns if the flashblock is the first fallback block + pub(crate) fn is_first_flashblock(&self) -> bool { + self.flashblock_index() == 0 + } + + /// Returns if the flashblock is the last one + pub(crate) fn is_last_flashblock(&self) -> bool { + self.flashblock_index() == self.target_flashblock_count() + } +} + +/// Optimism's payload builder +#[derive(Debug, Clone)] +pub(super) struct OpPayloadBuilder { + /// The type responsible for creating the evm. + pub evm_config: OpEvmConfig, + /// The transaction pool + pub pool: Pool, + /// Node client + pub client: Client, + /// Sender for sending built payloads to [`PayloadHandler`], + /// which broadcasts outgoing payloads via p2p. + pub payload_tx: mpsc::Sender, + /// WebSocket publisher for broadcasting flashblocks + /// to all connected subscribers. + pub ws_pub: Arc, + /// System configuration for the builder + pub config: BuilderConfig, + /// The metrics for the builder + pub metrics: Arc, + /// The end of builder transaction type + pub builder_tx: BuilderTx, + /// Rate limiting based on gas. This is an optional feature. + pub address_gas_limiter: AddressGasLimiter, +} + +impl OpPayloadBuilder { + /// `OpPayloadBuilder` constructor. + #[allow(clippy::too_many_arguments)] + pub(super) fn new( + evm_config: OpEvmConfig, + pool: Pool, + client: Client, + config: BuilderConfig, + builder_tx: BuilderTx, + payload_tx: mpsc::Sender, + ws_pub: Arc, + metrics: Arc, + ) -> Self { + let address_gas_limiter = AddressGasLimiter::new(config.gas_limiter_config.clone()); + Self { + evm_config, + pool, + client, + payload_tx, + ws_pub, + config, + metrics, + builder_tx, + address_gas_limiter, + } + } +} + +impl reth_basic_payload_builder::PayloadBuilder + for OpPayloadBuilder +where + Pool: Clone + Send + Sync, + Client: Clone + Send + Sync, + BuilderTx: Clone + Send + Sync, +{ + type Attributes = OpPayloadAttrs; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + _args: reth_basic_payload_builder::BuildArguments, + ) -> Result, PayloadBuilderError> { + unimplemented!() + } + + fn build_empty_payload( + &self, + _config: reth_basic_payload_builder::PayloadConfig< + Self::Attributes, + reth_basic_payload_builder::HeaderForPayload, + >, + ) -> Result { + unimplemented!() + } +} + +impl OpPayloadBuilder +where + Pool: PoolBounds, + Client: ClientBounds, + BuilderTx: BuilderTransactions + Send + Sync, +{ + fn get_op_payload_builder_ctx( + &self, + config: reth_basic_payload_builder::PayloadConfig< + OpPayloadBuilderAttributes, + >, + cancel: CancellationToken, + extra_ctx: FlashblocksExtraCtx, + ) -> eyre::Result> { + let chain_spec = self.client.chain_spec(); + let timestamp = config.attributes.timestamp(); + + let extra_data = if chain_spec.is_jovian_active_at_timestamp(timestamp) { + config + .attributes + .get_jovian_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .wrap_err("failed to get holocene extra data for flashblocks payload builder")? + } else if chain_spec.is_holocene_active_at_timestamp(timestamp) { + config + .attributes + .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .wrap_err("failed to get holocene extra data for flashblocks payload builder")? + } else { + Default::default() + }; + + let block_env_attributes = OpNextBlockEnvAttributes { + timestamp, + suggested_fee_recipient: config.attributes.suggested_fee_recipient(), + prev_randao: config.attributes.prev_randao(), + gas_limit: config + .attributes + .gas_limit + .unwrap_or(config.parent_header.gas_limit), + parent_beacon_block_root: config.attributes.parent_beacon_block_root(), + extra_data, + }; + + let evm_config = self.evm_config.clone(); + + let evm_env = evm_config + .next_evm_env(&config.parent_header, &block_env_attributes) + .wrap_err("failed to create next evm env")?; + + Ok(OpPayloadBuilderCtx:: { + evm_config: self.evm_config.clone(), + chain_spec, + config, + evm_env, + block_env_attributes, + cancel, + da_config: self.config.da_config.clone(), + gas_limit_config: self.config.gas_limit_config.clone(), + builder_signer: self.config.builder_signer, + metrics: Default::default(), + extra_ctx, + max_gas_per_txn: self.config.max_gas_per_txn, + address_gas_limiter: self.address_gas_limiter.clone(), + }) + } + + /// Constructs an Optimism payload from the transactions sent via the + /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in + /// the payload attributes, the transaction pool will be ignored and the only transactions + /// included in the payload will be those sent through the attributes. + /// + /// Given build arguments including an Optimism client, transaction pool, + /// and configuration, this function creates a transaction payload. Returns + /// a result indicating success with the payload or an error in case of failure. + async fn build_payload( + &self, + args: BuildArguments, OpBuiltPayload>, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + let block_build_start_time = Instant::now(); + let BuildArguments { + cached_reads: _, + config, + cancel: block_cancel, + } = args; + + // We log only every 100th block to reduce usage + let span = if cfg!(feature = "telemetry") + && config + .parent_header + .number + .is_multiple_of(self.config.sampling_ratio) + { + span!(Level::INFO, "build_payload") + } else { + tracing::Span::none() + }; + let _entered = span.enter(); + span.record("payload_id", config.attributes.id.to_string()); + + let timestamp = config.attributes.timestamp(); + let disable_state_root = self.config.specific.disable_state_root; + let ctx = self + .get_op_payload_builder_ctx( + config.clone(), + block_cancel.clone(), + FlashblocksExtraCtx { + target_flashblock_count: self.config.flashblocks_per_block(), + disable_state_root, + ..Default::default() + }, + ) + .map_err(|e| PayloadBuilderError::Other(e.into()))?; + + // The DB owns one StateProvider because `State` is held across awaits and + // `Box` is not `Sync` (so borrowing it would make + // the future non-`Send`). A second StateProvider is borrowed for builder-tx + // simulation calls below. + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let db_state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let db = StateProviderDatabase::new(db_state_provider); + self.address_gas_limiter.refresh(ctx.block_number()); + + // 1. execute the pre steps and seal an early block with that + let sequencer_tx_start_time = Instant::now(); + let mut state = State::builder() + .with_database(db) + .with_bundle_update() + .build(); + + let mut info = { + let mut builder = ctx.block_builder_for_next_block(&mut state)?; + builder.apply_pre_execution_changes()?; + let mut info = ctx.execute_sequencer_transactions(&mut builder)?; + let sequencer_tx_time = sequencer_tx_start_time.elapsed(); + ctx.metrics.sequencer_tx_duration.record(sequencer_tx_time); + ctx.metrics.sequencer_tx_gauge.set(sequencer_tx_time); + + // We add first builder tx right after deposits + if !ctx.attributes().no_tx_pool + && let Err(e) = self.builder_tx.add_builder_txs( + &state_provider, + &mut info, + &ctx, + &mut builder, + false, + ) + { + error!( + target: "payload_builder", + "Error adding builder txs to fallback block: {}", + e + ); + }; + + info + }; + + let (payload, fb_payload) = build_block( + &mut state, + &ctx, + &mut info, + !disable_state_root || ctx.attributes().no_tx_pool, // need to calculate state root for CL sync + )?; + + self.payload_tx + .try_send(payload.clone()) + .map_err(PayloadBuilderError::other)?; + best_payload.set(payload); + + info!( + target: "payload_builder", + message = "Fallback block built", + payload_id = fb_payload.payload_id.to_string(), + ); + + // not emitting flashblock if no_tx_pool in FCU, it's just syncing + if !ctx.attributes().no_tx_pool { + let flashblock_byte_size = self + .ws_pub + .publish(&fb_payload) + .map_err(PayloadBuilderError::other)?; + ctx.metrics + .flashblock_byte_size_histogram + .record(flashblock_byte_size as f64); + } + + if ctx.attributes().no_tx_pool { + info!( + target: "payload_builder", + "No transaction pool, skipping transaction pool processing", + ); + + let total_block_building_time = block_build_start_time.elapsed(); + ctx.metrics + .total_block_built_duration + .record(total_block_building_time); + ctx.metrics + .total_block_built_gauge + .set(total_block_building_time); + ctx.metrics + .payload_num_tx + .record(info.executed_transactions.len() as f64); + ctx.metrics + .payload_num_tx_gauge + .set(info.executed_transactions.len() as f64); + + // return early since we don't need to build a block with transactions from the pool + return Ok(()); + } + // We adjust our flashblocks timings based on time_drift if dynamic adjustment enable + let (flashblocks_per_block, first_flashblock_offset) = + self.calculate_flashblocks(timestamp); + info!( + target: "payload_builder", + message = "Performed flashblocks timing derivation", + flashblocks_per_block, + first_flashblock_offset = first_flashblock_offset.as_millis(), + flashblocks_interval = self.config.specific.interval.as_millis(), + ); + ctx.metrics.reduced_flashblocks_number.record( + self.config + .flashblocks_per_block() + .saturating_sub(ctx.target_flashblock_count()) as f64, + ); + ctx.metrics + .first_flashblock_time_offset + .record(first_flashblock_offset.as_millis() as f64); + let gas_per_batch = ctx.block_gas_limit() / flashblocks_per_block; + let da_per_batch = ctx + .da_config + .max_da_block_size() + .map(|da_limit| da_limit / flashblocks_per_block); + // Check that builder tx won't affect fb limit too much + if let Some(da_limit) = da_per_batch { + // We error if we can't insert any tx aside from builder tx in flashblock + if info.cumulative_da_bytes_used >= da_limit { + error!( + "Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included." + ); + } + } + let da_footprint_per_batch = info + .da_footprint_scalar + .map(|_| ctx.block_gas_limit() / flashblocks_per_block); + + let extra_ctx = FlashblocksExtraCtx { + flashblock_index: 1, + target_flashblock_count: flashblocks_per_block, + target_gas_for_batch: gas_per_batch, + target_da_for_batch: da_per_batch, + gas_per_batch, + da_per_batch, + da_footprint_per_batch, + disable_state_root, + target_da_footprint_for_batch: da_footprint_per_batch, + }; + + let mut fb_cancel = block_cancel.child_token(); + let mut ctx = self + .get_op_payload_builder_ctx(config, fb_cancel.clone(), extra_ctx) + .map_err(|e| PayloadBuilderError::Other(e.into()))?; + + // Create best_transaction iterator + let mut best_txs = BestFlashblocksTxs::new(BestPayloadTransactions::new( + self.pool + .best_transactions_with_attributes(ctx.best_transaction_attributes()), + )); + let interval = self.config.specific.interval; + let (tx, mut rx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); + + tokio::spawn({ + let block_cancel = block_cancel.clone(); + + async move { + let mut timer = tokio::time::interval_at( + tokio::time::Instant::now() + .checked_add(first_flashblock_offset) + .expect("can add flashblock offset to current time"), + interval, + ); + + loop { + tokio::select! { + _ = timer.tick() => { + // cancel current payload building job + fb_cancel.cancel(); + fb_cancel = block_cancel.child_token(); + // this will tick at first_flashblock_offset, + // starting the second flashblock + if tx.send(fb_cancel.clone()).await.is_err() { + // receiver channel was dropped, return. + // this will only happen if the `build_payload` function returns, + // due to payload building error or the main cancellation token being + // cancelled. + return; + } + } + _ = block_cancel.cancelled() => { + return; + } + } + } + } + }); + + // Process flashblocks in a blocking loop + loop { + let fb_span = if span.is_none() { + tracing::Span::none() + } else { + span!( + parent: &span, + Level::INFO, + "build_flashblock", + ) + }; + let _entered = fb_span.enter(); + + if ctx.flashblock_index() > ctx.target_flashblock_count() { + self.record_flashblocks_metrics( + &ctx, + &info, + flashblocks_per_block, + &span, + "Payload building complete, target flashblock count reached", + ); + return Ok(()); + } + + // build first flashblock immediately + let next_flashblocks_ctx = match self.build_next_flashblock( + &ctx, + &mut info, + &mut state, + &state_provider, + &mut best_txs, + &block_cancel, + &best_payload, + &fb_span, + ) { + Ok(Some(next_flashblocks_ctx)) => next_flashblocks_ctx, + Ok(None) => { + self.record_flashblocks_metrics( + &ctx, + &info, + flashblocks_per_block, + &span, + "Payload building complete, job cancelled or target flashblock count reached", + ); + return Ok(()); + } + Err(err) => { + error!( + target: "payload_builder", + "Failed to build flashblock {} for block number {}: {}", + ctx.flashblock_index(), + ctx.block_number(), + err + ); + return Err(PayloadBuilderError::Other(err.into())); + } + }; + + tokio::select! { + Some(fb_cancel) = rx.recv() => { + ctx = ctx.with_cancel(fb_cancel).with_extra_ctx(next_flashblocks_ctx); + }, + _ = block_cancel.cancelled() => { + self.record_flashblocks_metrics( + &ctx, + &info, + flashblocks_per_block, + &span, + "Payload building complete, channel closed or job cancelled", + ); + return Ok(()); + } + } + } + } + + #[allow(clippy::too_many_arguments)] + fn build_next_flashblock( + &self, + ctx: &OpPayloadBuilderCtx, + info: &mut ExecutionInfo, + state: &mut State, + state_provider: impl reth::providers::StateProvider + Clone, + best_txs: &mut NextBestFlashblocksTxs, + block_cancel: &CancellationToken, + best_payload: &BlockCell, + span: &tracing::Span, + ) -> eyre::Result> + where + DB: Database + std::fmt::Debug + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + { + let flashblock_index = ctx.flashblock_index(); + let mut target_gas_for_batch = ctx.extra_ctx.target_gas_for_batch; + let mut target_da_for_batch = ctx.extra_ctx.target_da_for_batch; + let mut target_da_footprint_for_batch = ctx.extra_ctx.target_da_footprint_for_batch; + + info!( + target: "payload_builder", + block_number = ctx.block_number(), + flashblock_index, + target_gas = target_gas_for_batch, + gas_used = info.cumulative_gas_used, + target_da = target_da_for_batch, + da_used = info.cumulative_da_bytes_used, + block_gas_used = ctx.block_gas_limit(), + target_da_footprint = target_da_footprint_for_batch, + "Building flashblock", + ); + let flashblock_build_start_time = Instant::now(); + + let mut builder = ctx.block_builder_for_next_block(state)?; + + let builder_txs = + match self + .builder_tx + .add_builder_txs(&state_provider, info, ctx, &mut builder, true) + { + Ok(builder_txs) => builder_txs, + Err(e) => { + error!(target: "payload_builder", "Error simulating builder txs: {}", e); + vec![] + } + }; + + // only reserve builder tx gas / da size that has not been committed yet + // committed builder txs would have counted towards the gas / da used + let builder_tx_gas = builder_txs + .iter() + .filter(|tx| !tx.is_top_of_block) + .fold(0, |acc, tx| acc + tx.gas_used); + let builder_tx_da_size: u64 = builder_txs + .iter() + .filter(|tx| !tx.is_top_of_block) + .fold(0, |acc, tx| acc + tx.da_size); + target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); + + // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size + if let Some(da_limit) = target_da_for_batch.as_mut() { + *da_limit = da_limit.saturating_sub(builder_tx_da_size); + } + + if let (Some(footprint), Some(scalar)) = ( + target_da_footprint_for_batch.as_mut(), + info.da_footprint_scalar, + ) { + *footprint = footprint.saturating_sub(builder_tx_da_size.saturating_mul(scalar as u64)); + } + + let best_txs_start_time = Instant::now(); + best_txs.refresh_iterator( + BestPayloadTransactions::new( + self.pool + .best_transactions_with_attributes(ctx.best_transaction_attributes()), + ), + flashblock_index, + ); + let transaction_pool_fetch_time = best_txs_start_time.elapsed(); + ctx.metrics + .transaction_pool_fetch_duration + .record(transaction_pool_fetch_time); + ctx.metrics + .transaction_pool_fetch_gauge + .set(transaction_pool_fetch_time); + + let tx_execution_start_time = Instant::now(); + ctx.execute_best_transactions( + info, + &mut builder, + best_txs, + target_gas_for_batch.min(ctx.block_gas_limit()), + target_da_for_batch, + target_da_footprint_for_batch, + ) + .wrap_err("failed to execute best transactions")?; + // Extract last transactions + let new_transactions = info.executed_transactions[info.extra.last_flashblock_index..] + .to_vec() + .iter() + .map(|tx| tx.tx_hash()) + .collect::>(); + best_txs.mark_commited(new_transactions); + + // We got block cancelled, we won't need anything from the block at this point + // Caution: this assume that block cancel token only cancelled when new FCU is received + if block_cancel.is_cancelled() { + self.record_flashblocks_metrics( + ctx, + info, + ctx.target_flashblock_count(), + span, + "Payload building complete, channel closed or job cancelled", + ); + return Ok(None); + } + + let payload_transaction_simulation_time = tx_execution_start_time.elapsed(); + ctx.metrics + .payload_transaction_simulation_duration + .record(payload_transaction_simulation_time); + ctx.metrics + .payload_transaction_simulation_gauge + .set(payload_transaction_simulation_time); + + if let Err(e) = + self.builder_tx + .add_builder_txs(&state_provider, info, ctx, &mut builder, false) + { + error!(target: "payload_builder", "Error simulating builder txs: {}", e); + }; + + drop(builder); + + let total_block_built_duration = Instant::now(); + let build_result = build_block( + state, + ctx, + info, + !ctx.extra_ctx.disable_state_root || ctx.attributes().no_tx_pool, + ); + let total_block_built_duration = total_block_built_duration.elapsed(); + ctx.metrics + .total_block_built_duration + .record(total_block_built_duration); + ctx.metrics + .total_block_built_gauge + .set(total_block_built_duration); + + match build_result { + Err(err) => { + ctx.metrics.invalid_built_blocks_count.increment(1); + Err(err).wrap_err("failed to build payload") + } + Ok((new_payload, mut fb_payload)) => { + fb_payload.index = flashblock_index; + fb_payload.base = None; + + // If main token got canceled in here that means we received get_payload and we should drop everything and now update best_payload + // To ensure that we will return same blocks as rollup-boost (to leverage caches) + if block_cancel.is_cancelled() { + self.record_flashblocks_metrics( + ctx, + info, + ctx.target_flashblock_count(), + span, + "Payload building complete, channel closed or job cancelled", + ); + return Ok(None); + } + let flashblock_byte_size = self + .ws_pub + .publish(&fb_payload) + .wrap_err("failed to publish flashblock via websocket")?; + self.payload_tx + .try_send(new_payload.clone()) + .wrap_err("failed to send built payload to handler")?; + best_payload.set(new_payload); + + // Record flashblock build duration + ctx.metrics + .flashblock_build_duration + .record(flashblock_build_start_time.elapsed()); + ctx.metrics + .flashblock_byte_size_histogram + .record(flashblock_byte_size as f64); + ctx.metrics + .flashblock_num_tx_histogram + .record(info.executed_transactions.len() as f64); + + // Update bundle_state for next iteration + if let Some(da_limit) = ctx.extra_ctx.da_per_batch { + if let Some(da) = target_da_for_batch.as_mut() { + *da += da_limit; + } else { + error!( + "Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set" + ); + } + } + + let target_gas_for_batch = + ctx.extra_ctx.target_gas_for_batch + ctx.extra_ctx.gas_per_batch; + + if let (Some(footprint), Some(da_footprint_limit)) = ( + target_da_footprint_for_batch.as_mut(), + ctx.extra_ctx.da_footprint_per_batch, + ) { + *footprint += da_footprint_limit; + } + + let next_extra_ctx = ctx.extra_ctx.clone().next( + target_gas_for_batch, + target_da_for_batch, + target_da_footprint_for_batch, + ); + + info!( + target: "payload_builder", + message = "Flashblock built", + flashblock_index = flashblock_index, + current_gas = info.cumulative_gas_used, + current_da = info.cumulative_da_bytes_used, + target_flashblocks = ctx.target_flashblock_count(), + ); + + Ok(Some(next_extra_ctx)) + } + } + } + + /// Do some logging and metric recording when we stop build flashblocks + fn record_flashblocks_metrics( + &self, + ctx: &OpPayloadBuilderCtx, + info: &ExecutionInfo, + flashblocks_per_block: u64, + span: &tracing::Span, + message: &str, + ) { + ctx.metrics.block_built_success.increment(1); + ctx.metrics + .flashblock_count + .record(ctx.flashblock_index() as f64); + ctx.metrics + .missing_flashblocks_count + .record(flashblocks_per_block.saturating_sub(ctx.flashblock_index()) as f64); + ctx.metrics + .payload_num_tx + .record(info.executed_transactions.len() as f64); + ctx.metrics + .payload_num_tx_gauge + .set(info.executed_transactions.len() as f64); + + debug!( + target: "payload_builder", + message = message, + flashblocks_per_block = flashblocks_per_block, + flashblock_index = ctx.flashblock_index(), + ); + + span.record("flashblock_count", ctx.flashblock_index()); + } + + /// Calculate number of flashblocks. + /// If dynamic is enabled this function will take time drift into the account. + pub(super) fn calculate_flashblocks(&self, timestamp: u64) -> (u64, Duration) { + if self.config.specific.fixed { + return ( + self.config.flashblocks_per_block(), + // We adjust first FB to ensure that we have at least some time to make all FB in time + self.config.specific.interval - self.config.specific.leeway_time, + ); + } + + // We use this system time to determine remining time to build a block + // Things to consider: + // FCU(a) - FCU with attributes + // FCU(a) could arrive with `block_time - fb_time < delay`. In this case we could only produce 1 flashblock + // FCU(a) could arrive with `delay < fb_time` - in this case we will shrink first flashblock + // FCU(a) could arrive with `fb_time < delay < block_time - fb_time` - in this case we will issue less flashblocks + let target_time = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp) + - self.config.specific.leeway_time; + let now = std::time::SystemTime::now(); + let Ok(time_drift) = target_time.duration_since(now) else { + error!( + target: "payload_builder", + message = "FCU arrived too late or system clock are unsynced", + ?target_time, + ?now, + ); + return ( + self.config.flashblocks_per_block(), + self.config.specific.interval, + ); + }; + self.metrics.flashblocks_time_drift.record( + self.config + .block_time + .as_millis() + .saturating_sub(time_drift.as_millis()) as f64, + ); + debug!( + target: "payload_builder", + message = "Time drift for building round", + ?target_time, + time_drift = self.config.block_time.as_millis().saturating_sub(time_drift.as_millis()), + ?timestamp + ); + // This is extra check to ensure that we would account at least for block time in case we have any timer discrepancies. + let time_drift = time_drift.min(self.config.block_time); + let interval = self.config.specific.interval.as_millis() as u64; + let time_drift = time_drift.as_millis() as u64; + let first_flashblock_offset = time_drift.rem(interval); + if first_flashblock_offset == 0 { + // We have perfect division, so we use interval as first fb offset + (time_drift.div(interval), Duration::from_millis(interval)) + } else { + // Non-perfect division, so we account for it. + ( + time_drift.div(interval) + 1, + Duration::from_millis(first_flashblock_offset), + ) + } + } +} + +#[async_trait::async_trait] +impl PayloadBuilder for OpPayloadBuilder +where + Pool: PoolBounds, + Client: ClientBounds, + BuilderTx: + BuilderTransactions + Clone + Send + Sync, +{ + type Attributes = OpPayloadAttrs; + type BuiltPayload = OpBuiltPayload; + + async fn try_build( + &self, + args: BuildArguments, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + let payload_id = args.config.payload_id; + let builder_attrs = OpPayloadBuilderAttributes::from_rpc_attrs( + args.config.parent_header.hash(), + payload_id, + args.config.attributes.0, + ) + .map_err(PayloadBuilderError::other)?; + let args = BuildArguments { + cached_reads: args.cached_reads, + config: PayloadConfig { + parent_header: args.config.parent_header, + attributes: builder_attrs, + payload_id, + }, + cancel: args.cancel, + }; + self.build_payload(args, best_payload).await + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct FlashblocksMetadata { + receipts: HashMap::Receipt>, + new_account_balances: HashMap, + block_number: u64, +} + +pub(super) fn build_block( + state: &mut State, + ctx: &OpPayloadBuilderCtx, + info: &mut ExecutionInfo, + calculate_state_root: bool, +) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError> +where + DB: Database + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + ExtraCtx: std::fmt::Debug + Default, +{ + // We use it to preserve state, so we run merge_transitions on transition state at most once + let untouched_transition_state = state.transition_state.clone(); + let state_merge_start_time = Instant::now(); + state.merge_transitions(BundleRetention::Reverts); + let state_transition_merge_time = state_merge_start_time.elapsed(); + ctx.metrics + .state_transition_merge_duration + .record(state_transition_merge_time); + ctx.metrics + .state_transition_merge_gauge + .set(state_transition_merge_time); + + let block_number = ctx.block_number(); + assert_eq!(block_number, ctx.parent().number + 1); + + let execution_outcome = ExecutionOutcome::new( + state.bundle_state.clone(), + vec![info.receipts.clone()], + block_number, + vec![], + ); + + let receipts_root = execution_outcome + .generic_receipts_root_slow(block_number, |receipts| { + calculate_receipt_root_no_memo_optimism( + receipts, + &ctx.chain_spec, + ctx.attributes().timestamp(), + ) + }) + .expect("Number is in range"); + let logs_bloom = execution_outcome + .block_logs_bloom(block_number) + .expect("Number is in range"); + + // TODO: maybe recreate state with bundle in here + // calculate the state root + let state_root_start_time = Instant::now(); + let mut state_root = B256::ZERO; + let mut trie_output = TrieUpdates::default(); + let mut hashed_state = HashedPostState::default(); + + if calculate_state_root { + let state_provider = state.database.as_ref(); + hashed_state = state_provider.hashed_post_state(execution_outcome.state()); + (state_root, trie_output) = { + state + .database + .as_ref() + .state_root_with_updates(hashed_state.clone()) + .inspect_err(|err| { + warn!(target: "payload_builder", + parent_header=%ctx.parent().hash(), + %err, + "failed to calculate state root for payload" + ); + })? + }; + let state_root_calculation_time = state_root_start_time.elapsed(); + ctx.metrics + .state_root_calculation_duration + .record(state_root_calculation_time); + ctx.metrics + .state_root_calculation_gauge + .set(state_root_calculation_time); + } + + let mut requests_hash = None; + let withdrawals_root = if ctx + .chain_spec + .is_isthmus_active_at_timestamp(ctx.attributes().timestamp()) + { + // always empty requests hash post isthmus + requests_hash = Some(EMPTY_REQUESTS_HASH); + + // withdrawals root field in block header is used for storage root of L2 predeploy + // `l2tol1-message-passer` + Some( + isthmus::withdrawals_root(execution_outcome.state(), state.database.as_ref()) + .map_err(PayloadBuilderError::other)?, + ) + } else if ctx + .chain_spec + .is_canyon_active_at_timestamp(ctx.attributes().timestamp()) + { + Some(EMPTY_WITHDRAWALS) + } else { + None + }; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); + + let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(info); + let block_gas_used = info.cumulative_gas_used; + let extra_data = ctx.extra_data()?; + + let header = Header { + parent_hash: ctx.parent().hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: ctx.evm_env.block_env.beneficiary, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp: ctx.attributes().timestamp(), + mix_hash: ctx.attributes().prev_randao(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: Some(ctx.base_fee()), + number: ctx.parent().number + 1, + gas_limit: ctx.block_gas_limit(), + difficulty: U256::ZERO, + gas_used: block_gas_used, + extra_data, + parent_beacon_block_root: ctx.attributes().parent_beacon_block_root(), + blob_gas_used, + excess_blob_gas, + requests_hash, + block_access_list_hash: None, + slot_number: None, + }; + + // seal the block + let block = alloy_consensus::Block::::new( + header, + BlockBody { + transactions: info.executed_transactions.clone(), + ommers: vec![], + withdrawals: ctx.withdrawals().cloned(), + }, + ); + + let recovered_block = + RecoveredBlock::new_unhashed(block.clone(), info.executed_senders.clone()); + // create the executed block data + + let execution_output = BlockExecutionOutput { + state: execution_outcome.bundle.clone(), + result: BlockExecutionResult { + receipts: execution_outcome + .receipts + .first() + .cloned() + .unwrap_or_default(), + requests: Default::default(), + gas_used: block_gas_used, + blob_gas_used: blob_gas_used.unwrap_or_default(), + }, + }; + let executed = BuiltPayloadExecutedBlock { + recovered_block: Arc::new(recovered_block), + execution_output: Arc::new(execution_output), + hashed_state: Arc::new(hashed_state), + trie_updates: Arc::new(trie_output), + }; + debug!(target: "payload_builder", message = "Executed block created"); + + let sealed_block = Arc::new(block.seal_slow()); + debug!(target: "payload_builder", ?sealed_block, "sealed built block"); + + let block_hash = sealed_block.hash(); + + // pick the new transactions from the info field and update the last flashblock index + let new_transactions = info.executed_transactions[info.extra.last_flashblock_index..].to_vec(); + + let new_transactions_encoded = new_transactions + .clone() + .into_iter() + .map(|tx| tx.encoded_2718().into()) + .collect::>(); + + let new_receipts = info.receipts[info.extra.last_flashblock_index..].to_vec(); + info.extra.last_flashblock_index = info.executed_transactions.len(); + let receipts_with_hash = new_transactions + .iter() + .zip(new_receipts.iter()) + .map(|(tx, receipt)| (tx.tx_hash(), receipt.clone())) + .collect::>(); + let new_account_balances = state + .bundle_state + .state + .iter() + .filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance))) + .collect::>(); + + let metadata: FlashblocksMetadata = FlashblocksMetadata { + receipts: receipts_with_hash, + new_account_balances, + block_number: ctx.parent().number + 1, + }; + + let (_, blob_gas_used) = ctx.blob_fields(info); + + // Prepare the flashblocks message + let fb_payload = FlashblocksPayloadV1 { + payload_id: ctx.payload_id(), + index: 0, + base: Some(ExecutionPayloadBaseV1 { + parent_beacon_block_root: ctx.attributes().parent_beacon_block_root().unwrap(), + parent_hash: ctx.parent().hash(), + fee_recipient: ctx.attributes().suggested_fee_recipient(), + prev_randao: ctx.attributes().prev_randao(), + block_number: ctx.parent().number + 1, + gas_limit: ctx.block_gas_limit(), + timestamp: ctx.attributes().timestamp(), + extra_data: ctx.extra_data()?, + base_fee_per_gas: ctx.base_fee().try_into().unwrap(), + }), + diff: ExecutionPayloadFlashblockDeltaV1 { + state_root, + receipts_root, + logs_bloom, + gas_used: block_gas_used, + block_hash, + transactions: new_transactions_encoded, + withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), + withdrawals_root: withdrawals_root.unwrap_or_default(), + blob_gas_used, + }, + metadata: serde_json::to_value(&metadata).unwrap_or_default(), + }; + + // We clean bundle and place initial state transaction back + state.take_bundle(); + state.transition_state = untouched_transition_state; + + Ok(( + OpBuiltPayload::new( + ctx.payload_id(), + sealed_block, + info.total_fees, + Some(executed), + ), + fb_payload, + )) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/payload_handler.rs new file mode 100644 index 00000000000..318e185f78d --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/payload_handler.rs @@ -0,0 +1,320 @@ +use crate::{ + builders::{ + context::last_receipt_with_cumulative_gas, + flashblocks::{ctx::OpPayloadSyncerCtx, p2p::Message, payload::FlashblocksExecutionInfo}, + }, + primitives::reth::ExecutionInfo, + traits::ClientBounds, +}; +use alloy_evm::block::BlockExecutor as AlloyBlockExecutor; +use alloy_primitives::B64; +use eyre::{WrapErr as _, bail}; +use reth::revm::{State, database::StateProviderDatabase}; +use reth_basic_payload_builder::PayloadConfig; +use reth_node_builder::Events; +use reth_optimism_evm::OpNextBlockEnvAttributes; +use reth_optimism_node::{OpEngineTypes, OpPayloadBuilderAttributes}; +use reth_optimism_payload_builder::OpBuiltPayload; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +use rollup_boost::FlashblocksPayloadV1; +use std::sync::Arc; +use tokio::sync::mpsc; +use tracing::warn; + +/// Handles newly built or received flashblock payloads. +/// +/// In the case of a payload built by this node, it is broadcast to peers and an event is sent to the payload builder. +/// In the case of a payload received from a peer, it is executed and if successful, an event is sent to the payload builder. +pub(crate) struct PayloadHandler { + // receives new payloads built by this builder. + built_rx: mpsc::Receiver, + // receives incoming p2p messages from peers. + p2p_rx: mpsc::Receiver, + // outgoing p2p channel to broadcast new payloads to peers. + p2p_tx: mpsc::Sender, + // sends a `Events::BuiltPayload` to the reth payload builder when a new payload is received. + payload_events_handle: tokio::sync::broadcast::Sender>, + // context required for execution of blocks during syncing + ctx: OpPayloadSyncerCtx, + // chain client + client: Client, + cancel: tokio_util::sync::CancellationToken, +} + +impl PayloadHandler +where + Client: ClientBounds + 'static, +{ + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + built_rx: mpsc::Receiver, + p2p_rx: mpsc::Receiver, + p2p_tx: mpsc::Sender, + payload_events_handle: tokio::sync::broadcast::Sender>, + ctx: OpPayloadSyncerCtx, + client: Client, + cancel: tokio_util::sync::CancellationToken, + ) -> Self { + Self { + built_rx, + p2p_rx, + p2p_tx, + payload_events_handle, + ctx, + client, + cancel, + } + } + + pub(crate) async fn run(self) { + let Self { + mut built_rx, + mut p2p_rx, + p2p_tx, + payload_events_handle, + ctx, + client, + cancel, + } = self; + + tracing::debug!("flashblocks payload handler started"); + + loop { + tokio::select! { + Some(payload) = built_rx.recv() => { + if let Err(e) = payload_events_handle.send(Events::BuiltPayload(payload.clone())) { + warn!(e = ?e, "failed to send BuiltPayload event"); + } + // ignore error here; if p2p was disabled, the channel will be closed. + let _ = p2p_tx.send(payload.into()).await; + } + Some(message) = p2p_rx.recv() => { + match message { + Message::OpBuiltPayload(payload) => { + let payload: OpBuiltPayload = payload.into(); + let ctx = ctx.clone(); + let client = client.clone(); + let payload_events_handle = payload_events_handle.clone(); + let cancel = cancel.clone(); + + // execute the flashblock on a thread where blocking is acceptable, + // as it's potentially a heavy operation + tokio::task::spawn_blocking(move || { + let res = execute_flashblock( + payload, + ctx, + client, + cancel, + ); + match res { + Ok((payload, _)) => { + tracing::info!(hash = payload.block().hash().to_string(), block_number = payload.block().header().number, "successfully executed received flashblock"); + if let Err(e) = payload_events_handle.send(Events::BuiltPayload(payload)) { + warn!(e = ?e, "failed to send BuiltPayload event on synced block"); + } + } + Err(e) => { + tracing::error!(error = ?e, "failed to execute received flashblock"); + } + } + }); + } + } + } + else => break, + } + } + } +} + +fn execute_flashblock( + payload: OpBuiltPayload, + ctx: OpPayloadSyncerCtx, + client: Client, + cancel: tokio_util::sync::CancellationToken, +) -> eyre::Result<(OpBuiltPayload, FlashblocksPayloadV1)> +where + Client: ClientBounds, +{ + use alloy_consensus::BlockHeader as _; + use reth_evm::{ConfigureEvm as _, execute::BlockBuilder as _}; + use reth_primitives_traits::SealedHeader; + + let start = tokio::time::Instant::now(); + + tracing::info!(header = ?payload.block().header(), "executing flashblock"); + + let mut cached_reads = reth::revm::cached::CachedReads::default(); + let parent_hash = payload.block().sealed_header().parent_hash; + let parent_header = client + .header_by_id(parent_hash.into()) + .wrap_err("failed to get parent header")? + .ok_or_else(|| eyre::eyre!("parent header not found"))?; + + let state_provider = client + .state_by_block_hash(parent_hash) + .wrap_err("failed to get state for parent hash")?; + let db = StateProviderDatabase::new(&state_provider); + let mut state = State::builder() + .with_database(cached_reads.as_db_mut(db)) + .with_bundle_update() + .build(); + + let timestamp = payload.block().header().timestamp(); + let block_env_attributes = OpNextBlockEnvAttributes { + timestamp, + suggested_fee_recipient: payload.block().sealed_header().beneficiary, + prev_randao: payload.block().sealed_header().mix_hash, + gas_limit: payload.block().sealed_header().gas_limit, + parent_beacon_block_root: payload.block().sealed_header().parent_beacon_block_root, + extra_data: payload.block().sealed_header().extra_data.clone(), + }; + + let evm_env = ctx + .evm_config() + .next_evm_env(&parent_header, &block_env_attributes) + .wrap_err("failed to create next evm env")?; + + let mut info = ExecutionInfo::with_capacity(payload.block().body().transactions.len()); + + let extra_data = payload.block().sealed_header().extra_data.clone(); + if extra_data.len() != 9 { + tracing::error!(len = extra_data.len(), data = ?extra_data, "invalid extra data length in flashblock"); + bail!("extra data length should be 9 bytes"); + } + + // see https://specs.optimism.io/protocol/holocene/exec-engine.html#eip-1559-parameters-in-block-header + let eip_1559_parameters: B64 = extra_data[1..9].try_into().unwrap(); + let payload_config = PayloadConfig::new( + Arc::new(SealedHeader::new(parent_header.clone(), parent_hash)), + OpPayloadBuilderAttributes { + id: payload.id(), + parent: parent_hash, + timestamp, + suggested_fee_recipient: payload.block().sealed_header().beneficiary, + prev_randao: payload.block().sealed_header().mix_hash, + withdrawals: payload + .block() + .body() + .withdrawals + .clone() + .unwrap_or_default(), + parent_beacon_block_root: payload.block().sealed_header().parent_beacon_block_root, + no_tx_pool: false, + transactions: Vec::new(), + gas_limit: Some(payload.block().sealed_header().gas_limit), + eip_1559_params: Some(eip_1559_parameters), + min_base_fee: None, + }, + payload.id(), + ); + + let max_gas_per_txn = ctx.max_gas_per_txn(); + let builder_ctx = ctx.into_op_payload_builder_ctx( + payload_config, + evm_env.clone(), + block_env_attributes, + cancel, + ); + + { + let mut builder = builder_ctx + .block_builder_for_next_block(&mut state) + .wrap_err("failed to create evm builder for next block")?; + builder + .apply_pre_execution_changes() + .wrap_err("failed to apply pre execution changes")?; + execute_transactions( + &mut info, + &mut builder, + payload.block().body().transactions.clone(), + payload.block().header().gas_used, + max_gas_per_txn, + ) + .wrap_err("failed to execute flashblock transactions")?; + } + + let (built_payload, fb_payload) = crate::builders::flashblocks::payload::build_block( + &mut state, + &builder_ctx, + &mut info, + true, + ) + .wrap_err("failed to build flashblock")?; + + builder_ctx + .metrics + .flashblock_sync_duration + .record(start.elapsed()); + + if built_payload.block().hash() != payload.block().hash() { + tracing::error!( + expected = %payload.block().hash(), + got = %built_payload.block().hash(), + "flashblock hash mismatch after execution" + ); + builder_ctx.metrics.invalid_synced_blocks_count.increment(1); + bail!("flashblock hash mismatch after execution"); + } + + builder_ctx.metrics.block_synced_success.increment(1); + + tracing::info!(header = ?built_payload.block().header(), "successfully executed flashblock"); + Ok((built_payload, fb_payload)) +} + +fn execute_transactions( + info: &mut ExecutionInfo, + builder: &mut Builder, + txs: Vec, + gas_limit: u64, + max_gas_per_txn: Option, +) -> eyre::Result<()> +where + Builder: reth_evm::execute::BlockBuilder, + Builder::Executor: AlloyBlockExecutor, +{ + use reth_primitives_traits::SignerRecoverable as _; + + for tx in txs { + let sender = tx + .recover_signer() + .wrap_err("failed to recover tx signer")?; + + let gas_used = builder + .execute_transaction(reth_primitives_traits::Recovered::new_unchecked( + tx.clone(), + sender, + )) + .wrap_err("failed to execute flashblock transaction")?; + + if let Some(max_gas_per_txn) = max_gas_per_txn + && gas_used.tx_gas_used() > max_gas_per_txn + { + return Err(eyre::eyre!( + "transaction exceeded max gas per txn limit in flashblock" + )); + } + + info.cumulative_gas_used = info + .cumulative_gas_used + .checked_add(gas_used.tx_gas_used()) + .ok_or_else(|| { + eyre::eyre!("total gas used overflowed when executing flashblock transactions") + })?; + if info.cumulative_gas_used > gas_limit { + bail!("flashblock exceeded gas limit when executing transactions"); + } + + info.receipts.push( + last_receipt_with_cumulative_gas(builder.executor(), info.cumulative_gas_used) + .ok_or_else(|| { + eyre::eyre!("missing receipt for executed flashblock transaction") + })?, + ); + info.executed_senders.push(sender); + info.executed_transactions.push(tx.clone()); + } + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/service.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/service.rs new file mode 100644 index 00000000000..052efe7b92c --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/service.rs @@ -0,0 +1,218 @@ +use super::{FlashblocksConfig, payload::OpPayloadBuilder}; +use crate::{ + builders::{ + BuilderConfig, + builder_tx::BuilderTransactions, + flashblocks::{ + builder_tx::{FlashblocksBuilderTx, FlashblocksNumberBuilderTx}, + p2p::{AGENT_VERSION, FLASHBLOCKS_STREAM_PROTOCOL, Message}, + payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, + payload_handler::PayloadHandler, + wspub::WebSocketPublisher, + }, + generator::BlockPayloadJobGenerator, + }, + flashtestations::service::bootstrap_flashtestations, + metrics::OpRBuilderMetrics, + traits::{NodeBounds, PoolBounds}, +}; +use eyre::WrapErr as _; +use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; +use reth_node_api::NodeTypes; +use reth_node_builder::{BuilderContext, components::PayloadServiceBuilder}; +use reth_optimism_evm::OpEvmConfig; +use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; +use reth_provider::CanonStateSubscriptions; +use std::sync::Arc; + +pub struct FlashblocksServiceBuilder(pub BuilderConfig); + +impl FlashblocksServiceBuilder { + fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + pool: Pool, + builder_tx: BuilderTx, + ) -> eyre::Result::Payload>> + where + Node: NodeBounds, + Pool: PoolBounds, + BuilderTx: BuilderTransactions + + Unpin + + Clone + + Send + + Sync + + 'static, + { + // TODO: is there a different global token? + // this is effectively unused right now due to the usage of reth's `task_executor`. + let cancel = tokio_util::sync::CancellationToken::new(); + + let (incoming_message_rx, outgoing_message_tx) = if self.0.specific.p2p_enabled { + let mut builder = p2p::NodeBuilder::new(); + + if let Some(ref private_key_file) = self.0.specific.p2p_private_key_file + && !private_key_file.is_empty() + { + let private_key_hex = std::fs::read_to_string(private_key_file) + .wrap_err_with(|| { + format!("failed to read p2p private key file: {private_key_file}") + })? + .trim() + .to_string(); + builder = builder.with_keypair_hex_string(private_key_hex); + } + + let known_peers: Vec = + if let Some(ref p2p_known_peers) = self.0.specific.p2p_known_peers { + p2p_known_peers + .split(',') + .filter_map(|s| s.parse().ok()) + .collect() + } else { + vec![] + }; + + let p2p::NodeBuildResult { + node, + outgoing_message_tx, + mut incoming_message_rxs, + } = builder + .with_agent_version(AGENT_VERSION.to_string()) + .with_protocol(FLASHBLOCKS_STREAM_PROTOCOL) + .with_known_peers(known_peers) + .with_port(self.0.specific.p2p_port) + .with_cancellation_token(cancel.clone()) + .with_max_peer_count(self.0.specific.p2p_max_peer_count) + .try_build::() + .wrap_err("failed to build flashblocks p2p node")?; + let multiaddrs = node.multiaddrs(); + ctx.task_executor().spawn_task(async move { + if let Err(e) = node.run().await { + tracing::error!(error = %e, "p2p node exited"); + } + }); + tracing::info!(multiaddrs = ?multiaddrs, "flashblocks p2p node started"); + + let incoming_message_rx = incoming_message_rxs + .remove(&FLASHBLOCKS_STREAM_PROTOCOL) + .expect("flashblocks p2p protocol must be found in receiver map"); + (incoming_message_rx, outgoing_message_tx) + } else { + let (_incoming_message_tx, incoming_message_rx) = tokio::sync::mpsc::channel(16); + let (outgoing_message_tx, _outgoing_message_rx) = tokio::sync::mpsc::channel(16); + (incoming_message_rx, outgoing_message_tx) + }; + + let metrics = Arc::new(OpRBuilderMetrics::default()); + let (built_payload_tx, built_payload_rx) = tokio::sync::mpsc::channel(16); + let evm_config = OpEvmConfig::optimism(ctx.chain_spec()); + + let ws_pub: Arc = + WebSocketPublisher::new(self.0.specific.ws_addr, metrics.clone()) + .wrap_err("failed to create ws publisher")? + .into(); + let payload_builder = OpPayloadBuilder::new( + evm_config.clone(), + pool, + ctx.provider().clone(), + self.0.clone(), + builder_tx, + built_payload_tx, + ws_pub.clone(), + metrics.clone(), + ); + let payload_job_config = BasicPayloadJobGeneratorConfig::default(); + + let payload_generator = BlockPayloadJobGenerator::with_builder( + ctx.provider().clone(), + ctx.task_executor().clone(), + payload_job_config, + payload_builder, + true, + self.0.block_time_leeway, + ); + + let (payload_service, payload_builder_handle) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + let syncer_ctx = crate::builders::flashblocks::ctx::OpPayloadSyncerCtx::new( + &ctx.provider().clone(), + self.0, + evm_config, + metrics.clone(), + ) + .wrap_err("failed to create flashblocks payload builder context")?; + + let payload_handler = PayloadHandler::new( + built_payload_rx, + incoming_message_rx, + outgoing_message_tx, + payload_service.payload_events_handle(), + syncer_ctx, + ctx.provider().clone(), + cancel, + ); + + ctx.task_executor() + .spawn_critical_task("custom payload builder service", payload_service); + ctx.task_executor() + .spawn_critical_task("flashblocks payload handler", payload_handler.run()); + + tracing::info!("Flashblocks payload builder service started"); + Ok(payload_builder_handle) + } +} + +impl PayloadServiceBuilder for FlashblocksServiceBuilder +where + Node: NodeBounds, + Pool: PoolBounds, +{ + async fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + pool: Pool, + _: OpEvmConfig, + ) -> eyre::Result::Payload>> { + let signer = self.0.builder_signer; + let flashtestations_builder_tx = if let Some(builder_key) = signer + && self.0.flashtestations_config.flashtestations_enabled + { + match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key) + .await + { + Ok(builder_tx) => Some(builder_tx), + Err(e) => { + tracing::warn!(error = %e, "Failed to bootstrap flashtestations, builder will not include flashtestations txs"); + None + } + } + } else { + None + }; + + if let Some(builder_signer) = signer + && let Some(flashblocks_number_contract_address) = + self.0.specific.flashblocks_number_contract_address + { + let use_permit = self.0.specific.flashblocks_number_contract_use_permit; + self.spawn_payload_builder_service( + ctx, + pool, + FlashblocksNumberBuilderTx::new( + builder_signer, + flashblocks_number_contract_address, + use_permit, + flashtestations_builder_tx, + ), + ) + } else { + self.spawn_payload_builder_service( + ctx, + pool, + FlashblocksBuilderTx::new(signer, flashtestations_builder_tx), + ) + } + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/wspub.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/wspub.rs new file mode 100644 index 00000000000..e2f003dc114 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -0,0 +1,238 @@ +use core::{ + fmt::{Debug, Formatter}, + net::SocketAddr, + sync::atomic::{AtomicUsize, Ordering}, +}; +use futures::SinkExt; +use futures_util::StreamExt; +use rollup_boost::FlashblocksPayloadV1; +use std::{io, net::TcpListener, sync::Arc}; +use tokio::{ + net::TcpStream, + sync::{ + broadcast::{self, Receiver, error::RecvError}, + watch, + }, +}; +use tokio_tungstenite::{ + WebSocketStream, accept_async, + tungstenite::{Message, Utf8Bytes}, +}; +use tracing::{debug, warn}; + +use crate::metrics::OpRBuilderMetrics; + +/// A WebSockets publisher that accepts connections from client websockets and broadcasts to them +/// updates about new flashblocks. It maintains a count of sent messages and active subscriptions. +/// +/// This is modelled as a `futures::Sink` that can be used to send `FlashblocksPayloadV1` messages. +pub(super) struct WebSocketPublisher { + sent: Arc, + subs: Arc, + term: watch::Sender, + pipe: broadcast::Sender, +} + +impl WebSocketPublisher { + pub(super) fn new(addr: SocketAddr, metrics: Arc) -> io::Result { + let (pipe, _) = broadcast::channel(100); + let (term, _) = watch::channel(false); + + let sent = Arc::new(AtomicUsize::new(0)); + let subs = Arc::new(AtomicUsize::new(0)); + let listener = TcpListener::bind(addr)?; + + tokio::spawn(listener_loop( + listener, + metrics, + pipe.subscribe(), + term.subscribe(), + Arc::clone(&sent), + Arc::clone(&subs), + )); + + Ok(Self { + sent, + subs, + term, + pipe, + }) + } + + pub(super) fn publish(&self, payload: &FlashblocksPayloadV1) -> io::Result { + // Serialize the payload to a UTF-8 string + // serialize only once, then just copy around only a pointer + // to the serialized data for each subscription. + debug!( + target: "payload_builder", + message = "Sending flashblock to rollup-boost", + payload_id = payload.payload_id.to_string(), + index = payload.index, + base = payload.base.is_some(), + ); + + let serialized = serde_json::to_string(payload)?; + let utf8_bytes = Utf8Bytes::from(serialized); + let size = utf8_bytes.len(); + // Send the serialized payload to all subscribers + self.pipe + .send(utf8_bytes) + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionAborted, e))?; + Ok(size) + } +} + +impl Drop for WebSocketPublisher { + fn drop(&mut self) { + // Notify the listener loop to terminate + let _ = self.term.send(true); + tracing::info!("WebSocketPublisher dropped, terminating listener loop"); + } +} + +async fn listener_loop( + listener: TcpListener, + metrics: Arc, + receiver: Receiver, + term: watch::Receiver, + sent: Arc, + subs: Arc, +) { + listener + .set_nonblocking(true) + .expect("Failed to set TcpListener socket to non-blocking"); + + let listener = tokio::net::TcpListener::from_std(listener) + .expect("Failed to convert TcpListener to tokio TcpListener"); + + let listen_addr = listener + .local_addr() + .expect("Failed to get local address of listener"); + tracing::info!("Flashblocks WebSocketPublisher listening on {listen_addr}"); + + let mut term = term; + + loop { + let subs = Arc::clone(&subs); + let metrics = Arc::clone(&metrics); + + tokio::select! { + // drop this connection if the `WebSocketPublisher` is dropped + _ = term.changed() => { + if *term.borrow() { + return; + } + } + + // Accept new connections on the websocket listener + // when a new connection is established, spawn a dedicated task to handle + // the connection and broadcast with that connection. + Ok((connection, peer_addr)) = listener.accept() => { + let sent = Arc::clone(&sent); + let term = term.clone(); + let receiver_clone = receiver.resubscribe(); + + match accept_async(connection).await { + Ok(stream) => { + tokio::spawn(async move { + subs.fetch_add(1, Ordering::Relaxed); + tracing::debug!("WebSocket connection established with {}", peer_addr); + + // Handle the WebSocket connection in a dedicated task + broadcast_loop(stream, metrics, term, receiver_clone, sent).await; + + subs.fetch_sub(1, Ordering::Relaxed); + tracing::debug!("WebSocket connection closed for {}", peer_addr); + }); + } + Err(e) => { + warn!("Failed to accept WebSocket connection from {peer_addr}: {e}"); + } + } + } + } + } +} + +/// An instance of this loop is spawned for each connected WebSocket client. +/// It listens for broadcast updates about new flashblocks and sends them to the client. +/// It also handles termination signals to gracefully close the connection. +/// Any connectivity errors will terminate the loop, which will in turn +/// decrement the subscription count in the `WebSocketPublisher`. +async fn broadcast_loop( + stream: WebSocketStream, + metrics: Arc, + term: watch::Receiver, + blocks: broadcast::Receiver, + sent: Arc, +) { + let mut term = term; + let mut blocks = blocks; + let mut stream = stream; + let Ok(peer_addr) = stream.get_ref().peer_addr() else { + return; + }; + + loop { + let metrics = Arc::clone(&metrics); + + tokio::select! { + // Check if the publisher is terminated + _ = term.changed() => { + if *term.borrow() { + tracing::info!("WebSocketPublisher is terminating, closing broadcast loop"); + return; + } + } + + // Receive payloads from the broadcast channel + payload = blocks.recv() => match payload { + Ok(payload) => { + // Here you would typically send the payload to the WebSocket clients. + // For this example, we just increment the sent counter. + sent.fetch_add(1, Ordering::Relaxed); + metrics.messages_sent_count.increment(1); + + tracing::debug!("Broadcasted payload: {:?}", payload); + if let Err(e) = stream.send(Message::Text(payload)).await { + tracing::debug!("Closing flashblocks subscription for {peer_addr}: {e}"); + break; // Exit the loop if sending fails + } + } + Err(RecvError::Closed) => { + tracing::debug!("Broadcast channel closed, exiting broadcast loop"); + return; + } + Err(RecvError::Lagged(_)) => { + tracing::warn!("Broadcast channel lagged, some messages were dropped"); + } + }, + + // Ping-pong handled by tokio_tungstenite when you perform read on the socket + message = stream.next() => if let Some(message) = message { match message { + // We handle only close frame to highlight conn closing + Ok(Message::Close(_)) => { + tracing::info!("Closing frame received, stopping connection for {peer_addr}"); + break; + } + Err(e) => { + tracing::warn!("Received error. Closing flashblocks subscription for {peer_addr}: {e}"); + break; + } + _ => (), + } } + } + } +} + +impl Debug for WebSocketPublisher { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let subs = self.subs.load(Ordering::Relaxed); + let sent = self.sent.load(Ordering::Relaxed); + + f.debug_struct("WebSocketPublisher") + .field("subs", &subs) + .field("payloads_sent", &sent) + .finish() + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/generator.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/generator.rs new file mode 100644 index 00000000000..9c1f661c06b --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/generator.rs @@ -0,0 +1,741 @@ +use alloy_primitives::B256; +use futures_util::{Future, FutureExt}; +use reth::providers::{BlockReaderIdExt, StateProviderFactory}; +use reth_basic_payload_builder::{ + BasicPayloadJobGeneratorConfig, HeaderForPayload, PayloadConfig, PrecachedState, +}; +use reth_node_api::NodePrimitives; +use reth_payload_builder::{ + BuildNewPayload, KeepPayloadJobAlive, PayloadBuilderError, PayloadId, PayloadJob, + PayloadJobGenerator, +}; +use reth_payload_primitives::{BuiltPayload, PayloadAttributes, PayloadKind}; +use reth_primitives_traits::HeaderTy; +use reth_provider::CanonStateNotification; +use reth_revm::cached::CachedReads; +use std::{ + sync::{Arc, Mutex}, + time::{SystemTime, UNIX_EPOCH}, +}; +use tokio::{ + sync::{Notify, oneshot}, + time::{Duration, Sleep}, +}; +use tokio_util::sync::CancellationToken; +use tracing::info; + +pub(super) trait TaskSpawner: Clone + Send + Sync + 'static { + fn spawn_task(&self, fut: F) + where + F: Future + Send + 'static; +} + +impl TaskSpawner for reth_tasks::Runtime { + fn spawn_task(&self, fut: F) + where + F: Future + Send + 'static, + { + std::mem::drop(reth_tasks::Runtime::spawn_task(self, fut)); + } +} + +/// A trait for building payloads that encapsulate Ethereum transactions. +/// +/// This trait provides the `try_build` method to construct a transaction payload +/// using `BuildArguments`. It returns a `Result` indicating success or a +/// `PayloadBuilderError` if building fails. +/// +/// Generic parameters `Pool` and `Client` represent the transaction pool and +/// Ethereum client types. +#[async_trait::async_trait] +pub(super) trait PayloadBuilder: Send + Sync + Clone { + /// The payload attributes type to accept for building. + type Attributes: PayloadAttributes; + /// The type of the built payload. + type BuiltPayload: BuiltPayload; + + /// Tries to build a transaction payload using provided arguments. + /// + /// Constructs a transaction payload based on the given arguments, + /// returning a `Result` indicating success or an error if building fails. + /// + /// # Arguments + /// + /// - `args`: Build arguments containing necessary components. + /// + /// # Returns + /// + /// A `Result` indicating the build outcome or an error. + async fn try_build( + &self, + args: BuildArguments, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError>; +} + +/// The generator type that creates new jobs that builds empty blocks. +#[derive(Debug)] +pub(super) struct BlockPayloadJobGenerator { + /// The client that can interact with the chain. + client: Client, + /// How to spawn building tasks + executor: Tasks, + /// The configuration for the job generator. + _config: BasicPayloadJobGeneratorConfig, + /// The type responsible for building payloads. + /// + /// See [PayloadBuilder] + builder: Builder, + /// Whether to ensure only one payload is being processed at a time + ensure_only_one_payload: bool, + /// The last payload being processed + last_payload: Arc>, + /// The extra block deadline in seconds + extra_block_deadline: std::time::Duration, + /// Stored `cached_reads` for new payload jobs. + pre_cached: Option, +} + +// === impl EmptyBlockPayloadJobGenerator === + +impl BlockPayloadJobGenerator { + /// Creates a new [EmptyBlockPayloadJobGenerator] with the given config and custom + /// [PayloadBuilder] + pub(super) fn with_builder( + client: Client, + executor: Tasks, + config: BasicPayloadJobGeneratorConfig, + builder: Builder, + ensure_only_one_payload: bool, + extra_block_deadline: std::time::Duration, + ) -> Self { + Self { + client, + executor, + _config: config, + builder, + ensure_only_one_payload, + last_payload: Arc::new(Mutex::new(CancellationToken::new())), + extra_block_deadline, + pre_cached: None, + } + } + + /// Returns the pre-cached reads for the given parent header if it matches the cached state's + /// block. + fn maybe_pre_cached(&self, parent: B256) -> Option { + self.pre_cached + .as_ref() + .filter(|pc| pc.block == parent) + .map(|pc| pc.cached.clone()) + } +} + +impl PayloadJobGenerator + for BlockPayloadJobGenerator +where + Client: StateProviderFactory + + BlockReaderIdExt

> + + Clone + + Unpin + + 'static, + Tasks: TaskSpawner + Clone + Unpin + 'static, + Builder: PayloadBuilder + Unpin + 'static, + Builder::Attributes: Unpin + Clone, + Builder::BuiltPayload: Unpin + Clone, +{ + type Job = BlockPayloadJob; + + /// This is invoked when the node receives payload attributes from the beacon node via + /// `engine_forkchoiceUpdatedVX` + fn new_payload_job( + &self, + input: BuildNewPayload<::Attributes>, + id: PayloadId, + ) -> Result { + let cancel_token = if self.ensure_only_one_payload { + // Cancel existing payload + { + let last_payload = self.last_payload.lock().unwrap(); + last_payload.cancel(); + } + + // Create and set new cancellation token with a fresh lock + let cancel_token = CancellationToken::new(); + { + let mut last_payload = self.last_payload.lock().unwrap(); + *last_payload = cancel_token.clone(); + } + cancel_token + } else { + CancellationToken::new() + }; + + let parent_header = if input.parent_hash.is_zero() { + self.client + .latest_header()? + .ok_or_else(|| PayloadBuilderError::MissingParentHeader(B256::ZERO))? + } else { + self.client + .sealed_header_by_hash(input.parent_hash)? + .ok_or_else(|| PayloadBuilderError::MissingParentHeader(input.parent_hash))? + }; + + info!("Spawn block building job"); + + // The deadline is critical for payload availability. If we reach the deadline, + // the payload job stops and cannot be queried again. With tight deadlines close + // to the block number, we risk reaching the deadline before the node queries the payload. + // + // Adding 0.5 seconds as wiggle room since block times are shorter here. + // TODO: A better long-term solution would be to implement cancellation logic + // that cancels existing jobs when receiving new block building requests. + // + // When batcher's max channel duration is big enough (e.g. 10m), the + // sequencer would send an avalanche of FCUs/getBlockByNumber on + // each batcher update (with 10m channel it's ~800 FCUs at once). + // At such moment it can happen that the time b/w FCU and ensuing + // getPayload would be on the scale of ~2.5s. Therefore we should + // "remember" the payloads long enough to accommodate this corner-case + // (without it we are losing blocks). Postponing the deadline for 5s + // (not just 0.5s) because of that. + let deadline = job_deadline(input.attributes.timestamp()) + self.extra_block_deadline; + + let deadline = Box::pin(tokio::time::sleep(deadline)); + let config = PayloadConfig::new(Arc::new(parent_header.clone()), input.attributes, id); + + let mut job = BlockPayloadJob { + executor: self.executor.clone(), + builder: self.builder.clone(), + config, + cell: BlockCell::new(), + cancel: cancel_token, + deadline, + build_complete: None, + cached_reads: self.maybe_pre_cached(parent_header.hash()), + }; + + job.spawn_build_job(); + + Ok(job) + } + + fn on_new_state(&mut self, new_state: CanonStateNotification) { + let mut cached = CachedReads::default(); + + // extract the state from the notification and put it into the cache + let committed = new_state.committed(); + let new_execution_outcome = committed.execution_outcome(); + for (addr, acc) in new_execution_outcome.bundle_accounts_iter() { + if let Some(info) = acc.info.clone() { + // we want pre cache existing accounts and their storage + // this only includes changed accounts and storage but is better than nothing + let storage = acc + .storage + .iter() + .map(|(key, slot)| (*key, slot.present_value)) + .collect(); + cached.insert_account(addr, info, storage); + } + } + + self.pre_cached = Some(PrecachedState { + block: committed.tip().hash(), + cached, + }); + } +} + +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +/// A [PayloadJob] that builds empty blocks. +pub(super) struct BlockPayloadJob +where + Builder: PayloadBuilder, +{ + /// The configuration for how the payload will be created. + pub(crate) config: PayloadConfig>, + /// How to spawn building tasks + pub(crate) executor: Tasks, + /// The type responsible for building payloads. + /// + /// See [PayloadBuilder] + pub(crate) builder: Builder, + /// The cell that holds the built payload. + pub(crate) cell: BlockCell, + /// Cancellation token for the running job + pub(crate) cancel: CancellationToken, + pub(crate) deadline: Pin>, // Add deadline + pub(crate) build_complete: Option>>, + /// Caches all disk reads for the state the new payloads builds on + /// + /// This is used to avoid reading the same state over and over again when new attempts are + /// triggered, because during the building process we'll repeatedly execute the transactions. + pub(crate) cached_reads: Option, +} + +impl PayloadJob for BlockPayloadJob +where + Tasks: TaskSpawner + Clone + Unpin + 'static, + Builder: PayloadBuilder + Unpin + 'static, + Builder::Attributes: Unpin + Clone, + Builder::BuiltPayload: Unpin + Clone, +{ + type PayloadAttributes = Builder::Attributes; + type ResolvePayloadFuture = ResolvePayload; + type BuiltPayload = Builder::BuiltPayload; + + fn best_payload(&self) -> Result { + unimplemented!() + } + + fn payload_attributes(&self) -> Result { + Ok(self.config.attributes.clone()) + } + + fn resolve_kind( + &mut self, + kind: PayloadKind, + ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) { + tracing::info!("Resolve kind {:?}", kind); + + // check if self.cell has a payload + self.cancel.cancel(); + + let resolve_future = ResolvePayload::new(self.cell.wait_for_value()); + (resolve_future, KeepPayloadJobAlive::No) + } +} + +pub(super) struct BuildArguments { + /// Previously cached disk reads + pub cached_reads: CachedReads, + /// How to configure the payload. + pub config: PayloadConfig>, + /// A marker that can be used to cancel the job. + pub cancel: CancellationToken, +} + +/// A [PayloadJob] is a future that's being polled by the `PayloadBuilderService` +impl BlockPayloadJob +where + Tasks: TaskSpawner + Clone + Unpin + 'static, + Builder: PayloadBuilder + Unpin + 'static, + Builder::Attributes: Unpin + Clone, + Builder::BuiltPayload: Unpin + Clone, +{ + pub(super) fn spawn_build_job(&mut self) { + let builder = self.builder.clone(); + let payload_config = self.config.clone(); + let cell = self.cell.clone(); + let cancel = self.cancel.clone(); + + let (tx, rx) = oneshot::channel(); + self.build_complete = Some(rx); + let cached_reads = self.cached_reads.take().unwrap_or_default(); + self.executor.spawn_task(async move { + let args = BuildArguments { + cached_reads, + config: payload_config, + cancel, + }; + + let result = builder.try_build(args, cell).await; + let _ = tx.send(result); + }); + } +} + +/// A [PayloadJob] is a a future that's being polled by the `PayloadBuilderService` +impl Future for BlockPayloadJob +where + Tasks: TaskSpawner + Clone + Unpin + 'static, + Builder: PayloadBuilder + Unpin + 'static, + Builder::Attributes: Unpin + Clone, + Builder::BuiltPayload: Unpin + Clone, +{ + type Output = Result<(), PayloadBuilderError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + tracing::trace!("Polling job"); + let this = self.get_mut(); + + // Check if deadline is reached + if this.deadline.as_mut().poll(cx).is_ready() { + this.cancel.cancel(); + tracing::debug!("Deadline reached"); + return Poll::Ready(Ok(())); + } + + // If cancelled via resolve_kind() + if this.cancel.is_cancelled() { + tracing::debug!("Job cancelled"); + return Poll::Ready(Ok(())); + } + + Poll::Pending + } +} + +// A future that resolves when a payload becomes available in the BlockCell +pub(super) struct ResolvePayload { + future: WaitForValue, +} + +impl ResolvePayload { + pub(super) fn new(future: WaitForValue) -> Self { + Self { future } + } +} + +impl Future for ResolvePayload { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut().future.poll_unpin(cx) { + Poll::Ready(value) => Poll::Ready(Ok(value)), + Poll::Pending => Poll::Pending, + } + } +} + +#[derive(Clone)] +pub(super) struct BlockCell { + inner: Arc>>, + notify: Arc, +} + +impl BlockCell { + pub(super) fn new() -> Self { + Self { + inner: Arc::new(Mutex::new(None)), + notify: Arc::new(Notify::new()), + } + } + + pub(super) fn set(&self, value: T) { + let mut inner = self.inner.lock().unwrap(); + *inner = Some(value); + self.notify.notify_one(); + } + + pub(super) fn get(&self) -> Option { + let inner = self.inner.lock().unwrap(); + inner.clone() + } + + // Return a future that resolves when value is set + pub(super) fn wait_for_value(&self) -> WaitForValue { + WaitForValue { cell: self.clone() } + } +} + +#[derive(Clone)] +// Future that resolves when a value is set in BlockCell +pub(super) struct WaitForValue { + cell: BlockCell, +} + +impl Future for WaitForValue { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if let Some(value) = self.cell.get() { + Poll::Ready(value) + } else { + // Instead of register, we use notified() to get a future + cx.waker().wake_by_ref(); + Poll::Pending + } + } +} + +impl Default for BlockCell { + fn default() -> Self { + Self::new() + } +} + +fn job_deadline(unix_timestamp_secs: u64) -> std::time::Duration { + let unix_now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + // Safe subtraction that handles the case where timestamp is in the past + let duration_until = unix_timestamp_secs.saturating_sub(unix_now); + + if duration_until == 0 { + // Enforce a minimum block time of 1 second by rounding up any duration less than 1 second + Duration::from_secs(1) + } else { + Duration::from_secs(duration_until) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_eips::eip7685::Requests; + use alloy_primitives::U256; + use rand::rng; + use reth::tasks::Runtime; + use reth_node_api::{BuiltPayloadExecutedBlock, NodePrimitives}; + use reth_optimism_payload_builder::{OpPayloadPrimitives, payload::OpPayloadBuilderAttributes}; + use reth_optimism_primitives::OpPrimitives; + use reth_primitives_traits::SealedBlock; + use reth_provider::test_utils::MockEthProvider; + use reth_testing_utils::generators::{BlockRangeParams, random_block_range}; + use tokio::{ + task, + time::{Duration, sleep}, + }; + + #[tokio::test] + async fn test_block_cell_wait_for_value() { + let cell = BlockCell::new(); + + // Spawn a task that will set the value after a delay + let cell_clone = cell.clone(); + task::spawn(async move { + sleep(Duration::from_millis(100)).await; + cell_clone.set(42); + }); + + // Wait for the value and verify + let wait_future = cell.wait_for_value(); + let result = wait_future.await; + assert_eq!(result, 42); + } + + #[tokio::test] + async fn test_block_cell_immediate_value() { + let cell = BlockCell::new(); + cell.set(42); + + // Value should be immediately available + let wait_future = cell.wait_for_value(); + let result = wait_future.await; + assert_eq!(result, 42); + } + + #[tokio::test] + async fn test_block_cell_multiple_waiters() { + let cell = BlockCell::new(); + + // Spawn multiple waiters + let wait1 = task::spawn({ + let cell = cell.clone(); + async move { cell.wait_for_value().await } + }); + + let wait2 = task::spawn({ + let cell = cell.clone(); + async move { cell.wait_for_value().await } + }); + + // Set value after a delay + sleep(Duration::from_millis(100)).await; + cell.set(42); + + // All waiters should receive the value + assert_eq!(wait1.await.unwrap(), 42); + assert_eq!(wait2.await.unwrap(), 42); + } + + #[tokio::test] + async fn test_block_cell_update_value() { + let cell = BlockCell::new(); + + // Set initial value + cell.set(42); + + // Set new value + cell.set(43); + + // Waiter should get the latest value + let result = cell.wait_for_value().await; + assert_eq!(result, 43); + } + + #[derive(Debug, Clone)] + struct MockBuilder { + events: Arc>>, + _marker: std::marker::PhantomData, + } + + impl MockBuilder { + fn new() -> Self { + Self { + events: Arc::new(Mutex::new(vec![])), + _marker: std::marker::PhantomData, + } + } + + fn new_event(&self, event: BlockEvent) { + let mut events = self.events.lock().unwrap(); + events.push(event); + } + + fn get_events(&self) -> Vec { + let mut events = self.events.lock().unwrap(); + std::mem::take(&mut *events) + } + } + + #[derive(Clone, Debug, Default)] + struct MockPayload; + + impl BuiltPayload for MockPayload { + type Primitives = OpPrimitives; + + fn block(&self) -> &SealedBlock<::Block> { + unimplemented!() + } + + /// Returns the fees collected for the built block + fn fees(&self) -> U256 { + unimplemented!() + } + + /// Returns the entire execution data for the built block, if available. + fn executed_block(&self) -> Option> { + None + } + + /// Returns the EIP-7865 requests for the payload if any. + fn requests(&self) -> Option { + unimplemented!() + } + } + + #[derive(Debug, PartialEq, Clone)] + enum BlockEvent { + Started, + Cancelled, + } + + #[async_trait::async_trait] + impl PayloadBuilder for MockBuilder + where + N: OpPayloadPrimitives, + { + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = MockPayload; + + async fn try_build( + &self, + args: BuildArguments, + _best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + self.new_event(BlockEvent::Started); + + loop { + if args.cancel.is_cancelled() { + self.new_event(BlockEvent::Cancelled); + return Ok(()); + } + + // Small sleep to prevent tight loop + tokio::time::sleep(Duration::from_millis(10)).await; + } + } + } + + #[tokio::test] + async fn test_job_deadline() { + // Test future deadline + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let future_timestamp = now + Duration::from_secs(2); + // 2 seconds from now + let deadline = job_deadline(future_timestamp.as_secs()); + assert!(deadline <= Duration::from_secs(2)); + assert!(deadline > Duration::from_secs(0)); + + // Test past deadline + let past_timestamp = now - Duration::from_secs(10); + let deadline = job_deadline(past_timestamp.as_secs()); + // Should default to 1 second when timestamp is in the past + assert_eq!(deadline, Duration::from_secs(1)); + + // Test current timestamp + let deadline = job_deadline(now.as_secs()); + // Should use 1 second when timestamp is current + assert_eq!(deadline, Duration::from_secs(1)); + } + + #[tokio::test] + async fn test_payload_generator() -> eyre::Result<()> { + let mut rng = rng(); + + let client = MockEthProvider::default(); + let executor = Runtime::test(); + let config = BasicPayloadJobGeneratorConfig::default(); + let builder = MockBuilder::::new(); + + let (start, count) = (1, 10); + let blocks = random_block_range( + &mut rng, + start..=start + count - 1, + BlockRangeParams { + tx_count: 0..2, + ..Default::default() + }, + ); + + client.extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.unseal()))); + + let generator = BlockPayloadJobGenerator::with_builder( + client.clone(), + executor, + config, + builder.clone(), + false, + std::time::Duration::from_secs(1), + ); + + // this is not nice but necessary + let mut attr = OpPayloadBuilderAttributes::default(); + attr.parent = client.latest_header()?.unwrap().hash(); + + { + let input = BuildNewPayload { + parent_hash: attr.parent, + attributes: attr.clone(), + cache: None, + trie_handle: None, + }; + let job = generator.new_payload_job(input, attr.payload_id())?; + let _ = job.await; + + // you need to give one second for the job to be dropped and cancelled the internal job + tokio::time::sleep(Duration::from_secs(1)).await; + + let events = builder.get_events(); + assert_eq!(events, vec![BlockEvent::Started, BlockEvent::Cancelled]); + } + + { + // job resolve triggers cancellations from the build task + let input = BuildNewPayload { + parent_hash: attr.parent, + attributes: attr.clone(), + cache: None, + trie_handle: None, + }; + let mut job = generator.new_payload_job(input, attr.payload_id())?; + let _ = job.resolve(); + let _ = job.await; + + tokio::time::sleep(Duration::from_secs(1)).await; + + let events = builder.get_events(); + assert_eq!(events, vec![BlockEvent::Started, BlockEvent::Cancelled]); + } + + Ok(()) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/mod.rs new file mode 100644 index 00000000000..4edb2088d16 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/mod.rs @@ -0,0 +1,203 @@ +use core::{ + convert::{Infallible, TryFrom}, + fmt::Debug, + time::Duration, +}; +use reth_node_builder::components::PayloadServiceBuilder; +use reth_optimism_evm::OpEvmConfig; +use reth_optimism_payload_builder::config::{OpDAConfig, OpGasLimitConfig}; + +use crate::{ + args::OpRbuilderArgs, + flashtestations::args::FlashtestationsArgs, + gas_limiter::args::GasLimiterArgs, + traits::{NodeBounds, PoolBounds}, + tx_signer::Signer, +}; + +mod builder_tx; +mod context; +mod flashblocks; +mod generator; +mod standard; + +pub use builder_tx::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, InvalidContractDataError, + SimulationSuccessResult, get_balance, get_nonce, +}; +pub use context::OpPayloadBuilderCtx; +pub use flashblocks::FlashblocksBuilder; +pub use standard::StandardBuilder; + +/// Defines the payload building mode for the OP builder. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum BuilderMode { + /// Uses the plain OP payload builder that produces blocks every chain blocktime. + #[default] + Standard, + /// Uses the flashblocks payload builder that progressively builds chunks of a + /// block every short interval and makes it available through a websocket update + /// then merges them into a full block every chain block time. + Flashblocks, +} + +/// Defines the interface for any block builder implementation API entry point. +/// +/// Instances of this trait are used during Reth node construction as an argument +/// to the `NodeBuilder::with_components` method to construct the payload builder +/// service that gets called whenver the current node is asked to build a block. +pub trait PayloadBuilder: Send + Sync + 'static { + /// The type that has an implementation specific variant of the Config struct. + /// This is used to configure the payload builder service during startup. + type Config: TryFrom + Clone + Debug + Send + Sync + 'static; + + /// The type that is used to instantiate the payload builder service + /// that will be used by reth to build blocks whenever the node is + /// asked to do so. + type ServiceBuilder: PayloadServiceBuilder + where + Node: NodeBounds, + Pool: PoolBounds; + + /// Called during node startup by reth. Returns a [`PayloadBuilderService`] instance + /// that is preloaded with a [`PayloadJobGenerator`] instance specific to the builder + /// type. + fn new_service( + config: BuilderConfig, + ) -> eyre::Result> + where + Node: NodeBounds, + Pool: PoolBounds; +} + +/// Configuration values that are applicable to any type of block builder. +#[derive(Clone)] +pub struct BuilderConfig { + /// Secret key of the builder that is used to sign the end of block transaction. + pub builder_signer: Option, + + /// When set to true, transactions are simulated by the builder and excluded from the block + /// if they revert. They may still be included in the block if individual transactions + /// opt-out of revert protection. + pub revert_protection: bool, + + /// When enabled, this will invoke the flashtestions workflow. This involves a + /// bootstrapping step that generates a new pubkey for the TEE service + pub flashtestations_config: FlashtestationsArgs, + + /// The interval at which blocks are added to the chain. + /// This is also the frequency at which the builder will be receiving FCU requests from the + /// sequencer. + pub block_time: Duration, + + /// Data Availability configuration for the OP builder + /// Defines constraints for the maximum size of data availability transactions. + pub da_config: OpDAConfig, + + /// Gas limit configuration for the payload builder + pub gas_limit_config: OpGasLimitConfig, + + // The deadline is critical for payload availability. If we reach the deadline, + // the payload job stops and cannot be queried again. With tight deadlines close + // to the block number, we risk reaching the deadline before the node queries the payload. + // + // Adding 0.5 seconds as wiggle room since block times are shorter here. + // TODO: A better long-term solution would be to implement cancellation logic + // that cancels existing jobs when receiving new block building requests. + // + // When batcher's max channel duration is big enough (e.g. 10m), the + // sequencer would send an avalanche of FCUs/getBlockByNumber on + // each batcher update (with 10m channel it's ~800 FCUs at once). + // At such moment it can happen that the time b/w FCU and ensuing + // getPayload would be on the scale of ~2.5s. Therefore we should + // "remember" the payloads long enough to accommodate this corner-case + // (without it we are losing blocks). Postponing the deadline for 5s + // (not just 0.5s) because of that. + pub block_time_leeway: Duration, + + /// Inverted sampling frequency in blocks. 1 - each block, 100 - every 100th block. + pub sampling_ratio: u64, + + /// Configuration values that are specific to the block builder implementation used. + pub specific: Specific, + + /// Maximum gas a transaction can use before being excluded. + pub max_gas_per_txn: Option, + + /// Address gas limiter stuff + pub gas_limiter_config: GasLimiterArgs, +} + +impl core::fmt::Debug for BuilderConfig { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Config") + .field( + "builder_signer", + &match self.builder_signer.as_ref() { + Some(signer) => signer.address.to_string(), + None => "None".into(), + }, + ) + .field("revert_protection", &self.revert_protection) + .field("flashtestations", &self.flashtestations_config) + .field("block_time", &self.block_time) + .field("block_time_leeway", &self.block_time_leeway) + .field("da_config", &self.da_config) + .field("gas_limit_config", &self.gas_limit_config) + .field("sampling_ratio", &self.sampling_ratio) + .field("specific", &self.specific) + .field("max_gas_per_txn", &self.max_gas_per_txn) + .field("gas_limiter_config", &self.gas_limiter_config) + .finish() + } +} + +impl Default for BuilderConfig { + fn default() -> Self { + Self { + builder_signer: None, + revert_protection: false, + flashtestations_config: FlashtestationsArgs::default(), + block_time: Duration::from_secs(2), + block_time_leeway: Duration::from_millis(500), + da_config: OpDAConfig::default(), + gas_limit_config: OpGasLimitConfig::default(), + specific: S::default(), + sampling_ratio: 100, + max_gas_per_txn: None, + gas_limiter_config: GasLimiterArgs::default(), + } + } +} + +impl TryFrom for BuilderConfig +where + S: TryFrom + Clone, +{ + type Error = S::Error; + + fn try_from(args: OpRbuilderArgs) -> Result { + Ok(Self { + builder_signer: args.builder_signer, + revert_protection: args.enable_revert_protection, + flashtestations_config: args.flashtestations.clone(), + block_time: Duration::from_millis(args.chain_block_time), + block_time_leeway: Duration::from_secs(args.extra_block_deadline_secs), + da_config: Default::default(), + gas_limit_config: Default::default(), + sampling_ratio: args.telemetry.sampling_ratio, + max_gas_per_txn: args.max_gas_per_txn, + gas_limiter_config: args.gas_limiter.clone(), + specific: S::try_from(args)?, + }) + } +} + +#[expect(clippy::infallible_try_from)] +impl TryFrom for () { + type Error = Infallible; + + fn try_from(_: OpRbuilderArgs) -> Result { + Ok(()) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/builder_tx.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/builder_tx.rs new file mode 100644 index 00000000000..ececc887e7d --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/builder_tx.rs @@ -0,0 +1,69 @@ +use alloy_evm::Database; +use core::fmt::Debug; +use reth_provider::StateProvider; +use reth_revm::State; +use revm::DatabaseRef; +use tracing::warn; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, + builder_tx::BuilderTxBase, context::OpPayloadBuilderCtx, + }, + flashtestations::builder_tx::FlashtestationsBuilderTx, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +// This will be the end of block transaction of a regular block +#[derive(Debug, Clone)] +pub(super) struct StandardBuilderTx { + pub base_builder_tx: BuilderTxBase, + pub flashtestations_builder_tx: Option, +} + +impl StandardBuilderTx { + pub(super) fn new( + signer: Option, + flashtestations_builder_tx: Option, + ) -> Self { + let base_builder_tx = BuilderTxBase::new(signer); + Self { + base_builder_tx, + flashtestations_builder_tx, + } + } +} + +impl BuilderTransactions for StandardBuilderTx { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + top_of_block: bool, + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + let standard_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; + builder_txs.extend(standard_builder_tx.clone()); + if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { + if let Some(builder_tx) = standard_builder_tx { + self.commit_txs(vec![builder_tx.signed_tx], ctx, db)?; + } + match flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + db, + top_of_block, + ) { + Ok(flashtestations_builder_txs) => builder_txs.extend(flashtestations_builder_txs), + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx") + } + } + } + Ok(builder_txs) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/mod.rs new file mode 100644 index 00000000000..e26bbc6cc96 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/mod.rs @@ -0,0 +1,34 @@ +use crate::{ + builders::standard::service::StandardServiceBuilder, + traits::{NodeBounds, PoolBounds}, +}; + +use super::BuilderConfig; + +mod builder_tx; +mod payload; +mod service; + +/// Block building strategy that builds blocks using the standard approach by +/// producing blocks every chain block time. +pub struct StandardBuilder; + +impl super::PayloadBuilder for StandardBuilder { + type Config = (); + + type ServiceBuilder + = StandardServiceBuilder + where + Node: NodeBounds, + Pool: PoolBounds; + + fn new_service( + config: BuilderConfig, + ) -> eyre::Result> + where + Node: NodeBounds, + Pool: PoolBounds, + { + Ok(StandardServiceBuilder(config)) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/payload.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/payload.rs new file mode 100644 index 00000000000..33fbb407428 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/payload.rs @@ -0,0 +1,663 @@ +use super::super::context::OpPayloadBuilderCtx; +use crate::{ + builders::{BuilderConfig, BuilderTransactions, generator::BuildArguments}, + gas_limiter::AddressGasLimiter, + metrics::OpRBuilderMetrics, + primitives::reth::ExecutionInfo, + traits::{ClientBounds, PayloadTxsBounds, PoolBounds}, +}; +use alloy_consensus::{ + BlockBody, EMPTY_OMMER_ROOT_HASH, Header, constants::EMPTY_WITHDRAWALS, proofs, +}; +use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; +use alloy_evm::Database; +use alloy_primitives::U256; +use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; +use reth_evm::{ConfigureEvm, execute::BlockBuilder}; +use reth_execution_types::{BlockExecutionOutput, BlockExecutionResult}; +use reth_node_api::{Block, PayloadBuilderError}; +use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_optimism_payload_builder::OpPayloadAttrs; +use reth_optimism_primitives::OpTransactionSigned; +use reth_payload_primitives::BuiltPayloadExecutedBlock; +use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; +use reth_primitives_traits::{InMemorySize, RecoveredBlock}; +use reth_provider::{ExecutionOutcome, StateProvider}; +use reth_revm::{ + State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, +}; +use reth_transaction_pool::{ + BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool, +}; +use std::{sync::Arc, time::Instant}; +use tokio_util::sync::CancellationToken; +use tracing::{error, info, warn}; + +/// Optimism's payload builder +#[derive(Debug, Clone)] +pub(super) struct StandardOpPayloadBuilder { + /// The type responsible for creating the evm. + pub evm_config: OpEvmConfig, + /// The transaction pool + pub pool: Pool, + /// Node client + pub client: Client, + /// Settings for the builder, e.g. DA settings. + pub config: BuilderConfig<()>, + /// The type responsible for yielding the best transactions for the payload if mempool + /// transactions are allowed. + pub best_transactions: Txs, + /// The metrics for the builder + pub metrics: Arc, + /// Rate limiting based on gas. This is an optional feature. + pub address_gas_limiter: AddressGasLimiter, + /// The type responsible for creating the builder transactions + pub builder_tx: BuilderTx, +} + +impl StandardOpPayloadBuilder { + /// `OpPayloadBuilder` constructor. + pub(super) fn new( + evm_config: OpEvmConfig, + pool: Pool, + client: Client, + config: BuilderConfig<()>, + builder_tx: BuilderTx, + ) -> Self { + let address_gas_limiter = AddressGasLimiter::new(config.gas_limiter_config.clone()); + Self { + pool, + client, + config, + evm_config, + best_transactions: (), + metrics: Default::default(), + address_gas_limiter, + builder_tx, + } + } +} + +/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. +pub(super) trait OpPayloadTransactions: + Clone + Send + Sync + Unpin + 'static +{ + /// Returns an iterator that yields the transaction in the order they should get included in the + /// new payload. + fn best_transactions>( + &self, + pool: Pool, + attr: BestTransactionsAttributes, + ) -> impl PayloadTransactions; +} + +impl OpPayloadTransactions for () { + fn best_transactions>( + &self, + pool: Pool, + attr: BestTransactionsAttributes, + ) -> impl PayloadTransactions { + // TODO: once this issue is fixed we could remove without_updates and rely on regular impl + // https://github.com/paradigmxyz/reth/issues/17325 + BestPayloadTransactions::new( + pool.best_transactions_with_attributes(attr) + .without_updates(), + ) + } +} + +impl reth_basic_payload_builder::PayloadBuilder + for StandardOpPayloadBuilder +where + Pool: PoolBounds, + Client: ClientBounds, + BuilderTx: BuilderTransactions + Clone + Send + Sync, + Txs: OpPayloadTransactions, +{ + type Attributes = OpPayloadAttrs; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + args: reth_basic_payload_builder::BuildArguments, + ) -> Result, PayloadBuilderError> { + let pool = self.pool.clone(); + + let reth_basic_payload_builder::BuildArguments { + cached_reads, + config, + // TODO: thread `cancel` through the build path. + .. + } = args; + + let payload_id = config.payload_id; + let builder_attrs = OpPayloadBuilderAttributes::from_rpc_attrs( + config.parent_header.hash(), + payload_id, + config.attributes.0, + ) + .map_err(PayloadBuilderError::other)?; + + let args = BuildArguments { + cached_reads, + config: reth_basic_payload_builder::PayloadConfig { + parent_header: config.parent_header, + attributes: builder_attrs, + payload_id, + }, + cancel: CancellationToken::new(), + }; + + self.build_payload(args, |attrs| { + #[allow(clippy::unit_arg)] + self.best_transactions + .best_transactions(pool.clone(), attrs) + }) + } + + fn on_missing_payload( + &self, + _args: reth_basic_payload_builder::BuildArguments, + ) -> MissingPayloadBehaviour { + MissingPayloadBehaviour::AwaitInProgress + } + + fn build_empty_payload( + &self, + config: reth_basic_payload_builder::PayloadConfig< + Self::Attributes, + reth_basic_payload_builder::HeaderForPayload, + >, + ) -> Result { + let payload_id = config.payload_id; + let builder_attrs = OpPayloadBuilderAttributes::from_rpc_attrs( + config.parent_header.hash(), + payload_id, + config.attributes.0, + ) + .map_err(PayloadBuilderError::other)?; + let args = BuildArguments { + config: reth_basic_payload_builder::PayloadConfig { + parent_header: config.parent_header, + attributes: builder_attrs, + payload_id, + }, + cached_reads: Default::default(), + cancel: Default::default(), + }; + self.build_payload(args, |_| { + NoopPayloadTransactions::::default() + })? + .into_payload() + .ok_or_else(|| PayloadBuilderError::MissingPayload) + } +} + +impl StandardOpPayloadBuilder +where + Pool: PoolBounds, + Client: ClientBounds, + BuilderTx: BuilderTransactions + Clone, +{ + /// Constructs an Optimism payload from the transactions sent via the + /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in + /// the payload attributes, the transaction pool will be ignored and the only transactions + /// included in the payload will be those sent through the attributes. + /// + /// Given build arguments including an Optimism client, transaction pool, + /// and configuration, this function creates a transaction payload. Returns + /// a result indicating success with the payload or an error in case of failure. + fn build_payload<'a, Txs: PayloadTxsBounds>( + &self, + args: BuildArguments, OpBuiltPayload>, + best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, + ) -> Result, PayloadBuilderError> { + let block_build_start_time = Instant::now(); + + let BuildArguments { + mut cached_reads, + config, + cancel, + } = args; + + let chain_spec = self.client.chain_spec(); + let timestamp = config.attributes.timestamp(); + + let extra_data = if chain_spec.is_jovian_active_at_timestamp(timestamp) { + config + .attributes + .get_jovian_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .map_err(PayloadBuilderError::other)? + } else if chain_spec.is_holocene_active_at_timestamp(timestamp) { + config + .attributes + .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .map_err(PayloadBuilderError::other)? + } else { + Default::default() + }; + + let block_env_attributes = OpNextBlockEnvAttributes { + timestamp, + suggested_fee_recipient: config.attributes.suggested_fee_recipient(), + prev_randao: config.attributes.prev_randao(), + gas_limit: config + .attributes + .gas_limit + .unwrap_or(config.parent_header.gas_limit), + parent_beacon_block_root: config.attributes.parent_beacon_block_root(), + extra_data, + }; + + let evm_env = self + .evm_config + .next_evm_env(&config.parent_header, &block_env_attributes) + .map_err(PayloadBuilderError::other)?; + + let ctx = OpPayloadBuilderCtx { + evm_config: self.evm_config.clone(), + da_config: self.config.da_config.clone(), + gas_limit_config: self.config.gas_limit_config.clone(), + chain_spec, + config, + evm_env, + block_env_attributes, + cancel, + builder_signer: self.config.builder_signer, + metrics: self.metrics.clone(), + extra_ctx: Default::default(), + max_gas_per_txn: self.config.max_gas_per_txn, + address_gas_limiter: self.address_gas_limiter.clone(), + }; + + let builder = OpBuilder::new(best); + + self.address_gas_limiter.refresh(ctx.block_number()); + + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let db = StateProviderDatabase::new(&state_provider); + let metrics = ctx.metrics.clone(); + if ctx.attributes().no_tx_pool { + let state = State::builder() + .with_database(db) + .with_bundle_update() + .build(); + builder.build(state, &state_provider, ctx, self.builder_tx.clone()) + } else { + // sequencer mode we can reuse cachedreads from previous runs + let state = State::builder() + .with_database(cached_reads.as_db_mut(db)) + .with_bundle_update() + .build(); + builder.build(state, &state_provider, ctx, self.builder_tx.clone()) + } + .map(|out| { + let total_block_building_time = block_build_start_time.elapsed(); + metrics + .total_block_built_duration + .record(total_block_building_time); + metrics + .total_block_built_gauge + .set(total_block_building_time); + + out.with_cached_reads(cached_reads) + }) + } +} + +/// The type that builds the payload. +/// +/// Payload building for optimism is composed of several steps. +/// The first steps are mandatory and defined by the protocol. +/// +/// 1. first all System calls are applied. +/// 2. After canyon the forced deployed `create2deployer` must be loaded +/// 3. all sequencer transactions are executed (part of the payload attributes) +/// +/// Depending on whether the node acts as a sequencer and is allowed to include additional +/// transactions (`no_tx_pool == false`): +/// 4. include additional transactions +/// +/// And finally +/// 5. build the block: compute all roots (txs, state) +#[derive(derive_more::Debug)] +pub(super) struct OpBuilder<'a, Txs> { + /// Yields the best transaction to include if transactions from the mempool are allowed. + best: Box Txs + 'a>, +} + +impl<'a, Txs> OpBuilder<'a, Txs> { + fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self { + Self { + best: Box::new(best), + } + } +} + +/// Holds the state after execution +#[derive(Debug)] +pub(super) struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, +} + +impl OpBuilder<'_, Txs> { + /// Executes the payload and returns the outcome. + pub(crate) fn execute( + self, + state_provider: impl StateProvider, + db: &mut State, + ctx: &OpPayloadBuilderCtx, + builder_tx: BuilderTx, + ) -> Result, PayloadBuilderError> + where + BuilderTx: BuilderTransactions, + { + let Self { best } = self; + info!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); + + // 1. apply pre-execution changes + let mut builder = ctx.block_builder_for_next_block(db)?; + builder.apply_pre_execution_changes()?; + + let sequencer_tx_start_time = Instant::now(); + + // 3. execute sequencer transactions + let mut info = ctx.execute_sequencer_transactions(&mut builder)?; + + let sequencer_tx_time = sequencer_tx_start_time.elapsed(); + ctx.metrics.sequencer_tx_duration.record(sequencer_tx_time); + ctx.metrics.sequencer_tx_gauge.set(sequencer_tx_time); + + // 4. if mem pool transactions are requested we execute them + + // gas reserved for builder tx + let builder_txs = + match builder_tx.add_builder_txs(&state_provider, &mut info, ctx, &mut builder, true) { + Ok(builder_txs) => builder_txs, + Err(e) => { + error!(target: "payload_builder", "Error adding builder txs to block: {}", e); + vec![] + } + }; + + let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); + + let block_gas_limit = ctx.block_gas_limit().saturating_sub(builder_tx_gas); + if block_gas_limit == 0 { + error!( + "Builder tx gas subtraction resulted in block gas limit to be 0. No transactions would be included" + ); + } + // Save some space in the block_da_limit for builder tx + let builder_tx_da_size = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + let block_da_limit = ctx + .da_config + .max_da_block_size() + .map(|da_limit| { + let da_limit = da_limit.saturating_sub(builder_tx_da_size); + if da_limit == 0 { + error!("Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included."); + } + da_limit + }); + let block_da_footprint = info.da_footprint_scalar + .map(|da_footprint_scalar| { + let da_footprint_limit = ctx.block_gas_limit().saturating_sub(builder_tx_da_size.saturating_mul(da_footprint_scalar as u64)); + if da_footprint_limit == 0 { + error!("Builder tx da size subtraction caused max_da_footprint to be 0. No transaction would be included."); + } + da_footprint_limit + }); + + if !ctx.attributes().no_tx_pool { + let best_txs_start_time = Instant::now(); + let mut best_txs = best(ctx.best_transaction_attributes()); + let transaction_pool_fetch_time = best_txs_start_time.elapsed(); + ctx.metrics + .transaction_pool_fetch_duration + .record(transaction_pool_fetch_time); + ctx.metrics + .transaction_pool_fetch_gauge + .set(transaction_pool_fetch_time); + + if ctx + .execute_best_transactions( + &mut info, + &mut builder, + &mut best_txs, + block_gas_limit, + block_da_limit, + block_da_footprint, + )? + .is_some() + { + return Ok(BuildOutcomeKind::Cancelled); + } + } + + // Add builder tx to the block + if let Err(e) = + builder_tx.add_builder_txs(&state_provider, &mut info, ctx, &mut builder, false) + { + error!(target: "payload_builder", "Error adding builder txs to fallback block: {}", e); + }; + + drop(builder); + + let state_merge_start_time = Instant::now(); + + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + db.merge_transitions(BundleRetention::Reverts); + + let state_transition_merge_time = state_merge_start_time.elapsed(); + ctx.metrics + .state_transition_merge_duration + .record(state_transition_merge_time); + ctx.metrics + .state_transition_merge_gauge + .set(state_transition_merge_time); + + ctx.metrics + .payload_num_tx + .record(info.executed_transactions.len() as f64); + ctx.metrics + .payload_num_tx_gauge + .set(info.executed_transactions.len() as f64); + + let payload = ExecutedPayload { info }; + + ctx.metrics.block_built_success.increment(1); + Ok(BuildOutcomeKind::Better { payload }) + } + + /// Builds the payload on top of the state. + pub(super) fn build( + self, + state: impl Database, + state_provider: impl StateProvider, + ctx: OpPayloadBuilderCtx, + builder_tx: BuilderTx, + ) -> Result, PayloadBuilderError> + where + BuilderTx: BuilderTransactions, + { + let mut db = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + let ExecutedPayload { info } = + match self.execute(&state_provider, &mut db, &ctx, builder_tx)? { + BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, + BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), + BuildOutcomeKind::Aborted { fees } => { + return Ok(BuildOutcomeKind::Aborted { fees }); + } + }; + + let block_number = ctx.block_number(); + // OP doesn't support blobs/EIP-4844. + // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions + // Need [Some] or [None] based on hardfork to match block hash. + let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(&info); + + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![info.receipts], + block_number, + Vec::new(), + ); + let receipts_root = execution_outcome + .generic_receipts_root_slow(block_number, |receipts| { + calculate_receipt_root_no_memo_optimism( + receipts, + &ctx.chain_spec, + ctx.attributes().timestamp(), + ) + }) + .expect("Number is in range"); + let logs_bloom = execution_outcome + .block_logs_bloom(block_number) + .expect("Number is in range"); + + // calculate the state root + let state_root_start_time = Instant::now(); + + let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); + let (state_root, trie_output) = { + state_provider + .state_root_with_updates(hashed_state.clone()) + .inspect_err(|err| { + warn!(target: "payload_builder", + parent_header=%ctx.parent().hash(), + %err, + "failed to calculate state root for payload" + ); + })? + }; + + let state_root_calculation_time = state_root_start_time.elapsed(); + ctx.metrics + .state_root_calculation_duration + .record(state_root_calculation_time); + ctx.metrics + .state_root_calculation_gauge + .set(state_root_calculation_time); + + let (withdrawals_root, requests_hash) = if ctx.is_isthmus_active() { + // withdrawals root field in block header is used for storage root of L2 predeploy + // `l2tol1-message-passer` + ( + Some( + isthmus::withdrawals_root(execution_outcome.state(), state_provider) + .map_err(PayloadBuilderError::other)?, + ), + Some(EMPTY_REQUESTS_HASH), + ) + } else if ctx.is_canyon_active() { + (Some(EMPTY_WITHDRAWALS), None) + } else { + (None, None) + }; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); + + let extra_data = ctx.extra_data()?; + + let header = Header { + parent_hash: ctx.parent().hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: ctx.evm_env.block_env.beneficiary, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp: ctx.attributes().timestamp(), + mix_hash: ctx.attributes().prev_randao(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: Some(ctx.base_fee()), + number: ctx.parent().number + 1, + gas_limit: ctx.block_gas_limit(), + difficulty: U256::ZERO, + gas_used: info.cumulative_gas_used, + extra_data, + parent_beacon_block_root: ctx.attributes().parent_beacon_block_root(), + blob_gas_used, + excess_blob_gas, + requests_hash, + block_access_list_hash: None, + slot_number: None, + }; + + // seal the block + let block = alloy_consensus::Block::::new( + header, + BlockBody { + transactions: info.executed_transactions, + ommers: vec![], + withdrawals: ctx.withdrawals().cloned(), + }, + ); + + let sealed_block = Arc::new(block.seal_slow()); + info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); + + let execution_output = BlockExecutionOutput { + state: execution_outcome.bundle.clone(), + result: BlockExecutionResult { + receipts: execution_outcome + .receipts + .first() + .cloned() + .unwrap_or_default(), + requests: Default::default(), + gas_used: info.cumulative_gas_used, + blob_gas_used: blob_gas_used.unwrap_or_default(), + }, + }; + + // create the executed block data + let executed = BuiltPayloadExecutedBlock { + recovered_block: Arc::new( + RecoveredBlock::>::new_sealed( + sealed_block.as_ref().clone(), + info.executed_senders, + ), + ), + execution_output: Arc::new(execution_output), + hashed_state: Arc::new(hashed_state), + trie_updates: Arc::new(trie_output), + }; + + let no_tx_pool = ctx.attributes().no_tx_pool; + + let payload = OpBuiltPayload::new( + ctx.payload_id(), + sealed_block, + info.total_fees, + Some(executed), + ); + + ctx.metrics + .payload_byte_size + .record(InMemorySize::size(payload.block()) as f64); + ctx.metrics + .payload_byte_size_gauge + .set(InMemorySize::size(payload.block()) as f64); + + if no_tx_pool { + // if `no_tx_pool` is set only transactions from the payload attributes will be included + // in the payload. In other words, the payload is deterministic and we can + // freeze it once we've successfully built it. + Ok(BuildOutcomeKind::Freeze(payload)) + } else { + Ok(BuildOutcomeKind::Better { payload }) + } + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/service.rs b/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/service.rs new file mode 100644 index 00000000000..939f9d2e3ae --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/builders/standard/service.rs @@ -0,0 +1,98 @@ +use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; +use reth_node_api::NodeTypes; +use reth_node_builder::{BuilderContext, components::PayloadServiceBuilder}; +use reth_optimism_evm::OpEvmConfig; +use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; +use reth_provider::CanonStateSubscriptions; + +use crate::{ + builders::{ + BuilderConfig, BuilderTransactions, + standard::{builder_tx::StandardBuilderTx, payload::StandardOpPayloadBuilder}, + }, + flashtestations::service::bootstrap_flashtestations, + traits::{NodeBounds, PoolBounds}, +}; + +pub struct StandardServiceBuilder(pub BuilderConfig<()>); + +impl StandardServiceBuilder { + pub fn spawn_payload_builder_service( + self, + evm_config: OpEvmConfig, + ctx: &BuilderContext, + pool: Pool, + builder_tx: BuilderTx, + ) -> eyre::Result::Payload>> + where + Node: NodeBounds, + Pool: PoolBounds, + BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static, + { + let payload_builder = StandardOpPayloadBuilder::new( + evm_config, + pool, + ctx.provider().clone(), + self.0.clone(), + builder_tx, + ); + + let conf = ctx.config().builder.clone(); + + let payload_job_config = BasicPayloadJobGeneratorConfig::default() + .interval(conf.interval) + .deadline(conf.deadline) + .max_payload_tasks(conf.max_payload_tasks); + + let payload_generator = BasicPayloadJobGenerator::with_builder( + ctx.provider().clone(), + ctx.task_executor().clone(), + payload_job_config, + payload_builder, + ); + let (payload_service, payload_service_handle) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical_task("payload builder service", payload_service); + + Ok(payload_service_handle) + } +} + +impl PayloadServiceBuilder for StandardServiceBuilder +where + Node: NodeBounds, + Pool: PoolBounds, +{ + async fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + pool: Pool, + evm_config: OpEvmConfig, + ) -> eyre::Result::Payload>> { + let signer = self.0.builder_signer; + let flashtestations_builder_tx = if let Some(builder_key) = signer + && self.0.flashtestations_config.flashtestations_enabled + { + match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key) + .await + { + Ok(builder_tx) => Some(builder_tx), + Err(e) => { + tracing::warn!(error = %e, "Failed to bootstrap flashtestations, builderb will not include flashtestations txs"); + None + } + } + } else { + None + }; + + self.spawn_payload_builder_service( + evm_config, + ctx, + pool, + StandardBuilderTx::new(signer, flashtestations_builder_tx), + ) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/args.rs b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/args.rs new file mode 100644 index 00000000000..7856bb01282 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/args.rs @@ -0,0 +1,96 @@ +use alloy_primitives::Address; +use clap::Parser; +use reth_optimism_cli::commands::Commands; + +use crate::args::Cli; + +/// Parameters for Flashtestations configuration +/// The names in the struct are prefixed with `flashtestations` +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] +pub struct FlashtestationsArgs { + /// When set to true, the builder will initiate the flashtestations + /// workflow within the bootstrapping and block building process. + #[arg( + long = "flashtestations.enabled", + default_value = "false", + env = "ENABLE_FLASHTESTATIONS" + )] + pub flashtestations_enabled: bool, + + /// Whether to use the debug HTTP service for quotes + #[arg( + long = "flashtestations.debug", + default_value = "false", + env = "FLASHTESTATIONS_DEBUG" + )] + pub debug: bool, + + // Debug static key for the tee key. DO NOT USE IN PRODUCTION + #[arg( + long = "flashtestations.debug-tee-key-seed", + env = "FLASHTESTATIONS_DEBUG_TEE_KEY_SEED", + default_value = "debug" + )] + pub debug_tee_key_seed: String, + + /// Path to save ephemeral TEE key between restarts + #[arg( + long = "flashtestations.tee-key-path", + env = "FLASHTESTATIONS_TEE_KEY_PATH", + default_value = "/run/flashtestation.key" + )] + pub flashtestations_key_path: String, + + // Remote url for attestations + #[arg( + long = "flashtestations.quote-provider", + env = "FLASHTESTATIONS_QUOTE_PROVIDER" + )] + pub quote_provider: Option, + + /// The rpc url to post the onchain attestation requests to + #[arg(long = "flashtestations.rpc-url", env = "FLASHTESTATIONS_RPC_URL")] + pub rpc_url: Option, + + /// Enable end of block TEE proof + #[arg( + long = "flashtestations.enable-block-proofs", + env = "FLASHTESTATIONS_ENABLE_BLOCK_PROOFS", + default_value = "false" + )] + pub enable_block_proofs: bool, + + /// The address of the flashtestations registry contract + #[arg( + long = "flashtestations.registry-address", + env = "FLASHTESTATIONS_REGISTRY_ADDRESS", + required_if_eq("flashtestations_enabled", "true") + )] + pub registry_address: Option
, + + /// The address of the builder policy contract + #[arg( + long = "flashtestations.builder-policy-address", + env = "FLASHTESTATIONS_BUILDER_POLICY_ADDRESS", + required_if_eq("flashtestations_enabled", "true") + )] + pub builder_policy_address: Option
, + + /// The version of the block builder verification proof + #[arg( + long = "flashtestations.builder-proof-version", + env = "FLASHTESTATIONS_BUILDER_PROOF_VERSION", + default_value = "1" + )] + pub builder_proof_version: u8, +} + +impl Default for FlashtestationsArgs { + fn default() -> Self { + let args = Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext.flashtestations + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/attestation.rs b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/attestation.rs new file mode 100644 index 00000000000..df712f988be --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/attestation.rs @@ -0,0 +1,231 @@ +use reqwest::Client; +use sha3::{Digest, Keccak256}; +use tracing::info; + +const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/attest"; + +// Raw TDX v4 quote structure constants +// Raw quote has a 48-byte header before the TD10ReportBody +const HEADER_LENGTH: usize = 48; +const TD_REPORT10_LENGTH: usize = 584; + +// TD10ReportBody field offsets +// These offsets correspond to the Solidity parseRawReportBody implementation +const OFFSET_TD_ATTRIBUTES: usize = 120; +const OFFSET_XFAM: usize = 128; +const OFFSET_MR_TD: usize = 136; +const OFFSET_MR_CONFIG_ID: usize = 184; +const OFFSET_MR_OWNER: usize = 232; +const OFFSET_MR_OWNER_CONFIG: usize = 280; +const OFFSET_RT_MR0: usize = 328; +const OFFSET_RT_MR1: usize = 376; +const OFFSET_RT_MR2: usize = 424; +const OFFSET_RT_MR3: usize = 472; + +// Field lengths +const MEASUREMENT_REGISTER_LENGTH: usize = 48; +const ATTRIBUTE_LENGTH: usize = 8; + +/// Parsed TDX quote report body containing measurement registers and attributes +#[derive(Debug, Clone)] +pub struct ParsedQuote { + pub mr_td: [u8; 48], + pub rt_mr0: [u8; 48], + pub rt_mr1: [u8; 48], + pub rt_mr2: [u8; 48], + pub rt_mr3: [u8; 48], + pub mr_config_id: [u8; 48], + pub mr_owner: [u8; 48], + pub mr_owner_config: [u8; 48], + pub xfam: u64, + pub td_attributes: u64, +} + +/// Configuration for attestation +#[derive(Default)] +pub struct AttestationConfig { + /// If true, uses the debug HTTP service instead of real TDX hardware + pub debug: bool, + /// The URL of the quote provider + pub quote_provider: Option, +} +/// Remote attestation provider +#[derive(Debug, Clone)] +pub struct RemoteAttestationProvider { + client: Client, + service_url: String, +} + +impl RemoteAttestationProvider { + pub fn new(service_url: String) -> Self { + let client = Client::new(); + Self { + client, + service_url, + } + } +} + +impl RemoteAttestationProvider { + pub async fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { + let report_data_hex = hex::encode(report_data); + let url = format!("{}/{}", self.service_url, report_data_hex); + + info!(target: "flashtestations", url = url, "fetching quote from remote attestation provider"); + + let response = self + .client + .get(&url) + .timeout(std::time::Duration::from_secs(10)) + .send() + .await? + .error_for_status()?; + let body = response.bytes().await?.to_vec(); + + Ok(body) + } +} + +pub fn get_attestation_provider(config: AttestationConfig) -> RemoteAttestationProvider { + if config.debug { + RemoteAttestationProvider::new( + config + .quote_provider + .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), + ) + } else { + RemoteAttestationProvider::new( + config + .quote_provider + .expect("remote quote provider must be specified when not in debug mode"), + ) + } +} + +/// Parse the TDX report body from a raw quote +/// Extracts measurement registers and attributes according to TD10ReportBody specification +/// https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216 +pub fn parse_report_body(raw_quote: &[u8]) -> eyre::Result { + // Validate quote length + if raw_quote.len() < HEADER_LENGTH + TD_REPORT10_LENGTH { + eyre::bail!( + "invalid quote length: {}, expected at least {}", + raw_quote.len(), + HEADER_LENGTH + TD_REPORT10_LENGTH + ); + } + + // Skip the 48-byte header to get to the TD10ReportBody + let report_body = &raw_quote[HEADER_LENGTH..]; + + // Extract fields exactly as parseRawReportBody does in Solidity + // Using named offset constants to match Solidity implementation exactly + let mr_td: [u8; 48] = report_body[OFFSET_MR_TD..OFFSET_MR_TD + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract mr_td"))?; + let rt_mr0: [u8; 48] = report_body[OFFSET_RT_MR0..OFFSET_RT_MR0 + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract rt_mr0"))?; + let rt_mr1: [u8; 48] = report_body[OFFSET_RT_MR1..OFFSET_RT_MR1 + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract rt_mr1"))?; + let rt_mr2: [u8; 48] = report_body[OFFSET_RT_MR2..OFFSET_RT_MR2 + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract rt_mr2"))?; + let rt_mr3: [u8; 48] = report_body[OFFSET_RT_MR3..OFFSET_RT_MR3 + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract rt_mr3"))?; + let mr_config_id: [u8; 48] = report_body + [OFFSET_MR_CONFIG_ID..OFFSET_MR_CONFIG_ID + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract mr_config_id"))?; + let mr_owner: [u8; 48] = report_body + [OFFSET_MR_OWNER..OFFSET_MR_OWNER + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract mr_owner"))?; + let mr_owner_config: [u8; 48] = report_body + [OFFSET_MR_OWNER_CONFIG..OFFSET_MR_OWNER_CONFIG + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract mr_owner_config"))?; + + // Extract xFAM and tdAttributes (8 bytes each) + // In Solidity, bytes8 is treated as big-endian for bitwise operations + let xfam = u64::from_be_bytes( + report_body[OFFSET_XFAM..OFFSET_XFAM + ATTRIBUTE_LENGTH] + .try_into() + .map_err(|e| eyre::eyre!("failed to parse xfam: {}", e))?, + ); + let td_attributes = u64::from_be_bytes( + report_body[OFFSET_TD_ATTRIBUTES..OFFSET_TD_ATTRIBUTES + ATTRIBUTE_LENGTH] + .try_into() + .map_err(|e| eyre::eyre!("failed to parse td_attributes: {}", e))?, + ); + + Ok(ParsedQuote { + mr_td, + rt_mr0, + rt_mr1, + rt_mr2, + rt_mr3, + mr_config_id, + mr_owner, + mr_owner_config, + xfam, + td_attributes, + }) +} + +/// Compute workload ID from parsed quote data +/// This corresponds to QuoteParser.parseV4VerifierOutput in Solidity implementation +/// The workload ID uniquely identifies a TEE workload based on its measurement registers +pub fn compute_workload_id_from_parsed(parsed: &ParsedQuote) -> [u8; 32] { + // Concatenate all fields + let mut concatenated = Vec::new(); + concatenated.extend_from_slice(&parsed.mr_td); + concatenated.extend_from_slice(&parsed.rt_mr0); + concatenated.extend_from_slice(&parsed.rt_mr1); + concatenated.extend_from_slice(&parsed.rt_mr2); + concatenated.extend_from_slice(&parsed.rt_mr3); + concatenated.extend_from_slice(&parsed.mr_config_id); + concatenated.extend_from_slice(&parsed.xfam.to_be_bytes()); + concatenated.extend_from_slice(&parsed.td_attributes.to_be_bytes()); + + // Compute keccak256 hash + let mut hasher = Keccak256::new(); + hasher.update(&concatenated); + let result = hasher.finalize(); + + let mut workload_id = [0u8; 32]; + workload_id.copy_from_slice(&result); + + workload_id +} + +/// Compute workload ID from raw quote bytes +/// This is a convenience function that combines parsing and computation +pub fn compute_workload_id(raw_quote: &[u8]) -> eyre::Result<[u8; 32]> { + let parsed = parse_report_body(raw_quote)?; + Ok(compute_workload_id_from_parsed(&parsed)) +} + +#[cfg(test)] +mod tests { + use crate::tests::WORKLOAD_ID; + + use super::*; + + #[test] + fn test_compute_workload_id_from_test_quote() { + // Load the test quote output used in integration tests + let quote_output = include_bytes!("../tests/framework/artifacts/test-quote.bin"); + + // Compute the workload ID + let workload_id = compute_workload_id(quote_output) + .expect("failed to compute workload ID from test quote"); + + assert_eq!( + workload_id, WORKLOAD_ID, + "workload ID mismatch for test quote" + ); + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/builder_tx.rs b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/builder_tx.rs new file mode 100644 index 00000000000..7651b846ebe --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/builder_tx.rs @@ -0,0 +1,412 @@ +use alloy_eips::Encodable2718; +use alloy_evm::Database; +use alloy_op_evm::OpEvm; +use alloy_primitives::{Address, B256, Bytes, Signature, U256, keccak256}; +use alloy_rpc_types_eth::TransactionInput; +use alloy_sol_types::{SolCall, SolEvent, SolValue}; +use core::fmt::Debug; +use op_alloy_rpc_types::OpTransactionRequest; +use reth_evm::{ConfigureEvm, Evm, precompiles::PrecompilesMap}; +use reth_optimism_primitives::OpTransactionSigned; +use reth_provider::StateProvider; +use reth_revm::{State, database::StateProviderDatabase}; +use revm::{DatabaseCommit, DatabaseRef, inspector::NoOpInspector}; +use std::sync::{Arc, atomic::AtomicBool}; +use tracing::{debug, info, warn}; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, + SimulationSuccessResult, get_nonce, + }, + flashtestations::{ + BlockData, + IBlockBuilderPolicy::{self, BlockBuilderProofVerified}, + IERC20Permit, + IFlashtestationRegistry::{self, TEEServiceRegistered}, + }, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +pub struct FlashtestationsBuilderTxArgs { + pub attestation: Vec, + pub extra_registration_data: Bytes, + pub tee_service_signer: Signer, + pub registry_address: Address, + pub builder_policy_address: Address, + pub builder_proof_version: u8, + pub enable_block_proofs: bool, + pub registered: bool, + pub builder_key: Signer, +} + +#[derive(Debug, Clone)] +pub struct FlashtestationsBuilderTx +where + ExtraCtx: Debug + Default, + Extra: Debug + Default, +{ + // Attestation for the builder + attestation: Vec, + // Extra registration data for the builder + extra_registration_data: Bytes, + // TEE service generated key + tee_service_signer: Signer, + // Registry address for the attestation + registry_address: Address, + // Builder policy address for the block builder proof + builder_policy_address: Address, + // Builder proof version + builder_proof_version: u8, + // Whether the workload and address has been registered + registered: Arc, + // Whether block proofs are enabled + enable_block_proofs: bool, + // Builder key for the flashtestation permit tx + builder_signer: Signer, + // Extra context and data + _marker: std::marker::PhantomData<(ExtraCtx, Extra)>, +} + +impl FlashtestationsBuilderTx +where + ExtraCtx: Debug + Default, + Extra: Debug + Default, +{ + pub fn new(args: FlashtestationsBuilderTxArgs) -> Self { + Self { + attestation: args.attestation, + extra_registration_data: args.extra_registration_data, + tee_service_signer: args.tee_service_signer, + registry_address: args.registry_address, + builder_policy_address: args.builder_policy_address, + builder_proof_version: args.builder_proof_version, + registered: Arc::new(AtomicBool::new(args.registered)), + enable_block_proofs: args.enable_block_proofs, + builder_signer: args.builder_key, + _marker: std::marker::PhantomData, + } + } + + pub fn tee_signer(&self) -> &Signer { + &self.tee_service_signer + } + + /// Computes the block content hash according to the formula: + /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) + /// https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#block-building-process + fn compute_block_content_hash( + transactions: &[OpTransactionSigned], + parent_hash: B256, + block_number: u64, + timestamp: u64, + ) -> B256 { + // Create ordered list of transaction hashes + let transaction_hashes: Vec = transactions + .iter() + .map(|tx| { + // RLP encode the transaction and hash it + let mut encoded = Vec::new(); + tx.encode_2718(&mut encoded); + keccak256(&encoded) + }) + .collect(); + + // Create struct and ABI encode + let block_data = BlockData { + parentHash: parent_hash, + blockNumber: U256::from(block_number), + timestamp: U256::from(timestamp), + transactionHashes: transaction_hashes, + }; + + let encoded = block_data.abi_encode(); + keccak256(&encoded) + } + + fn set_registered( + &self, + state_provider: impl StateProvider + Clone, + ctx: &OpPayloadBuilderCtx, + ) -> Result<(), BuilderTransactionError> { + let state = StateProviderDatabase::new(state_provider.clone()); + let mut simulation_state = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + let mut evm = ctx + .evm_config + .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + cfg.disable_nonce_check = true; + }); + let calldata = IFlashtestationRegistry::getRegistrationStatusCall { + teeAddress: self.tee_service_signer.address, + }; + let SimulationSuccessResult { output, .. } = + self.flashtestations_contract_read(self.registry_address, calldata, ctx, &mut evm)?; + if output.isValid { + self.registered + .store(true, std::sync::atomic::Ordering::SeqCst); + } + Ok(()) + } + + fn get_permit_nonce( + &self, + contract_address: Address, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let calldata = IERC20Permit::noncesCall { + owner: self.tee_service_signer.address, + }; + let SimulationSuccessResult { output, .. } = + self.flashtestations_contract_read(contract_address, calldata, ctx, evm)?; + Ok(output) + } + + fn registration_permit_signature( + &self, + permit_nonce: U256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let struct_hash_calldata = IFlashtestationRegistry::computeStructHashCall { + rawQuote: self.attestation.clone().into(), + extendedRegistrationData: self.extra_registration_data.clone(), + nonce: permit_nonce, + deadline: U256::from(ctx.timestamp()), + }; + let SimulationSuccessResult { output, .. } = self.flashtestations_contract_read( + self.registry_address, + struct_hash_calldata, + ctx, + evm, + )?; + let typed_data_hash_calldata = + IFlashtestationRegistry::hashTypedDataV4Call { structHash: output }; + let SimulationSuccessResult { output, .. } = self.flashtestations_contract_read( + self.registry_address, + typed_data_hash_calldata, + ctx, + evm, + )?; + let signature = self.tee_service_signer.sign_message(output)?; + Ok(signature) + } + + fn signed_registration_permit_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, + ) -> Result { + let permit_nonce = self.get_permit_nonce(self.registry_address, ctx, evm)?; + let signature = self.registration_permit_signature(permit_nonce, ctx, evm)?; + let calldata = IFlashtestationRegistry::permitRegisterTEEServiceCall { + rawQuote: self.attestation.clone().into(), + extendedRegistrationData: self.extra_registration_data.clone(), + nonce: permit_nonce, + deadline: U256::from(ctx.timestamp()), + signature: signature.as_bytes().into(), + }; + let SimulationSuccessResult { + gas_used, + state_changes, + .. + } = self.flashtestations_call( + self.registry_address, + calldata.clone(), + vec![TEEServiceRegistered::SIGNATURE_HASH], + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.registry_address, + self.builder_signer, + gas_used, + calldata.abi_encode().into(), + ctx, + evm.db(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + // commit the register transaction state so the block proof transaction can succeed + evm.db_mut().commit(state_changes); + Ok(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + }) + } + + fn block_proof_permit_signature( + &self, + permit_nonce: U256, + block_content_hash: B256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let struct_hash_calldata = IBlockBuilderPolicy::computeStructHashCall { + version: self.builder_proof_version, + blockContentHash: block_content_hash, + nonce: permit_nonce, + }; + let SimulationSuccessResult { output, .. } = self.flashtestations_contract_read( + self.builder_policy_address, + struct_hash_calldata, + ctx, + evm, + )?; + let typed_data_hash_calldata = + IBlockBuilderPolicy::getHashedTypeDataV4Call { structHash: output }; + let SimulationSuccessResult { output, .. } = self.flashtestations_contract_read( + self.builder_policy_address, + typed_data_hash_calldata, + ctx, + evm, + )?; + let signature = self.tee_service_signer.sign_message(output)?; + Ok(signature) + } + + fn signed_block_proof_permit_tx( + &self, + transactions: &[OpTransactionSigned], + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let permit_nonce = self.get_permit_nonce(self.builder_policy_address, ctx, evm)?; + let block_content_hash = Self::compute_block_content_hash( + transactions, + ctx.parent_hash(), + ctx.block_number(), + ctx.timestamp(), + ); + let signature = + self.block_proof_permit_signature(permit_nonce, block_content_hash, ctx, evm)?; + let calldata = IBlockBuilderPolicy::permitVerifyBlockBuilderProofCall { + blockContentHash: block_content_hash, + nonce: permit_nonce, + version: self.builder_proof_version, + eip712Sig: signature.as_bytes().into(), + }; + let SimulationSuccessResult { gas_used, .. } = self.flashtestations_call( + self.builder_policy_address, + calldata.clone(), + vec![BlockBuilderProofVerified::SIGNATURE_HASH], + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.builder_policy_address, + self.builder_signer, + gas_used, + calldata.abi_encode().into(), + ctx, + evm.db(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + }) + } + + fn flashtestations_contract_read( + &self, + contract_address: Address, + calldata: T, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + self.flashtestations_call(contract_address, calldata, vec![], ctx, evm) + } + + fn flashtestations_call( + &self, + contract_address: Address, + calldata: T, + expected_topics: Vec, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + let tx_req = OpTransactionRequest::default() + .gas_limit(ctx.block_gas_limit()) + .max_fee_per_gas(ctx.base_fee().into()) + .to(contract_address) + .from(self.builder_signer.address) + .nonce(get_nonce(evm.db(), self.builder_signer.address)?) + .input(TransactionInput::new(calldata.abi_encode().into())); + if contract_address == self.registry_address { + self.simulate_call::( + tx_req, + expected_topics, + evm, + ) + } else if contract_address == self.builder_policy_address { + self.simulate_call::( + tx_req, + expected_topics, + evm, + ) + } else { + Err(BuilderTransactionError::msg( + "invalid contract address for flashtestations", + )) + } + } +} + +impl BuilderTransactions + for FlashtestationsBuilderTx +where + ExtraCtx: Debug + Default, + Extra: Debug + Default, +{ + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + _top_of_block: bool, + ) -> Result, BuilderTransactionError> { + // set registered by simulating against the committed state + if !self.registered.load(std::sync::atomic::Ordering::SeqCst) { + self.set_registered(state_provider, ctx)?; + } + + let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + cfg.disable_block_gas_limit = true; + }); + + let mut builder_txs = Vec::::new(); + + if !self.registered.load(std::sync::atomic::Ordering::SeqCst) { + info!(target: "flashtestations", "tee service not registered yet, attempting to register"); + let register_tx = self.signed_registration_permit_tx(ctx, &mut evm)?; + builder_txs.push(register_tx); + } + + // don't return on error for block proof as previous txs in builder_txs will not be returned + if self.enable_block_proofs { + debug!(target: "flashtestations", "adding permit verify block proof tx"); + match self.signed_block_proof_permit_tx(&info.executed_transactions, ctx, &mut evm) { + Ok(block_proof_tx) => builder_txs.push(block_proof_tx), + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add permit block proof transaction") + } + } + } + Ok(builder_txs) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/mod.rs new file mode 100644 index 00000000000..f80a2e0c1d2 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/mod.rs @@ -0,0 +1,124 @@ +use alloy_sol_types::sol; + +// https://github.com/flashbots/flashtestations/commit/7cc7f68492fe672a823dd2dead649793aac1f216 +sol!( + #[sol(rpc, abi)] + #[derive(Debug)] + interface IFlashtestationRegistry { + function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external; + + function permitRegisterTEEService( + bytes calldata rawQuote, + bytes calldata extendedRegistrationData, + uint256 nonce, + uint256 deadline, + bytes calldata signature + ) external payable; + + function computeStructHash( + bytes calldata rawQuote, + bytes calldata extendedRegistrationData, + uint256 nonce, + uint256 deadline + ) external pure returns (bytes32); + + function hashTypedDataV4(bytes32 structHash) external view returns (bytes32); + + function getRegistrationStatus(address teeAddress) external view returns (bool isValid, bytes32 quoteHash); + + /// @notice Emitted when a TEE service is registered + /// @param teeAddress The address of the TEE service + /// @param rawQuote The raw quote from the TEE device + /// @param alreadyExists Whether the TEE service is already registered + event TEEServiceRegistered(address indexed teeAddress, bytes rawQuote, bool alreadyExists); + + /// @notice Emitted when the attestation contract is the 0x0 address + error InvalidAttestationContract(); + /// @notice Emitted when the signature is expired because the deadline has passed + error ExpiredSignature(uint256 deadline); + /// @notice Emitted when the quote is invalid according to the Automata DCAP Attestation contract + error InvalidQuote(bytes output); + /// @notice Emitted when the report data length is too short + error InvalidReportDataLength(uint256 length); + /// @notice Emitted when the registration data hash does not match the expected hash + error InvalidRegistrationDataHash(bytes32 expected, bytes32 received); + /// @notice Emitted when the byte size is exceeded + error ByteSizeExceeded(uint256 size); + /// @notice Emitted when the TEE service is already registered when registering + error TEEServiceAlreadyRegistered(address teeAddress); + /// @notice Emitted when the signer doesn't match the TEE address + error SignerMustMatchTEEAddress(address signer, address teeAddress); + /// @notice Emitted when the TEE service is not registered + error TEEServiceNotRegistered(address teeAddress); + /// @notice Emitted when the TEE service is already invalid when trying to invalidate a TEE registration + error TEEServiceAlreadyInvalid(address teeAddress); + /// @notice Emitted when the TEE service is still valid when trying to invalidate a TEE registration + error TEEIsStillValid(address teeAddress); + /// @notice Emitted when the nonce is invalid when verifying a signature + error InvalidNonce(uint256 expected, uint256 provided); + } + + #[sol(rpc, abi)] + #[derive(Debug)] + interface IBlockBuilderPolicy { + function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + + function permitVerifyBlockBuilderProof( + uint8 version, + bytes32 blockContentHash, + uint256 nonce, + bytes calldata eip712Sig + ) external; + + function computeStructHash(uint8 version, bytes32 blockContentHash, uint256 nonce) + external + pure + returns (bytes32); + + function getHashedTypeDataV4(bytes32 structHash) external view returns (bytes32); + + /// @notice Emitted when a block builder proof is successfully verified + /// @param caller The address that called the verification function (TEE address) + /// @param workloadId The workload identifier of the TEE + /// @param version The flashtestation protocol version used + /// @param blockContentHash The hash of the block content + /// @param commitHash The git commit hash associated with the workload + event BlockBuilderProofVerified( + address caller, bytes32 workloadId, uint8 version, bytes32 blockContentHash, string commitHash + ); + + /// @notice Emitted when the registry is the 0x0 address + error InvalidRegistry(); + /// @notice Emitted when a workload to be added is already in the policy + error WorkloadAlreadyInPolicy(); + /// @notice Emitted when a workload to be removed is not in the policy + error WorkloadNotInPolicy(); + /// @notice Emitted when the address is not in the approvedWorkloads mapping + error UnauthorizedBlockBuilder(address caller); + /// @notice Emitted when the nonce is invalid + error InvalidNonce(uint256 expected, uint256 provided); + /// @notice Emitted when the commit hash is empty + error EmptyCommitHash(); + /// @notice Emitted when the source locators array is empty + error EmptySourceLocators(); + } + + interface IERC20Permit { + function nonces(address owner) external view returns (uint256); + } + + struct BlockData { + bytes32 parentHash; + uint256 blockNumber; + uint256 timestamp; + bytes32[] transactionHashes; + } + + type WorkloadId is bytes32; +); + +pub mod args; +pub mod attestation; +pub mod builder_tx; +pub mod service; +pub mod tx_manager; diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/service.rs b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/service.rs new file mode 100644 index 00000000000..73896119c88 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/service.rs @@ -0,0 +1,175 @@ +use alloy_primitives::{B256, Bytes, keccak256}; +use std::{ + fs::{self, OpenOptions}, + io::Write, + os::unix::fs::OpenOptionsExt, + path::Path, +}; +use tracing::{info, warn}; + +use super::{ + args::FlashtestationsArgs, + attestation::{AttestationConfig, get_attestation_provider}, + tx_manager::TxManager, +}; +use crate::{ + flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, + metrics::record_tee_metrics, + tx_signer::{Signer, generate_key_from_seed, generate_signer}, +}; +use std::fmt::Debug; + +pub async fn bootstrap_flashtestations( + args: FlashtestationsArgs, + builder_key: Signer, +) -> eyre::Result> +where + ExtraCtx: Debug + Default, + Extra: Debug + Default, +{ + let tee_service_signer = load_or_generate_tee_key( + &args.flashtestations_key_path, + args.debug, + &args.debug_tee_key_seed, + )?; + + info!( + "Flashtestations TEE address: {}", + tee_service_signer.address + ); + + let registry_address = args + .registry_address + .expect("registry address required when flashtestations enabled"); + let builder_policy_address = args + .builder_policy_address + .expect("builder policy address required when flashtestations enabled"); + + let attestation_provider = get_attestation_provider(AttestationConfig { + debug: args.debug, + quote_provider: args.quote_provider, + }); + + // Prepare report data: + // - TEE address (20 bytes) at reportData[0:20] + // - Extended registration data hash (32 bytes) at reportData[20:52] + // - Total: 52 bytes, padded to 64 bytes with zeros + + // Extract TEE address as 20 bytes + let tee_address_bytes: [u8; 20] = tee_service_signer.address.into(); + + // Calculate keccak256 hash of empty bytes (32 bytes) + let ext_data = Bytes::from(b""); + let ext_data_hash = keccak256(&ext_data); + + // Create 64-byte report data array + let mut report_data = [0u8; 64]; + + // Copy TEE address (20 bytes) to positions 0-19 + report_data[0..20].copy_from_slice(&tee_address_bytes); + + // Copy extended registration data hash (32 bytes) to positions 20-51 + report_data[20..52].copy_from_slice(ext_data_hash.as_ref()); + + // Request TDX attestation + info!(target: "flashtestations", "requesting TDX attestation"); + let attestation = attestation_provider.get_attestation(report_data).await?; + + // Record TEE metrics (workload ID, MRTD, RTMR0) + record_tee_metrics(&attestation, &tee_service_signer.address)?; + + // Use an external rpc when the builder is not the same as the builder actively building blocks onchain + let registered = if let Some(rpc_url) = args.rpc_url { + let tx_manager = TxManager::new( + tee_service_signer, + builder_key, + rpc_url.clone(), + registry_address, + ); + // Submit report onchain by registering the key of the tee service + match tx_manager + .register_tee_service(attestation.clone(), ext_data.clone()) + .await + { + Ok(_) => true, + Err(e) => { + warn!(error = %e, "Failed to register tee service via rpc"); + false + } + } + } else { + false + }; + + let flashtestations_builder_tx = FlashtestationsBuilderTx::new(FlashtestationsBuilderTxArgs { + attestation, + extra_registration_data: ext_data, + tee_service_signer, + registry_address, + builder_policy_address, + builder_proof_version: args.builder_proof_version, + enable_block_proofs: args.enable_block_proofs, + registered, + builder_key, + }); + + Ok(flashtestations_builder_tx) +} + +/// Load ephemeral TEE key from file, or generate and save a new one +fn load_or_generate_tee_key(key_path: &str, debug: bool, debug_seed: &str) -> eyre::Result { + if debug { + info!("Flashtestations debug mode enabled, generating debug key from seed"); + return Ok(generate_key_from_seed(debug_seed)); + } + + let path = Path::new(key_path); + + if let Some(signer) = load_tee_key(path) { + return Ok(signer); + } + + // Generate new key + info!("Generating new ephemeral TEE key"); + let signer = generate_signer(); + + let key_hex = hex::encode(signer.secret.secret_bytes()); + + // Create file with 0600 permissions atomically + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .mode(0o600) + .open(path) + .and_then(|mut file| file.write_all(key_hex.as_bytes())) + .inspect_err(|e| warn!("Failed to write key to {}: {:?}", key_path, e)) + .ok(); + + Ok(signer) +} + +fn load_tee_key(path: &Path) -> Option { + // Try to load existing key + if !path.exists() { + return None; + } + + info!("Loading TEE key from {:?}", path); + let key_hex = fs::read_to_string(path) + .inspect_err(|e| warn!("failed to read key file: {:?}", e)) + .ok()?; + + let secret_bytes = B256::try_from( + hex::decode(key_hex.trim()) + .inspect_err(|e| warn!("failed to decode hex from file {:?}", e)) + .ok()? + .as_slice(), + ) + .inspect_err(|e| warn!("failed to parse key from file: {:?}", e)) + .ok()?; + + Signer::try_from_secret(secret_bytes) + .inspect_err(|e| warn!("failed to create signer from key: {:?}", e)) + .ok() +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/tx_manager.rs new file mode 100644 index 00000000000..d6412bf6d0f --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/flashtestations/tx_manager.rs @@ -0,0 +1,191 @@ +use alloy_json_rpc::RpcError; +use alloy_network::ReceiptResponse; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256}; +use alloy_rpc_types_eth::TransactionRequest; +use alloy_sol_types::SolCall; +use alloy_transport::{TransportError, TransportErrorKind, TransportResult}; +use k256::ecdsa; +use std::time::Duration; + +use alloy_provider::{ + PendingTransactionBuilder, PendingTransactionError, Provider, ProviderBuilder, +}; +use alloy_signer_local::PrivateKeySigner; +use op_alloy_network::Optimism; +use tracing::{debug, info, warn}; + +use crate::{ + flashtestations::{ + IERC20Permit::{self}, + IFlashtestationRegistry, + }, + tx_signer::Signer, +}; + +#[derive(Debug, thiserror::Error)] +pub enum TxManagerError { + #[error("rpc error: {0}")] + RpcError(#[from] TransportError), + #[error("tx reverted: {0}")] + TxReverted(TxHash), + #[error("error checking tx confirmation: {0}")] + TxConfirmationError(PendingTransactionError), + #[error("tx rpc error: {0}")] + TxRpcError(RpcError), + #[error("signer error: {0}")] + SignerError(ecdsa::Error), + #[error("error signing message: {0}")] + SignatureError(secp256k1::Error), +} + +#[derive(Debug, Clone)] +pub struct TxManager { + tee_service_signer: Signer, + builder_signer: Signer, + rpc_url: String, + registry_address: Address, +} + +impl TxManager { + pub fn new( + tee_service_signer: Signer, + builder_signer: Signer, + rpc_url: String, + registry_address: Address, + ) -> Self { + Self { + tee_service_signer, + builder_signer, + rpc_url, + registry_address, + } + } + + pub async fn register_tee_service( + &self, + attestation: Vec, + extra_registration_data: Bytes, + ) -> Result<(), TxManagerError> { + info!(target: "flashtestations", "funding TEE address at {}", self.tee_service_signer.address); + let quote_bytes = Bytes::from(attestation); + let wallet = + PrivateKeySigner::from_bytes(&self.builder_signer.secret.secret_bytes().into()) + .map_err(TxManagerError::SignerError)?; + let provider = ProviderBuilder::new() + .disable_recommended_fillers() + .fetch_chain_id() + .with_gas_estimation() + .with_cached_nonce_management() + .wallet(wallet) + .network::() + .connect(self.rpc_url.as_str()) + .await?; + + info!(target: "flashtestations", "submitting quote to registry at {}", self.registry_address); + + // Get permit nonce + let nonce_call = IERC20Permit::noncesCall { + owner: self.tee_service_signer.address, + }; + let nonce_tx = TransactionRequest { + to: Some(TxKind::Call(self.registry_address)), + input: nonce_call.abi_encode().into(), + ..Default::default() + }; + let nonce = U256::from_be_slice(provider.call(nonce_tx.into()).await?.as_ref()); + + // Set deadline 1 hour from now + let deadline = U256::from( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + + 3600, + ); + + // Call computeStructHash to get the struct hash + let struct_hash_call = IFlashtestationRegistry::computeStructHashCall { + rawQuote: quote_bytes.clone(), + extendedRegistrationData: extra_registration_data.clone(), + nonce, + deadline, + }; + let struct_hash_tx = TransactionRequest { + to: Some(TxKind::Call(self.registry_address)), + input: struct_hash_call.abi_encode().into(), + ..Default::default() + }; + let struct_hash = B256::from_slice(provider.call(struct_hash_tx.into()).await?.as_ref()); + + // Get typed data hash + let typed_hash_call = IFlashtestationRegistry::hashTypedDataV4Call { + structHash: struct_hash, + }; + let typed_hash_tx = TransactionRequest { + to: Some(TxKind::Call(self.registry_address)), + input: typed_hash_call.abi_encode().into(), + ..Default::default() + }; + let message_hash = B256::from_slice(provider.call(typed_hash_tx.into()).await?.as_ref()); + + // Sign the hash + let signature = self + .tee_service_signer + .sign_message(message_hash) + .map_err(TxManagerError::SignatureError)?; + + let calldata = IFlashtestationRegistry::permitRegisterTEEServiceCall { + rawQuote: quote_bytes, + extendedRegistrationData: extra_registration_data, + nonce, + deadline, + signature: signature.as_bytes().into(), + } + .abi_encode(); + let tx = TransactionRequest { + from: Some(self.tee_service_signer.address), + to: Some(TxKind::Call(self.registry_address)), + input: calldata.into(), + ..Default::default() + }; + match Self::process_pending_tx(provider.send_transaction(tx.into()).await).await { + Ok(tx_hash) => { + info!(target: "flashtestations", tx_hash = %tx_hash, "attestation transaction confirmed successfully"); + Ok(()) + } + Err(e) => { + warn!(target: "flashtestations", error = %e, "attestation transaction failed to be sent"); + Err(e) + } + } + } + + /// Processes a pending transaction and logs whether the transaction succeeded or not + async fn process_pending_tx( + pending_tx_result: TransportResult>, + ) -> Result { + match pending_tx_result { + Ok(pending_tx) => { + let tx_hash = *pending_tx.tx_hash(); + debug!(target: "flashtestations", tx_hash = %tx_hash, "transaction submitted"); + + // Wait for funding transaction confirmation + match pending_tx + .with_timeout(Some(Duration::from_secs(30))) + .get_receipt() + .await + { + Ok(receipt) => { + if receipt.status() { + Ok(receipt.transaction_hash()) + } else { + Err(TxManagerError::TxReverted(tx_hash)) + } + } + Err(e) => Err(TxManagerError::TxConfirmationError(e)), + } + } + Err(e) => Err(TxManagerError::TxRpcError(e)), + } + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/args.rs b/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/args.rs new file mode 100644 index 00000000000..ec7f8008bdb --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/args.rs @@ -0,0 +1,28 @@ +use clap::Args; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Args)] +pub struct GasLimiterArgs { + /// Enable address-based gas rate limiting + #[arg(long = "gas-limiter.enabled", env)] + pub gas_limiter_enabled: bool, + + /// Maximum gas per address in token bucket. Defaults to 10 million gas. + #[arg( + long = "gas-limiter.max-gas-per-address", + env, + default_value = "10000000" + )] + pub max_gas_per_address: u64, + + /// Gas refill rate per block. Defaults to 1 million gas per block. + #[arg( + long = "gas-limiter.refill-rate-per-block", + env, + default_value = "1000000" + )] + pub refill_rate_per_block: u64, + + /// How many blocks to wait before cleaning up stale buckets for addresses. + #[arg(long = "gas-limiter.cleanup-interval", env, default_value = "100")] + pub cleanup_interval: u64, +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/error.rs b/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/error.rs new file mode 100644 index 00000000000..a85b7f77ca5 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/error.rs @@ -0,0 +1,13 @@ +use alloy_primitives::Address; + +#[derive(Debug, thiserror::Error)] +pub enum GasLimitError { + #[error( + "Address {address} exceeded gas limit: {requested} gwei requested, {available} gwei available" + )] + AddressLimitExceeded { + address: Address, + requested: u64, + available: u64, + }, +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/metrics.rs b/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/metrics.rs new file mode 100644 index 00000000000..f7898657052 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/metrics.rs @@ -0,0 +1,47 @@ +use std::time::Duration; + +use metrics::{Counter, Gauge, Histogram}; +use reth_metrics::Metrics; + +use crate::gas_limiter::error::GasLimitError; + +#[derive(Metrics, Clone)] +#[metrics(scope = "op_rbuilder.gas_limiter")] +pub(super) struct GasLimiterMetrics { + /// Transactions rejected by gas limits Labeled by reason: "per_address", + /// "global", "burst" + pub rejections: Counter, + + /// Time spent in rate limiting logic + pub check_time: Histogram, + + /// Number of addresses with active budgets + pub active_address_count: Gauge, + + /// Time to refill buckets + pub refresh_duration: Histogram, +} + +impl GasLimiterMetrics { + pub(super) fn record_gas_check( + &self, + check_result: &Result, + duration: Duration, + ) { + if let Ok(created_new_bucket) = check_result { + if *created_new_bucket { + self.active_address_count.increment(1); + } + } else { + self.rejections.increment(1); + } + + self.check_time.record(duration); + } + + pub(super) fn record_refresh(&self, removed_addresses: usize, duration: Duration) { + self.active_address_count + .decrement(removed_addresses as f64); + self.refresh_duration.record(duration); + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/mod.rs new file mode 100644 index 00000000000..71daae7c287 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/gas_limiter/mod.rs @@ -0,0 +1,223 @@ +use std::{cmp::min, sync::Arc, time::Instant}; + +use alloy_primitives::Address; +use dashmap::DashMap; + +use crate::gas_limiter::{args::GasLimiterArgs, error::GasLimitError, metrics::GasLimiterMetrics}; + +pub mod args; +pub mod error; +mod metrics; + +#[derive(Debug, Clone)] +pub struct AddressGasLimiter { + inner: Option, +} + +#[derive(Debug, Clone)] +struct AddressGasLimiterInner { + config: GasLimiterArgs, + // We don't need an Arc> here, we can get away with RefCell, but + // the reth PayloadBuilder trait needs this to be Send + Sync + address_buckets: Arc>, + metrics: GasLimiterMetrics, +} + +#[derive(Debug, Clone)] +struct TokenBucket { + capacity: u64, + available: u64, +} + +impl AddressGasLimiter { + pub fn new(config: GasLimiterArgs) -> Self { + Self { + inner: AddressGasLimiterInner::try_new(config), + } + } + + /// Check if there's enough gas for this address and consume it. Returns + /// Ok(()) if there's enough otherwise returns an error. + pub fn consume_gas(&self, address: Address, gas_requested: u64) -> Result<(), GasLimitError> { + if let Some(inner) = &self.inner { + inner.consume_gas(address, gas_requested) + } else { + Ok(()) + } + } + + /// Should be called upon each new block. Refills buckets/Garbage collection + pub fn refresh(&self, block_number: u64) { + if let Some(inner) = self.inner.as_ref() { + inner.refresh(block_number) + } + } +} + +impl AddressGasLimiterInner { + fn try_new(config: GasLimiterArgs) -> Option { + if !config.gas_limiter_enabled { + return None; + } + + Some(Self { + config, + address_buckets: Default::default(), + metrics: Default::default(), + }) + } + + fn consume_gas_inner( + &self, + address: Address, + gas_requested: u64, + ) -> Result { + let mut created_new_bucket = false; + let mut bucket = self + .address_buckets + .entry(address) + // if we don't find a bucket we need to initialize a new one + .or_insert_with(|| { + created_new_bucket = true; + TokenBucket::new(self.config.max_gas_per_address) + }); + + if gas_requested > bucket.available { + return Err(GasLimitError::AddressLimitExceeded { + address, + requested: gas_requested, + available: bucket.available, + }); + } + + bucket.available -= gas_requested; + + Ok(created_new_bucket) + } + + fn consume_gas(&self, address: Address, gas_requested: u64) -> Result<(), GasLimitError> { + let start = Instant::now(); + let result = self.consume_gas_inner(address, gas_requested); + + self.metrics.record_gas_check(&result, start.elapsed()); + + result.map(|_| ()) + } + + fn refresh_inner(&self, block_number: u64) -> usize { + let active_addresses = self.address_buckets.len(); + + self.address_buckets.iter_mut().for_each(|mut bucket| { + bucket.available = min( + bucket.capacity, + bucket.available + self.config.refill_rate_per_block, + ) + }); + + // Only clean up stale buckets every `cleanup_interval` blocks + if block_number.is_multiple_of(self.config.cleanup_interval) { + self.address_buckets + .retain(|_, bucket| bucket.available <= bucket.capacity); + } + + active_addresses - self.address_buckets.len() + } + + fn refresh(&self, block_number: u64) { + let start = Instant::now(); + let removed_addresses = self.refresh_inner(block_number); + + self.metrics + .record_refresh(removed_addresses, start.elapsed()); + } +} + +impl TokenBucket { + fn new(capacity: u64) -> Self { + Self { + capacity, + available: capacity, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::Address; + + fn create_test_config(max_gas: u64, refill_rate: u64, cleanup_interval: u64) -> GasLimiterArgs { + GasLimiterArgs { + gas_limiter_enabled: true, + max_gas_per_address: max_gas, + refill_rate_per_block: refill_rate, + cleanup_interval, + } + } + + fn test_address() -> Address { + Address::from([1u8; 20]) + } + + #[test] + fn test_basic_refill() { + let config = create_test_config(1000, 200, 10); + let limiter = AddressGasLimiter::new(config); + + // Consume all gas + assert!(limiter.consume_gas(test_address(), 1000).is_ok()); + assert!(limiter.consume_gas(test_address(), 1).is_err()); + + // Refill and check available gas increased + limiter.refresh(1); + assert!(limiter.consume_gas(test_address(), 200).is_ok()); + assert!(limiter.consume_gas(test_address(), 1).is_err()); + } + + #[test] + fn test_over_capacity_request() { + let config = create_test_config(1000, 100, 10); + let limiter = AddressGasLimiter::new(config); + + // Request more than capacity should fail + let result = limiter.consume_gas(test_address(), 1500); + assert!(result.is_err()); + + if let Err(GasLimitError::AddressLimitExceeded { available, .. }) = result { + assert_eq!(available, 1000); + } + + // Bucket should still be full after failed request + assert!(limiter.consume_gas(test_address(), 1000).is_ok()); + } + + #[test] + fn test_multiple_users() { + // Simulate more realistic scenario + let config = create_test_config(10_000_000, 1_000_000, 100); // 10M max, 1M refill + let limiter = AddressGasLimiter::new(config); + + let searcher1 = Address::from([0x1; 20]); + let searcher2 = Address::from([0x2; 20]); + let attacker = Address::from([0x3; 20]); + + // Normal searchers use reasonable amounts + assert!(limiter.consume_gas(searcher1, 500_000).is_ok()); + assert!(limiter.consume_gas(searcher2, 750_000).is_ok()); + + // Attacker tries to consume massive amounts + assert!(limiter.consume_gas(attacker, 15_000_000).is_err()); // Should fail - over capacity + assert!(limiter.consume_gas(attacker, 5_000_000).is_ok()); // Should succeed - within capacity + + // Attacker tries to consume more + assert!(limiter.consume_gas(attacker, 6_000_000).is_err()); // Should fail - would exceed remaining + + // New block - refill + limiter.refresh(1); + + // Everyone should get some gas back + assert!(limiter.consume_gas(searcher1, 1_000_000).is_ok()); // Had 9.5M + 1M refill, now 9.5M + assert!(limiter.consume_gas(searcher2, 1_000_000).is_ok()); // Had 9.25M + 1M refill, now 9.25M + assert!(limiter.consume_gas(attacker, 1_000_000).is_ok()); // Had 5M + 1M refill, now 5M + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/launcher.rs b/rust/op-rbuilder/crates/op-rbuilder/src/launcher.rs new file mode 100644 index 00000000000..66de71a363c --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/launcher.rs @@ -0,0 +1,185 @@ +use eyre::Result; +use reth_optimism_rpc::OpEthApiBuilder; + +use crate::{ + args::*, + builders::{BuilderConfig, BuilderMode, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, + metrics::{VERSION, record_flag_gauge_metrics}, + monitor_tx_pool::monitor_tx_pool, + primitives::reth::engine_api_builder::OpEngineApiBuilder, + revert_protection::{EthApiExtServer, RevertProtectionExt}, + tx::FBPooledTransaction, +}; +use core::fmt::Debug; +use moka::future::Cache; +use reth::builder::{NodeBuilder, WithLaunchContext}; +use reth_cli_commands::launcher::Launcher; +use reth_db::mdbx::DatabaseEnv; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_cli::chainspec::OpChainSpecParser; +use reth_optimism_node::{ + OpNode, + node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, +}; +use reth_transaction_pool::TransactionPool; +use std::marker::PhantomData; + +pub fn launch() -> Result<()> { + let cli = Cli::parsed(); + let mode = cli.builder_mode(); + + #[cfg(feature = "telemetry")] + let telemetry_args = match &cli.command { + reth_optimism_cli::commands::Commands::Node(node_command) => { + node_command.ext.telemetry.clone() + } + _ => Default::default(), + }; + + #[cfg(not(feature = "telemetry"))] + let cli_app = cli.configure(); + + #[cfg(feature = "telemetry")] + let mut cli_app = cli.configure(); + #[cfg(feature = "telemetry")] + { + use crate::primitives::telemetry::setup_telemetry_layer; + let telemetry_layer = setup_telemetry_layer(&telemetry_args)?; + cli_app.access_tracing_layers()?.add_layer(telemetry_layer); + } + + match mode { + BuilderMode::Standard => { + tracing::info!("Starting OP builder in standard mode"); + let launcher = BuilderLauncher::::new(); + cli_app.run(launcher)?; + } + BuilderMode::Flashblocks => { + tracing::info!("Starting OP builder in flashblocks mode"); + let launcher = BuilderLauncher::::new(); + cli_app.run(launcher)?; + } + } + Ok(()) +} + +pub struct BuilderLauncher { + _builder: PhantomData, +} + +impl BuilderLauncher +where + B: PayloadBuilder, +{ + pub fn new() -> Self { + Self { + _builder: PhantomData, + } + } +} + +impl Default for BuilderLauncher +where + B: PayloadBuilder, +{ + fn default() -> Self { + Self::new() + } +} + +impl Launcher for BuilderLauncher +where + B: PayloadBuilder, + BuilderConfig: TryFrom, + as TryFrom>::Error: Debug, +{ + async fn entrypoint( + self, + builder: WithLaunchContext>, + builder_args: OpRbuilderArgs, + ) -> Result<()> { + let builder_config = BuilderConfig::::try_from(builder_args.clone()) + .expect("Failed to convert rollup args to builder config"); + + record_flag_gauge_metrics(&builder_args); + + let da_config = builder_config.da_config.clone(); + let gas_limit_config = builder_config.gas_limit_config.clone(); + let rollup_args = builder_args.rollup_args; + let op_node = OpNode::new(rollup_args.clone()); + let reverted_cache = Cache::builder().max_capacity(100).build(); + let reverted_cache_copy = reverted_cache.clone(); + + let mut addons: OpAddOns< + _, + OpEthApiBuilder, + OpEngineValidatorBuilder, + OpEngineApiBuilder, + > = OpAddOnsBuilder::default() + .with_sequencer(rollup_args.sequencer.clone()) + .with_enable_tx_conditional(rollup_args.enable_tx_conditional) + .with_da_config(da_config) + .with_gas_limit_config(gas_limit_config) + .build(); + if cfg!(feature = "custom-engine-api") { + let engine_builder: OpEngineApiBuilder = + OpEngineApiBuilder::default(); + addons = addons.with_engine_api(engine_builder); + } + let handle = builder + .with_types::() + .with_components( + op_node + .components() + .pool( + OpPoolBuilder::::default() + .with_enable_tx_conditional( + // Revert protection uses the same internal pool logic as conditional transactions + // to garbage collect transactions out of the bundle range. + rollup_args.enable_tx_conditional + || builder_args.enable_revert_protection, + ) + .with_supervisor( + rollup_args.supervisor_http.clone(), + rollup_args.supervisor_safety_level, + ), + ) + .payload(B::new_service(builder_config)?), + ) + .with_add_ons(addons) + .extend_rpc_modules(move |ctx| { + if builder_args.enable_revert_protection { + tracing::info!("Revert protection enabled"); + + let pool = ctx.pool().clone(); + let provider = ctx.provider().clone(); + let revert_protection_ext = RevertProtectionExt::new( + pool, + provider, + ctx.registry.eth_api().clone(), + reverted_cache, + ); + + ctx.modules + .add_or_replace_configured(revert_protection_ext.into_rpc())?; + } + + Ok(()) + }) + .on_node_started(move |ctx| { + VERSION.register_version_metrics(); + if builder_args.log_pool_transactions { + tracing::info!("Logging pool transactions"); + let listener = ctx.pool.all_transactions_event_listener(); + let task = monitor_tx_pool(listener, reverted_cache_copy); + ctx.task_executor.spawn_critical_task("txlogging", task); + } + Ok(()) + }) + .launch() + .await?; + + handle.node_exit_future.await?; + Ok(()) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/lib.rs b/rust/op-rbuilder/crates/op-rbuilder/src/lib.rs new file mode 100644 index 00000000000..7817ba2d09e --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/lib.rs @@ -0,0 +1,17 @@ +pub mod args; +pub mod builders; +pub mod flashtestations; +pub mod gas_limiter; +pub mod launcher; +pub mod metrics; +mod monitor_tx_pool; +pub mod primitives; +pub mod revert_protection; +pub mod traits; +pub mod tx; +pub mod tx_signer; + +#[cfg(test)] +pub mod mock_tx; +#[cfg(any(test, feature = "testing"))] +pub mod tests; diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/metrics.rs b/rust/op-rbuilder/crates/op-rbuilder/src/metrics.rs new file mode 100644 index 00000000000..c381fed8244 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/metrics.rs @@ -0,0 +1,282 @@ +use alloy_primitives::{Address, hex}; +use metrics::IntoF64; +use reth_metrics::{ + Metrics, + metrics::{Counter, Gauge, Histogram, gauge}, +}; + +use crate::{ + args::OpRbuilderArgs, + flashtestations::attestation::{compute_workload_id_from_parsed, parse_report_body}, +}; + +/// The latest version from Cargo.toml. +pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// The 8 character short SHA of the latest commit. +pub const VERGEN_GIT_SHA: &str = env!("VERGEN_GIT_SHA_SHORT"); + +/// The build timestamp. +pub const VERGEN_BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP"); + +/// The target triple. +pub const VERGEN_CARGO_TARGET_TRIPLE: &str = env!("VERGEN_CARGO_TARGET_TRIPLE"); + +/// The build features. +pub const VERGEN_CARGO_FEATURES: &str = env!("VERGEN_CARGO_FEATURES"); + +/// The latest commit message and author name and email. +pub const VERGEN_GIT_AUTHOR: &str = env!("VERGEN_GIT_COMMIT_AUTHOR"); +pub const VERGEN_GIT_COMMIT_MESSAGE: &str = env!("VERGEN_GIT_COMMIT_MESSAGE"); + +/// The build profile name. +pub const BUILD_PROFILE_NAME: &str = env!("OP_RBUILDER_BUILD_PROFILE"); + +/// The short version information for op-rbuilder. +pub const SHORT_VERSION: &str = env!("OP_RBUILDER_SHORT_VERSION"); + +/// The long version information for op-rbuilder. +pub const LONG_VERSION: &str = concat!( + env!("OP_RBUILDER_LONG_VERSION_0"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_1"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_2"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_3"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_4"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_5"), +); + +pub const VERSION: VersionInfo = VersionInfo { + version: CARGO_PKG_VERSION, + build_timestamp: VERGEN_BUILD_TIMESTAMP, + cargo_features: VERGEN_CARGO_FEATURES, + git_sha: VERGEN_GIT_SHA, + target_triple: VERGEN_CARGO_TARGET_TRIPLE, + build_profile: BUILD_PROFILE_NAME, + commit_author: VERGEN_GIT_AUTHOR, + commit_message: VERGEN_GIT_COMMIT_MESSAGE, +}; + +/// op-rbuilder metrics +#[derive(Metrics, Clone)] +#[metrics(scope = "op_rbuilder")] +pub struct OpRBuilderMetrics { + /// Block built success + pub block_built_success: Counter, + /// Block synced success + pub block_synced_success: Counter, + /// Number of flashblocks added to block (Total per block) + pub flashblock_count: Histogram, + /// Number of messages sent + pub messages_sent_count: Counter, + /// Histogram of the time taken to build a block + pub total_block_built_duration: Histogram, + /// Latest time taken to build a block + pub total_block_built_gauge: Gauge, + /// Histogram of the time taken to build a Flashblock + pub flashblock_build_duration: Histogram, + /// Histogram of the time taken to sync a Flashblock + pub flashblock_sync_duration: Histogram, + /// Flashblock UTF8 payload byte size histogram + pub flashblock_byte_size_histogram: Histogram, + /// Histogram of transactions in a Flashblock + pub flashblock_num_tx_histogram: Histogram, + /// Number of invalid blocks + pub invalid_built_blocks_count: Counter, + /// Number of invalid synced blocks + pub invalid_synced_blocks_count: Counter, + /// Histogram of fetching transactions from the pool duration + pub transaction_pool_fetch_duration: Histogram, + /// Latest time taken to fetch tx from the pool + pub transaction_pool_fetch_gauge: Gauge, + /// Histogram of state root calculation duration + pub state_root_calculation_duration: Histogram, + /// Latest state root calculation duration + pub state_root_calculation_gauge: Gauge, + /// Histogram of sequencer transaction execution duration + pub sequencer_tx_duration: Histogram, + /// Latest sequencer transaction execution duration + pub sequencer_tx_gauge: Gauge, + /// Histogram of state merge transitions duration + pub state_transition_merge_duration: Histogram, + /// Latest state merge transitions duration + pub state_transition_merge_gauge: Gauge, + /// Histogram of the duration of payload simulation of all transactions + pub payload_transaction_simulation_duration: Histogram, + /// Latest payload simulation of all transactions duration + pub payload_transaction_simulation_gauge: Gauge, + /// Number of transaction considered for inclusion in the block + pub payload_num_tx_considered: Histogram, + /// Latest number of transactions considered for inclusion in the block + pub payload_num_tx_considered_gauge: Gauge, + /// Payload byte size histogram + pub payload_byte_size: Histogram, + /// Latest Payload byte size + pub payload_byte_size_gauge: Gauge, + /// Histogram of transactions in the payload + pub payload_num_tx: Histogram, + /// Latest number of transactions in the payload + pub payload_num_tx_gauge: Gauge, + /// Histogram of transactions in the payload that were successfully simulated + pub payload_num_tx_simulated: Histogram, + /// Latest number of transactions in the payload that were successfully simulated + pub payload_num_tx_simulated_gauge: Gauge, + /// Histogram of transactions in the payload that were successfully simulated + pub payload_num_tx_simulated_success: Histogram, + /// Latest number of transactions in the payload that were successfully simulated + pub payload_num_tx_simulated_success_gauge: Gauge, + /// Histogram of transactions in the payload that failed simulation + pub payload_num_tx_simulated_fail: Histogram, + /// Latest number of transactions in the payload that failed simulation + pub payload_num_tx_simulated_fail_gauge: Gauge, + /// Histogram of gas used by successful transactions + pub successful_tx_gas_used: Histogram, + /// Histogram of gas used by reverted transactions + pub reverted_tx_gas_used: Histogram, + /// Gas used by reverted transactions in the latest block + pub payload_reverted_tx_gas_used: Gauge, + /// Histogram of tx simulation duration + pub tx_simulation_duration: Histogram, + /// Byte size of transactions + pub tx_byte_size: Histogram, + /// How much less flashblocks we issue to be on time with block construction + pub reduced_flashblocks_number: Histogram, + /// How much less flashblocks we issued in reality, comparing to calculated number for block + pub missing_flashblocks_count: Histogram, + /// How much time we have deducted from block building time + pub flashblocks_time_drift: Histogram, + /// Time offset we used for first flashblock + pub first_flashblock_time_offset: Histogram, + /// Number of requests sent to the eth_sendBundle endpoint + pub bundle_requests: Counter, + /// Number of valid bundles received at the eth_sendBundle endpoint + pub valid_bundles: Counter, + /// Number of bundles that failed to execute + pub failed_bundles: Counter, + /// Number of reverted bundles + pub bundles_reverted: Histogram, + /// Histogram of eth_sendBundle request duration + pub bundle_receive_duration: Histogram, +} + +impl OpRBuilderMetrics { + #[expect(clippy::too_many_arguments)] + pub fn set_payload_builder_metrics( + &self, + payload_transaction_simulation_time: impl IntoF64 + Copy, + num_txs_considered: impl IntoF64 + Copy, + num_txs_simulated: impl IntoF64 + Copy, + num_txs_simulated_success: impl IntoF64 + Copy, + num_txs_simulated_fail: impl IntoF64 + Copy, + num_bundles_reverted: impl IntoF64, + reverted_gas_used: impl IntoF64, + ) { + self.payload_transaction_simulation_duration + .record(payload_transaction_simulation_time); + self.payload_transaction_simulation_gauge + .set(payload_transaction_simulation_time); + self.payload_num_tx_considered.record(num_txs_considered); + self.payload_num_tx_considered_gauge.set(num_txs_considered); + self.payload_num_tx_simulated.record(num_txs_simulated); + self.payload_num_tx_simulated_gauge.set(num_txs_simulated); + self.payload_num_tx_simulated_success + .record(num_txs_simulated_success); + self.payload_num_tx_simulated_success_gauge + .set(num_txs_simulated_success); + self.payload_num_tx_simulated_fail + .record(num_txs_simulated_fail); + self.payload_num_tx_simulated_fail_gauge + .set(num_txs_simulated_fail); + self.bundles_reverted.record(num_bundles_reverted); + self.payload_reverted_tx_gas_used.set(reverted_gas_used); + } +} + +/// Set gauge metrics for some flags so we can inspect which ones are set +/// and which ones aren't. +pub fn record_flag_gauge_metrics(builder_args: &OpRbuilderArgs) { + gauge!("op_rbuilder_flags_flashblocks_enabled").set(builder_args.flashblocks.enabled as i32); + gauge!("op_rbuilder_flags_flashtestations_enabled") + .set(builder_args.flashtestations.flashtestations_enabled as i32); + gauge!("op_rbuilder_flags_enable_revert_protection") + .set(builder_args.enable_revert_protection as i32); +} + +/// Record TEE workload ID and measurement metrics +/// Parses the quote, computes workload ID, and records workload_id, mr_td (TEE measurement), and rt_mr0 (runtime measurement register 0) +/// These identify the trusted execution environment configuration provided by GCP +pub fn record_tee_metrics(raw_quote: &[u8], tee_address: &Address) -> eyre::Result<()> { + let parsed_quote = parse_report_body(raw_quote)?; + let workload_id = compute_workload_id_from_parsed(&parsed_quote); + + let workload_id_hex = hex::encode(workload_id); + let mr_td_hex = hex::encode(parsed_quote.mr_td); + let rt_mr0_hex = hex::encode(parsed_quote.rt_mr0); + + let tee_address_static: &'static str = Box::leak(tee_address.to_string().into_boxed_str()); + let workload_id_static: &'static str = Box::leak(workload_id_hex.into_boxed_str()); + let mr_td_static: &'static str = Box::leak(mr_td_hex.into_boxed_str()); + let rt_mr0_static: &'static str = Box::leak(rt_mr0_hex.into_boxed_str()); + + // Record TEE address + let tee_address_labels: [(&str, &str); 1] = [("tee_address", tee_address_static)]; + gauge!("op_rbuilder_tee_address", &tee_address_labels).set(1); + + // Record workload ID + let workload_labels: [(&str, &str); 1] = [("workload_id", workload_id_static)]; + gauge!("op_rbuilder_tee_workload_id", &workload_labels).set(1); + + // Record MRTD (TEE measurement) + let mr_td_labels: [(&str, &str); 1] = [("mr_td", mr_td_static)]; + gauge!("op_rbuilder_tee_mr_td", &mr_td_labels).set(1); + + // Record RTMR0 (runtime measurement register 0) + let rt_mr0_labels: [(&str, &str); 1] = [("rt_mr0", rt_mr0_static)]; + gauge!("op_rbuilder_tee_rt_mr0", &rt_mr0_labels).set(1); + + Ok(()) +} + +/// Contains version information for the application. +#[derive(Debug, Clone)] +pub struct VersionInfo { + /// The version of the application. + pub version: &'static str, + /// The build timestamp of the application. + pub build_timestamp: &'static str, + /// The cargo features enabled for the build. + pub cargo_features: &'static str, + /// The Git SHA of the build. + pub git_sha: &'static str, + /// The target triple for the build. + pub target_triple: &'static str, + /// The build profile (e.g., debug or release). + pub build_profile: &'static str, + /// The author of the latest commit. + pub commit_author: &'static str, + /// The message of the latest commit. + pub commit_message: &'static str, +} + +impl VersionInfo { + /// This exposes op-rbuilder's version information over prometheus. + pub fn register_version_metrics(&self) { + let labels: [(&str, &str); 8] = [ + ("version", self.version), + ("build_timestamp", self.build_timestamp), + ("cargo_features", self.cargo_features), + ("git_sha", self.git_sha), + ("target_triple", self.target_triple), + ("build_profile", self.build_profile), + ("commit_author", self.commit_author), + ("commit_message", self.commit_message), + ]; + + let gauge = gauge!("builder_info", &labels); + gauge.set(1); + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/mock_tx.rs b/rust/op-rbuilder/crates/op-rbuilder/src/mock_tx.rs new file mode 100644 index 00000000000..93e43ce3b54 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/mock_tx.rs @@ -0,0 +1,457 @@ +use crate::tx::MaybeFlashblockFilter; +use alloy_consensus::{ + EthereumTxEnvelope, TxEip4844, TxEip4844WithSidecar, TxType, error::ValueError, + transaction::Recovered, +}; +use alloy_eips::{ + Typed2718, + eip2930::AccessList, + eip4844::{BlobTransactionValidationError, env_settings::KzgSettings}, + eip7594::BlobTransactionSidecarVariant, + eip7702::SignedAuthorization, +}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256}; +use reth::primitives::TransactionSigned; +use reth_primitives_traits::{InMemorySize, SignedTransaction}; +use reth_transaction_pool::{ + EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction, TransactionOrigin, + ValidPoolTransaction, + identifier::TransactionId, + test_utils::{MockTransaction, MockTransactionFactory}, +}; +use std::{sync::Arc, time::Instant}; + +/// A factory for creating and managing various types of mock transactions. +#[derive(Debug, Default)] +pub struct MockFbTransactionFactory { + pub(crate) factory: MockTransactionFactory, +} + +// === impl MockTransactionFactory === + +impl MockFbTransactionFactory { + /// Generates a transaction ID for the given [`MockTransaction`]. + pub fn tx_id(&mut self, tx: &MockFbTransaction) -> TransactionId { + self.factory.tx_id(&tx.inner) + } + + /// Validates a [`MockTransaction`] and returns a [`MockValidFbTx`]. + pub fn validated(&mut self, transaction: MockFbTransaction) -> MockValidFbTx { + self.validated_with_origin(TransactionOrigin::External, transaction) + } + + /// Validates a [`MockTransaction`] and returns a shared [`Arc`]. + pub fn validated_arc(&mut self, transaction: MockFbTransaction) -> Arc { + Arc::new(self.validated(transaction)) + } + + /// Converts the transaction into a validated transaction with a specified origin. + pub fn validated_with_origin( + &mut self, + origin: TransactionOrigin, + transaction: MockFbTransaction, + ) -> MockValidFbTx { + MockValidFbTx { + propagate: false, + transaction_id: self.tx_id(&transaction), + transaction, + timestamp: Instant::now(), + origin, + authority_ids: None, + } + } + + /// Creates a validated legacy [`MockTransaction`]. + pub fn create_legacy(&mut self) -> MockValidFbTx { + self.validated(MockFbTransaction { + inner: MockTransaction::legacy(), + reverted_hashes: None, + flashblock_number_max: None, + flashblock_number_min: None, + }) + } + + /// Creates a validated legacy [`MockTransaction`]. + pub fn create_legacy_fb(&mut self, min: Option, max: Option) -> MockValidFbTx { + self.validated(MockFbTransaction { + inner: MockTransaction::legacy(), + reverted_hashes: None, + flashblock_number_max: max, + flashblock_number_min: min, + }) + } + + /// Creates a validated EIP-1559 [`MockTransaction`]. + pub fn create_eip1559(&mut self) -> MockValidFbTx { + self.validated(MockFbTransaction { + inner: MockTransaction::eip1559(), + reverted_hashes: None, + flashblock_number_max: None, + flashblock_number_min: None, + }) + } + + /// Creates a validated EIP-4844 [`MockTransaction`]. + pub fn create_eip4844(&mut self) -> MockValidFbTx { + self.validated(MockFbTransaction { + inner: MockTransaction::eip4844(), + reverted_hashes: None, + flashblock_number_max: None, + flashblock_number_min: None, + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MockFbTransaction { + pub inner: MockTransaction, + /// reverted hashes for the transaction. If the transaction is a bundle, + /// this is the list of hashes of the transactions that reverted. If the + /// transaction is not a bundle, this is `None`. + pub reverted_hashes: Option>, + + pub flashblock_number_min: Option, + pub flashblock_number_max: Option, +} + +/// A validated transaction in the transaction pool, using [`MockTransaction`] as the transaction +/// type. +/// +/// This type is an alias for [`ValidPoolTransaction`]. +pub type MockValidFbTx = ValidPoolTransaction; + +impl PoolTransaction for MockFbTransaction { + type TryFromConsensusError = ValueError>; + + type Consensus = TransactionSigned; + + type Pooled = PooledTransactionVariant; + + fn consensus_ref(&self) -> Recovered<&Self::Consensus> { + unimplemented!("mock transaction does not wrap a consensus transaction") + } + + fn into_consensus(self) -> Recovered { + self.inner.into() + } + + fn from_pooled(pooled: Recovered) -> Self { + Self { + inner: pooled.into(), + reverted_hashes: None, + flashblock_number_min: None, + flashblock_number_max: None, + } + } + + fn hash(&self) -> &TxHash { + self.inner.get_hash() + } + + fn sender(&self) -> Address { + *self.inner.get_sender() + } + + fn sender_ref(&self) -> &Address { + self.inner.get_sender() + } + + // Having `get_cost` from `make_setters_getters` would be cleaner but we didn't + // want to also generate the error-prone cost setters. For now cost should be + // correct at construction and auto-updated per field update via `update_cost`, + // not to be manually set. + fn cost(&self) -> &U256 { + match &self.inner { + MockTransaction::Legacy { cost, .. } + | MockTransaction::Eip2930 { cost, .. } + | MockTransaction::Eip1559 { cost, .. } + | MockTransaction::Eip4844 { cost, .. } + | MockTransaction::Eip7702 { cost, .. } => cost, + } + } + + /// Returns the encoded length of the transaction. + fn encoded_length(&self) -> usize { + self.inner.size() + } +} + +impl InMemorySize for MockFbTransaction { + fn size(&self) -> usize { + *self.inner.get_size() + } +} + +impl Typed2718 for MockFbTransaction { + fn ty(&self) -> u8 { + match self.inner { + MockTransaction::Legacy { .. } => TxType::Legacy.into(), + MockTransaction::Eip1559 { .. } => TxType::Eip1559.into(), + MockTransaction::Eip4844 { .. } => TxType::Eip4844.into(), + MockTransaction::Eip2930 { .. } => TxType::Eip2930.into(), + MockTransaction::Eip7702 { .. } => TxType::Eip7702.into(), + } + } +} + +impl alloy_consensus::Transaction for MockFbTransaction { + fn chain_id(&self) -> Option { + match &self.inner { + MockTransaction::Legacy { chain_id, .. } => *chain_id, + MockTransaction::Eip1559 { chain_id, .. } + | MockTransaction::Eip4844 { chain_id, .. } + | MockTransaction::Eip2930 { chain_id, .. } + | MockTransaction::Eip7702 { chain_id, .. } => Some(*chain_id), + } + } + + fn nonce(&self) -> u64 { + *self.inner.get_nonce() + } + + fn gas_limit(&self) -> u64 { + *self.inner.get_gas_limit() + } + + fn gas_price(&self) -> Option { + match &self.inner { + MockTransaction::Legacy { gas_price, .. } + | MockTransaction::Eip2930 { gas_price, .. } => Some(*gas_price), + _ => None, + } + } + + fn max_fee_per_gas(&self) -> u128 { + match &self.inner { + MockTransaction::Legacy { gas_price, .. } + | MockTransaction::Eip2930 { gas_price, .. } => *gas_price, + MockTransaction::Eip1559 { + max_fee_per_gas, .. + } + | MockTransaction::Eip4844 { + max_fee_per_gas, .. + } + | MockTransaction::Eip7702 { + max_fee_per_gas, .. + } => *max_fee_per_gas, + } + } + + fn max_priority_fee_per_gas(&self) -> Option { + match &self.inner { + MockTransaction::Legacy { .. } | MockTransaction::Eip2930 { .. } => None, + MockTransaction::Eip1559 { + max_priority_fee_per_gas, + .. + } + | MockTransaction::Eip4844 { + max_priority_fee_per_gas, + .. + } + | MockTransaction::Eip7702 { + max_priority_fee_per_gas, + .. + } => Some(*max_priority_fee_per_gas), + } + } + + fn max_fee_per_blob_gas(&self) -> Option { + match &self.inner { + MockTransaction::Eip4844 { + max_fee_per_blob_gas, + .. + } => Some(*max_fee_per_blob_gas), + _ => None, + } + } + + fn priority_fee_or_price(&self) -> u128 { + match &self.inner { + MockTransaction::Legacy { gas_price, .. } + | MockTransaction::Eip2930 { gas_price, .. } => *gas_price, + MockTransaction::Eip1559 { + max_priority_fee_per_gas, + .. + } + | MockTransaction::Eip4844 { + max_priority_fee_per_gas, + .. + } + | MockTransaction::Eip7702 { + max_priority_fee_per_gas, + .. + } => *max_priority_fee_per_gas, + } + } + + fn effective_gas_price(&self, base_fee: Option) -> u128 { + base_fee.map_or_else( + || self.max_fee_per_gas(), + |base_fee| { + // if the tip is greater than the max priority fee per gas, set it to the max + // priority fee per gas + base fee + let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128); + if let Some(max_tip) = self.max_priority_fee_per_gas() { + if tip > max_tip { + max_tip + base_fee as u128 + } else { + // otherwise return the max fee per gas + self.max_fee_per_gas() + } + } else { + self.max_fee_per_gas() + } + }, + ) + } + + fn is_dynamic_fee(&self) -> bool { + !matches!( + self.inner, + MockTransaction::Legacy { .. } | MockTransaction::Eip2930 { .. } + ) + } + + fn kind(&self) -> TxKind { + match &self.inner { + MockTransaction::Legacy { to, .. } + | MockTransaction::Eip1559 { to, .. } + | MockTransaction::Eip2930 { to, .. } => *to, + MockTransaction::Eip4844 { to, .. } | MockTransaction::Eip7702 { to, .. } => { + TxKind::Call(*to) + } + } + } + + fn is_create(&self) -> bool { + match &self.inner { + MockTransaction::Legacy { to, .. } + | MockTransaction::Eip1559 { to, .. } + | MockTransaction::Eip2930 { to, .. } => to.is_create(), + MockTransaction::Eip4844 { .. } | MockTransaction::Eip7702 { .. } => false, + } + } + + fn value(&self) -> U256 { + match &self.inner { + MockTransaction::Legacy { value, .. } + | MockTransaction::Eip1559 { value, .. } + | MockTransaction::Eip2930 { value, .. } + | MockTransaction::Eip4844 { value, .. } + | MockTransaction::Eip7702 { value, .. } => *value, + } + } + + fn input(&self) -> &Bytes { + self.inner.get_input() + } + + fn access_list(&self) -> Option<&AccessList> { + match &self.inner { + MockTransaction::Legacy { .. } => None, + MockTransaction::Eip1559 { + access_list: accesslist, + .. + } + | MockTransaction::Eip4844 { + access_list: accesslist, + .. + } + | MockTransaction::Eip2930 { + access_list: accesslist, + .. + } + | MockTransaction::Eip7702 { + access_list: accesslist, + .. + } => Some(accesslist), + } + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + match &self.inner { + MockTransaction::Eip4844 { + blob_versioned_hashes, + .. + } => Some(blob_versioned_hashes), + _ => None, + } + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + match &self.inner { + MockTransaction::Eip7702 { + authorization_list, .. + } => Some(authorization_list), + _ => None, + } + } +} + +impl EthPoolTransaction for MockFbTransaction { + fn take_blob(&mut self) -> EthBlobTransactionSidecar { + match &self.inner { + MockTransaction::Eip4844 { sidecar, .. } => { + EthBlobTransactionSidecar::Present(sidecar.clone()) + } + _ => EthBlobTransactionSidecar::None, + } + } + + fn try_into_pooled_eip4844( + self, + sidecar: Arc, + ) -> Option> { + let (tx, signer) = self.into_consensus().into_parts(); + tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar)) + .map(|tx| tx.with_signer(signer)) + .ok() + } + + fn try_from_eip4844( + tx: Recovered, + sidecar: BlobTransactionSidecarVariant, + ) -> Option { + let (tx, signer) = tx.into_parts(); + tx.try_into_pooled_eip4844(sidecar) + .map(|tx| tx.with_signer(signer)) + .ok() + .map(Self::from_pooled) + } + + fn validate_blob( + &self, + _blob: &BlobTransactionSidecarVariant, + _settings: &KzgSettings, + ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> { + match &self.inner { + MockTransaction::Eip4844 { .. } => Ok(()), + _ => Err(BlobTransactionValidationError::NotBlobTransaction( + self.inner.tx_type(), + )), + } + } +} + +pub type PooledTransactionVariant = + alloy_consensus::EthereumTxEnvelope>; + +impl MaybeFlashblockFilter for MockFbTransaction { + fn with_flashblock_number_min(mut self, flashblock_number_min: Option) -> Self { + self.flashblock_number_min = flashblock_number_min; + self + } + + fn with_flashblock_number_max(mut self, flashblock_number_max: Option) -> Self { + self.flashblock_number_max = flashblock_number_max; + self + } + + fn flashblock_number_min(&self) -> Option { + self.flashblock_number_min + } + + fn flashblock_number_max(&self) -> Option { + self.flashblock_number_max + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/monitor_tx_pool.rs b/rust/op-rbuilder/crates/op-rbuilder/src/monitor_tx_pool.rs new file mode 100644 index 00000000000..00b341f2506 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/monitor_tx_pool.rs @@ -0,0 +1,80 @@ +use crate::tx::FBPooledTransaction; +use alloy_primitives::B256; +use futures_util::StreamExt; +use moka::future::Cache; +use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent}; +use tracing::info; + +pub(crate) async fn monitor_tx_pool( + mut new_transactions: AllTransactionsEvents, + reverted_cache: Cache, +) { + while let Some(event) = new_transactions.next().await { + transaction_event_log(event, &reverted_cache).await; + } +} + +async fn transaction_event_log( + event: FullTransactionEvent, + reverted_cache: &Cache, +) { + match event { + FullTransactionEvent::Pending(hash) => { + info!( + target = "monitoring", + tx_hash = hash.to_string(), + kind = "pending", + "Transaction event received" + ) + } + FullTransactionEvent::Queued(hash, _) => { + info!( + target = "monitoring", + tx_hash = hash.to_string(), + kind = "queued", + "Transaction event received" + ) + } + FullTransactionEvent::Mined { + tx_hash, + block_hash, + } => info!( + target = "monitoring", + tx_hash = tx_hash.to_string(), + kind = "mined", + block_hash = block_hash.to_string(), + "Transaction event received" + ), + FullTransactionEvent::Replaced { + transaction, + replaced_by, + } => info!( + target = "monitoring", + tx_hash = transaction.hash().to_string(), + kind = "replaced", + replaced_by = replaced_by.to_string(), + "Transaction event received" + ), + FullTransactionEvent::Discarded(hash) => { + // add the transaction hash to the reverted cache to notify the + // eth get transaction receipt method + reverted_cache.insert(hash, ()).await; + + info!( + target = "monitoring", + tx_hash = hash.to_string(), + kind = "discarded", + "Transaction event received" + ) + } + FullTransactionEvent::Invalid(hash) => { + info!( + target = "monitoring", + tx_hash = hash.to_string(), + kind = "invalid", + "Transaction event received" + ) + } + FullTransactionEvent::Propagated(_propagated) => {} + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/primitives/bundle.rs b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/bundle.rs new file mode 100644 index 00000000000..6e4c471b26a --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/bundle.rs @@ -0,0 +1,500 @@ +use alloy_primitives::{B256, Bytes}; +use alloy_rpc_types_eth::erc4337::TransactionConditional; +use reth_rpc_eth_types::EthApiError; +use serde::{Deserialize, Serialize}; + +/// Maximum number of blocks allowed in the block range for bundle execution. +/// +/// This constant limits how far into the future a bundle can be scheduled to +/// prevent excessive resource usage and ensure timely execution. When no +/// maximum block number is specified, this value is added to the current block +/// number to set the default upper bound. +pub const MAX_BLOCK_RANGE_BLOCKS: u64 = 10; + +/// A bundle represents a collection of transactions that should be executed +/// together with specific conditional constraints. +/// +/// Bundles allow for sophisticated transaction ordering and conditional +/// execution based on block numbers, flashblock numbers, and timestamps. They +/// are a key primitive in MEV (Maximal Extractable Value) strategies and block +/// building. +/// +/// # Validation +/// +/// The following validations are performed before adding the transaction to the +/// mempool: +/// - Block number ranges are valid (min ≤ max) +/// - Maximum block numbers are not in the past +/// - Block ranges don't exceed `MAX_BLOCK_RANGE_BLOCKS` (currently 10) +/// - There's only one transaction in the bundle +/// - Flashblock number ranges are valid (min ≤ max) +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct Bundle { + /// List of raw transaction data to be included in the bundle. + /// + /// Each transaction is represented as raw bytes that will be decoded and + /// executed in the specified order when the bundle conditions are met. + #[serde(rename = "txs")] + pub transactions: Vec, + + /// Optional list of transaction hashes that are allowed to revert. + /// + /// By default, if any transaction in a bundle reverts, the entire bundle is + /// considered invalid. This field allows specific transactions to revert + /// without invalidating the bundle, enabling more sophisticated MEV + /// strategies. + #[serde(rename = "revertingTxHashes")] + pub reverting_hashes: Option>, + + /// Minimum block number at which this bundle can be included. + /// + /// If specified, the bundle will only be considered for inclusion in blocks + /// at or after this block number. This allows for scheduling bundles for + /// future execution. + #[serde( + default, + rename = "minBlockNumber", + with = "alloy_serde::quantity::opt", + skip_serializing_if = "Option::is_none" + )] + pub block_number_min: Option, + + /// Maximum block number at which this bundle can be included. + /// + /// If specified, the bundle will be considered invalid for inclusion in + /// blocks after this block number. If not specified, defaults to the + /// current block number plus `MAX_BLOCK_RANGE_BLOCKS`. + #[serde( + default, + rename = "maxBlockNumber", + with = "alloy_serde::quantity::opt", + skip_serializing_if = "Option::is_none" + )] + pub block_number_max: Option, + + /// Minimum flashblock number at which this bundle can be included. + /// + /// Flashblocks are preconfirmations that are built incrementally. This + /// field along with `maxFlashblockNumber` allows bundles to be scheduled + /// for more precise execution. + #[serde( + default, + rename = "minFlashblockNumber", + with = "alloy_serde::quantity::opt", + skip_serializing_if = "Option::is_none" + )] + pub flashblock_number_min: Option, + + /// Maximum flashblock number at which this bundle can be included. + /// + /// Similar to `minFlashblockNumber`, this sets an upper bound on which + /// flashblocks can include this bundle. + #[serde( + default, + rename = "maxFlashblockNumber", + with = "alloy_serde::quantity::opt", + skip_serializing_if = "Option::is_none" + )] + pub flashblock_number_max: Option, + + /// Minimum timestamp (Unix epoch seconds) for bundle inclusion. + /// + /// **Warning**: Not recommended for production use as it depends on the + /// builder node's clock, which may not be perfectly synchronized with + /// network time. Block number constraints are preferred for deterministic + /// behavior. + #[serde( + default, + rename = "minTimestamp", + skip_serializing_if = "Option::is_none" + )] + pub min_timestamp: Option, + + /// Maximum timestamp (Unix epoch seconds) for bundle inclusion. + /// + /// **Warning**: Not recommended for production use as it depends on the + /// builder node's clock, which may not be perfectly synchronized with + /// network time. Block number constraints are preferred for deterministic + /// behavior. + #[serde( + default, + rename = "maxTimestamp", + skip_serializing_if = "Option::is_none" + )] + pub max_timestamp: Option, +} + +impl From for EthApiError { + fn from(err: BundleConditionalError) -> Self { + EthApiError::InvalidParams(err.to_string()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum BundleConditionalError { + #[error("block_number_min ({min}) is greater than block_number_max ({max})")] + MinGreaterThanMax { min: u64, max: u64 }, + #[error("block_number_max ({max}) is a past block (current: {current})")] + MaxBlockInPast { max: u64, current: u64 }, + /// To prevent resource exhaustion and ensure timely execution, bundles + /// cannot be scheduled more than `MAX_BLOCK_RANGE_BLOCKS` blocks into the + /// future. + #[error( + "block_number_max ({max}) is too high (current: {current}, max allowed: {max_allowed})" + )] + MaxBlockTooHigh { + max: u64, + current: u64, + max_allowed: u64, + }, + /// When no explicit maximum block number is provided, the system uses + /// `current_block + MAX_BLOCK_RANGE_BLOCKS` as the default maximum. This + /// error occurs when the specified minimum exceeds this default maximum. + #[error( + "block_number_min ({min}) is too high with default max range (max allowed: {max_allowed})" + )] + MinTooHighForDefaultRange { min: u64, max_allowed: u64 }, + #[error("flashblock_number_min ({min}) is greater than flashblock_number_max ({max})")] + FlashblockMinGreaterThanMax { min: u64, max: u64 }, +} + +pub struct BundleConditional { + pub transaction_conditional: TransactionConditional, + pub flashblock_number_min: Option, + pub flashblock_number_max: Option, +} + +impl Bundle { + pub fn conditional( + &self, + last_block_number: u64, + ) -> Result { + let mut block_number_max = self.block_number_max; + let block_number_min = self.block_number_min; + + // Validate block number ranges + if let Some(max) = block_number_max { + // Check if min > max + if let Some(min) = block_number_min + && min > max + { + return Err(BundleConditionalError::MinGreaterThanMax { min, max }); + } + + // The max block cannot be a past block + if max <= last_block_number { + return Err(BundleConditionalError::MaxBlockInPast { + max, + current: last_block_number, + }); + } + + // Validate that it is not greater than the max_block_range + let max_allowed = last_block_number + MAX_BLOCK_RANGE_BLOCKS; + if max > max_allowed { + return Err(BundleConditionalError::MaxBlockTooHigh { + max, + current: last_block_number, + max_allowed, + }); + } + } else { + // If no upper bound is set, use the maximum block range + let default_max = last_block_number + MAX_BLOCK_RANGE_BLOCKS; + block_number_max = Some(default_max); + + // Ensure that the new max is not smaller than the min + if let Some(min) = block_number_min + && min > default_max + { + return Err(BundleConditionalError::MinTooHighForDefaultRange { + min, + max_allowed: default_max, + }); + } + } + + // Validate flashblock number range + if let Some(min) = self.flashblock_number_min + && let Some(max) = self.flashblock_number_max + && min > max + { + return Err(BundleConditionalError::FlashblockMinGreaterThanMax { min, max }); + } + + Ok(BundleConditional { + transaction_conditional: TransactionConditional { + block_number_min, + block_number_max, + known_accounts: Default::default(), + timestamp_max: self.max_timestamp, + timestamp_min: self.min_timestamp, + }, + flashblock_number_min: self.flashblock_number_min, + flashblock_number_max: self.flashblock_number_max, + }) + } +} + +/// Result returned after successfully submitting a bundle for inclusion. +/// +/// This struct contains the unique identifier for the submitted bundle, which +/// can be used to track the bundle's status and inclusion in future blocks. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct BundleResult { + /// Transaction hash of the single transaction in the bundle. + /// + /// This hash can be used to: + /// - Track bundle inclusion in blocks + /// - Query bundle status + /// - Reference the bundle in subsequent operations + #[serde(rename = "bundleHash")] + pub bundle_hash: B256, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bundle_conditional_no_bounds() { + let bundle = Bundle { + transactions: vec![], + ..Default::default() + }; + + let last_block = 1000; + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; + + assert_eq!(result.block_number_min, None); + assert_eq!( + result.block_number_max, + Some(last_block + MAX_BLOCK_RANGE_BLOCKS) + ); + } + + #[test] + fn test_bundle_conditional_with_valid_bounds() { + let bundle = Bundle { + block_number_max: Some(1005), + block_number_min: Some(1002), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; + + assert_eq!(result.block_number_min, Some(1002)); + assert_eq!(result.block_number_max, Some(1005)); + } + + #[test] + fn test_bundle_conditional_min_greater_than_max() { + let bundle = Bundle { + block_number_max: Some(1005), + block_number_min: Some(1010), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::MinGreaterThanMax { + min: 1010, + max: 1005 + }) + )); + } + + #[test] + fn test_bundle_conditional_max_in_past() { + let bundle = Bundle { + block_number_max: Some(999), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::MaxBlockInPast { + max: 999, + current: 1000 + }) + )); + } + + #[test] + fn test_bundle_conditional_max_too_high() { + let bundle = Bundle { + block_number_max: Some(1020), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::MaxBlockTooHigh { + max: 1020, + current: 1000, + max_allowed: 1010 + }) + )); + } + + #[test] + fn test_bundle_conditional_min_too_high_for_default_range() { + let bundle = Bundle { + block_number_min: Some(1015), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::MinTooHighForDefaultRange { + min: 1015, + max_allowed: 1010 + }) + )); + } + + #[test] + fn test_bundle_conditional_with_only_min() { + let bundle = Bundle { + block_number_min: Some(1005), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; + + assert_eq!(result.block_number_min, Some(1005)); + assert_eq!(result.block_number_max, Some(1010)); // last_block + MAX_BLOCK_RANGE_BLOCKS + } + + #[test] + fn test_bundle_conditional_with_only_max() { + let bundle = Bundle { + block_number_max: Some(1008), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; + + assert_eq!(result.block_number_min, None); + assert_eq!(result.block_number_max, Some(1008)); + } + + #[test] + fn test_bundle_conditional_min_lower_than_last_block() { + let bundle = Bundle { + block_number_min: Some(999), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; + + assert_eq!(result.block_number_min, Some(999)); + assert_eq!(result.block_number_max, Some(1010)); + } + + #[test] + fn test_bundle_conditional_flashblock_min_greater_than_max() { + let bundle = Bundle { + flashblock_number_min: Some(105), + flashblock_number_max: Some(100), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::FlashblockMinGreaterThanMax { min: 105, max: 100 }) + )); + } + + #[test] + fn test_bundle_conditional_with_valid_flashblock_range() { + let bundle = Bundle { + flashblock_number_min: Some(100), + flashblock_number_max: Some(105), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.flashblock_number_min, Some(100)); + assert_eq!(result.flashblock_number_max, Some(105)); + } + + #[test] + fn test_bundle_conditional_with_only_flashblock_min() { + let bundle = Bundle { + flashblock_number_min: Some(100), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.flashblock_number_min, Some(100)); + assert_eq!(result.flashblock_number_max, None); + } + + #[test] + fn test_bundle_conditional_with_only_flashblock_max() { + let bundle = Bundle { + flashblock_number_max: Some(105), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.flashblock_number_min, None); + assert_eq!(result.flashblock_number_max, Some(105)); + } + + #[test] + fn test_bundle_conditional_flashblock_equal_values() { + let bundle = Bundle { + flashblock_number_min: Some(100), + flashblock_number_max: Some(100), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.flashblock_number_min, Some(100)); + assert_eq!(result.flashblock_number_max, Some(100)); + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/primitives/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/mod.rs new file mode 100644 index 00000000000..da146d3062c --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/mod.rs @@ -0,0 +1,4 @@ +pub mod bundle; +pub mod reth; +#[cfg(feature = "telemetry")] +pub mod telemetry; diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/engine_api_builder.rs b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/engine_api_builder.rs new file mode 100644 index 00000000000..71d86e486d7 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/engine_api_builder.rs @@ -0,0 +1,418 @@ +//! RPC component builder + +use reth_node_api::AddOnsContext; +use reth_node_builder::rpc::{EngineApiBuilder, PayloadValidatorBuilder}; +use reth_node_core::version::{CLIENT_CODE, version_metadata}; +use reth_optimism_node::OpEngineTypes; +pub use reth_optimism_rpc::OpEngineApi; +use reth_optimism_rpc::engine::OP_ENGINE_CAPABILITIES; +use reth_payload_builder::PayloadStore; +use reth_rpc_engine_api::EngineCapabilities; + +use crate::traits::NodeComponents; +use alloy_eips::eip7685::Requests; +use alloy_primitives::{B256, BlockHash, U64}; +use alloy_rpc_types_engine::{ + ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV3, + ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, +}; +use jsonrpsee::proc_macros::rpc; +use jsonrpsee_core::{RpcResult, server::RpcModule}; +use op_alloy_rpc_types_engine::{ + OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4, +}; +use reth::builder::NodeTypes; +use reth_node_api::{EngineApiValidator, EngineTypes}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_payload_builder::OpPayloadAttrs; +use reth_optimism_rpc::OpEngineApiServer; +use reth_rpc_api::IntoEngineApiRpcModule; +use reth_storage_api::{BalProvider, BlockReader, HeaderProvider, StateProviderFactory}; +use reth_transaction_pool::TransactionPool; + +/// Builder for basic [`OpEngineApi`] implementation. +#[derive(Debug, Clone)] +pub struct OpEngineApiBuilder { + engine_validator_builder: EV, +} + +impl Default for OpEngineApiBuilder +where + EV: Default, +{ + fn default() -> Self { + Self { + engine_validator_builder: EV::default(), + } + } +} + +impl EngineApiBuilder for OpEngineApiBuilder +where + N: NodeComponents, + EV: PayloadValidatorBuilder, + EV::Validator: EngineApiValidator<::Payload>, +{ + type EngineApi = OpEngineApiExt; + + async fn build_engine_api(self, ctx: &AddOnsContext<'_, N>) -> eyre::Result { + let Self { + engine_validator_builder, + } = self; + + let engine_validator = engine_validator_builder.build(ctx).await?; + let client = ClientVersionV1 { + code: CLIENT_CODE, + name: version_metadata().name_client.to_string(), + version: version_metadata().cargo_pkg_version.to_string(), + commit: version_metadata().vergen_git_sha.to_string(), + }; + let inner = reth_rpc_engine_api::EngineApi::new( + ctx.node.provider().clone(), + ctx.config.chain.clone(), + ctx.beacon_engine_handle.clone(), + PayloadStore::new(ctx.node.payload_builder_handle().clone()), + ctx.node.pool().clone(), + ctx.node.task_executor().clone(), + client, + EngineCapabilities::new(OP_ENGINE_CAPABILITIES.iter().copied()), + engine_validator, + ctx.config.engine.accept_execution_requests_hash, + ctx.node.network().clone(), + ); + + Ok(OpEngineApiExt::new(OpEngineApi::new(inner))) + } +} + +pub struct OpEngineApiExt { + inner: OpEngineApi, +} + +impl OpEngineApiExt +where + Provider: HeaderProvider + BlockReader + StateProviderFactory + BalProvider + 'static, + Pool: TransactionPool + 'static, + Validator: EngineApiValidator, +{ + pub fn new(engine: OpEngineApi) -> Self { + Self { inner: engine } + } +} + +#[async_trait::async_trait] +impl OpRbuilderEngineApiServer + for OpEngineApiExt +where + Provider: HeaderProvider + BlockReader + StateProviderFactory + BalProvider + 'static, + Pool: TransactionPool + 'static, + Validator: EngineApiValidator, +{ + async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult { + self.inner.new_payload_v2(payload).await + } + + async fn new_payload_v3( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> RpcResult { + self.inner + .new_payload_v3(payload, versioned_hashes, parent_beacon_block_root) + .await + } + + async fn new_payload_v4( + &self, + payload: OpExecutionPayloadV4, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + execution_requests: Requests, + ) -> RpcResult { + self.inner + .new_payload_v4( + payload, + versioned_hashes, + parent_beacon_block_root, + execution_requests, + ) + .await + } + + async fn fork_choice_updated_v1( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult { + self.inner + .fork_choice_updated_v1(fork_choice_state, payload_attributes) + .await + } + + async fn fork_choice_updated_v2( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult { + self.inner + .fork_choice_updated_v2(fork_choice_state, payload_attributes) + .await + } + + async fn fork_choice_updated_v3( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult { + self.inner + .fork_choice_updated_v3(fork_choice_state, payload_attributes) + .await + } + + async fn get_payload_v2( + &self, + payload_id: PayloadId, + ) -> RpcResult<::ExecutionPayloadEnvelopeV2> { + self.inner.get_payload_v2(payload_id).await + } + + async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> RpcResult { + self.inner.get_payload_v3(payload_id).await + } + + async fn get_payload_v4( + &self, + payload_id: PayloadId, + ) -> RpcResult { + self.inner.get_payload_v4(payload_id).await + } + + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec, + ) -> RpcResult { + self.inner.get_payload_bodies_by_hash_v1(block_hashes).await + } + + async fn get_payload_bodies_by_range_v1( + &self, + start: U64, + count: U64, + ) -> RpcResult { + self.inner + .get_payload_bodies_by_range_v1(start, count) + .await + } + + async fn get_client_version_v1( + &self, + client: ClientVersionV1, + ) -> RpcResult> { + self.inner.get_client_version_v1(client).await + } + + async fn exchange_capabilities(&self, capabilities: Vec) -> RpcResult> { + self.inner.exchange_capabilities(capabilities).await + } +} + +impl IntoEngineApiRpcModule for OpEngineApiExt +where + Self: OpRbuilderEngineApiServer, +{ + fn into_rpc_module(self) -> RpcModule<()> { + self.into_rpc().remove_context() + } +} + +/// Extension trait that gives access to Optimism engine API RPC methods. +/// +/// Note: +/// > The provider should use a JWT authentication layer. +/// +/// This follows the Optimism specs that can be found at: +/// +#[rpc(server, namespace = "engine", server_bounds(Engine::PayloadAttributes: jsonrpsee::core::DeserializeOwned))] +pub trait OpRbuilderEngineApi { + /// Sends the given payload to the execution layer client, as specified for the Shanghai fork. + /// + /// See also + /// + /// No modifications needed for OP compatibility. + #[method(name = "newPayloadV2")] + async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult; + + /// Sends the given payload to the execution layer client, as specified for the Cancun fork. + /// + /// See also + /// + /// OP modifications: + /// - expected versioned hashes MUST be an empty array: therefore the `versioned_hashes` + /// parameter is removed. + /// - parent beacon block root MUST be the parent beacon block root from the L1 origin block of + /// the L2 block. + /// - blob versioned hashes MUST be empty list. + #[method(name = "newPayloadV3")] + async fn new_payload_v3( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> RpcResult; + + /// Sends the given payload to the execution layer client, as specified for the Prague fork. + /// + /// See also + /// + /// - blob versioned hashes MUST be empty list. + /// - execution layer requests MUST be empty list. + #[method(name = "newPayloadV4")] + async fn new_payload_v4( + &self, + payload: OpExecutionPayloadV4, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + execution_requests: Requests, + ) -> RpcResult; + + /// See also + /// + /// This exists because it is used by op-node: + /// + /// Caution: This should not accept the `withdrawals` field in the payload attributes. + #[method(name = "forkchoiceUpdatedV1")] + async fn fork_choice_updated_v1( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult; + + /// Updates the execution layer client with the given fork choice, as specified for the Shanghai + /// fork. + /// + /// Caution: This should not accept the `parentBeaconBlockRoot` field in the payload attributes. + /// + /// See also + /// + /// OP modifications: + /// - The `payload_attributes` parameter is extended with the [`EngineTypes::PayloadAttributes`](EngineTypes) type as described in + #[method(name = "forkchoiceUpdatedV2")] + async fn fork_choice_updated_v2( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult; + + /// Updates the execution layer client with the given fork choice, as specified for the Cancun + /// fork. + /// + /// See also + /// + /// OP modifications: + /// - Must be called with an Ecotone payload + /// - Attributes must contain the parent beacon block root field + /// - The `payload_attributes` parameter is extended with the [`EngineTypes::PayloadAttributes`](EngineTypes) type as described in + #[method(name = "forkchoiceUpdatedV3")] + async fn fork_choice_updated_v3( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult; + + /// Retrieves an execution payload from a previously started build process, as specified for the + /// Shanghai fork. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// No modifications needed for OP compatibility. + #[method(name = "getPayloadV2")] + async fn get_payload_v2( + &self, + payload_id: PayloadId, + ) -> RpcResult; + + /// Retrieves an execution payload from a previously started build process, as specified for the + /// Cancun fork. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// OP modifications: + /// - the response type is extended to [`EngineTypes::ExecutionPayloadEnvelopeV3`]. + #[method(name = "getPayloadV3")] + async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> RpcResult; + + /// Returns the most recent version of the payload that is available in the corresponding + /// payload build process at the time of receiving this call. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// OP modifications: + /// - the response type is extended to [`EngineTypes::ExecutionPayloadEnvelopeV4`]. + #[method(name = "getPayloadV4")] + async fn get_payload_v4( + &self, + payload_id: PayloadId, + ) -> RpcResult; + + /// Returns the execution payload bodies by the given hash. + /// + /// See also + #[method(name = "getPayloadBodiesByHashV1")] + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec, + ) -> RpcResult; + + /// Returns the execution payload bodies by the range starting at `start`, containing `count` + /// blocks. + /// + /// WARNING: This method is associated with the BeaconBlocksByRange message in the consensus + /// layer p2p specification, meaning the input should be treated as untrusted or potentially + /// adversarial. + /// + /// Implementers should take care when acting on the input to this method, specifically + /// ensuring that the range is limited properly, and that the range boundaries are computed + /// correctly and without panics. + /// + /// See also + #[method(name = "getPayloadBodiesByRangeV1")] + async fn get_payload_bodies_by_range_v1( + &self, + start: U64, + count: U64, + ) -> RpcResult; + + /// Returns the execution client version information. + /// + /// Note: + /// > The `client_version` parameter identifies the consensus client. + /// + /// See also + #[method(name = "getClientVersionV1")] + async fn get_client_version_v1( + &self, + client_version: ClientVersionV1, + ) -> RpcResult>; + + /// Returns the list of Engine API methods supported by the execution layer client software. + /// + /// See also + #[method(name = "exchangeCapabilities")] + async fn exchange_capabilities(&self, capabilities: Vec) -> RpcResult>; +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/execution.rs b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/execution.rs new file mode 100644 index 00000000000..7865a1c8434 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/execution.rs @@ -0,0 +1,113 @@ +//! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570) +use alloy_primitives::{Address, U256}; +use core::fmt::Debug; +use derive_more::Display; +use op_revm::OpTransactionError; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; + +#[derive(Debug, Display)] +pub enum TxnExecutionResult { + TransactionDALimitExceeded, + #[display("BlockDALimitExceeded: total_da_used={_0} tx_da_size={_1} block_da_limit={_2}")] + BlockDALimitExceeded(u64, u64, u64), + #[display("TransactionGasLimitExceeded: total_gas_used={_0} tx_gas_limit={_1}")] + TransactionGasLimitExceeded(u64, u64, u64), + SequencerTransaction, + NonceTooLow, + InteropFailed, + #[display("InternalError({_0})")] + InternalError(OpTransactionError), + EvmError, + Success, + Reverted, + RevertedAndExcluded, + MaxGasUsageExceeded, +} + +#[derive(Default, Debug)] +pub struct ExecutionInfo { + /// All executed transactions (unrecovered). + pub executed_transactions: Vec, + /// The recovered senders for the executed transactions. + pub executed_senders: Vec
, + /// The transaction receipts + pub receipts: Vec, + /// All gas used so far + pub cumulative_gas_used: u64, + /// Estimated DA size + pub cumulative_da_bytes_used: u64, + /// Tracks fees from executed mempool transactions + pub total_fees: U256, + /// Extra execution information that can be attached by individual builders. + pub extra: Extra, + /// DA Footprint Scalar for Jovian + pub da_footprint_scalar: Option, +} + +impl ExecutionInfo { + /// Create a new instance with allocated slots. + pub fn with_capacity(capacity: usize) -> Self { + Self { + executed_transactions: Vec::with_capacity(capacity), + executed_senders: Vec::with_capacity(capacity), + receipts: Vec::with_capacity(capacity), + cumulative_gas_used: 0, + cumulative_da_bytes_used: 0, + total_fees: U256::ZERO, + extra: Default::default(), + da_footprint_scalar: None, + } + } + + /// Returns true if the transaction would exceed the block limits: + /// - block gas limit: ensures the transaction still fits into the block. + /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit + /// per tx. + /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the + /// maximum allowed DA limit per block. + #[allow(clippy::too_many_arguments)] + pub fn is_tx_over_limits( + &self, + tx_da_size: u64, + block_gas_limit: u64, + tx_data_limit: Option, + block_data_limit: Option, + tx_gas_limit: u64, + da_footprint_gas_scalar: Option, + block_da_footprint_limit: Option, + ) -> Result<(), TxnExecutionResult> { + if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) { + return Err(TxnExecutionResult::TransactionDALimitExceeded); + } + let total_da_bytes_used = self.cumulative_da_bytes_used.saturating_add(tx_da_size); + if block_data_limit.is_some_and(|da_limit| total_da_bytes_used > da_limit) { + return Err(TxnExecutionResult::BlockDALimitExceeded( + self.cumulative_da_bytes_used, + tx_da_size, + block_data_limit.unwrap_or_default(), + )); + } + + // Post Jovian: the tx DA footprint must be less than the block gas limit + if let Some(da_footprint_gas_scalar) = da_footprint_gas_scalar { + let tx_da_footprint = + total_da_bytes_used.saturating_mul(da_footprint_gas_scalar as u64); + if tx_da_footprint > block_da_footprint_limit.unwrap_or(block_gas_limit) { + return Err(TxnExecutionResult::BlockDALimitExceeded( + total_da_bytes_used, + tx_da_size, + tx_da_footprint, + )); + } + } + + if self.cumulative_gas_used + tx_gas_limit > block_gas_limit { + return Err(TxnExecutionResult::TransactionGasLimitExceeded( + self.cumulative_gas_used, + tx_gas_limit, + block_gas_limit, + )); + } + Ok(()) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/mod.rs new file mode 100644 index 00000000000..ed2b38b92aa --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/reth/mod.rs @@ -0,0 +1,3 @@ +pub mod engine_api_builder; +mod execution; +pub use execution::{ExecutionInfo, TxnExecutionResult}; diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/primitives/telemetry.rs b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/telemetry.rs new file mode 100644 index 00000000000..e8fac89d85a --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/primitives/telemetry.rs @@ -0,0 +1,36 @@ +use crate::args::TelemetryArgs; +use tracing_subscriber::{Layer, filter::Targets}; +use url::Url; + +/// Setup telemetry layer with sampling and custom endpoint configuration +pub fn setup_telemetry_layer( + args: &TelemetryArgs, +) -> eyre::Result> { + use tracing::level_filters::LevelFilter; + + let Some(otlp_endpoint) = &args.otlp_endpoint else { + return Err(eyre::eyre!("OTLP endpoint is not set")); + }; + + // OTLP uses env vars internally. + if let Some(headers) = &args.otlp_headers { + unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", headers) }; + } + + // Create OTLP layer with custom configuration + let protocol = reth_tracing_otlp::OtlpProtocol::Http; + let mut endpoint = Url::parse(otlp_endpoint)?; + protocol.validate_endpoint(&mut endpoint)?; + let otlp_config = reth_tracing_otlp::OtlpConfig::new("op-rbuilder", endpoint, protocol, None)?; + let otlp_layer = reth_tracing_otlp::span_layer(otlp_config)?; + + // Create a trace filter that sends more data to OTLP but less to stdout + let trace_filter = Targets::new() + .with_default(LevelFilter::WARN) + .with_target("op_rbuilder", LevelFilter::INFO) + .with_target("payload_builder", LevelFilter::DEBUG); + + let filtered_layer = otlp_layer.with_filter(trace_filter); + + Ok(filtered_layer) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/revert_protection.rs b/rust/op-rbuilder/crates/op-rbuilder/src/revert_protection.rs new file mode 100644 index 00000000000..f3fa8924e40 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/revert_protection.rs @@ -0,0 +1,163 @@ +use std::{sync::Arc, time::Instant}; + +use crate::{ + metrics::OpRBuilderMetrics, + primitives::bundle::{Bundle, BundleResult}, + tx::{FBPooledTransaction, MaybeFlashblockFilter, MaybeRevertingTransaction}, +}; +use alloy_json_rpc::RpcObject; +use alloy_primitives::B256; +use jsonrpsee::{ + core::{RpcResult, async_trait}, + proc_macros::rpc, +}; +use moka::future::Cache; +use reth::rpc::api::eth::{RpcReceipt, helpers::FullEthApi}; +use reth_optimism_txpool::{OpPooledTransaction, conditional::MaybeConditionalTransaction}; +use reth_provider::StateProviderFactory; +use reth_rpc_eth_types::{EthApiError, utils::recover_raw_transaction}; +use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool}; +use tracing::error; + +// Namespace overrides for revert protection support +#[cfg_attr(not(test), rpc(server, namespace = "eth"))] +#[cfg_attr(test, rpc(server, client, namespace = "eth"))] +pub trait EthApiExt { + #[method(name = "sendBundle")] + async fn send_bundle(&self, tx: Bundle) -> RpcResult; + + #[method(name = "getTransactionReceipt")] + async fn transaction_receipt(&self, hash: B256) -> RpcResult>; +} + +pub struct RevertProtectionExt { + pool: Pool, + provider: Provider, + eth_api: Eth, + metrics: Arc, + reverted_cache: Cache, +} + +impl RevertProtectionExt +where + Pool: Clone, + Provider: Clone, + Eth: Clone, +{ + pub fn new( + pool: Pool, + provider: Provider, + eth_api: Eth, + reverted_cache: Cache, + ) -> Self { + Self { + pool, + provider, + eth_api, + metrics: Arc::new(OpRBuilderMetrics::default()), + reverted_cache, + } + } +} + +#[async_trait] +impl EthApiExtServer> + for RevertProtectionExt +where + Pool: TransactionPool + Clone + 'static, + Provider: StateProviderFactory + Send + Sync + Clone + 'static, + Eth: FullEthApi + Send + Sync + Clone + 'static, +{ + async fn send_bundle(&self, bundle: Bundle) -> RpcResult { + let request_start_time = Instant::now(); + self.metrics.bundle_requests.increment(1); + + let bundle_result = self + .send_bundle_inner(bundle) + .await + .inspect_err(|err| error!("eth_sendBundle request failed: {err:?}")); + + if bundle_result.is_ok() { + self.metrics.valid_bundles.increment(1); + } else { + self.metrics.failed_bundles.increment(1); + } + + self.metrics + .bundle_receive_duration + .record(request_start_time.elapsed()); + + bundle_result + } + + async fn transaction_receipt( + &self, + hash: B256, + ) -> RpcResult>> { + if let Some(receipt) = self.eth_api.transaction_receipt(hash).await.unwrap() { + Ok(Some(receipt)) + } else if self.reverted_cache.get(&hash).await.is_some() { + // Found the transaction in the reverted cache + Err( + EthApiError::InvalidParams("the transaction was dropped from the pool".into()) + .into(), + ) + } else { + Ok(None) + } + } +} + +impl RevertProtectionExt +where + Pool: TransactionPool + Clone + 'static, + Provider: StateProviderFactory + Send + Sync + Clone + 'static, + Eth: FullEthApi + Send + Sync + Clone + 'static, +{ + async fn send_bundle_inner(&self, bundle: Bundle) -> RpcResult { + let last_block_number = self + .provider + .best_block_number() + .map_err(|_e| EthApiError::InternalEthError)?; + + // Only one transaction in the bundle is expected + let bundle_transaction = match bundle.transactions.len() { + 0 => { + return Err(EthApiError::InvalidParams( + "bundle must contain at least one transaction".into(), + ) + .into()); + } + 1 => bundle.transactions[0].clone(), + _ => { + return Err(EthApiError::InvalidParams( + "bundle must contain exactly one transaction".into(), + ) + .into()); + } + }; + + let conditional = bundle + .conditional(last_block_number) + .map_err(EthApiError::from)?; + + let recovered = recover_raw_transaction(&bundle_transaction)?; + let pool_transaction = + FBPooledTransaction::from(OpPooledTransaction::from_pooled(recovered)) + .with_reverted_hashes(bundle.reverting_hashes.clone().unwrap_or_default()) + .with_flashblock_number_min(conditional.flashblock_number_min) + .with_flashblock_number_max(conditional.flashblock_number_max) + .with_conditional(conditional.transaction_conditional); + + let outcome = self + .pool + .add_transaction(TransactionOrigin::Local, pool_transaction) + .await + .map_err(EthApiError::from)?; + + let result = BundleResult { + bundle_hash: outcome.hash, + }; + Ok(result) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/data_availability.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/data_availability.rs new file mode 100644 index 00000000000..9621f3986b1 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/data_availability.rs @@ -0,0 +1,218 @@ +use crate::tests::{BlockTransactionsExt, ChainDriverExt, LocalInstance, TransactionBuilderExt}; +use alloy_provider::Provider; +use macros::{if_flashblocks, if_standard, rb_test}; + +/// This test ensures that the transaction size limit is respected. +/// We will set limit to 1 byte and see that the builder will not include any transactions. +#[rb_test] +async fn tx_size_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // Set (max_tx_da_size, max_block_da_size), with this case block won't fit any transaction + let call = driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (1, 0)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + let unfit_tx = driver + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + let block = driver.build_new_block().await?; + + // tx should not be included because we set the tx_size_limit to 1 + assert!( + !block.includes(unfit_tx.tx_hash()), + "transaction should not be included in the block" + ); + + Ok(()) +} + +/// This test ensures that the block size limit is respected. +/// We will set limit to 1 byte and see that the builder will not include any transactions. +#[rb_test] +async fn block_size_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // Set block da size to be small, so it won't include tx + let call = driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 1)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + let (unfit_tx, block) = driver.build_new_block_with_valid_transaction().await?; + + // tx should not be included because we set the tx_size_limit to 1 + assert!( + !block.includes(&unfit_tx), + "transaction should not be included in the block" + ); + + Ok(()) +} + +/// This test ensures that block will fill up to the limit. +/// Size of each transaction is 100 +/// We will set limit to 3 txs and see that the builder will include 3 transactions. +/// We should not forget about builder transaction so we will spawn only 2 regular txs. +#[rb_test] +async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // Set block big enough so it could fit 3 transactions without tx size limit + let call = driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 4)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + // We already have 2 so we will spawn one more to check that it won't be included (it won't fit + // because of builder tx) + let fit_tx_1 = driver + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + let fit_tx_2 = driver + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + let fit_tx_3 = driver + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + let unfit_tx_4 = driver.create_transaction().send().await?; + + let block = driver.build_new_block_with_current_timestamp(None).await?; + + if_standard! { + // Now the first 2 txs will fit into the block + assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_2.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_3.tx_hash()), "tx should be in block"); + } + + if_flashblocks! { + // in flashblocks the DA quota is divided by the number of flashblocks + // so we will include only one tx in the block because not all of them + // will fit within DA quote / flashblocks count. + assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_2.tx_hash()), "tx should be in block"); + assert!(!block.includes(fit_tx_3.tx_hash()), "tx should not be in block"); + } + + assert!( + !block.includes(unfit_tx_4.tx_hash()), + "unfit tx should not be in block" + ); + assert!( + driver.latest_full().await?.transactions.len() == 5, + "builder + deposit + 3 valid txs should be in the block" + ); + + Ok(()) +} + +/// This test ensures that the DA footprint limit (Jovian) is respected and the block fills +/// to the DA footprint limit. The DA footprint is calculated as: +/// total_da_bytes_used * da_footprint_gas_scalar (stored in blob_gas_used). +/// This must not exceed the block gas limit, accounting for the builder transaction. +#[rb_test] +async fn da_footprint_fills_to_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // DA footprint scalar from JOVIAN_DATA is 400 + // Set a constrained gas limit so DA footprint becomes the limiting factor + // With gas limit = 200_000 and scalar = 400: + // Max DA bytes = 200_000 / 400 = 500 bytes + let gas_limit = 400_000u64; + let call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (gas_limit,)) + .await?; + assert!(call, "miner_setGasLimit should be executed successfully"); + + // Set DA size limit to be permissive (not the constraint) + let call = driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100_000)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + let mut tx_hashes = Vec::new(); + for _ in 0..10 { + // 400 * 100 = 400_000 total da footprint + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_gas_limit(21000) + .send() + .await?; + tx_hashes.push(tx.tx_hash().clone()); + } + + let block = driver.build_new_block_with_current_timestamp(None).await?; + + // Verify that blob_gas_used (DA footprint) is set and respects limits + assert!( + block.header.blob_gas_used.is_some(), + "blob_gas_used should be set in Jovian" + ); + + let blob_gas = block.header.blob_gas_used.unwrap(); + + // The DA footprint must not exceed the block gas limit + assert!( + blob_gas == gas_limit, + "DA footprint (blob_gas_used={}) must not exceed block gas limit ({})", + blob_gas, + gas_limit + ); + + // Verify the block fills up to the DA footprint limit + // accounting for builder tx DA contribution + if_standard! { + for i in 0..8 { + assert!( + block.includes(&tx_hashes[i]), + "tx {} should be included in the block", + i + ); + } + + // Verify the last tx doesn't fit due to DA footprint limit + assert!( + !block.includes(&tx_hashes[9]), + "tx 9 should not fit in the block due to DA footprint limit" + ); + } + + if_flashblocks! { + for i in 0..7 { + assert!( + block.includes(&tx_hashes[i]), + "tx {} should be included in the block", + i + ); + } + + // Verify the last 2 tx doesn't fit due to DA footprint limit + assert!( + !block.includes(&tx_hashes[8]), + "tx 8 should not fit in the block due to DA footprint limit" + ); + + assert!( + !block.includes(&tx_hashes[9]), + "tx 9 should not fit in the block due to DA footprint limit" + ); + } + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/flashblocks.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/flashblocks.rs new file mode 100644 index 00000000000..d023a542d96 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/flashblocks.rs @@ -0,0 +1,670 @@ +use alloy_consensus::Transaction; +use alloy_eips::Decodable2718; +use alloy_primitives::{Address, TxHash, U256}; +use alloy_provider::Provider; +use macros::rb_test; +use op_alloy_consensus::OpTxEnvelope; +use std::time::Duration; + +use crate::{ + args::{FlashblocksArgs, OpRbuilderArgs}, + tests::{ + BlockTransactionsExt, BundleOpts, ChainDriver, FLASHBLOCKS_NUMBER_ADDRESS, LocalInstance, + TransactionBuilderExt, flashblocks_number_contract::FlashblocksNumber, + }, +}; + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 2000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + ..Default::default() + }, + ..Default::default() +})] +async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + // We align out block timestamps with current unix timestamp + for _ in 0..10 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver.build_new_block_with_current_timestamp(None).await?; + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(110, flashblocks.len()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + ..Default::default() + }, + ..Default::default() +})] +async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + // We align out block timestamps with current unix timestamp + for _ in 0..10 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver.build_new_block_with_current_timestamp(None).await?; + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(60, flashblocks.len()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 50, + flashblocks_fixed: true, + ..Default::default() + }, + ..Default::default() +})] +async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + // We align out block timestamps with current unix timestamp + for _ in 0..10 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver.build_new_block().await?; + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(60, flashblocks.len()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 2000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 50, + flashblocks_fixed: true, + ..Default::default() + }, + ..Default::default() +})] +async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + // We align out block timestamps with current unix timestamp + for _ in 0..10 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver.build_new_block().await?; + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(110, flashblocks.len()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + ..Default::default() + }, + ..Default::default() +})] +async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + // We align out block timestamps with current unix timestamp + for i in 0..9 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver + .build_new_block_with_current_timestamp(Some(Duration::from_millis(i * 100))) + .await?; + assert_eq!( + block.transactions.len(), + 8, + "Got: {:#?}", + block.transactions + ); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(34, flashblocks.len()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 0, + flashblocks_fixed: false, + ..Default::default() + }, + ..Default::default() +})] +async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver + .build_new_block_with_current_timestamp(Some(Duration::from_millis(999))) + .await?; + // Lower bounds only: after the reth 2.x / alloy 2.x bump the flashblock + // builder can still pack a full flashblock when the FCU arrives in the + // slot's last millisecond, so the original `== 2 txs, == 1 flashblock` + // invariant no longer holds. Mirrors upstream's `late_fcu_reduces_flashblocks` + // bound-based assertion style. + assert!( + block.transactions.len() >= 2, + "expected at least deposit + builder tx, got {:#?}", + block.transactions + ); + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert!(!flashblocks.is_empty()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + // Create two transactions and set their tips so that while ordinarily + // tx2 would come before tx1 because its tip is bigger, now tx1 comes + // first because it has a lower minimum flashblock requirement. + let tx1 = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(0)) + .with_max_priority_fee_per_gas(0) + .send() + .await?; + + let tx2 = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(3)) + .with_max_priority_fee_per_gas(10) + .send() + .await?; + + let _block1 = driver.build_new_block_with_current_timestamp(None).await?; + + // Check that tx1 comes before tx2 + let tx1_hash = *tx1.tx_hash(); + let tx2_hash = *tx2.tx_hash(); + let tx1_pos = flashblocks_listener + .find_transaction_flashblock(&tx1_hash) + .unwrap(); + let tx2_pos = flashblocks_listener + .find_transaction_flashblock(&tx2_hash) + .unwrap(); + + assert!( + tx1_pos < tx2_pos, + "tx {tx1_hash:?} does not come before {tx2_hash:?}" + ); + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(6, flashblocks.len()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashblock_max_filtering(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + // Since we cannot directly trigger flashblock creation in tests, we + // instead fill up the gas of flashblocks so that our tx with the + // flashblock_number_max parameter set is properly delayed, simulating + // the scenario where we'd sent the tx after the flashblock max number + // had passed. + let call = driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 3)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + let _fit_tx_1 = driver + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + + let tx1 = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_max(1)) + .send() + .await?; + + let block = driver.build_new_block_with_current_timestamp(None).await?; + assert!(!block.includes(tx1.tx_hash())); + assert!( + flashblocks_listener + .find_transaction_flashblock(tx1.tx_hash()) + .is_none() + ); + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(6, flashblocks.len()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashblock_min_max_filtering(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + let tx1 = driver + .create_transaction() + .random_valid_transfer() + .with_bundle( + BundleOpts::default() + .with_flashblock_number_max(2) + .with_flashblock_number_min(2), + ) + .send() + .await?; + + let _block = driver.build_new_block_with_current_timestamp(None).await?; + + // It ends up in the 2nd flashblock + assert_eq!( + 2, + flashblocks_listener + .find_transaction_flashblock(tx1.tx_hash()) + .unwrap(), + "Transaction should be in the 2nd flashblock" + ); + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(6, flashblocks.len(), "Flashblocks length should be 6"); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + flashblocks_disable_state_root: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashblocks_no_state_root_calculation(rbuilder: LocalInstance) -> eyre::Result<()> { + use alloy_primitives::B256; + + let driver = rbuilder.driver().await?; + + // Send a transaction to ensure block has some activity + let _tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + + // Build a block with current timestamp (not historical) and disable_state_root: true + let block = driver.build_new_block_with_current_timestamp(None).await?; + + // Verify that flashblocks are still produced (block should have transactions) + assert!( + block.transactions.len() > 2, + "Block should contain transactions" + ); // deposit + builder tx + user tx + + // Verify that state root is not calculated (should be zero) + assert_eq!( + block.header.state_root, + B256::ZERO, + "State root should be zero when disable_state_root is true" + ); + + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashblocks_number_contract_builder_tx(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + let provider = rbuilder.provider().await?; + + // Deploy flashblocks number contract which will be in flashblocks 1 + let deploy_tx = driver + .create_transaction() + .deploy_flashblock_number_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // Create valid transactions for flashblocks 2-4 + let user_transactions = create_flashblock_transactions(&driver, 2..5).await?; + + // Build block with deploy tx in first flashblock, and a random valid transfer in every other flashblock + let block = driver.build_new_block_with_current_timestamp(None).await?; + + // Verify contract deployment + let receipt = provider + .get_transaction_receipt(*deploy_tx.tx_hash()) + .await? + .expect("flashblock number contract deployment not mined"); + let contract_address = receipt + .inner + .contract_address + .expect("contract receipt does not contain flashblock number contract address"); + assert_eq!( + contract_address, FLASHBLOCKS_NUMBER_ADDRESS, + "Flashblocks number contract address mismatch" + ); + + // Verify first block structure + assert_eq!(block.transactions.len(), 10); + let txs = block + .transactions + .as_transactions() + .expect("transactions not in block"); + + // Verify builder txs (should be regular since builder tx is not registered yet) + verify_builder_txs( + &txs, + &[1, 2, 4, 6, 8], + Some(Address::ZERO), + "Should have regular builder tx", + ); + + // Verify deploy tx position + assert_eq!( + txs[3].inner.inner.tx_hash(), + *deploy_tx.tx_hash(), + "Deploy tx not in correct position" + ); + + // Verify user transactions + verify_user_tx_hashes(&txs, &[5, 7, 9], &user_transactions); + + // Initialize contract + let init_tx = driver + .create_transaction() + .init_flashblock_number_contract(true) + .with_to(FLASHBLOCKS_NUMBER_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // Mine initialization + driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*init_tx.tx_hash()) + .await? + .expect("init tx not mined"); + + // Create user transactions for flashblocks 1 - 5 + let user_transactions = create_flashblock_transactions(&driver, 1..5).await?; + + // Build second block after initialization which will call the flashblock number contract + // with builder registered + let block = driver.build_new_block_with_current_timestamp(None).await?; + assert_eq!(block.transactions.len(), 10); + let txs = block + .transactions + .as_transactions() + .expect("transactions not in block"); + + // Fallback block should have regular builder tx after deposit tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "Fallback block should have regular builder tx" + ); + + // Other builder txs should call the contract + verify_builder_txs( + &txs, + &[2, 4, 6, 8], + Some(contract_address), + "Should call flashblocks contract", + ); + + // Verify user transactions, 3 blocks in total built + verify_user_tx_hashes(&txs, &[3, 5, 7, 9], &user_transactions); + + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(contract_address, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert_eq!( + current_number, + U256::from(7), + "Flashblock number not incremented correctly" + ); + + // Verify flashblocks + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(flashblocks.len(), 15); + + // Verify builder tx in each flashblock + for (i, flashblock) in flashblocks.iter().enumerate() { + // In fallback blocks, builder tx is the 2nd tx (index 1) + // In regular flashblocks, builder tx is the 1st tx (index 0) + let is_fallback = i % 5 == 0; + let tx_index = if is_fallback { 1 } else { 0 }; + + let tx_bytes = flashblock.diff.transactions.get(tx_index).expect(&format!( + "Flashblock {} should have tx at index {}", + i, tx_index + )); + let tx = OpTxEnvelope::decode_2718(&mut tx_bytes.as_ref()) + .expect("failed to decode transaction"); + + let expected_to = if i < 7 || i == 10 { + Some(Address::ZERO) + } else { + Some(contract_address) + }; + + assert_eq!( + tx.to(), + expected_to, + "Flashblock {} builder tx (at index {}) should have to = {:?}", + i, + tx_index, + expected_to + ); + } + + flashblocks_listener.stop().await?; + Ok(()) +} + +// Helper to create transactions for flashblocks +async fn create_flashblock_transactions( + driver: &ChainDriver, + range: std::ops::Range, +) -> eyre::Result> { + let mut txs = Vec::new(); + for i in range { + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(i)) + .send() + .await?; + txs.push(*tx.tx_hash()); + } + Ok(txs) +} + +// Helper to verify builder transactions +fn verify_builder_txs( + block_txs: &[impl Transaction], + indices: &[usize], + expected_to: Option
, + msg: &str, +) { + for &idx in indices { + assert_eq!(block_txs[idx].to(), expected_to, "{} at index {}", msg, idx); + } +} + +// Helper to verify transaction matches +fn verify_user_tx_hashes( + block_txs: &[impl AsRef], + indices: &[usize], + expected_txs: &[TxHash], +) { + for (i, &idx) in indices.iter().enumerate() { + assert_eq!( + *block_txs[idx].as_ref().tx_hash(), + expected_txs[i], + "Transaction at index {} doesn't match", + idx + ); + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/flashtestations.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/flashtestations.rs new file mode 100644 index 00000000000..002ce5cfc97 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/flashtestations.rs @@ -0,0 +1,670 @@ +use alloy_consensus::Transaction; +use alloy_network::TransactionResponse; +use alloy_primitives::{Address, U256}; +use alloy_provider::{Provider, RootProvider}; +use macros::{if_flashblocks, if_standard, rb_test}; +use op_alloy_network::Optimism; + +use crate::{ + args::{FlashblocksArgs, OpRbuilderArgs}, + flashtestations::args::FlashtestationsArgs, + tests::{ + BLOCK_BUILDER_POLICY_ADDRESS, BundleOpts, ChainDriver, ChainDriverExt, + FLASHBLOCKS_NUMBER_ADDRESS, FLASHTESTATION_REGISTRY_ADDRESS, LocalInstance, + MOCK_DCAP_ADDRESS, TEE_DEBUG_ADDRESS, TransactionBuilderExt, + block_builder_policy::BlockBuilderPolicy, builder_signer, + flashblocks_number_contract::FlashblocksNumber, + flashtestation_registry::FlashtestationRegistry, + }, +}; + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_invalid_quote(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, false, false).await?; + // verify not registered + let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); + let result = contract + .getRegistrationStatus(TEE_DEBUG_ADDRESS) + .call() + .await?; + assert!( + !result.isValid, + "The tee key is registered for invalid quote" + ); + // check that only regular builder tx is in the block + let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; + let txs = block.transactions.into_transactions_vec(); + + if_flashblocks!( + assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + ); + if_standard!( + assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + ); + let last_txs = &txs[txs.len() - 2..]; + // Check user transaction + assert_eq!( + last_txs[0].inner.tx_hash(), + tx_hash, + "tx hash for user transaction should match" + ); + // Check builder tx + assert_eq!( + last_txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, false).await?; + // check that only the regular builder tx is in the block + let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; + let txs = block.transactions.into_transactions_vec(); + if_flashblocks!( + assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + ); + if_standard!( + assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + ); + let last_txs = &txs[txs.len() - 2..]; + // Check user transaction + assert_eq!( + last_txs[0].inner.tx_hash(), + tx_hash, + "tx hash for user transaction should match" + ); + // Check builder tx + assert_eq!( + last_txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + ..Default::default() + }, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashblock_number_contract(&driver, &provider, true).await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + // 1 deposit tx, 1 fallback builder tx, 4 flashblocks number tx, valid tx, block proof + let txs = block.transactions.into_transactions_vec(); + assert_eq!(txs.len(), 8, "Expected 8 transactions in block"); + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "fallback builder tx should send to zero address" + ); + // flashblocks number contract + for i in 2..6 { + assert_eq!( + txs[i].to(), + Some(FLASHBLOCKS_NUMBER_ADDRESS), + "builder tx should send to flashblocks number contract at index {}", + i + ); + } + // check regular tx + assert_eq!( + txs[6].tx_hash(), + *tx.tx_hash(), + "bundle tx was not in block" + ); + // check block proof tx + assert_eq!( + txs[7].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "block proof tx should call block policy address" + ); + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert!( + current_number.gt(&U256::from(8)), // contract deployments incremented the number but we built at least 2 full blocks + "Flashblock number not incremented" + ); + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + debug: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_registration(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + // check builder does not try to register again + let block = driver.build_new_block_with_current_timestamp(None).await?; + let num_txs = block.transactions.len(); + if_flashblocks!( + assert!(num_txs == 3, "Expected 3 transactions in block"); // deposit + 2 builder tx + ); + if_standard!( + assert!(num_txs == 2, "Expected 2 transactions in block"); // deposit + builder tx + ); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_block_proof(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + // check builder does not try to register again + let block = driver.build_new_block_with_current_timestamp(None).await?; + let num_txs = block.transactions.len(); + if_flashblocks!( + assert!(num_txs == 4, "Expected 4 transactions in block"); // deposit + 2 builder tx + 1 block proof + ); + if_standard!( + assert!(num_txs == 3, "Expected 3 transactions in block"); // deposit + 2 builder tx + ); + let last_2_txs = &block.transactions.into_transactions_vec()[num_txs - 2..]; + // Check builder tx + assert_eq!( + last_2_txs[0].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + // check builder proof + assert_eq!( + last_2_txs[1].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "builder tx should send to flashtestations builder policy address" + ); + assert_eq!( + last_2_txs[1].from(), + builder_signer().address, + "block proof tx should come from builder address" + ); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); + + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + ..Default::default() + }, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_with_flashblocks_number_contract( + rbuilder: LocalInstance, +) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + setup_flashblock_number_contract(&driver, &provider, true).await?; + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + let num_txs = block.transactions.len(); + let txs = block.transactions.into_transactions_vec(); + // // 1 deposit tx, 1 regular builder tx, 4 flashblocks number tx, 1 user tx, 1 block proof tx + assert_eq!(num_txs, 8, "Expected 8 transactions in block"); + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + // flashblocks number contract + for i in 2..6 { + assert_eq!( + txs[i].to(), + Some(FLASHBLOCKS_NUMBER_ADDRESS), + "builder tx should send to flashblocks number contract at index {}", + i + ); + } + // user tx + assert_eq!( + txs[6].tx_hash(), + *tx.tx_hash(), + "user tx should be in correct position in block" + ); + assert_eq!( + txs[7].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "builder tx should send verify block builder proof tx" + ); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert!( + current_number.gt(&U256::from(4)), // contract deployments incremented the number but we built at least 1 full block + "Flashblock number not incremented" + ); + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + flashblocks_number_contract_use_permit: true, + ..Default::default() + }, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_with_flashblocks_number_permit( + rbuilder: LocalInstance, +) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashblock_number_contract(&driver, &provider, false).await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + // Verify flashblock number is not incremented and builder address is not authorized + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert!( + current_number.is_zero(), // contract deployments incremented the number but we built at least 1 full block + "Flashblock number should not be incremented" + ); + let is_authorized = contract.isBuilder(builder_signer().address).call().await?; + assert!(!is_authorized, "builder should not be authorized"); + + // add tee signer address to authorized builders + let add_builder_tx = driver + .create_transaction() + .add_authorized_builder(TEE_DEBUG_ADDRESS) + .with_to(FLASHBLOCKS_NUMBER_ADDRESS) + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*add_builder_tx.tx_hash()) + .await? + .expect("add builder tx not mined"); + let num_txs = block.transactions.len(); + let txs = block.transactions.into_transactions_vec(); + // 1 deposit tx, 5 regular builder tx, 1 add builder tx, 1 block proof tx + assert_eq!(num_txs, 8, "Expected 8 transactions in block"); + // Check no transactions to the flashblocks number contract as tee signer is not authorized + for i in 1..6 { + assert_eq!( + txs[i].to(), + Some(Address::ZERO), + "builder tx should send to flashblocks number contract at index {}", + i + ); + } + // add builder tx + assert_eq!( + txs[6].tx_hash(), + *add_builder_tx.tx_hash(), + "add builder tx should be in correct position in block" + ); + assert_eq!( + txs[7].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "builder tx should send verify block builder proof" + ); + + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + let txs = block.transactions.into_transactions_vec(); + // 1 deposit tx, 1 regular builder tx, 4 flashblocks builder tx, 1 user tx, 1 block proof tx + assert_eq!(txs.len(), 8, "Expected 8 transactions in block"); + // flashblocks number contract + for i in 2..6 { + assert_eq!( + txs[i].to(), + Some(FLASHBLOCKS_NUMBER_ADDRESS), + "builder tx should send to flashblocks number contract at index {}", + i + ); + } + // user tx + assert_eq!( + txs[6].tx_hash(), + *tx.tx_hash(), + "user tx should be in correct position in block" + ); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert_eq!( + current_number, + U256::from(4), + "Flashblock number not incremented correctly" + ); + Ok(()) +} + +async fn setup_flashtestation_contracts( + driver: &ChainDriver, + provider: &RootProvider, + add_quote: bool, + authorize_workload: bool, +) -> eyre::Result<()> { + // deploy the mock contract and register a mock quote + let mock_dcap_deploy_tx = driver + .create_transaction() + .deploy_mock_dcap_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // deploy the flashtestations registry contract + let flashtestations_registry_tx = driver + .create_transaction() + .deploy_flashtestation_registry_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // init the flashtestation registry contract + let init_registry = driver + .create_transaction() + .init_flashtestation_registry_contract(MOCK_DCAP_ADDRESS) + .with_to(FLASHTESTATION_REGISTRY_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // deploy the block builder policy contract + let block_builder_policy_tx = driver + .create_transaction() + .deploy_builder_policy_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // init the builder block policy contract + let init_builder_policy = driver + .create_transaction() + .init_builder_policy_contract(FLASHTESTATION_REGISTRY_ADDRESS) + .with_to(BLOCK_BUILDER_POLICY_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // include the deployment and initialization in a block + driver.build_new_block_with_current_timestamp(None).await?; + + if add_quote { + // Add test quote + let mock_quote_tx = driver + .create_transaction() + .add_mock_quote() + .with_to(MOCK_DCAP_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*mock_quote_tx.tx_hash()) + .await? + .expect("add mock quote not mined"); + // verify registered + let registry_contract = + FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); + let registration = registry_contract + .getRegistration(TEE_DEBUG_ADDRESS) + .call() + .await?; + assert!(registration._0, "The tee key is not registered"); + } + + if authorize_workload { + // add the workload id to the block builder policy + let add_workload = driver + .create_transaction() + .add_workload_to_policy() + .with_to(BLOCK_BUILDER_POLICY_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*add_workload.tx_hash()) + .await? + .expect("add workload to builder policy tx not mined"); + // verify workload id added + let policy_contract = + BlockBuilderPolicy::new(BLOCK_BUILDER_POLICY_ADDRESS, provider.clone()); + let is_allowed = policy_contract + .isAllowedPolicy(TEE_DEBUG_ADDRESS) + .call() + .await?; + assert!(is_allowed.allowed, "The policy is not allowed") + } + + // Verify mock dcap contract deployment + let receipt = provider + .get_transaction_receipt(*mock_dcap_deploy_tx.tx_hash()) + .await? + .expect("mock dcap contract deployment not mined"); + let mock_dcap_address = receipt + .inner + .contract_address + .expect("contract receipt does not contain flashblock number contract address"); + assert_eq!( + mock_dcap_address, MOCK_DCAP_ADDRESS, + "mock dcap contract address mismatch" + ); + // verify flashtestations registry contract deployment + let receipt = provider + .get_transaction_receipt(*flashtestations_registry_tx.tx_hash()) + .await?; + let flashtestations_registry_address = receipt + .expect("flashtestations registry contract deployment not mined") + .inner + .contract_address + .expect("contract receipt does not contain flashtestations registry contract address"); + assert_eq!( + flashtestations_registry_address, FLASHTESTATION_REGISTRY_ADDRESS, + "flashtestations registry contract address mismatch" + ); + // verify flashtestations registry contract initialization + provider + .get_transaction_receipt(*init_registry.tx_hash()) + .await? + .expect("init registry tx not mined"); + + // verify block builder policy contract deployment + let receipt = provider + .get_transaction_receipt(*block_builder_policy_tx.tx_hash()) + .await?; + let block_builder_policy_address = receipt + .expect("block builder policy contract deployment not mined") + .inner + .contract_address + .expect("contract receipt does not contain block builder policy contract address"); + assert_eq!( + block_builder_policy_address, BLOCK_BUILDER_POLICY_ADDRESS, + "block builder policy contract address mismatch" + ); + // verify block builder policy contract initialization + provider + .get_transaction_receipt(*init_builder_policy.tx_hash()) + .await? + .expect("init builder policy tx not mined"); + + Ok(()) +} + +async fn setup_flashblock_number_contract( + driver: &ChainDriver, + provider: &RootProvider, + authorize_builder: bool, +) -> eyre::Result<()> { + // Deploy flashblocks number contract + let deploy_tx = driver + .create_transaction() + .deploy_flashblock_number_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // Initialize contract + let init_tx = driver + .create_transaction() + .init_flashblock_number_contract(authorize_builder) + .with_to(FLASHBLOCKS_NUMBER_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + driver.build_new_block_with_current_timestamp(None).await?; + + // Verify contract deployment + let receipt = provider + .get_transaction_receipt(*deploy_tx.tx_hash()) + .await? + .expect("flashblock number contract deployment not mined"); + let contract_address = receipt + .inner + .contract_address + .expect("contract receipt does not contain flashblock number contract address"); + assert_eq!( + contract_address, FLASHBLOCKS_NUMBER_ADDRESS, + "Flashblocks number contract address mismatch" + ); + + // Verify initialization + provider + .get_transaction_receipt(*init_tx.tx_hash()) + .await? + .expect("init tx not mined"); + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/forks.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/forks.rs new file mode 100644 index 00000000000..d136c375f52 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/forks.rs @@ -0,0 +1,150 @@ +use crate::tests::{BlockTransactionsExt, LocalInstance}; +use alloy_eips::{BlockNumberOrTag::Latest, Encodable2718, eip1559::MIN_PROTOCOL_BASE_FEE}; +use alloy_primitives::bytes; +use macros::{if_flashblocks, if_standard, rb_test}; +use std::time::Duration; + +#[rb_test] +async fn jovian_block_parameters_set(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let tx_one = driver.create_transaction().send().await?; + let tx_two = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + assert!(block.includes(tx_one.tx_hash())); + assert!(block.includes(tx_two.tx_hash())); + + assert!(block.header.excess_blob_gas.is_some()); + + assert!(block.header.blob_gas_used.is_some()); + + // Two user transactions + two builder transactions, all minimum size + if_flashblocks! { + assert_eq!(block.header.blob_gas_used.unwrap(), 160_000); + } + + // Two user transactions + one builder transactions, all minimum size + if_standard! { + assert_eq!(block.header.blob_gas_used.unwrap(), 120_000); + } + + // Version byte + assert_eq!(block.header.extra_data.slice(0..1), bytes!("0x01")); + + // Min Base Fee of zero by default + assert_eq!( + block.header.extra_data.slice(9..=16), + bytes!("0x0000000000000000"), + ); + + Ok(()) +} + +#[rb_test] +async fn jovian_no_tx_pool_sync(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let block = driver + .build_new_block_with_txs_timestamp(vec![], Some(true), None, None, Some(0)) + .await?; + + // Deposit transaction + user transaction + if_flashblocks! { + assert_eq!(block.transactions.len(), 1); + assert_eq!(block.header.blob_gas_used, Some(0)); + } + + // Standard includes a builder transaction when no-tx-pool is set + if_standard! { + assert_eq!(block.transactions.len(), 2); + assert_eq!(block.header.blob_gas_used, Some(40_000)); + } + + let tx = driver.create_transaction().build().await; + let block = driver + .build_new_block_with_txs_timestamp( + vec![tx.encoded_2718().into()], + Some(true), + None, + None, + Some(0), + ) + .await?; + + // Deposit transaction + user transaction + if_flashblocks! { + assert_eq!(block.transactions.len(), 2); + assert_eq!(block.header.blob_gas_used, Some(40_000)); + } + + // Standard includes a builder transaction when no-tx-pool is set + if_standard! { + assert_eq!(block.transactions.len(), 3); + assert_eq!(block.header.blob_gas_used, Some(80_000)); + } + + Ok(()) +} + +#[rb_test] +async fn jovian_minimum_base_fee(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let genesis = driver + .get_block(Latest) + .await? + .expect("must have genesis block"); + + assert_eq!(genesis.header.base_fee_per_gas, Some(1)); + + let min_base_fee = Some(MIN_PROTOCOL_BASE_FEE * 2); + + let block_timestamp = Duration::from_secs(genesis.header.timestamp) + Duration::from_secs(1); + let block_one = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, min_base_fee) + .await?; + + assert_eq!( + block_one.header.extra_data.slice(9..=16), + bytes!("0x000000000000000E"), + ); + + let overpriced_tx = driver + .create_transaction() + .with_max_fee_per_gas(MIN_PROTOCOL_BASE_FEE as u128 * 4) + .send() + .await?; + let underpriced_tx = driver + .create_transaction() + .with_max_fee_per_gas(MIN_PROTOCOL_BASE_FEE as u128) + .send() + .await?; + + let block_timestamp = Duration::from_secs(block_one.header.timestamp) + Duration::from_secs(1); + let block_two = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, min_base_fee) + .await?; + + assert_eq!( + block_two.header.extra_data.slice(9..=16), + bytes!("0x000000000000000E"), + ); + + assert!(block_two.includes(overpriced_tx.tx_hash())); + assert!(!block_two.includes(underpriced_tx.tx_hash())); + + Ok(()) +} + +#[rb_test] +async fn jovian_minimum_fee_must_be_set(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let genesis = driver + .get_block(Latest) + .await? + .expect("must have genesis block"); + let block_timestamp = Duration::from_secs(genesis.header.timestamp) + Duration::from_secs(1); + let response = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, None) + .await; + assert!(response.is_err()); + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/apis.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/apis.rs new file mode 100644 index 00000000000..5638293b0df --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/apis.rs @@ -0,0 +1,231 @@ +use super::DEFAULT_JWT_TOKEN; +use alloy_eips::{BlockNumberOrTag, eip7685::Requests}; +use alloy_primitives::B256; + +use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadStatus}; +use core::{future::Future, marker::PhantomData}; +use jsonrpsee::{ + core::{RpcResult, client::SubscriptionClientT}, + proc_macros::rpc, +}; +use op_alloy_rpc_types_engine::OpExecutionPayloadV4; +use reth::rpc::types::engine::ForkchoiceState; +use reth_node_api::{EngineTypes, PayloadTypes}; +use reth_optimism_node::OpEngineTypes; +use reth_optimism_rpc::engine::OpEngineApiClient; +use reth_payload_builder::PayloadId; +use reth_rpc_layer::{AuthClientLayer, JwtSecret}; +use serde_json::Value; +use tracing::debug; + +#[derive(Clone, Debug)] +pub enum Address { + Ipc(String), + Http(url::Url), +} + +pub trait Protocol { + fn client( + jwt: JwtSecret, + address: Address, + ) -> impl Future; +} + +pub struct Http; +impl Protocol for Http { + async fn client( + jwt: JwtSecret, + address: Address, + ) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + let Address::Http(url) = address else { + unreachable!(); + }; + + let secret_layer = AuthClientLayer::new(jwt); + let middleware = tower::ServiceBuilder::default().layer(secret_layer); + jsonrpsee::http_client::HttpClientBuilder::default() + .set_http_middleware(middleware) + .build(url) + .expect("Failed to create http client") + } +} + +pub struct Ipc; +impl Protocol for Ipc { + async fn client( + _: JwtSecret, // ipc does not use JWT + address: Address, + ) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + let Address::Ipc(path) = address else { + unreachable!(); + }; + reth_ipc::client::IpcClientBuilder::default() + .build(&path) + .await + .expect("Failed to create ipc client") + } +} + +/// Helper for engine api operations +pub struct EngineApi { + address: Address, + jwt_secret: JwtSecret, + _tag: PhantomData

, +} + +impl EngineApi

{ + async fn client(&self) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static + use

{ + P::client(self.jwt_secret, self.address.clone()).await + } +} + +// http specific +impl EngineApi { + pub fn with_http(url: &str) -> EngineApi { + EngineApi:: { + address: Address::Http(url.parse().expect("Invalid URL")), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, + } + } + + pub fn with_localhost_port(port: u16) -> EngineApi { + EngineApi:: { + address: Address::Http( + format!("http://localhost:{port}") + .parse() + .expect("Invalid URL"), + ), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, + } + } + + pub fn with_port(mut self, port: u16) -> Self { + let Address::Http(url) = &mut self.address else { + unreachable!(); + }; + + url.set_port(Some(port)).expect("Invalid port"); + self + } + + pub fn with_jwt_secret(mut self, jwt_secret: &str) -> Self { + self.jwt_secret = jwt_secret.parse().expect("Invalid JWT"); + self + } + + pub fn url(&self) -> &url::Url { + let Address::Http(url) = &self.address else { + unreachable!(); + }; + url + } +} + +// ipc specific +impl EngineApi { + pub fn with_ipc(path: &str) -> EngineApi { + EngineApi:: { + address: Address::Ipc(path.into()), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, + } + } + + pub fn path(&self) -> &str { + let Address::Ipc(path) = &self.address else { + unreachable!(); + }; + path + } +} + +impl EngineApi

{ + pub async fn get_payload( + &self, + payload_id: PayloadId, + ) -> eyre::Result<::ExecutionPayloadEnvelopeV4> { + debug!( + "Fetching payload with id: {} at {}", + payload_id, + chrono::Utc::now() + ); + Ok( + OpEngineApiClient::::get_payload_v4(&self.client().await, payload_id) + .await?, + ) + } + + pub async fn new_payload( + &self, + payload: OpExecutionPayloadV4, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + execution_requests: Requests, + ) -> eyre::Result { + debug!("Submitting new payload at {}...", chrono::Utc::now()); + Ok(OpEngineApiClient::::new_payload_v4( + &self.client().await, + payload, + versioned_hashes, + parent_beacon_block_root, + execution_requests, + ) + .await?) + } + + pub async fn update_forkchoice( + &self, + current_head: B256, + new_head: B256, + payload_attributes: Option<::PayloadAttributes>, + ) -> eyre::Result { + debug!("Updating forkchoice at {}...", chrono::Utc::now()); + Ok(OpEngineApiClient::::fork_choice_updated_v3( + &self.client().await, + ForkchoiceState { + head_block_hash: new_head, + safe_block_hash: current_head, + finalized_block_hash: current_head, + }, + payload_attributes, + ) + .await?) + } +} + +#[rpc(server, client, namespace = "eth")] +pub trait BlockApi { + #[method(name = "getBlockByNumber")] + async fn get_block_by_number( + &self, + block_number: BlockNumberOrTag, + include_txs: bool, + ) -> RpcResult>; +} + +pub fn generate_genesis(output: Option) -> eyre::Result<()> { + // Read the template file + let template = include_str!("artifacts/genesis.json.tmpl"); + + // Parse the JSON + let mut genesis: Value = serde_json::from_str(template)?; + + // Update the timestamp field - example using current timestamp + let timestamp = chrono::Utc::now().timestamp(); + if let Some(config) = genesis.as_object_mut() { + // Assuming timestamp is at the root level - adjust path as needed + config["timestamp"] = Value::String(format!("0x{timestamp:x}")); + } + + // Write the result to the output file + if let Some(output) = output { + std::fs::write(&output, serde_json::to_string_pretty(&genesis)?)?; + println!("Generated genesis file at: {output}"); + } else { + println!("{}", serde_json::to_string_pretty(&genesis)?); + } + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json new file mode 100644 index 00000000000..b661a1b281b --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json @@ -0,0 +1,10910 @@ +{ + "abi": [ + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addWorkloadToPolicy", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "internalType": "WorkloadId" + }, + { + "name": "commitHash", + "type": "string", + "internalType": "string" + }, + { + "name": "sourceLocators", + "type": "string[]", + "internalType": "string[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "computeStructHash", + "inputs": [ + { + "name": "version", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "blockContentHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "domainSeparator", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { + "name": "fields", + "type": "bytes1", + "internalType": "bytes1" + }, + { + "name": "name", + "type": "string", + "internalType": "string" + }, + { + "name": "version", + "type": "string", + "internalType": "string" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getHashedTypeDataV4", + "inputs": [ + { + "name": "structHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getWorkloadMetadata", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "internalType": "WorkloadId" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IBlockBuilderPolicy.WorkloadMetadata", + "components": [ + { + "name": "commitHash", + "type": "string", + "internalType": "string" + }, + { + "name": "sourceLocators", + "type": "string[]", + "internalType": "string[]" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_initialOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "_registry", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "isAllowedPolicy", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "allowed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "", + "type": "bytes32", + "internalType": "WorkloadId" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "permitNonce", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permitVerifyBlockBuilderProof", + "inputs": [ + { + "name": "version", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "blockContentHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "eip712Sig", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "registry", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeWorkloadFromPolicy", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "internalType": "WorkloadId" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "verifyBlockBuilderProof", + "inputs": [ + { + "name": "version", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "blockContentHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "workloadIdForTDRegistration", + "inputs": [ + { + "name": "registration", + "type": "tuple", + "internalType": "struct IFlashtestationRegistry.RegisteredTEE", + "components": [ + { + "name": "isValid", + "type": "bool", + "internalType": "bool" + }, + { + "name": "rawQuote", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "parsedReportBody", + "type": "tuple", + "internalType": "struct TD10ReportBody", + "components": [ + { + "name": "teeTcbSvn", + "type": "bytes16", + "internalType": "bytes16" + }, + { + "name": "mrSeam", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "mrsignerSeam", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "seamAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "tdAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "xFAM", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "mrTd", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "mrConfigId", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "mrOwner", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "mrOwnerConfig", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "rtMr0", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "rtMr1", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "rtMr2", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "rtMr3", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "reportData", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "quoteHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "WorkloadId" + } + ], + "stateMutability": "pure" + }, + { + "type": "event", + "name": "BlockBuilderProofVerified", + "inputs": [ + { + "name": "caller", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "workloadId", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + }, + { + "name": "blockContentHash", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "commitHash", + "type": "string", + "indexed": false, + "internalType": "string" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RegistrySet", + "inputs": [ + { + "name": "registry", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WorkloadAddedToPolicy", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WorkloadRemovedFromPolicy", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignature", + "inputs": [] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [ + { + "name": "s", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC1967NonPayable", + "inputs": [] + }, + { + "type": "error", + "name": "EmptyCommitHash", + "inputs": [] + }, + { + "type": "error", + "name": "EmptySourceLocators", + "inputs": [] + }, + { + "type": "error", + "name": "FailedCall", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidNonce", + "inputs": [ + { + "name": "expected", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "provided", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidRegistry", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "UnauthorizedBlockBuilder", + "inputs": [ + { + "name": "caller", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "WorkloadAlreadyInPolicy", + "inputs": [] + }, + { + "type": "error", + "name": "WorkloadNotInPolicy", + "inputs": [] + } + ], + "bytecode": { + "object": "0x60a0806040523460295730608052613226908161002e8239608051818181610a3801526110790152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80632dd8abfe14611e4c578063485cc955146116b05780634d37fc7a146113365780634f1ef286146110105780634f3a415a14610ab057806352d1902d146109f35780635c40e542146109095780636931164e146108cd578063715018a6146107f3578063730169231461079b5780637b1039991461074a5780637dec71a9146106f95780637ecebe001461069657806384b0196e146104f15780638da5cb5b14610481578063abd45d21146102fc578063ad3cb1cc1461027b578063b33d59da14610237578063d2753561146101e8578063f2fde38b1461019f5763f698da2514610100575f80fd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206101386130d1565b61014061313b565b60405190838201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261019060c082612063565b519020604051908152f35b5f80fd5b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e66101d9611feb565b6101e1612cef565b612b2d565b005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576040610229610224611feb565b612794565b825191151582526020820152f35b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e6610271611fad565b6024359033612c1a565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576102f86040516102ba604082612063565b600581527f352e302e30000000000000000000000000000000000000000000000000000000602082015260405191829160208352602083019061215f565b0390f35b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576060602060405161033b81612047565b82815201526004355f525f60205260405f2060016040519161035c83612047565b61036581612658565b83520180546103738161230c565b916103816040519384612063565b81835260208301905f5260205f205f915b838310610464576103c1868660208201908152604051928392602084525160406020850152606084019061215f565b9051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0838203016040840152815180825260208201916020808360051b8301019401925f915b8383106104155786860387f35b919395509193602080610452837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08660019603018752895161215f565b97019301930190928695949293610408565b60016020819261047385612658565b815201920192019190610392565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10054158061066d575b1561060f576105b3610558612474565b610560612585565b60206105c1604051926105738385612063565b5f84525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e087019061215f565b90858203604087015261215f565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b8281106105f857505050500390f35b8351855286955093810193928101926001016105e9565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415610548565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff6106e2611feb565b165f526002602052602060405f2054604051908152f35b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742610735611fad565b6044359060243590612426565b604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206040517f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc8152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57610829612cef565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742600435612389565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57600435610943612cef565b805f525f60205261095760405f20546122bb565b156109cb57805f525f602052600160405f206109728161233a565b018054905f8155816109a6575b827f56c387a9be1bf0e0e4f852c577a225db98e8253ad401d1b4ea73926f27d6af095f80a2005b5f5260205f20908101905b8181101561097f57806109c560019261233a565b016109b1565b7f22faf042000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163003610a885760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043560243567ffffffffffffffff811161019b57610b02903690600401611fbd565b919060443567ffffffffffffffff811161019b573660238201121561019b5780600401359167ffffffffffffffff831161019b5760248360051b8301019036821161019b57610b4f612cef565b8515610fe8578315610fc057845f525f602052610b6f60405f20546122bb565b610f9857610b8b9060405196610b8488612047565b36916120de565b8552610b968361230c565b92610ba46040519485612063565b83526024820191602084015b828410610f57575050505060208301908152815f525f60205260405f20925192835167ffffffffffffffff8111610e2057610beb82546122bb565b601f8111610f27575b506020601f8211600114610e85579080610c469260019596975f92610e7a575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b81555b019051805190680100000000000000008211610e20578254828455808310610e4d575b50602001915f5260205f20915f905b828210610ca957847fcbb92e241e191fed6d0b0da0a918c7dcf595e77d868e2e3bf9e6b0b91589c7ad5f80a2005b805180519067ffffffffffffffff8211610e2057610cc786546122bb565b601f8111610de5575b50602090601f8311600114610d3f5792610d25836001959460209487965f92610d345750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b87555b01940191019092610c7b565b015190508b80610c14565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0831691875f52815f20925f5b818110610dcd5750936020936001969387969383889510610d96575b505050811b018755610d28565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558a8080610d89565b92936020600181928786015181550195019301610d6d565b610e1090875f5260205f20601f850160051c81019160208610610e16575b601f0160051c0190612324565b87610cd0565b9091508190610e03565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b835f528260205f2091820191015b818110610e685750610c6c565b80610e7460019261233a565b01610e5b565b015190508780610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821695835f52815f20965f5b818110610f0f5750916001959697918487959410610ed8575b505050811b018155610c49565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080610ecb565b83830151895560019098019760209384019301610eb2565b610f5190835f5260205f20601f840160051c81019160208510610e1657601f0160051c0190612324565b85610bf4565b833567ffffffffffffffff811161019b5782013660438201121561019b57602091610f8d839236906044602482013591016120de565b815201930192610bb0565b7f72477348000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f6890d9d4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8423f262000000000000000000000000000000000000000000000000000000005f5260045ffd5b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611042611feb565b60243567ffffffffffffffff811161019b57611062903690600401612114565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168030149081156112f4575b50610a88576110b1612cef565b73ffffffffffffffffffffffffffffffffffffffff8216916040517f52d1902d000000000000000000000000000000000000000000000000000000008152602081600481875afa5f91816112c0575b5061113157837f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8592036112955750813b1561126a57807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2815115611239575f808360206101e695519101845af43d15611231573d91611215836120a4565b926112236040519485612063565b83523d5f602085013e613180565b606091613180565b50503461124257005b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7faa1d49a4000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091506020813d6020116112ec575b816112dc60209383612063565b8101031261019b57519085611100565b3d91506112cf565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54161415836110a4565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043567ffffffffffffffff811161019b5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906113b08261200e565b8060040135801515810361019b578252602481013567ffffffffffffffff811161019b576113e49060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b5781016101e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906114398261202a565b60048101357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602481013567ffffffffffffffff811161019b5761148d9060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b576114b69060043691840101612114565b60408301526114c760648201612132565b60608301526114d860848201612132565b60808301526114e960a48201612132565b60a083015260c481013567ffffffffffffffff811161019b576115129060043691840101612114565b60c083015260e481013567ffffffffffffffff811161019b5761153b9060043691840101612114565b60e083015261010481013567ffffffffffffffff811161019b576115659060043691840101612114565b61010083015261012481013567ffffffffffffffff811161019b576115909060043691840101612114565b61012083015261014481013567ffffffffffffffff811161019b576115bb9060043691840101612114565b61014083015261016481013567ffffffffffffffff811161019b576115e69060043691840101612114565b61016083015261018481013567ffffffffffffffff811161019b576116119060043691840101612114565b6101808301526101a481013567ffffffffffffffff811161019b5761163c9060043691840101612114565b6101a08301526101c48101359067ffffffffffffffff821161019b5760046116679236920101612114565b6101c0820152604083015260648101359167ffffffffffffffff831161019b5760846107429261169f60209560043691840101612114565b6060840152013560808201526121a2565b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576116e7611feb565b60243573ffffffffffffffffffffffffffffffffffffffff811680910361019b577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c16159267ffffffffffffffff821680159081611e44575b6001149081611e3a575b159081611e31575b50611e0957818460017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006117c19516177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055611db4575b506117b9612feb565b6101e1612feb565b6117c9612feb565b6040918251926117d98185612063565b601284527f426c6f636b4275696c646572506f6c696379000000000000000000000000000060208501528051936118108286612063565b600185527f31000000000000000000000000000000000000000000000000000000000000006020860152611842612feb565b61184a612feb565b80519067ffffffffffffffff8211610e20576118867fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546122bb565b601f8111611d47575b50602090601f8311600114611c67576118dc92915f9183610e7a5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102555b835167ffffffffffffffff8111610e205761193a7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546122bb565b601f8111611bfa575b50602094601f8211600114611b1c576119939293949582915f92611b115750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103555b5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100555f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101558215611ae957827fffffffffffffffffffffffff0000000000000000000000000000000000000000600154161760015551917f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b5f80a2611a5857005b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2917fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005560018152a1005b7f11a1e697000000000000000000000000000000000000000000000000000000005f5260045ffd5b015190508680610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52805f20915f5b888110611be257508360019596979810611bab575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103556119b6565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080611b7e565b91926020600181928685015181550194019201611b69565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52611c61907f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c81019160208510610e1657601f0160051c0190612324565b85611943565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52815f20925f5b818110611d2f5750908460019594939210611cf8575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102556118ff565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080611ccb565b92936020600181928786015181550195019301611cb5565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52611dae907f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f850160051c81019160208610610e1657601f0160051c0190612324565b8661188f565b7fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055846117b0565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b90501585611759565b303b159150611751565b859150611747565b3461019b5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611e83611fad565b602435604435916064359267ffffffffffffffff841161019b57611ed7611ed1611eb4611ee0963690600401611fbd565b9190611ec9611ec4868989612426565b612389565b9236916120de565b90612d5b565b90959195612d95565b73ffffffffffffffffffffffffffffffffffffffff841690815f52600260205260405f2054808203611f7f5750505f52600260205260405f20928354937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8514611f525760016101e695019055612c1a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7f06427aeb000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b6004359060ff8216820361019b57565b9181601f8401121561019b5782359167ffffffffffffffff831161019b576020838186019501011161019b57565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361019b57565b60a0810190811067ffffffffffffffff821117610e2057604052565b6101e0810190811067ffffffffffffffff821117610e2057604052565b6040810190811067ffffffffffffffff821117610e2057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610e2057604052565b67ffffffffffffffff8111610e2057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926120ea826120a4565b916120f86040519384612063565b82948184528183011161019b578281602093845f960137010152565b9080601f8301121561019b5781602061212f933591016120de565b90565b35907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b6040015160c081015190610140810151610160820151916101808101516101a082015160e08301519060a08401517fffffffffffffffff0000000000000000000000000000000000000000000000001693608001517fffffffffffffffff00000000000000000000000000000000000000000000000016926040519687966020880199805160208192018c5e880160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e019182526008820152037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0810182526010016122b59082612063565b51902090565b90600182811c92168015612302575b60208310146122d557565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916122ca565b67ffffffffffffffff8111610e205760051b60200190565b81811061232f575050565b5f8155600101612324565b61234481546122bb565b908161234e575050565b81601f5f93116001146123605750555b565b8183526020832061237c91601f0160051c810190600101612324565b8082528160208120915555565b6042906123946130d1565b61239c61313b565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526123ed60c082612063565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b916040519160ff60208401947f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc865216604084015260608301526080820152608081526122b560a082612063565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10254916124a6836122bb565b808352926001811690811561254857506001146124ca575b61235e92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b81831061252c57505090602061235e928201016124be565b6020919350806001915483858901015201910190918492612514565b6020925061235e9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016124be565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10354916125b7836122bb565b808352926001811690811561254857506001146125da5761235e92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b81831061263c57505090602061235e928201016124be565b6020919350806001915483858901015201910190918492612624565b9060405191825f82549261266b846122bb565b80845293600181169081156126d45750600114612690575b5061235e92500383612063565b90505f9291925260205f20905f915b8183106126b857505090602061235e928201015f612683565b602091935080600191548385890101520191019091849261269f565b6020935061235e9592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201015f612683565b5190811515820361019b57565b81601f8201121561019b57805190612738826120a4565b926127466040519485612063565b8284526020838301011161019b57815f9260208093018386015e8301015290565b51907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b5f73ffffffffffffffffffffffffffffffffffffffff602481600154169360405194859384927f727310620000000000000000000000000000000000000000000000000000000084521660048301525afa908115612b22575f9161282f575b5080511561282857612804906121a2565b805f525f60205261281860405f20546122bb565b61282357505f905f90565b600191565b505f905f90565b90503d805f833e6128408183612063565b81019060408183031261019b5761285681612714565b5060208101519067ffffffffffffffff821161019b57019060a08282031261019b57604051916128858361200e565b61288e81612714565b8352602081015167ffffffffffffffff811161019b57826128b0918301612721565b6020840152604081015167ffffffffffffffff811161019b5781016101e08184031261019b57604051906128e38261202a565b80517fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602081015167ffffffffffffffff811161019b5784612930918301612721565b6020830152604081015167ffffffffffffffff811161019b5784612955918301612721565b604083015261296660608201612767565b606083015261297760808201612767565b608083015261298860a08201612767565b60a083015260c081015167ffffffffffffffff811161019b57846129ad918301612721565b60c083015260e081015167ffffffffffffffff811161019b57846129d2918301612721565b60e083015261010081015167ffffffffffffffff811161019b57846129f8918301612721565b61010083015261012081015167ffffffffffffffff811161019b5784612a1f918301612721565b61012083015261014081015167ffffffffffffffff811161019b5784612a46918301612721565b61014083015261016081015167ffffffffffffffff811161019b5784612a6d918301612721565b61016083015261018081015167ffffffffffffffff811161019b5784612a94918301612721565b6101808301526101a081015167ffffffffffffffff811161019b5784612abb918301612721565b6101a08301526101c08101519067ffffffffffffffff821161019b57612ae391859101612721565b6101c08201526040840152606081015167ffffffffffffffff811161019b57608092612b10918301612721565b6060840152015160808201525f6127f3565b6040513d5f823e3d90fd5b73ffffffffffffffffffffffffffffffffffffffff168015612bee5773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b91612c2483612e6d565b929015612cad57827f3fa039a23466a52e08acb25376ac7d81de184fa6549ffffb2fc920c47cb623ed949260ff612ca89373ffffffffffffffffffffffffffffffffffffffff965f525f602052612c7d60405f20612658565b936040519788971687526020870152166040850152606084015260a0608084015260a083019061215f565b0390a1565b73ffffffffffffffffffffffffffffffffffffffff847f4c547670000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303612d2f57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b8151919060418303612d8b57612d849250602082015190606060408401519301515f1a90613042565b9192909190565b50505f9160029190565b6004811015612e405780612da7575050565b60018103612dd7577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b60028103612e0b57507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600314612e155750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff600154166040517fa8af4ff500000000000000000000000000000000000000000000000000000000815260408160248173ffffffffffffffffffffffffffffffffffffffff8716958660048301525afa908115612b22575f905f92612faa575b5015612fa157815f52600360205260405f209160405193612f0285612047565b60018454948587520154806020870152838515159182612f97575b505015612f45575050505f525f602052612f3a60405f20546122bb565b156128285751600191565b909250612f5491949350612794565b93819291612f63575b50509190565b60019060405192612f7384612047565b868452602084019182525f52600360205260405f2092518355519101555f80612f5d565b149050835f612f1d565b5050505f905f90565b9150506040813d604011612fe3575b81612fc660409383612063565b8101031261019b576020612fd982612714565b910151905f612ee2565b3d9150612fb9565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561301a57565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116130c6579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612b22575f5173ffffffffffffffffffffffffffffffffffffffff8116156130bc57905f905f90565b505f906001905f90565b5050505f9160039190565b6130d9612474565b80519081156130e9576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1005480156131165790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b613143612585565b8051908115613153576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015480156131165790565b906131bd575080511561319557602081519101fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580613210575b6131ce575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156131c656fea164736f6c634300081c000a", + "sourceMap": "2021:11391:71:-:0;;;;;;;1171:4:32;1163:13;;2021:11391:71;;;;;;1163:13:32;2021:11391:71;;;;;;;;;;;;;;", + "linkReferences": {} + }, + "deployedBytecode": { + "object": "0x60806040526004361015610011575f80fd5b5f3560e01c80632dd8abfe14611e4c578063485cc955146116b05780634d37fc7a146113365780634f1ef286146110105780634f3a415a14610ab057806352d1902d146109f35780635c40e542146109095780636931164e146108cd578063715018a6146107f3578063730169231461079b5780637b1039991461074a5780637dec71a9146106f95780637ecebe001461069657806384b0196e146104f15780638da5cb5b14610481578063abd45d21146102fc578063ad3cb1cc1461027b578063b33d59da14610237578063d2753561146101e8578063f2fde38b1461019f5763f698da2514610100575f80fd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206101386130d1565b61014061313b565b60405190838201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261019060c082612063565b519020604051908152f35b5f80fd5b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e66101d9611feb565b6101e1612cef565b612b2d565b005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576040610229610224611feb565b612794565b825191151582526020820152f35b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e6610271611fad565b6024359033612c1a565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576102f86040516102ba604082612063565b600581527f352e302e30000000000000000000000000000000000000000000000000000000602082015260405191829160208352602083019061215f565b0390f35b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576060602060405161033b81612047565b82815201526004355f525f60205260405f2060016040519161035c83612047565b61036581612658565b83520180546103738161230c565b916103816040519384612063565b81835260208301905f5260205f205f915b838310610464576103c1868660208201908152604051928392602084525160406020850152606084019061215f565b9051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0838203016040840152815180825260208201916020808360051b8301019401925f915b8383106104155786860387f35b919395509193602080610452837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08660019603018752895161215f565b97019301930190928695949293610408565b60016020819261047385612658565b815201920192019190610392565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10054158061066d575b1561060f576105b3610558612474565b610560612585565b60206105c1604051926105738385612063565b5f84525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e087019061215f565b90858203604087015261215f565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b8281106105f857505050500390f35b8351855286955093810193928101926001016105e9565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415610548565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff6106e2611feb565b165f526002602052602060405f2054604051908152f35b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742610735611fad565b6044359060243590612426565b604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206040517f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc8152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57610829612cef565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742600435612389565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57600435610943612cef565b805f525f60205261095760405f20546122bb565b156109cb57805f525f602052600160405f206109728161233a565b018054905f8155816109a6575b827f56c387a9be1bf0e0e4f852c577a225db98e8253ad401d1b4ea73926f27d6af095f80a2005b5f5260205f20908101905b8181101561097f57806109c560019261233a565b016109b1565b7f22faf042000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163003610a885760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043560243567ffffffffffffffff811161019b57610b02903690600401611fbd565b919060443567ffffffffffffffff811161019b573660238201121561019b5780600401359167ffffffffffffffff831161019b5760248360051b8301019036821161019b57610b4f612cef565b8515610fe8578315610fc057845f525f602052610b6f60405f20546122bb565b610f9857610b8b9060405196610b8488612047565b36916120de565b8552610b968361230c565b92610ba46040519485612063565b83526024820191602084015b828410610f57575050505060208301908152815f525f60205260405f20925192835167ffffffffffffffff8111610e2057610beb82546122bb565b601f8111610f27575b506020601f8211600114610e85579080610c469260019596975f92610e7a575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b81555b019051805190680100000000000000008211610e20578254828455808310610e4d575b50602001915f5260205f20915f905b828210610ca957847fcbb92e241e191fed6d0b0da0a918c7dcf595e77d868e2e3bf9e6b0b91589c7ad5f80a2005b805180519067ffffffffffffffff8211610e2057610cc786546122bb565b601f8111610de5575b50602090601f8311600114610d3f5792610d25836001959460209487965f92610d345750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b87555b01940191019092610c7b565b015190508b80610c14565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0831691875f52815f20925f5b818110610dcd5750936020936001969387969383889510610d96575b505050811b018755610d28565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558a8080610d89565b92936020600181928786015181550195019301610d6d565b610e1090875f5260205f20601f850160051c81019160208610610e16575b601f0160051c0190612324565b87610cd0565b9091508190610e03565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b835f528260205f2091820191015b818110610e685750610c6c565b80610e7460019261233a565b01610e5b565b015190508780610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821695835f52815f20965f5b818110610f0f5750916001959697918487959410610ed8575b505050811b018155610c49565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080610ecb565b83830151895560019098019760209384019301610eb2565b610f5190835f5260205f20601f840160051c81019160208510610e1657601f0160051c0190612324565b85610bf4565b833567ffffffffffffffff811161019b5782013660438201121561019b57602091610f8d839236906044602482013591016120de565b815201930192610bb0565b7f72477348000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f6890d9d4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8423f262000000000000000000000000000000000000000000000000000000005f5260045ffd5b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611042611feb565b60243567ffffffffffffffff811161019b57611062903690600401612114565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168030149081156112f4575b50610a88576110b1612cef565b73ffffffffffffffffffffffffffffffffffffffff8216916040517f52d1902d000000000000000000000000000000000000000000000000000000008152602081600481875afa5f91816112c0575b5061113157837f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8592036112955750813b1561126a57807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2815115611239575f808360206101e695519101845af43d15611231573d91611215836120a4565b926112236040519485612063565b83523d5f602085013e613180565b606091613180565b50503461124257005b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7faa1d49a4000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091506020813d6020116112ec575b816112dc60209383612063565b8101031261019b57519085611100565b3d91506112cf565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54161415836110a4565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043567ffffffffffffffff811161019b5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906113b08261200e565b8060040135801515810361019b578252602481013567ffffffffffffffff811161019b576113e49060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b5781016101e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906114398261202a565b60048101357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602481013567ffffffffffffffff811161019b5761148d9060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b576114b69060043691840101612114565b60408301526114c760648201612132565b60608301526114d860848201612132565b60808301526114e960a48201612132565b60a083015260c481013567ffffffffffffffff811161019b576115129060043691840101612114565b60c083015260e481013567ffffffffffffffff811161019b5761153b9060043691840101612114565b60e083015261010481013567ffffffffffffffff811161019b576115659060043691840101612114565b61010083015261012481013567ffffffffffffffff811161019b576115909060043691840101612114565b61012083015261014481013567ffffffffffffffff811161019b576115bb9060043691840101612114565b61014083015261016481013567ffffffffffffffff811161019b576115e69060043691840101612114565b61016083015261018481013567ffffffffffffffff811161019b576116119060043691840101612114565b6101808301526101a481013567ffffffffffffffff811161019b5761163c9060043691840101612114565b6101a08301526101c48101359067ffffffffffffffff821161019b5760046116679236920101612114565b6101c0820152604083015260648101359167ffffffffffffffff831161019b5760846107429261169f60209560043691840101612114565b6060840152013560808201526121a2565b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576116e7611feb565b60243573ffffffffffffffffffffffffffffffffffffffff811680910361019b577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c16159267ffffffffffffffff821680159081611e44575b6001149081611e3a575b159081611e31575b50611e0957818460017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006117c19516177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055611db4575b506117b9612feb565b6101e1612feb565b6117c9612feb565b6040918251926117d98185612063565b601284527f426c6f636b4275696c646572506f6c696379000000000000000000000000000060208501528051936118108286612063565b600185527f31000000000000000000000000000000000000000000000000000000000000006020860152611842612feb565b61184a612feb565b80519067ffffffffffffffff8211610e20576118867fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546122bb565b601f8111611d47575b50602090601f8311600114611c67576118dc92915f9183610e7a5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102555b835167ffffffffffffffff8111610e205761193a7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546122bb565b601f8111611bfa575b50602094601f8211600114611b1c576119939293949582915f92611b115750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103555b5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100555f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101558215611ae957827fffffffffffffffffffffffff0000000000000000000000000000000000000000600154161760015551917f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b5f80a2611a5857005b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2917fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005560018152a1005b7f11a1e697000000000000000000000000000000000000000000000000000000005f5260045ffd5b015190508680610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52805f20915f5b888110611be257508360019596979810611bab575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103556119b6565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080611b7e565b91926020600181928685015181550194019201611b69565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52611c61907f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c81019160208510610e1657601f0160051c0190612324565b85611943565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52815f20925f5b818110611d2f5750908460019594939210611cf8575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102556118ff565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080611ccb565b92936020600181928786015181550195019301611cb5565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52611dae907f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f850160051c81019160208610610e1657601f0160051c0190612324565b8661188f565b7fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055846117b0565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b90501585611759565b303b159150611751565b859150611747565b3461019b5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611e83611fad565b602435604435916064359267ffffffffffffffff841161019b57611ed7611ed1611eb4611ee0963690600401611fbd565b9190611ec9611ec4868989612426565b612389565b9236916120de565b90612d5b565b90959195612d95565b73ffffffffffffffffffffffffffffffffffffffff841690815f52600260205260405f2054808203611f7f5750505f52600260205260405f20928354937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8514611f525760016101e695019055612c1a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7f06427aeb000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b6004359060ff8216820361019b57565b9181601f8401121561019b5782359167ffffffffffffffff831161019b576020838186019501011161019b57565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361019b57565b60a0810190811067ffffffffffffffff821117610e2057604052565b6101e0810190811067ffffffffffffffff821117610e2057604052565b6040810190811067ffffffffffffffff821117610e2057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610e2057604052565b67ffffffffffffffff8111610e2057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926120ea826120a4565b916120f86040519384612063565b82948184528183011161019b578281602093845f960137010152565b9080601f8301121561019b5781602061212f933591016120de565b90565b35907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b6040015160c081015190610140810151610160820151916101808101516101a082015160e08301519060a08401517fffffffffffffffff0000000000000000000000000000000000000000000000001693608001517fffffffffffffffff00000000000000000000000000000000000000000000000016926040519687966020880199805160208192018c5e880160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e019182526008820152037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0810182526010016122b59082612063565b51902090565b90600182811c92168015612302575b60208310146122d557565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916122ca565b67ffffffffffffffff8111610e205760051b60200190565b81811061232f575050565b5f8155600101612324565b61234481546122bb565b908161234e575050565b81601f5f93116001146123605750555b565b8183526020832061237c91601f0160051c810190600101612324565b8082528160208120915555565b6042906123946130d1565b61239c61313b565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526123ed60c082612063565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b916040519160ff60208401947f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc865216604084015260608301526080820152608081526122b560a082612063565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10254916124a6836122bb565b808352926001811690811561254857506001146124ca575b61235e92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b81831061252c57505090602061235e928201016124be565b6020919350806001915483858901015201910190918492612514565b6020925061235e9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016124be565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10354916125b7836122bb565b808352926001811690811561254857506001146125da5761235e92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b81831061263c57505090602061235e928201016124be565b6020919350806001915483858901015201910190918492612624565b9060405191825f82549261266b846122bb565b80845293600181169081156126d45750600114612690575b5061235e92500383612063565b90505f9291925260205f20905f915b8183106126b857505090602061235e928201015f612683565b602091935080600191548385890101520191019091849261269f565b6020935061235e9592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201015f612683565b5190811515820361019b57565b81601f8201121561019b57805190612738826120a4565b926127466040519485612063565b8284526020838301011161019b57815f9260208093018386015e8301015290565b51907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b5f73ffffffffffffffffffffffffffffffffffffffff602481600154169360405194859384927f727310620000000000000000000000000000000000000000000000000000000084521660048301525afa908115612b22575f9161282f575b5080511561282857612804906121a2565b805f525f60205261281860405f20546122bb565b61282357505f905f90565b600191565b505f905f90565b90503d805f833e6128408183612063565b81019060408183031261019b5761285681612714565b5060208101519067ffffffffffffffff821161019b57019060a08282031261019b57604051916128858361200e565b61288e81612714565b8352602081015167ffffffffffffffff811161019b57826128b0918301612721565b6020840152604081015167ffffffffffffffff811161019b5781016101e08184031261019b57604051906128e38261202a565b80517fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602081015167ffffffffffffffff811161019b5784612930918301612721565b6020830152604081015167ffffffffffffffff811161019b5784612955918301612721565b604083015261296660608201612767565b606083015261297760808201612767565b608083015261298860a08201612767565b60a083015260c081015167ffffffffffffffff811161019b57846129ad918301612721565b60c083015260e081015167ffffffffffffffff811161019b57846129d2918301612721565b60e083015261010081015167ffffffffffffffff811161019b57846129f8918301612721565b61010083015261012081015167ffffffffffffffff811161019b5784612a1f918301612721565b61012083015261014081015167ffffffffffffffff811161019b5784612a46918301612721565b61014083015261016081015167ffffffffffffffff811161019b5784612a6d918301612721565b61016083015261018081015167ffffffffffffffff811161019b5784612a94918301612721565b6101808301526101a081015167ffffffffffffffff811161019b5784612abb918301612721565b6101a08301526101c08101519067ffffffffffffffff821161019b57612ae391859101612721565b6101c08201526040840152606081015167ffffffffffffffff811161019b57608092612b10918301612721565b6060840152015160808201525f6127f3565b6040513d5f823e3d90fd5b73ffffffffffffffffffffffffffffffffffffffff168015612bee5773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b91612c2483612e6d565b929015612cad57827f3fa039a23466a52e08acb25376ac7d81de184fa6549ffffb2fc920c47cb623ed949260ff612ca89373ffffffffffffffffffffffffffffffffffffffff965f525f602052612c7d60405f20612658565b936040519788971687526020870152166040850152606084015260a0608084015260a083019061215f565b0390a1565b73ffffffffffffffffffffffffffffffffffffffff847f4c547670000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303612d2f57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b8151919060418303612d8b57612d849250602082015190606060408401519301515f1a90613042565b9192909190565b50505f9160029190565b6004811015612e405780612da7575050565b60018103612dd7577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b60028103612e0b57507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600314612e155750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff600154166040517fa8af4ff500000000000000000000000000000000000000000000000000000000815260408160248173ffffffffffffffffffffffffffffffffffffffff8716958660048301525afa908115612b22575f905f92612faa575b5015612fa157815f52600360205260405f209160405193612f0285612047565b60018454948587520154806020870152838515159182612f97575b505015612f45575050505f525f602052612f3a60405f20546122bb565b156128285751600191565b909250612f5491949350612794565b93819291612f63575b50509190565b60019060405192612f7384612047565b868452602084019182525f52600360205260405f2092518355519101555f80612f5d565b149050835f612f1d565b5050505f905f90565b9150506040813d604011612fe3575b81612fc660409383612063565b8101031261019b576020612fd982612714565b910151905f612ee2565b3d9150612fb9565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561301a57565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116130c6579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612b22575f5173ffffffffffffffffffffffffffffffffffffffff8116156130bc57905f905f90565b505f906001905f90565b5050505f9160039190565b6130d9612474565b80519081156130e9576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1005480156131165790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b613143612585565b8051908115613153576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015480156131165790565b906131bd575080511561319557602081519101fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580613210575b6131ce575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156131c656fea164736f6c634300081c000a", + "sourceMap": "2021:11391:71:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4107:92:35;4129:17;;:::i;:::-;4148:20;;:::i;:::-;2021:11391:71;;4107:92:35;;;;2021:11391:71;1959:95:35;2021:11391:71;;;1959:95:35;;2021:11391:71;1959:95:35;;;2021:11391:71;4170:13:35;1959:95;;;2021:11391:71;4193:4:35;1959:95;;;2021:11391:71;1959:95:35;4107:92;;;;;;:::i;:::-;2021:11391:71;4097:103:35;;2021:11391:71;;;;;;;;;;;;;;;;;;;;;2357:1:30;2021:11391:71;;:::i;:::-;2303:62:30;;:::i;:::-;2357:1;:::i;:::-;2021:11391:71;;;;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;4428:16;2021:11391;;:::i;:::-;;;4407:10;;4428:16;:::i;2021:11391::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1280:65:30;2021:11391:71;;;;;;;;;;;;;;;;;;;2692:64:35;2021:11391:71;5647:18:35;:43;;;2021:11391:71;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;5835:13:35;2021:11391:71;;;;5870:4:35;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5647:43:35;2021:11391:71;5669:16:35;2021:11391:71;5669:21:35;5647:43;;2021:11391:71;;;;;;;;;;;;;;:::i;:::-;;;;3077:64;2021:11391;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;3007:23;2021:11391;;;;;;;;;;;;;;;;;;;;;;2361:90;2021:11391;;;;;;;;;;;;;;2303:62:30;;:::i;:::-;2021:11391:71;;1280:65:30;2021:11391:71;;;;1280:65:30;2021:11391:71;;3975:40:30;;;;2021:11391:71;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;2303:62:30;;:::i;:::-;2021:11391:71;;;;;;;;;;;;:::i;:::-;12350:59;2021:11391;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;12537:38;;2021:11391;12537:38;;2021:11391;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;5090:6:32;2021:11391:71;5081:4:32;5073:23;5069:145;;2021:11391:71;;;811:66:41;2021:11391:71;;;5069:145:32;5174:29;2021:11391:71;5174:29:32;2021:11391:71;;5174:29:32;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2303:62:30;;:::i;:::-;11561:28:71;;2021:11391;;11627:25;;2021:11391;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;11972:74;;;;2021:11391;11972:74;;2021:11391;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12062:34;;2021:11391;12062:34;;2021:11391;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;:::i;:::-;;4667:6:32;2021:11391:71;4658:4:32;;4650:23;:120;;;;;2021:11391:71;4633:251:32;;;2303:62:30;;:::i;:::-;2021:11391:71;;;;;;;6131:52:32;;2021:11391:71;6131:52:32;2021:11391:71;6131:52:32;;;;2021:11391:71;;6131:52:32;;;2021:11391:71;-1:-1:-1;6127:437:32;;6493:60;;2021:11391:71;6493:60:32;2021:11391:71;;;;6493:60:32;6127:437;6225:40;811:66:41;6225:40:32;;;6221:120;;1748:29:41;;;:34;1744:119;;2021:11391:71;;811:66:41;2021:11391:71;;;811:66:41;2021:11391:71;2407:36:41;2021:11391:71;2407:36:41;;2021:11391:71;;2458:15:41;:11;;2021:11391:71;4065:25:48;;2021:11391:71;4107:55:48;4065:25;;;;;;;2021:11391:71;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;4107:55:48;:::i;2021:11391:71:-;;;4107:55:48;:::i;2454:148:41:-;6163:9;;;6159:70;;2021:11391:71;6159:70:41;6199:19;2021:11391:71;6199:19:41;2021:11391:71;;6199:19:41;1744:119;1805:47;2021:11391:71;1805:47:41;2021:11391:71;;;;1805:47:41;6221:120:32;6292:34;2021:11391:71;6292:34:32;2021:11391:71;;;;6292:34:32;6131:52;;;;2021:11391:71;6131:52:32;;2021:11391:71;6131:52:32;;;;;;2021:11391:71;6131:52:32;;;:::i;:::-;;;2021:11391:71;;;;;6131:52:32;;;;;;;-1:-1:-1;6131:52:32;;4650:120;2021:11391:71;;;811:66:41;2021:11391:71;;4728:42:32;;4650:120;;;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;3147:66:31;2021:11391:71;;;;;;4301:16:31;2021:11391:71;;;;4724:16:31;;:34;;;;2021:11391:71;4803:1:31;4788:16;:50;;;;2021:11391:71;4853:13:31;:30;;;;2021:11391:71;4849:91:31;;;2021:11391:71;;4803:1:31;2021:11391:71;6959:1:31;2021:11391:71;;;3147:66:31;2021:11391:71;4977:67:31;;2021:11391:71;6891:76:31;;;:::i;:::-;;;:::i;6959:1::-;6891:76;;:::i;:::-;2021:11391:71;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;:::i;:::-;4803:1:31;2021:11391:71;;;;;;;6891:76:31;;:::i;:::-;;;:::i;:::-;2021:11391:71;;;;;;;;;3652:7:35;2021:11391:71;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3652:7:35;2021:11391:71;;;;;;;;;;3676:10:35;2021:11391:71;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3676:10:35;2021:11391:71;;;2692:64:35;2021:11391:71;;3788:16:35;2021:11391:71;3897:23;;2021:11391;;;;4803:1:31;2021:11391:71;;;4803:1:31;2021:11391:71;;3986:22;;2021:11391;3986:22;;5064:101:31;;2021:11391:71;5064:101:31;2021:11391:71;5140:14:31;2021:11391:71;;3147:66:31;2021:11391:71;;3147:66:31;2021:11391:71;4803:1:31;2021:11391:71;;5140:14:31;2021:11391:71;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;3676:10:35;2021:11391:71;;;;;;;;;;;;;;;4803:1:31;2021:11391:71;;;;;;;;;;;;;;3676:10:35;2021:11391:71;;;;;;;;;3676:10:35;2021:11391:71;;;;;;;;;;;;;;;;4803:1:31;2021:11391:71;;;;;;;;;;;;;;;;3676:10:35;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;3652:7:35;2021:11391:71;;;;;;;;;;;;;;;;4803:1:31;2021:11391:71;;;;;;;;;;;;;;3652:7:35;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;4803:1:31;2021:11391:71;;;;;;;;;;;;;;;;3652:7:35;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;4977:67:31;2021:11391:71;;;;3147:66:31;2021:11391:71;4977:67:31;;;4849:91;4906:23;2021:11391:71;4906:23:31;2021:11391:71;;4906:23:31;4853:30;4870:13;;;4853:30;;;4788:50;4816:4;4808:25;:30;;-1:-1:-1;4788:50:31;;4724:34;;;-1:-1:-1;4724:34:31;;2021:11391:71;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;3871:27:55;2021:11391:71;;3927:8:55;2021:11391:71;;;;;;:::i;:::-;4772:51;;4752:72;4772:51;;;;;:::i;:::-;4752:72;:::i;:::-;2021:11391;;;;:::i;:::-;3871:27:55;;:::i;:::-;3927:8;;;;;:::i;:::-;2021:11391:71;;;;;;;4943:6;2021:11391;;;;;;4979:22;;;2021:11391;;;;;;4943:6;2021:11391;;;;;;;;;;;;;;;5199:16;2021:11391;;;;5199:16;:::i;2021:11391::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;:::o;:::-;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;:::o;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;:::o;10512:815::-;10793:29;;;:34;;;;10849:35;;;;;10906;;;;10963;;;;;11020;;;;11118:40;;;;11180:34;;;;2021:11391;;;11236:42;;;2021:11391;;;;10793:29;2021:11391;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;10732:578;;10512:815;:::o;2021:11391::-;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;:::o;:::-;;;;;;;;;;;;;:::i;:::-;;;;;;;:::o;:::-;;;-1:-1:-1;2021:11391:71;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;:::o;12855:131::-;3993:249:56;12855:131:71;4129:17:35;;:::i;:::-;4148:20;;:::i;:::-;2021:11391:71;;4107:92:35;;;;2021:11391:71;1959:95:35;2021:11391:71;;;1959:95:35;;2021:11391:71;1959:95:35;;;2021:11391:71;4170:13:35;1959:95;;;2021:11391:71;4193:4:35;1959:95;;;2021:11391:71;1959:95:35;4107:92;;;;;;:::i;:::-;2021:11391:71;4097:103:35;;3993:249:56;2021:11391:71;3993:249:56;;;;;;;;;;;;;;12855:131:71;:::o;13032:229::-;;2021:11391;;13172:81;2021:11391;13172:81;;;2021:11391;2361:90;2021:11391;;;;;;;;;;;;;;;;13172:81;;;;;;:::i;2021:11391::-;;;;-1:-1:-1;2021:11391:71;6311:7:35;2021:11391:71;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;6311:7:35;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;6696:10:35;2021:11391:71;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;6696:10:35;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;2021:11391:71;;;;;-1:-1:-1;2021:11391:71;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;:::o;6750:906::-;-1:-1:-1;2021:11391:71;6999:60;2021:11391;7022:8;2021:11391;;;;;6999:60;;;;;2021:11391;6999:60;;2021:11391;6999:60;;;2021:11391;6999:60;;;;;;;-1:-1:-1;6999:60:71;;;6750:906;2021:11391;;;;7226:86;;7346:41;;;:::i;:::-;2021:11391;-1:-1:-1;2021:11391:71;-1:-1:-1;2021:11391:71;;;;-1:-1:-1;2021:11391:71;;;:::i;:::-;7472:133;;7615:34;-1:-1:-1;7615:34:71;-1:-1:-1;6750:906:71;:::o;7472:133::-;7022:8;;7569:25::o;7226:86::-;7267:34;-1:-1:-1;7267:34:71;-1:-1:-1;7267:34:71;:::o;6999:60::-;;;;;-1:-1:-1;6999:60:71;;;;;;:::i;:::-;;;2021:11391;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;6999:60;;;;2021:11391;;;-1:-1:-1;2021:11391:71;;;;;3405:215:30;2021:11391:71;;3489:22:30;;3485:91;;2021:11391:71;1280:65:30;2021:11391:71;;;;;;1280:65:30;2021:11391:71;;3975:40:30;3509:1;3975:40;;3405:215::o;3485:91::-;3534:31;3509:1;3534:31;3509:1;3534:31;2021:11391:71;;3509:1:30;3534:31;5648:1056:71;;5900:34;;;:::i;:::-;2021:11391;;;;;;6608:89;2021:11391;;;;;;;6552:17;2021:11391;6552:17;2021:11391;;;;6552:17;2021:11391;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;6608:89;;;5648:1056::o;2021:11391::-;;;;;;;;;;;;2658:162:30;2021:11391:71;1280:65:30;2021:11391:71;;966:10:33;2717:23:30;2713:101;;2658:162::o;2713:101::-;2763:40;-1:-1:-1;2763:40:30;966:10:33;2763:40:30;2021:11391:71;;-1:-1:-1;2763:40:30;2129:778:55;2021:11391:71;;;2129:778:55;2319:2;2299:22;;2319:2;;2751:25;2535:196;;;;;;;;;;;;;;;-1:-1:-1;2535:196:55;2751:25;;:::i;:::-;2744:32;;;;;:::o;2295:606::-;2807:83;;2823:1;2807:83;2827:35;2807:83;;:::o;7280:532::-;2021:11391:71;;;;;;7366:29:55;;;7411:7;;:::o;7362:444::-;2021:11391:71;7462:38:55;;2021:11391:71;;7523:23:55;7375:20;7523:23;2021:11391:71;7375:20:55;7523:23;7458:348;7576:35;7567:44;;7576:35;;7634:46;;7375:20;7634:46;2021:11391:71;;;7375:20:55;7634:46;7563:243;7710:30;7701:39;7697:109;;7563:243;7280:532::o;7697:109::-;7763:32;7375:20;7763:32;2021:11391:71;;;7375:20:55;7763:32;2021:11391:71;;7375:20:55;2021:11391:71;;;;;7375:20:55;2021:11391:71;8892:1574;2021:11391;9107:8;2021:11391;;;;;9084:66;;2021:11391;;9084:66;2021:11391;;;;9084:66;;;;;2021:11391;9084:66;;;;;;;-1:-1:-1;;;9084:66:71;;;8892:1574;9164:8;;9160:73;;2021:11391;-1:-1:-1;2021:11391:71;9338:15;2021:11391;;;-1:-1:-1;2021:11391:71;;;;;;;;:::i;:::-;9107:8;2021:11391;;;;;;;;;;;;;9536:21;;;;:54;;;;8892:1574;-1:-1:-1;;9532:928:71;;;2021:11391;;;-1:-1:-1;2021:11391:71;-1:-1:-1;2021:11391:71;;;;-1:-1:-1;2021:11391:71;;;:::i;:::-;9709:64;2021:11391;;;9107:8;;9793:32::o;9532:928::-;10170:27;;;;;;;;;:::i;:::-;10212:195;;;;;;9532:928;10421:28;;;;:::o;10212:195::-;9107:8;2021:11391;;;;;;;:::i;:::-;;;;;10330:62;;2021:11391;;;-1:-1:-1;2021:11391:71;9338:15;2021:11391;;;-1:-1:-1;2021:11391:71;;;;;;;;;10212:195;;;;9536:54;9561:29;;-1:-1:-1;9536:54:71;;;;9160:73;9188:34;;;-1:-1:-1;9188:34:71;-1:-1:-1;9188:34:71;:::o;9084:66::-;;;;2021:11391;9084:66;;2021:11391;9084:66;;;;;;2021:11391;9084:66;;;:::i;:::-;;;2021:11391;;;;;;;;:::i;:::-;;;;9084:66;;;;;;;-1:-1:-1;9084:66:71;;7082:141:31;2021:11391:71;3147:66:31;2021:11391:71;;;;7148:18:31;7144:73;;7082:141::o;7144:73::-;7189:17;-1:-1:-1;7189:17:31;;-1:-1:-1;7189:17:31;5203:1551:55;;;6283:66;6270:79;;6266:164;;2021:11391:71;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;6541:24:55;;;;;;;;;-1:-1:-1;6541:24:55;2021:11391:71;;;6579:20:55;6575:113;;6698:49;-1:-1:-1;6698:49:55;-1:-1:-1;5203:1551:55;:::o;6575:113::-;6615:62;-1:-1:-1;6615:62:55;6541:24;6615:62;-1:-1:-1;6615:62:55;:::o;6266:164::-;6365:54;;;6381:1;6365:54;6385:30;6365:54;;:::o;6928:687:35:-;2021:11391:71;;:::i;:::-;;;;7100:22:35;;;;2021:11391:71;;7145:22:35;7138:29;:::o;7096:513::-;-1:-1:-1;;2692:64:35;2021:11391:71;7473:15:35;;;;7508:17;:::o;7469:130::-;7564:20;7571:13;7564:20;:::o;7836:723::-;2021:11391:71;;:::i;:::-;;;;8017:25:35;;;;2021:11391:71;;8065:25:35;8058:32;:::o;8013:540::-;-1:-1:-1;;8377:16:35;2021:11391:71;8411:18:35;;;;8449:20;:::o;4437:582:48:-;;4609:8;;-1:-1:-1;2021:11391:71;;5690:21:48;:17;;5815:105;;;;;;5686:301;5957:19;5710:1;5957:19;;5710:1;5957:19;4605:408;2021:11391:71;;4857:22:48;:49;;;4605:408;4853:119;;4985:17;;:::o;4853:119::-;2021:11391:71;4933:24:48;;4878:1;4933:24;2021:11391:71;4933:24:48;2021:11391:71;;4878:1:48;4933:24;4857:49;4883:18;;;:23;4857:49;", + "linkReferences": {}, + "immutableReferences": { + "48412": [ + { + "start": 2616, + "length": 32 + }, + { + "start": 4217, + "length": 32 + } + ] + } + }, + "methodIdentifiers": { + "UPGRADE_INTERFACE_VERSION()": "ad3cb1cc", + "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH()": "73016923", + "addWorkloadToPolicy(bytes32,string,string[])": "4f3a415a", + "computeStructHash(uint8,bytes32,uint256)": "7dec71a9", + "domainSeparator()": "f698da25", + "eip712Domain()": "84b0196e", + "getHashedTypeDataV4(bytes32)": "6931164e", + "getWorkloadMetadata(bytes32)": "abd45d21", + "initialize(address,address)": "485cc955", + "isAllowedPolicy(address)": "d2753561", + "nonces(address)": "7ecebe00", + "owner()": "8da5cb5b", + "permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)": "2dd8abfe", + "proxiableUUID()": "52d1902d", + "registry()": "7b103999", + "removeWorkloadFromPolicy(bytes32)": "5c40e542", + "renounceOwnership()": "715018a6", + "transferOwnership(address)": "f2fde38b", + "upgradeToAndCall(address,bytes)": "4f1ef286", + "verifyBlockBuilderProof(uint8,bytes32)": "b33d59da", + "workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))": "4d37fc7a" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ECDSAInvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"ECDSAInvalidSignatureLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"ECDSAInvalidSignatureS\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyCommitHash\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptySourceLocators\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"provided\",\"type\":\"uint256\"}],\"name\":\"InvalidNonce\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRegistry\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UUPSUnauthorizedCallContext\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"slot\",\"type\":\"bytes32\"}],\"name\":\"UUPSUnsupportedProxiableUUID\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"}],\"name\":\"UnauthorizedBlockBuilder\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkloadAlreadyInPolicy\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkloadNotInPolicy\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"workloadId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"blockContentHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"commitHash\",\"type\":\"string\"}],\"name\":\"BlockBuilderProofVerified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"EIP712DomainChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"registry\",\"type\":\"address\"}],\"name\":\"RegistrySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workloadId\",\"type\":\"bytes32\"}],\"name\":\"WorkloadAddedToPolicy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workloadId\",\"type\":\"bytes32\"}],\"name\":\"WorkloadRemovedFromPolicy\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"UPGRADE_INTERFACE_VERSION\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"WorkloadId\",\"name\":\"workloadId\",\"type\":\"bytes32\"},{\"internalType\":\"string\",\"name\":\"commitHash\",\"type\":\"string\"},{\"internalType\":\"string[]\",\"name\":\"sourceLocators\",\"type\":\"string[]\"}],\"name\":\"addWorkloadToPolicy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"blockContentHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"name\":\"computeStructHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"domainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip712Domain\",\"outputs\":[{\"internalType\":\"bytes1\",\"name\":\"fields\",\"type\":\"bytes1\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"verifyingContract\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"salt\",\"type\":\"bytes32\"},{\"internalType\":\"uint256[]\",\"name\":\"extensions\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"structHash\",\"type\":\"bytes32\"}],\"name\":\"getHashedTypeDataV4\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"WorkloadId\",\"name\":\"workloadId\",\"type\":\"bytes32\"}],\"name\":\"getWorkloadMetadata\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"commitHash\",\"type\":\"string\"},{\"internalType\":\"string[]\",\"name\":\"sourceLocators\",\"type\":\"string[]\"}],\"internalType\":\"struct IBlockBuilderPolicy.WorkloadMetadata\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_initialOwner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_registry\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"teeAddress\",\"type\":\"address\"}],\"name\":\"isAllowedPolicy\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"},{\"internalType\":\"WorkloadId\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"teeAddress\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"permitNonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"blockContentHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"eip712Sig\",\"type\":\"bytes\"}],\"name\":\"permitVerifyBlockBuilderProof\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"registry\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"WorkloadId\",\"name\":\"workloadId\",\"type\":\"bytes32\"}],\"name\":\"removeWorkloadFromPolicy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"blockContentHash\",\"type\":\"bytes32\"}],\"name\":\"verifyBlockBuilderProof\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"isValid\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"rawQuote\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes16\",\"name\":\"teeTcbSvn\",\"type\":\"bytes16\"},{\"internalType\":\"bytes\",\"name\":\"mrSeam\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"mrsignerSeam\",\"type\":\"bytes\"},{\"internalType\":\"bytes8\",\"name\":\"seamAttributes\",\"type\":\"bytes8\"},{\"internalType\":\"bytes8\",\"name\":\"tdAttributes\",\"type\":\"bytes8\"},{\"internalType\":\"bytes8\",\"name\":\"xFAM\",\"type\":\"bytes8\"},{\"internalType\":\"bytes\",\"name\":\"mrTd\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"mrConfigId\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"mrOwner\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"mrOwnerConfig\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rtMr0\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rtMr1\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rtMr2\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rtMr3\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"reportData\",\"type\":\"bytes\"}],\"internalType\":\"struct TD10ReportBody\",\"name\":\"parsedReportBody\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"extendedRegistrationData\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"quoteHash\",\"type\":\"bytes32\"}],\"internalType\":\"struct IFlashtestationRegistry.RegisteredTEE\",\"name\":\"registration\",\"type\":\"tuple\"}],\"name\":\"workloadIdForTDRegistration\",\"outputs\":[{\"internalType\":\"WorkloadId\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ECDSAInvalidSignature()\":[{\"details\":\"The signature derives the `address(0)`.\"}],\"ECDSAInvalidSignatureLength(uint256)\":[{\"details\":\"The signature has an invalid length.\"}],\"ECDSAInvalidSignatureS(bytes32)\":[{\"details\":\"The signature has an S value that is in the upper half order.\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"UUPSUnauthorizedCallContext()\":[{\"details\":\"The call is from an unauthorized context.\"}],\"UUPSUnsupportedProxiableUUID(bytes32)\":[{\"details\":\"The storage `slot` is unsupported as a UUID.\"}]},\"events\":{\"BlockBuilderProofVerified(address,bytes32,uint8,bytes32,string)\":{\"params\":{\"blockContentHash\":\"The hash of the block content\",\"caller\":\"The address that called the verification function (TEE address)\",\"commitHash\":\"The git commit hash associated with the workload\",\"version\":\"The flashtestation protocol version used\",\"workloadId\":\"The workload identifier of the TEE\"}},\"EIP712DomainChanged()\":{\"details\":\"MAY be emitted to signal that the domain could have changed.\"},\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"RegistrySet(address)\":{\"params\":{\"registry\":\"The address of the registry\"}},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"},\"WorkloadAddedToPolicy(bytes32)\":{\"params\":{\"workloadId\":\"The workload identifier\"}},\"WorkloadRemovedFromPolicy(bytes32)\":{\"params\":{\"workloadId\":\"The workload identifier\"}}},\"kind\":\"dev\",\"methods\":{\"addWorkloadToPolicy(bytes32,string,string[])\":{\"details\":\"The commitHash solves the following problem; The only way for a smart contract like BlockBuilderPolicy to verify that a TEE (identified by its workloadId) is running a specific piece of code (for instance, op-rbuilder) is to reproducibly build that workload onchain. This is prohibitively expensive, so instead we rely on a permissioned multisig (the owner of this contract) to add a commit hash to the policy whenever it adds a new workloadId. We're already relying on the owner to verify that the workloadId is valid, so we can also assume the owner will not add a commit hash that is not associated with the workloadId. If the owner did act maliciously, this can easily be determined offchain by an honest actor building the TEE image from the given commit hash, deriving the image's workloadId, and then comparing it to the workloadId stored on the policy that is associated with the commit hash. If the workloadId is different, this can be used to prove that the owner acted maliciously. In the honest case, this Policy serves as a source of truth for which source code of build software (i.e. the commit hash) is used to build the TEE image identified by the workloadId.\",\"params\":{\"commitHash\":\"The 40-character hexadecimal commit hash of the git repository whose source code is used to build the TEE image identified by the workloadId\",\"sourceLocators\":\"An array of URIs pointing to the source code\",\"workloadId\":\"The workload identifier\"}},\"computeStructHash(uint8,bytes32,uint256)\":{\"params\":{\"blockContentHash\":\"The hash of the block content\",\"nonce\":\"The nonce to use for the EIP-712 signature\",\"version\":\"The version of the flashtestation's protocol\"},\"returns\":{\"_0\":\"The struct hash for the EIP-712 signature\"}},\"domainSeparator()\":{\"details\":\"This is useful for when both onchain and offchain users want to compute the domain separator for the EIP-712 signature, and then use it to verify the signature\",\"returns\":{\"_0\":\"The domain separator for the EIP-712 signature\"}},\"eip712Domain()\":{\"details\":\"returns the fields and values that describe the domain separator used by this contract for EIP-712 signature.\"},\"getHashedTypeDataV4(bytes32)\":{\"params\":{\"structHash\":\"The struct hash for the EIP-712 signature\"},\"returns\":{\"_0\":\"The digest for the EIP-712 signature\"}},\"getWorkloadMetadata(bytes32)\":{\"details\":\"This is only updateable by governance (i.e. the owner) of the Policy contract Adding and removing a workload is O(1)\",\"params\":{\"workloadId\":\"The workload identifier to query\"},\"returns\":{\"_0\":\"The metadata associated with the workload\"}},\"initialize(address,address)\":{\"params\":{\"_initialOwner\":\"The address of the initial owner of the contract\",\"_registry\":\"The address of the registry contract\"}},\"isAllowedPolicy(address)\":{\"params\":{\"teeAddress\":\"The TEE-controlled address\"},\"returns\":{\"_1\":\"The workloadId of the TEE that is using an approved workload in the policy, or 0 if the TEE is not using an approved workload in the policy\",\"allowed\":\"True if the TEE is using an approved workload in the policy\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)\":{\"details\":\"This function is useful if you do not want to deal with the operational difficulties of keeping your TEE-controlled addresses funded, but note that because of the larger number of function arguments, will cost more gas than the non-EIP-712 verifyBlockBuilderProof function\",\"params\":{\"blockContentHash\":\"The hash of the block content\",\"eip712Sig\":\"The EIP-712 signature of the verification message\",\"nonce\":\"The nonce to use for the EIP-712 signature\",\"version\":\"The version of the flashtestation's protocol used to generate the block builder proof\"}},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"removeWorkloadFromPolicy(bytes32)\":{\"params\":{\"workloadId\":\"The workload identifier\"}},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"},\"verifyBlockBuilderProof(uint8,bytes32)\":{\"details\":\"If you do not want to deal with the operational difficulties of keeping your TEE-controlled addresses funded, you can use the permitVerifyBlockBuilderProof function instead which costs more gas, but allows any EOA to submit a block builder proof on behalf of a TEE\",\"params\":{\"blockContentHash\":\"The hash of the block content\",\"version\":\"The version of the flashtestation's protocol used to generate the block builder proof\"}},\"workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))\":{\"details\":\"Think of the workload identifier as the version of the application for governance. The workloadId verifiably maps to a version of source code that builds the TEE VM image\",\"params\":{\"registration\":\"The registration data from a TEE device\"},\"returns\":{\"_0\":\"workloadId The computed workload identifier\"}}},\"stateVariables\":{\"__gap\":{\"details\":\"Storage gap to allow for future storage variable additions in upgradesThis reserves 46 storage slots (out of 50 total - 4 used for approvedWorkloads, registry, nonces, and cachedWorkloads)\"},\"approvedWorkloads\":{\"details\":\"This is only updateable by governance (i.e. the owner) of the Policy contract Adding and removing a workload is O(1). This means the critical `_cachedIsAllowedPolicy` function is O(1) since we can directly check if a workloadId exists in the mapping\"},\"cachedWorkloads\":{\"details\":\"Maps teeAddress to cached workload information for gas optimization\"}},\"title\":\"BlockBuilderPolicy\",\"version\":1},\"userdoc\":{\"errors\":{\"EmptyCommitHash()\":[{\"notice\":\"Emitted when the commit hash is empty\"}],\"EmptySourceLocators()\":[{\"notice\":\"Emitted when the source locators array is empty\"}],\"InvalidNonce(uint256,uint256)\":[{\"notice\":\"Emitted when the nonce is invalid\"}],\"InvalidRegistry()\":[{\"notice\":\"Emitted when the registry is the 0x0 address\"}],\"UnauthorizedBlockBuilder(address)\":[{\"notice\":\"Emitted when the address is not in the approvedWorkloads mapping\"}],\"WorkloadAlreadyInPolicy()\":[{\"notice\":\"Emitted when a workload to be added is already in the policy\"}],\"WorkloadNotInPolicy()\":[{\"notice\":\"Emitted when a workload to be removed is not in the policy\"}]},\"events\":{\"BlockBuilderProofVerified(address,bytes32,uint8,bytes32,string)\":{\"notice\":\"Emitted when a block builder proof is successfully verified\"},\"RegistrySet(address)\":{\"notice\":\"Emitted when the registry is set in the initializer\"},\"WorkloadAddedToPolicy(bytes32)\":{\"notice\":\"Emitted when a workload is added to the policy\"},\"WorkloadRemovedFromPolicy(bytes32)\":{\"notice\":\"Emitted when a workload is removed from the policy\"}},\"kind\":\"user\",\"methods\":{\"VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH()\":{\"notice\":\"EIP-712 Typehash, used in the permitVerifyBlockBuilderProof function\"},\"addWorkloadToPolicy(bytes32,string,string[])\":{\"notice\":\"Add a workload to a policy (governance only)Only the owner of this contract can add workloads to the policy and it is the responsibility of the owner to ensure that the workload is valid otherwise the address associated with this workload has full power to do anything who's authorization is based on this policy\"},\"computeStructHash(uint8,bytes32,uint256)\":{\"notice\":\"Computes the struct hash for the EIP-712 signature\"},\"domainSeparator()\":{\"notice\":\"Returns the domain separator for the EIP-712 signature\"},\"getHashedTypeDataV4(bytes32)\":{\"notice\":\"Computes the digest for the EIP-712 signature\"},\"getWorkloadMetadata(bytes32)\":{\"notice\":\"Mapping from workloadId to its metadata (commit hash and source locators)\"},\"initialize(address,address)\":{\"notice\":\"Initializer to set the FlashtestationRegistry contract which verifies TEE quotes and the initial owner of the contract\"},\"isAllowedPolicy(address)\":{\"notice\":\"Check if this TEE-controlled address has registered a valid TEE workload with the registry, and if the workload is approved under this policy\"},\"nonces(address)\":{\"notice\":\"Tracks nonces for EIP-712 signatures to prevent replay attacks\"},\"permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)\":{\"notice\":\"Verify a block builder proof with a Flashtestation Transaction using EIP-712 signaturesThis function allows any EOA to submit a block builder proof on behalf of a TEEThe TEE must sign a proper EIP-712-formatted message, and the signer must match a TEE-controlled address whose associated workload is approved under this policy\"},\"registry()\":{\"notice\":\"Address of the FlashtestationRegistry contract that verifies TEE quotes\"},\"removeWorkloadFromPolicy(bytes32)\":{\"notice\":\"Remove a workload from a policy (governance only)\"},\"verifyBlockBuilderProof(uint8,bytes32)\":{\"notice\":\"Verify a block builder proof with a Flashtestation TransactionThis function will only succeed if the caller is a registered TEE-controlled address from an attested TEE and the TEE is running an approved block builder workload (see `addWorkloadToPolicy`)The blockContentHash is a keccak256 hash of a subset of the block header, as specified by the version. See the [flashtestations spec](https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md) for more details\"},\"workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))\":{\"notice\":\"Application specific mapping of registration data to a workload identifier\"}},\"notice\":\"A reference implementation of a policy contract for the FlashtestationRegistryA Policy is a collection of related WorkloadIds. A Policy exists to specify which WorkloadIds are valid for a particular purpose, in this case for remote block building. It also exists to handle the problem that TEE workloads will need to change multiple times a year, either because of Intel DCAP Endorsement updates or updates to the TEE configuration (and thus its WorkloadId). Without Policies, consumer contracts that makes use of Flashtestations would need to be updated every time a TEE workload changes, which is a costly and error-prone process. Instead, consumer contracts need only check if a TEE address is allowed under any workload in a Policy, and the FlashtestationRegistry will handle the rest\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/BlockBuilderPolicy.sol\":\"BlockBuilderPolicy\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":44444444},\"remappings\":[\":@automata-network/on-chain-pccs/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":@sp1-contracts/=lib/automata-dcap-attestation/evm/lib/sp1-contracts/contracts/src/\",\":automata-dcap-attestation/=lib/automata-dcap-attestation/evm/\",\":automata-on-chain-pccs/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/\",\":ds-test/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\",\":risc0-ethereum/=lib/automata-dcap-attestation/evm/lib/risc0-ethereum/\",\":risc0/=lib/automata-dcap-attestation/evm/lib/risc0-ethereum/contracts/src/\",\":solady/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/\",\":solmate/=lib/solmate/\",\":sp1-contracts/=lib/automata-dcap-attestation/evm/lib/sp1-contracts/contracts/\"],\"viaIR\":true},\"sources\":{\"lib/automata-dcap-attestation/evm/contracts/types/CommonStruct.sol\":{\"keccak256\":\"0xc1968270cffd0b96268c9695d81647e707a6e1e7c1b851c1d6b1f9af6a88d4f2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d4e56c233fbb8e448961f5b847b92a8e725cc56ed9e0ad764d790f1cd4896245\",\"dweb:/ipfs/QmVoUKR62HT6DKaR3rNnNr5w1CmDSbrDriX41ycTAzKodz\"]},\"lib/automata-dcap-attestation/evm/contracts/types/Constants.sol\":{\"keccak256\":\"0xcfaac1552ee9277f6b0e4e38bdee7d2e07d1bdfe188c294c5c3f37f152b1d099\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://89a8a55be811abd8cc8af234d489c64a84f08023061ade4e67dbbaf11468cdc4\",\"dweb:/ipfs/QmR1TWqBcAEFbtteg2xjVG4VJXeFVWoAQidR3jyWuaGpUf\"]},\"lib/automata-dcap-attestation/evm/contracts/types/V4Structs.sol\":{\"keccak256\":\"0x39726e2cc0de02e8b34c0da2e6885fa9d18b7905970b9745e5310fb12cde9dd4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ddf5ab4f4188de35bb9c03137b207d56d69629283b9cb683eebdfafe7ff015ee\",\"dweb:/ipfs/QmTmYBmBEUbxMESjeaF6cSPMSK5P8hv16tDXTk78LXiKpz\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/DateTimeLib.sol\":{\"keccak256\":\"0x3945a985a34bc905beb149f7196f1dba021a60abc2f178ab2eb3a28ed4f94741\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://828a14ebcf4bd777c73ecbedd726819ac37c3b2e4fab7c8fe263f860db725f72\",\"dweb:/ipfs/QmNW32zDLCVjbGecmZvxAaSdmDudHQSsdrFfpMYX6baGAv\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/JSONParserLib.sol\":{\"keccak256\":\"0x8d4aeacf0459b28b40b63c9d331dc95cf7d751ca9961c83c89c1ad73b9b3cd81\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://42cc6de802e8c17668ae82518caa4c053e82f1884329d1d924fa7b9fccf5041f\",\"dweb:/ipfs/QmPGLfqWXDCjjcZ2VEG8kRwasGFxR4u62RpLLBuLqXy9wP\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/LibString.sol\":{\"keccak256\":\"0x74ec81acbea6db4afe149ab51e26961bcb801af42f7af98242be71b866066200\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://16bb49824fcfa9667aeed0eb515fdefda1016225085cf78ed852078c67168229\",\"dweb:/ipfs/QmZ59xrx5QLSx5N5CiTLrfwsPKR7kpK4RRpiEWSMEpvDzQ\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/helpers/FmspcTcbHelper.sol\":{\"keccak256\":\"0x3041391dfb9e2dcb6ed27e9b89445a9206c5348da3f2753785c22c832878f226\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://883bd1cb7d425828b8b9c92052f16fbcc893e6cdd175e3616a99b857b723a808\",\"dweb:/ipfs/QmZWqGm2D9AFrxzAsad7xkLSKuH1NQb7VEdFKwJbumj8EU\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/helpers/X509Helper.sol\":{\"keccak256\":\"0x4b4a56e80792b64604975e52d1aad2e075bd49c94b36814aa6461fbd69b6a03e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7ea019efcc3904401a694b5704d7cb219b20f7cc11a5d4268b0015628fb71713\",\"dweb:/ipfs/QmezJCskd9hVoERyUNk6Y9qK8oQxmen5HqasNgz8diyk6V\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/Asn1Decode.sol\":{\"keccak256\":\"0xc7a6f959ce3d98244e28ff140c0f3eb5fae6349d93e250a49cbf9fdd0e7ac8e6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7c0760856218edc38ead344bd2587118a2a72a23d0d410e57c7e48251f313e3c\",\"dweb:/ipfs/QmdNu9g5GWgEuS1ShgCqHckE1cSjfpt9LJb2t1A2CKAFfB\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/BytesUtils.sol\":{\"keccak256\":\"0x9a9028cd3bc73fb8ff9404907c980df141fd06351f9ea79b32754d05cae25f85\",\"license\":\"BSD 2-Clause License\",\"urls\":[\"bzz-raw://eaf5b855f3013a40e5d6b62fd9a7b7077a06b1750fabc53b8514ba4cf0006bed\",\"dweb:/ipfs/QmXKHL2zH51od64bYa8pifwEQF8UzccPBE2x7K5uvq4JeQ\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/DateTimeUtils.sol\":{\"keccak256\":\"0xee25670c66115185f0f0ab6070ade945faff8f75b502b70c2597a02868a02a4f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5c08c26dbd2e3196b3e16f7b8d2daa29c472997a845d5dc731f3bf17b7784d02\",\"dweb:/ipfs/QmUMwg7SvYQpgwcnDkGM9vnau2SqoYzwsGcB8CM2MLZueP\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6\",\"dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08\",\"dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0x574a7451e42724f7de29e2855c392a8a5020acd695169466a18459467d719d63\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5bc189f63b639ee173dd7b6fecc39baf7113bf161776aea22b34c57fdd1872ec\",\"dweb:/ipfs/QmZAf2VtjDLRULqjJkde6LNsxAg12tUqpPqgUQQZbAjgtZ\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9\",\"dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardTransientUpgradeable.sol\":{\"keccak256\":\"0x391a52a14dfcbe1a9ca16f1c052481de32242cf45714d92dab81be2a987e4bba\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://248b69f99e0452696ce5a2c90aac5602f496e2a697dacd5510d050f0dc833a3c\",\"dweb:/ipfs/QmcYkMiFQhTs2AW5fmcV5a3XQAGdQBUz1Y2NQD4RvBrNTM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol\":{\"keccak256\":\"0x89374b2a634f0a9c08f5891b6ecce0179bc2e0577819c787ed3268ca428c2459\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f13d2572e5bdd55e483dfac069aac47603644071616a41fce699e94368e38c13\",\"dweb:/ipfs/QmfKeyNT6vyb99vJQatPZ88UyZgXNmAiHUXSWnaR1TPE11\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c\",\"dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol\":{\"keccak256\":\"0xfb223a85dd0b2175cfbbaa325a744e2cd74ecd17c3df2b77b0722f991d2725ee\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://84bf1dea0589ec49c8d15d559cc6d86ee493048a89b2d4adb60fbe705a3d89ae\",\"dweb:/ipfs/Qmd56n556d529wk2pRMhYhm5nhMDhviwereodDikjs68w1\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422\",\"dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a\",\"dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d\",\"dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x6d0ae6e206645341fd122d278c2cb643dea260c190531f2f3f6a0426e77b00c0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://032d1201d839435be2c85b72e33206b3ea980c569d6ebf7fa57d811ab580a82f\",\"dweb:/ipfs/QmeqQjAtMvdZT2tG7zm39itcRJkuwu8AEReK6WRnLJ18DD\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Panic.sol\":{\"keccak256\":\"0xf7fe324703a64fc51702311dc51562d5cb1497734f074e4f483bfb6717572d7a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c6a5ff4f9fd8649b7ee20800b7fa387d3465bd77cf20c2d1068cd5c98e1ed57a\",\"dweb:/ipfs/QmVSaVJf9FXFhdYEYeCEfjMVHrxDh5qL4CGkxdMWpQCrqG\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Strings.sol\":{\"keccak256\":\"0xad148d59f05165f9217d0a9e1ac8f772abb02ea6aaad8a756315c532bf79f9f4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://15e3599867c2182f5831e9268b274b2ef2047825837df6b4d81c9e89254b093e\",\"dweb:/ipfs/QmZbL7XAYr5RmaNaooPgZRmcDXaudfsYQfYD9y5iAECvpS\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/TransientSlot.sol\":{\"keccak256\":\"0xac673fa1e374d9e6107504af363333e3e5f6344d2e83faf57d9bfd41d77cc946\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5982478dbbb218e9dd5a6e83f5c0e8d1654ddf20178484b43ef21dd2246809de\",\"dweb:/ipfs/QmaB1hS68n2kG8vTbt7EPEzmrGhkUbfiFyykGGLsAr9X22\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol\":{\"keccak256\":\"0x69f54c02b7d81d505910ec198c11ed4c6a728418a868b906b4a0cf29946fda84\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8e25e4bdb7ae1f21d23bfee996e22736fc0ab44cfabedac82a757b1edc5623b9\",\"dweb:/ipfs/QmQdWQvB6JCP9ZMbzi8EvQ1PTETqkcTWrbcVurS7DKpa5n\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol\":{\"keccak256\":\"0x26670fef37d4adf55570ba78815eec5f31cb017e708f61886add4fc4da665631\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b16d45febff462bafd8a5669f904796a835baf607df58a8461916d3bf4f08c59\",\"dweb:/ipfs/QmU2eJFpjmT4vxeJWJyLeQb8Xht1kdB8Y6MKLDPFA9WPux\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0x1225214420c83ebcca88f2ae2b50f053aaa7df7bd684c3e878d334627f2edfc6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6c5fab4970634f9ab9a620983dc1c8a30153981a0b1a521666e269d0a11399d3\",\"dweb:/ipfs/QmVRnBC575MESGkEHndjujtR7qub2FzU9RWy9eKLp4hPZB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol\":{\"keccak256\":\"0x195533c86d0ef72bcc06456a4f66a9b941f38eb403739b00f21fd7c1abd1ae54\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b1d578337048cad08c1c03041cca5978eff5428aa130c781b271ad9e5566e1f8\",\"dweb:/ipfs/QmPFKL2r9CBsMwmUqqdcFPfHZB2qcs9g1HDrPxzWSxomvy\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xb1970fac7b64e6c09611e6691791e848d5e3fe410fa5899e7df2e0afd77a99e3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://db5fbb3dddd8b7047465b62575d96231ba8a2774d37fb4737fbf23340fabbb03\",\"dweb:/ipfs/QmVUSvooZKEdEdap619tcJjTLcAuH6QBdZqAzWwnAXZAWJ\"]},\"src/BlockBuilderPolicy.sol\":{\"keccak256\":\"0x7c3d3ba04441108b91651c8b9c4dee65b2c3bb7e71fc0a55f1d3bd08ad8635cb\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://48d897893c99de682f4a44c3158f7bdf489c5d8557800906142cc300e2318806\",\"dweb:/ipfs/QmSjnpDXmwHcHei6dX6gQzMWUz3cZFF2JfKFH2yXnGQu3c\"]},\"src/FlashtestationRegistry.sol\":{\"keccak256\":\"0x7409a55e63172d86293b538b0dc0c55ff69c00b541f9b7a26709c8309cb194c2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e35b2e23522c9dcd0bfa45e5ea70d623f7a9fb61a353e00492f64a140a31eeab\",\"dweb:/ipfs/QmbaPWPRsrd42C79hgS4Nrx2mAba4rEzRqz8guFUAVM6js\"]},\"src/interfaces/IAttestation.sol\":{\"keccak256\":\"0x2171e821adc778a1d451e06df44f8efd56648425489f1032c52bb90dd2adedb8\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://887140a9dfb7384d1dd7ae58001cc2e36fba193493ca89376b91be41b788b79a\",\"dweb:/ipfs/QmVH8KdzFFDtLipA3WvojpRMc2uSmGjKnCAdgwkLHVq6Lf\"]},\"src/interfaces/IBlockBuilderPolicy.sol\":{\"keccak256\":\"0x54a5fd427b7e2b926ff3f6eb361211f2a84a2c52fa8c641471e53b6bbe859d97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4a8ca0f86c046fa571befee3dc0c8a839ec83d932d999c09fa3a701c8e842901\",\"dweb:/ipfs/QmP2pbZ1Xwhg2avcBxpsjL6vLGcS5vyoeqUDRq4xWNDKXw\"]},\"src/interfaces/IFlashtestationRegistry.sol\":{\"keccak256\":\"0xe21137526212ed0946cd655935b1d1efb214520047dfb84053ecc1bc00095db8\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://589ad601f708e5e8b0c66760ea18b08fb028c61919f5d5222cf8916f1c283365\",\"dweb:/ipfs/QmPGsofEZJigrAcSwawQ8EyUtyv15RmRZ36L2j97H1G6tK\"]},\"src/utils/QuoteParser.sol\":{\"keccak256\":\"0xf8462661ddcf40037053c2f1527e8f56b12211ee2311fd6c3b8979ed392df702\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://20a30b5ea00b46fff0d3624d5a4a04cad774457775ea362ed0d17cf1f454deeb\",\"dweb:/ipfs/QmbFB8yZdRj9j7id9ShHb7cxFFnKiaygLa6Abexa1b5tRG\"]}},\"version\":1}", + "metadata": { + "compiler": { + "version": "0.8.28+commit.7893614a" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "type": "error", + "name": "AddressEmptyCode" + }, + { + "inputs": [], + "type": "error", + "name": "ECDSAInvalidSignature" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "type": "error", + "name": "ECDSAInvalidSignatureLength" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "type": "error", + "name": "ECDSAInvalidSignatureS" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "type": "error", + "name": "ERC1967InvalidImplementation" + }, + { + "inputs": [], + "type": "error", + "name": "ERC1967NonPayable" + }, + { + "inputs": [], + "type": "error", + "name": "EmptyCommitHash" + }, + { + "inputs": [], + "type": "error", + "name": "EmptySourceLocators" + }, + { + "inputs": [], + "type": "error", + "name": "FailedCall" + }, + { + "inputs": [], + "type": "error", + "name": "InvalidInitialization" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "provided", + "type": "uint256" + } + ], + "type": "error", + "name": "InvalidNonce" + }, + { + "inputs": [], + "type": "error", + "name": "InvalidRegistry" + }, + { + "inputs": [], + "type": "error", + "name": "NotInitializing" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "type": "error", + "name": "OwnableInvalidOwner" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "type": "error", + "name": "OwnableUnauthorizedAccount" + }, + { + "inputs": [], + "type": "error", + "name": "UUPSUnauthorizedCallContext" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "type": "error", + "name": "UUPSUnsupportedProxiableUUID" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "type": "error", + "name": "UnauthorizedBlockBuilder" + }, + { + "inputs": [], + "type": "error", + "name": "WorkloadAlreadyInPolicy" + }, + { + "inputs": [], + "type": "error", + "name": "WorkloadNotInPolicy" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address", + "indexed": false + }, + { + "internalType": "bytes32", + "name": "workloadId", + "type": "bytes32", + "indexed": false + }, + { + "internalType": "uint8", + "name": "version", + "type": "uint8", + "indexed": false + }, + { + "internalType": "bytes32", + "name": "blockContentHash", + "type": "bytes32", + "indexed": false + }, + { + "internalType": "string", + "name": "commitHash", + "type": "string", + "indexed": false + } + ], + "type": "event", + "name": "BlockBuilderProofVerified", + "anonymous": false + }, + { + "inputs": [], + "type": "event", + "name": "EIP712DomainChanged", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "version", + "type": "uint64", + "indexed": false + } + ], + "type": "event", + "name": "Initialized", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "previousOwner", + "type": "address", + "indexed": true + }, + { + "internalType": "address", + "name": "newOwner", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "OwnershipTransferred", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registry", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "RegistrySet", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "Upgraded", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "workloadId", + "type": "bytes32", + "indexed": true + } + ], + "type": "event", + "name": "WorkloadAddedToPolicy", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "workloadId", + "type": "bytes32", + "indexed": true + } + ], + "type": "event", + "name": "WorkloadRemovedFromPolicy", + "anonymous": false + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "WorkloadId", + "name": "workloadId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "commitHash", + "type": "string" + }, + { + "internalType": "string[]", + "name": "sourceLocators", + "type": "string[]" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "addWorkloadToPolicy" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "blockContentHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function", + "name": "computeStructHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "domainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ] + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "structHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getHashedTypeDataV4", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "WorkloadId", + "name": "workloadId", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getWorkloadMetadata", + "outputs": [ + { + "internalType": "struct IBlockBuilderPolicy.WorkloadMetadata", + "name": "", + "type": "tuple", + "components": [ + { + "internalType": "string", + "name": "commitHash", + "type": "string" + }, + { + "internalType": "string[]", + "name": "sourceLocators", + "type": "string[]" + } + ] + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_initialOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "_registry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "initialize" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "teeAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "isAllowedPolicy", + "outputs": [ + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + }, + { + "internalType": "WorkloadId", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "teeAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "permitNonce", + "type": "uint256" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ] + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "blockContentHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "eip712Sig", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "permitVerifyBlockBuilderProof" + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "registry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ] + }, + { + "inputs": [ + { + "internalType": "WorkloadId", + "name": "workloadId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "removeWorkloadFromPolicy" + }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "name": "renounceOwnership" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "transferOwnership" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function", + "name": "upgradeToAndCall" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "blockContentHash", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "verifyBlockBuilderProof" + }, + { + "inputs": [ + { + "internalType": "struct IFlashtestationRegistry.RegisteredTEE", + "name": "registration", + "type": "tuple", + "components": [ + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "rawQuote", + "type": "bytes" + }, + { + "internalType": "struct TD10ReportBody", + "name": "parsedReportBody", + "type": "tuple", + "components": [ + { + "internalType": "bytes16", + "name": "teeTcbSvn", + "type": "bytes16" + }, + { + "internalType": "bytes", + "name": "mrSeam", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "mrsignerSeam", + "type": "bytes" + }, + { + "internalType": "bytes8", + "name": "seamAttributes", + "type": "bytes8" + }, + { + "internalType": "bytes8", + "name": "tdAttributes", + "type": "bytes8" + }, + { + "internalType": "bytes8", + "name": "xFAM", + "type": "bytes8" + }, + { + "internalType": "bytes", + "name": "mrTd", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "mrConfigId", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "mrOwner", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "mrOwnerConfig", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "rtMr0", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "rtMr1", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "rtMr2", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "rtMr3", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "reportData", + "type": "bytes" + } + ] + }, + { + "internalType": "bytes", + "name": "extendedRegistrationData", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ] + } + ], + "stateMutability": "pure", + "type": "function", + "name": "workloadIdForTDRegistration", + "outputs": [ + { + "internalType": "WorkloadId", + "name": "", + "type": "bytes32" + } + ] + } + ], + "devdoc": { + "kind": "dev", + "methods": { + "addWorkloadToPolicy(bytes32,string,string[])": { + "details": "The commitHash solves the following problem; The only way for a smart contract like BlockBuilderPolicy to verify that a TEE (identified by its workloadId) is running a specific piece of code (for instance, op-rbuilder) is to reproducibly build that workload onchain. This is prohibitively expensive, so instead we rely on a permissioned multisig (the owner of this contract) to add a commit hash to the policy whenever it adds a new workloadId. We're already relying on the owner to verify that the workloadId is valid, so we can also assume the owner will not add a commit hash that is not associated with the workloadId. If the owner did act maliciously, this can easily be determined offchain by an honest actor building the TEE image from the given commit hash, deriving the image's workloadId, and then comparing it to the workloadId stored on the policy that is associated with the commit hash. If the workloadId is different, this can be used to prove that the owner acted maliciously. In the honest case, this Policy serves as a source of truth for which source code of build software (i.e. the commit hash) is used to build the TEE image identified by the workloadId.", + "params": { + "commitHash": "The 40-character hexadecimal commit hash of the git repository whose source code is used to build the TEE image identified by the workloadId", + "sourceLocators": "An array of URIs pointing to the source code", + "workloadId": "The workload identifier" + } + }, + "computeStructHash(uint8,bytes32,uint256)": { + "params": { + "blockContentHash": "The hash of the block content", + "nonce": "The nonce to use for the EIP-712 signature", + "version": "The version of the flashtestation's protocol" + }, + "returns": { + "_0": "The struct hash for the EIP-712 signature" + } + }, + "domainSeparator()": { + "details": "This is useful for when both onchain and offchain users want to compute the domain separator for the EIP-712 signature, and then use it to verify the signature", + "returns": { + "_0": "The domain separator for the EIP-712 signature" + } + }, + "eip712Domain()": { + "details": "returns the fields and values that describe the domain separator used by this contract for EIP-712 signature." + }, + "getHashedTypeDataV4(bytes32)": { + "params": { + "structHash": "The struct hash for the EIP-712 signature" + }, + "returns": { + "_0": "The digest for the EIP-712 signature" + } + }, + "getWorkloadMetadata(bytes32)": { + "details": "This is only updateable by governance (i.e. the owner) of the Policy contract Adding and removing a workload is O(1)", + "params": { + "workloadId": "The workload identifier to query" + }, + "returns": { + "_0": "The metadata associated with the workload" + } + }, + "initialize(address,address)": { + "params": { + "_initialOwner": "The address of the initial owner of the contract", + "_registry": "The address of the registry contract" + } + }, + "isAllowedPolicy(address)": { + "params": { + "teeAddress": "The TEE-controlled address" + }, + "returns": { + "_1": "The workloadId of the TEE that is using an approved workload in the policy, or 0 if the TEE is not using an approved workload in the policy", + "allowed": "True if the TEE is using an approved workload in the policy" + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)": { + "details": "This function is useful if you do not want to deal with the operational difficulties of keeping your TEE-controlled addresses funded, but note that because of the larger number of function arguments, will cost more gas than the non-EIP-712 verifyBlockBuilderProof function", + "params": { + "blockContentHash": "The hash of the block content", + "eip712Sig": "The EIP-712 signature of the verification message", + "nonce": "The nonce to use for the EIP-712 signature", + "version": "The version of the flashtestation's protocol used to generate the block builder proof" + } + }, + "proxiableUUID()": { + "details": "Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier." + }, + "removeWorkloadFromPolicy(bytes32)": { + "params": { + "workloadId": "The workload identifier" + } + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + }, + "upgradeToAndCall(address,bytes)": { + "custom:oz-upgrades-unsafe-allow-reachable": "delegatecall", + "details": "Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event." + }, + "verifyBlockBuilderProof(uint8,bytes32)": { + "details": "If you do not want to deal with the operational difficulties of keeping your TEE-controlled addresses funded, you can use the permitVerifyBlockBuilderProof function instead which costs more gas, but allows any EOA to submit a block builder proof on behalf of a TEE", + "params": { + "blockContentHash": "The hash of the block content", + "version": "The version of the flashtestation's protocol used to generate the block builder proof" + } + }, + "workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))": { + "details": "Think of the workload identifier as the version of the application for governance. The workloadId verifiably maps to a version of source code that builds the TEE VM image", + "params": { + "registration": "The registration data from a TEE device" + }, + "returns": { + "_0": "workloadId The computed workload identifier" + } + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH()": { + "notice": "EIP-712 Typehash, used in the permitVerifyBlockBuilderProof function" + }, + "addWorkloadToPolicy(bytes32,string,string[])": { + "notice": "Add a workload to a policy (governance only)Only the owner of this contract can add workloads to the policy and it is the responsibility of the owner to ensure that the workload is valid otherwise the address associated with this workload has full power to do anything who's authorization is based on this policy" + }, + "computeStructHash(uint8,bytes32,uint256)": { + "notice": "Computes the struct hash for the EIP-712 signature" + }, + "domainSeparator()": { + "notice": "Returns the domain separator for the EIP-712 signature" + }, + "getHashedTypeDataV4(bytes32)": { + "notice": "Computes the digest for the EIP-712 signature" + }, + "getWorkloadMetadata(bytes32)": { + "notice": "Mapping from workloadId to its metadata (commit hash and source locators)" + }, + "initialize(address,address)": { + "notice": "Initializer to set the FlashtestationRegistry contract which verifies TEE quotes and the initial owner of the contract" + }, + "isAllowedPolicy(address)": { + "notice": "Check if this TEE-controlled address has registered a valid TEE workload with the registry, and if the workload is approved under this policy" + }, + "nonces(address)": { + "notice": "Tracks nonces for EIP-712 signatures to prevent replay attacks" + }, + "permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)": { + "notice": "Verify a block builder proof with a Flashtestation Transaction using EIP-712 signaturesThis function allows any EOA to submit a block builder proof on behalf of a TEEThe TEE must sign a proper EIP-712-formatted message, and the signer must match a TEE-controlled address whose associated workload is approved under this policy" + }, + "registry()": { + "notice": "Address of the FlashtestationRegistry contract that verifies TEE quotes" + }, + "removeWorkloadFromPolicy(bytes32)": { + "notice": "Remove a workload from a policy (governance only)" + }, + "verifyBlockBuilderProof(uint8,bytes32)": { + "notice": "Verify a block builder proof with a Flashtestation TransactionThis function will only succeed if the caller is a registered TEE-controlled address from an attested TEE and the TEE is running an approved block builder workload (see `addWorkloadToPolicy`)The blockContentHash is a keccak256 hash of a subset of the block header, as specified by the version. See the [flashtestations spec](https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md) for more details" + }, + "workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))": { + "notice": "Application specific mapping of registration data to a workload identifier" + } + }, + "version": 1 + } + }, + "settings": { + "remappings": [ + "@automata-network/on-chain-pccs/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/", + "@sp1-contracts/=lib/automata-dcap-attestation/evm/lib/sp1-contracts/contracts/src/", + "automata-dcap-attestation/=lib/automata-dcap-attestation/evm/", + "automata-on-chain-pccs/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/", + "ds-test/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/forge-std/lib/ds-test/src/", + "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/", + "forge-std/=lib/forge-std/src/", + "halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/", + "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/", + "openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/", + "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/", + "risc0-ethereum/=lib/automata-dcap-attestation/evm/lib/risc0-ethereum/", + "risc0/=lib/automata-dcap-attestation/evm/lib/risc0-ethereum/contracts/src/", + "solady/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/", + "solmate/=lib/solmate/", + "sp1-contracts/=lib/automata-dcap-attestation/evm/lib/sp1-contracts/contracts/" + ], + "optimizer": { + "enabled": true, + "runs": 44444444 + }, + "metadata": { + "bytecodeHash": "none" + }, + "compilationTarget": { + "src/BlockBuilderPolicy.sol": "BlockBuilderPolicy" + }, + "evmVersion": "prague", + "libraries": {}, + "viaIR": true + }, + "sources": { + "lib/automata-dcap-attestation/evm/contracts/types/CommonStruct.sol": { + "keccak256": "0xc1968270cffd0b96268c9695d81647e707a6e1e7c1b851c1d6b1f9af6a88d4f2", + "urls": [ + "bzz-raw://d4e56c233fbb8e448961f5b847b92a8e725cc56ed9e0ad764d790f1cd4896245", + "dweb:/ipfs/QmVoUKR62HT6DKaR3rNnNr5w1CmDSbrDriX41ycTAzKodz" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/contracts/types/Constants.sol": { + "keccak256": "0xcfaac1552ee9277f6b0e4e38bdee7d2e07d1bdfe188c294c5c3f37f152b1d099", + "urls": [ + "bzz-raw://89a8a55be811abd8cc8af234d489c64a84f08023061ade4e67dbbaf11468cdc4", + "dweb:/ipfs/QmR1TWqBcAEFbtteg2xjVG4VJXeFVWoAQidR3jyWuaGpUf" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/contracts/types/V4Structs.sol": { + "keccak256": "0x39726e2cc0de02e8b34c0da2e6885fa9d18b7905970b9745e5310fb12cde9dd4", + "urls": [ + "bzz-raw://ddf5ab4f4188de35bb9c03137b207d56d69629283b9cb683eebdfafe7ff015ee", + "dweb:/ipfs/QmTmYBmBEUbxMESjeaF6cSPMSK5P8hv16tDXTk78LXiKpz" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/DateTimeLib.sol": { + "keccak256": "0x3945a985a34bc905beb149f7196f1dba021a60abc2f178ab2eb3a28ed4f94741", + "urls": [ + "bzz-raw://828a14ebcf4bd777c73ecbedd726819ac37c3b2e4fab7c8fe263f860db725f72", + "dweb:/ipfs/QmNW32zDLCVjbGecmZvxAaSdmDudHQSsdrFfpMYX6baGAv" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/JSONParserLib.sol": { + "keccak256": "0x8d4aeacf0459b28b40b63c9d331dc95cf7d751ca9961c83c89c1ad73b9b3cd81", + "urls": [ + "bzz-raw://42cc6de802e8c17668ae82518caa4c053e82f1884329d1d924fa7b9fccf5041f", + "dweb:/ipfs/QmPGLfqWXDCjjcZ2VEG8kRwasGFxR4u62RpLLBuLqXy9wP" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/LibString.sol": { + "keccak256": "0x74ec81acbea6db4afe149ab51e26961bcb801af42f7af98242be71b866066200", + "urls": [ + "bzz-raw://16bb49824fcfa9667aeed0eb515fdefda1016225085cf78ed852078c67168229", + "dweb:/ipfs/QmZ59xrx5QLSx5N5CiTLrfwsPKR7kpK4RRpiEWSMEpvDzQ" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/helpers/FmspcTcbHelper.sol": { + "keccak256": "0x3041391dfb9e2dcb6ed27e9b89445a9206c5348da3f2753785c22c832878f226", + "urls": [ + "bzz-raw://883bd1cb7d425828b8b9c92052f16fbcc893e6cdd175e3616a99b857b723a808", + "dweb:/ipfs/QmZWqGm2D9AFrxzAsad7xkLSKuH1NQb7VEdFKwJbumj8EU" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/helpers/X509Helper.sol": { + "keccak256": "0x4b4a56e80792b64604975e52d1aad2e075bd49c94b36814aa6461fbd69b6a03e", + "urls": [ + "bzz-raw://7ea019efcc3904401a694b5704d7cb219b20f7cc11a5d4268b0015628fb71713", + "dweb:/ipfs/QmezJCskd9hVoERyUNk6Y9qK8oQxmen5HqasNgz8diyk6V" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/Asn1Decode.sol": { + "keccak256": "0xc7a6f959ce3d98244e28ff140c0f3eb5fae6349d93e250a49cbf9fdd0e7ac8e6", + "urls": [ + "bzz-raw://7c0760856218edc38ead344bd2587118a2a72a23d0d410e57c7e48251f313e3c", + "dweb:/ipfs/QmdNu9g5GWgEuS1ShgCqHckE1cSjfpt9LJb2t1A2CKAFfB" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/BytesUtils.sol": { + "keccak256": "0x9a9028cd3bc73fb8ff9404907c980df141fd06351f9ea79b32754d05cae25f85", + "urls": [ + "bzz-raw://eaf5b855f3013a40e5d6b62fd9a7b7077a06b1750fabc53b8514ba4cf0006bed", + "dweb:/ipfs/QmXKHL2zH51od64bYa8pifwEQF8UzccPBE2x7K5uvq4JeQ" + ], + "license": "BSD 2-Clause License" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/DateTimeUtils.sol": { + "keccak256": "0xee25670c66115185f0f0ab6070ade945faff8f75b502b70c2597a02868a02a4f", + "urls": [ + "bzz-raw://5c08c26dbd2e3196b3e16f7b8d2daa29c472997a845d5dc731f3bf17b7784d02", + "dweb:/ipfs/QmUMwg7SvYQpgwcnDkGM9vnau2SqoYzwsGcB8CM2MLZueP" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": { + "keccak256": "0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a", + "urls": [ + "bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6", + "dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": { + "keccak256": "0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05", + "urls": [ + "bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08", + "dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": { + "keccak256": "0x574a7451e42724f7de29e2855c392a8a5020acd695169466a18459467d719d63", + "urls": [ + "bzz-raw://5bc189f63b639ee173dd7b6fecc39baf7113bf161776aea22b34c57fdd1872ec", + "dweb:/ipfs/QmZAf2VtjDLRULqjJkde6LNsxAg12tUqpPqgUQQZbAjgtZ" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol": { + "keccak256": "0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397", + "urls": [ + "bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9", + "dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardTransientUpgradeable.sol": { + "keccak256": "0x391a52a14dfcbe1a9ca16f1c052481de32242cf45714d92dab81be2a987e4bba", + "urls": [ + "bzz-raw://248b69f99e0452696ce5a2c90aac5602f496e2a697dacd5510d050f0dc833a3c", + "dweb:/ipfs/QmcYkMiFQhTs2AW5fmcV5a3XQAGdQBUz1Y2NQD4RvBrNTM" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol": { + "keccak256": "0x89374b2a634f0a9c08f5891b6ecce0179bc2e0577819c787ed3268ca428c2459", + "urls": [ + "bzz-raw://f13d2572e5bdd55e483dfac069aac47603644071616a41fce699e94368e38c13", + "dweb:/ipfs/QmfKeyNT6vyb99vJQatPZ88UyZgXNmAiHUXSWnaR1TPE11" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol": { + "keccak256": "0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5", + "urls": [ + "bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c", + "dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol": { + "keccak256": "0xfb223a85dd0b2175cfbbaa325a744e2cd74ecd17c3df2b77b0722f991d2725ee", + "urls": [ + "bzz-raw://84bf1dea0589ec49c8d15d559cc6d86ee493048a89b2d4adb60fbe705a3d89ae", + "dweb:/ipfs/Qmd56n556d529wk2pRMhYhm5nhMDhviwereodDikjs68w1" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol": { + "keccak256": "0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b", + "urls": [ + "bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422", + "dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol": { + "keccak256": "0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618", + "urls": [ + "bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a", + "dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol": { + "keccak256": "0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b", + "urls": [ + "bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d", + "dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol": { + "keccak256": "0x6d0ae6e206645341fd122d278c2cb643dea260c190531f2f3f6a0426e77b00c0", + "urls": [ + "bzz-raw://032d1201d839435be2c85b72e33206b3ea980c569d6ebf7fa57d811ab580a82f", + "dweb:/ipfs/QmeqQjAtMvdZT2tG7zm39itcRJkuwu8AEReK6WRnLJ18DD" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol": { + "keccak256": "0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123", + "urls": [ + "bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf", + "dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Panic.sol": { + "keccak256": "0xf7fe324703a64fc51702311dc51562d5cb1497734f074e4f483bfb6717572d7a", + "urls": [ + "bzz-raw://c6a5ff4f9fd8649b7ee20800b7fa387d3465bd77cf20c2d1068cd5c98e1ed57a", + "dweb:/ipfs/QmVSaVJf9FXFhdYEYeCEfjMVHrxDh5qL4CGkxdMWpQCrqG" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol": { + "keccak256": "0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97", + "urls": [ + "bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b", + "dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Strings.sol": { + "keccak256": "0xad148d59f05165f9217d0a9e1ac8f772abb02ea6aaad8a756315c532bf79f9f4", + "urls": [ + "bzz-raw://15e3599867c2182f5831e9268b274b2ef2047825837df6b4d81c9e89254b093e", + "dweb:/ipfs/QmZbL7XAYr5RmaNaooPgZRmcDXaudfsYQfYD9y5iAECvpS" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/TransientSlot.sol": { + "keccak256": "0xac673fa1e374d9e6107504af363333e3e5f6344d2e83faf57d9bfd41d77cc946", + "urls": [ + "bzz-raw://5982478dbbb218e9dd5a6e83f5c0e8d1654ddf20178484b43ef21dd2246809de", + "dweb:/ipfs/QmaB1hS68n2kG8vTbt7EPEzmrGhkUbfiFyykGGLsAr9X22" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol": { + "keccak256": "0x69f54c02b7d81d505910ec198c11ed4c6a728418a868b906b4a0cf29946fda84", + "urls": [ + "bzz-raw://8e25e4bdb7ae1f21d23bfee996e22736fc0ab44cfabedac82a757b1edc5623b9", + "dweb:/ipfs/QmQdWQvB6JCP9ZMbzi8EvQ1PTETqkcTWrbcVurS7DKpa5n" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol": { + "keccak256": "0x26670fef37d4adf55570ba78815eec5f31cb017e708f61886add4fc4da665631", + "urls": [ + "bzz-raw://b16d45febff462bafd8a5669f904796a835baf607df58a8461916d3bf4f08c59", + "dweb:/ipfs/QmU2eJFpjmT4vxeJWJyLeQb8Xht1kdB8Y6MKLDPFA9WPux" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/Math.sol": { + "keccak256": "0x1225214420c83ebcca88f2ae2b50f053aaa7df7bd684c3e878d334627f2edfc6", + "urls": [ + "bzz-raw://6c5fab4970634f9ab9a620983dc1c8a30153981a0b1a521666e269d0a11399d3", + "dweb:/ipfs/QmVRnBC575MESGkEHndjujtR7qub2FzU9RWy9eKLp4hPZB" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol": { + "keccak256": "0x195533c86d0ef72bcc06456a4f66a9b941f38eb403739b00f21fd7c1abd1ae54", + "urls": [ + "bzz-raw://b1d578337048cad08c1c03041cca5978eff5428aa130c781b271ad9e5566e1f8", + "dweb:/ipfs/QmPFKL2r9CBsMwmUqqdcFPfHZB2qcs9g1HDrPxzWSxomvy" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol": { + "keccak256": "0xb1970fac7b64e6c09611e6691791e848d5e3fe410fa5899e7df2e0afd77a99e3", + "urls": [ + "bzz-raw://db5fbb3dddd8b7047465b62575d96231ba8a2774d37fb4737fbf23340fabbb03", + "dweb:/ipfs/QmVUSvooZKEdEdap619tcJjTLcAuH6QBdZqAzWwnAXZAWJ" + ], + "license": "MIT" + }, + "src/BlockBuilderPolicy.sol": { + "keccak256": "0x7c3d3ba04441108b91651c8b9c4dee65b2c3bb7e71fc0a55f1d3bd08ad8635cb", + "urls": [ + "bzz-raw://48d897893c99de682f4a44c3158f7bdf489c5d8557800906142cc300e2318806", + "dweb:/ipfs/QmSjnpDXmwHcHei6dX6gQzMWUz3cZFF2JfKFH2yXnGQu3c" + ], + "license": "MIT" + }, + "src/FlashtestationRegistry.sol": { + "keccak256": "0x7409a55e63172d86293b538b0dc0c55ff69c00b541f9b7a26709c8309cb194c2", + "urls": [ + "bzz-raw://e35b2e23522c9dcd0bfa45e5ea70d623f7a9fb61a353e00492f64a140a31eeab", + "dweb:/ipfs/QmbaPWPRsrd42C79hgS4Nrx2mAba4rEzRqz8guFUAVM6js" + ], + "license": "MIT" + }, + "src/interfaces/IAttestation.sol": { + "keccak256": "0x2171e821adc778a1d451e06df44f8efd56648425489f1032c52bb90dd2adedb8", + "urls": [ + "bzz-raw://887140a9dfb7384d1dd7ae58001cc2e36fba193493ca89376b91be41b788b79a", + "dweb:/ipfs/QmVH8KdzFFDtLipA3WvojpRMc2uSmGjKnCAdgwkLHVq6Lf" + ], + "license": "MIT" + }, + "src/interfaces/IBlockBuilderPolicy.sol": { + "keccak256": "0x54a5fd427b7e2b926ff3f6eb361211f2a84a2c52fa8c641471e53b6bbe859d97", + "urls": [ + "bzz-raw://4a8ca0f86c046fa571befee3dc0c8a839ec83d932d999c09fa3a701c8e842901", + "dweb:/ipfs/QmP2pbZ1Xwhg2avcBxpsjL6vLGcS5vyoeqUDRq4xWNDKXw" + ], + "license": "MIT" + }, + "src/interfaces/IFlashtestationRegistry.sol": { + "keccak256": "0xe21137526212ed0946cd655935b1d1efb214520047dfb84053ecc1bc00095db8", + "urls": [ + "bzz-raw://589ad601f708e5e8b0c66760ea18b08fb028c61919f5d5222cf8916f1c283365", + "dweb:/ipfs/QmPGsofEZJigrAcSwawQ8EyUtyv15RmRZ36L2j97H1G6tK" + ], + "license": "MIT" + }, + "src/utils/QuoteParser.sol": { + "keccak256": "0xf8462661ddcf40037053c2f1527e8f56b12211ee2311fd6c3b8979ed392df702", + "urls": [ + "bzz-raw://20a30b5ea00b46fff0d3624d5a4a04cad774457775ea362ed0d17cf1f454deeb", + "dweb:/ipfs/QmbFB8yZdRj9j7id9ShHb7cxFFnKiaygLa6Abexa1b5tRG" + ], + "license": "MIT" + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 60637, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "approvedWorkloads", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_bytes32,t_struct(WorkloadMetadata)61869_storage)" + }, + { + "astId": 60640, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "t_address" + }, + { + "astId": 60645, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "nonces", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 60651, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "cachedWorkloads", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_struct(CachedWorkload)60611_storage)" + }, + { + "astId": 60656, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "__gap", + "offset": 0, + "slot": "4", + "type": "t_array(t_uint256)46_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_string_storage)dyn_storage": { + "encoding": "dynamic_array", + "label": "string[]", + "numberOfBytes": "32", + "base": "t_string_storage" + }, + "t_array(t_uint256)46_storage": { + "encoding": "inplace", + "label": "uint256[46]", + "numberOfBytes": "1472", + "base": "t_uint256" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(CachedWorkload)60611_storage)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => struct CachedWorkload)", + "numberOfBytes": "32", + "value": "t_struct(CachedWorkload)60611_storage" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_bytes32,t_struct(WorkloadMetadata)61869_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata)", + "numberOfBytes": "32", + "value": "t_struct(WorkloadMetadata)61869_storage" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(CachedWorkload)60611_storage": { + "encoding": "inplace", + "label": "struct CachedWorkload", + "numberOfBytes": "64", + "members": [ + { + "astId": 60607, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "workloadId", + "offset": 0, + "slot": "0", + "type": "t_userDefinedValueType(WorkloadId)61861" + }, + { + "astId": 60610, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "quoteHash", + "offset": 0, + "slot": "1", + "type": "t_bytes32" + } + ] + }, + "t_struct(WorkloadMetadata)61869_storage": { + "encoding": "inplace", + "label": "struct IBlockBuilderPolicy.WorkloadMetadata", + "numberOfBytes": "64", + "members": [ + { + "astId": 61865, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "commitHash", + "offset": 0, + "slot": "0", + "type": "t_string_storage" + }, + { + "astId": 61868, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "sourceLocators", + "offset": 0, + "slot": "1", + "type": "t_array(t_string_storage)dyn_storage" + } + ] + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_userDefinedValueType(WorkloadId)61861": { + "encoding": "inplace", + "label": "WorkloadId", + "numberOfBytes": "32" + } + } + }, + "ast": { + "absolutePath": "src/BlockBuilderPolicy.sol", + "id": 61240, + "exportedSymbols": { + "BlockBuilderPolicy": [ + 61239 + ], + "CachedWorkload": [ + 60611 + ], + "ECDSA": [ + 52398 + ], + "EIP712Upgradeable": [ + 49049 + ], + "FlashtestationRegistry": [ + 61842 + ], + "IBlockBuilderPolicy": [ + 62049 + ], + "IFlashtestationRegistry": [ + 62267 + ], + "Initializable": [ + 48392 + ], + "OwnableUpgradeable": [ + 48124 + ], + "UUPSUpgradeable": [ + 48574 + ], + "WorkloadId": [ + 61861 + ] + }, + "nodeType": "SourceUnit", + "src": "32:13381:71", + "nodes": [ + { + "id": 60585, + "nodeType": "PragmaDirective", + "src": "32:23:71", + "nodes": [], + "literals": [ + "solidity", + "0.8", + ".28" + ] + }, + { + "id": 60587, + "nodeType": "ImportDirective", + "src": "57:96:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol", + "file": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 48393, + "symbolAliases": [ + { + "foreign": { + "id": 60586, + "name": "Initializable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48392, + "src": "65:13:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60589, + "nodeType": "ImportDirective", + "src": "154:100:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol", + "file": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 48575, + "symbolAliases": [ + { + "foreign": { + "id": 60588, + "name": "UUPSUpgradeable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48574, + "src": "162:15:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60591, + "nodeType": "ImportDirective", + "src": "255:101:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol", + "file": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 48125, + "symbolAliases": [ + { + "foreign": { + "id": 60590, + "name": "OwnableUpgradeable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48124, + "src": "263:18:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60593, + "nodeType": "ImportDirective", + "src": "357:111:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol", + "file": "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 49050, + "symbolAliases": [ + { + "foreign": { + "id": 60592, + "name": "EIP712Upgradeable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 49049, + "src": "365:17:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60595, + "nodeType": "ImportDirective", + "src": "469:75:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol", + "file": "@openzeppelin/contracts/utils/cryptography/ECDSA.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 52399, + "symbolAliases": [ + { + "foreign": { + "id": 60594, + "name": "ECDSA", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 52398, + "src": "477:5:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60597, + "nodeType": "ImportDirective", + "src": "545:68:71", + "nodes": [], + "absolutePath": "src/FlashtestationRegistry.sol", + "file": "./FlashtestationRegistry.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 61843, + "symbolAliases": [ + { + "foreign": { + "id": 60596, + "name": "FlashtestationRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61842, + "src": "553:22:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60599, + "nodeType": "ImportDirective", + "src": "614:81:71", + "nodes": [], + "absolutePath": "src/interfaces/IFlashtestationRegistry.sol", + "file": "./interfaces/IFlashtestationRegistry.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 62268, + "symbolAliases": [ + { + "foreign": { + "id": 60598, + "name": "IFlashtestationRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 62267, + "src": "622:23:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60602, + "nodeType": "ImportDirective", + "src": "696:85:71", + "nodes": [], + "absolutePath": "src/interfaces/IBlockBuilderPolicy.sol", + "file": "./interfaces/IBlockBuilderPolicy.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 62050, + "symbolAliases": [ + { + "foreign": { + "id": 60600, + "name": "IBlockBuilderPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 62049, + "src": "704:19:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + }, + { + "foreign": { + "id": 60601, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "725:10:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60611, + "nodeType": "StructDefinition", + "src": "944:208:71", + "nodes": [], + "canonicalName": "CachedWorkload", + "documentation": { + "id": 60603, + "nodeType": "StructuredDocumentation", + "src": "783:160:71", + "text": " @notice Cached workload information for gas optimization\n @dev Stores computed workloadId and associated quoteHash to avoid expensive recomputation" + }, + "members": [ + { + "constant": false, + "id": 60607, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "1032:10:71", + "nodeType": "VariableDeclaration", + "scope": 60611, + "src": "1021:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60606, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60605, + "name": "WorkloadId", + "nameLocations": [ + "1021:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "1021:10:71" + }, + "referencedDeclaration": 61861, + "src": "1021:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60610, + "mutability": "mutable", + "name": "quoteHash", + "nameLocation": "1140:9:71", + "nodeType": "VariableDeclaration", + "scope": 60611, + "src": "1132:17:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60609, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1132:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "name": "CachedWorkload", + "nameLocation": "951:14:71", + "scope": 61240, + "visibility": "public" + }, + { + "id": 61239, + "nodeType": "ContractDefinition", + "src": "2021:11391:71", + "nodes": [ + { + "id": 60625, + "nodeType": "UsingForDirective", + "src": "2169:24:71", + "nodes": [], + "global": false, + "libraryName": { + "id": 60623, + "name": "ECDSA", + "nameLocations": [ + "2175:5:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 52398, + "src": "2175:5:71" + }, + "typeName": { + "id": 60624, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2185:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + }, + { + "id": 60631, + "nodeType": "VariableDeclaration", + "src": "2291:160:71", + "nodes": [], + "baseFunctions": [ + 62048 + ], + "constant": true, + "documentation": { + "id": 60626, + "nodeType": "StructuredDocumentation", + "src": "2251:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "73016923", + "mutability": "constant", + "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", + "nameLocation": "2315:35:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60627, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2291:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": { + "arguments": [ + { + "hexValue": "566572696679426c6f636b4275696c64657250726f6f662875696e74382076657273696f6e2c6279746573333220626c6f636b436f6e74656e74486173682c75696e74323536206e6f6e636529", + "id": 60629, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2371:79:71", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc", + "typeString": "literal_string \"VerifyBlockBuilderProof(uint8 version,bytes32 blockContentHash,uint256 nonce)\"" + }, + "value": "VerifyBlockBuilderProof(uint8 version,bytes32 blockContentHash,uint256 nonce)" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc", + "typeString": "literal_string \"VerifyBlockBuilderProof(uint8 version,bytes32 blockContentHash,uint256 nonce)\"" + } + ], + "id": 60628, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "2361:9:71", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 60630, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "2361:90:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "public" + }, + { + "id": 60637, + "nodeType": "VariableDeclaration", + "src": "2887:73:71", + "nodes": [], + "constant": false, + "documentation": { + "id": 60632, + "nodeType": "StructuredDocumentation", + "src": "2510:372:71", + "text": "@notice Mapping from workloadId to its metadata (commit hash and source locators)\n @dev This is only updateable by governance (i.e. the owner) of the Policy contract\n Adding and removing a workload is O(1).\n This means the critical `_cachedIsAllowedPolicy` function is O(1) since we can directly check if a workloadId exists\n in the mapping" + }, + "mutability": "mutable", + "name": "approvedWorkloads", + "nameLocation": "2943:17:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata)" + }, + "typeName": { + "id": 60636, + "keyName": "workloadId", + "keyNameLocation": "2903:10:71", + "keyType": { + "id": 60633, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2895:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "Mapping", + "src": "2887:47:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata)" + }, + "valueName": "", + "valueNameLocation": "-1:-1:-1", + "valueType": { + "id": 60635, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60634, + "name": "WorkloadMetadata", + "nameLocations": [ + "2917:16:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61869, + "src": "2917:16:71" + }, + "referencedDeclaration": 61869, + "src": "2917:16:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage_ptr", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata" + } + } + }, + "visibility": "private" + }, + { + "id": 60640, + "nodeType": "VariableDeclaration", + "src": "3007:23:71", + "nodes": [], + "baseFunctions": [ + 62034 + ], + "constant": false, + "documentation": { + "id": 60638, + "nodeType": "StructuredDocumentation", + "src": "2967:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "7b103999", + "mutability": "mutable", + "name": "registry", + "nameLocation": "3022:8:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60639, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3007:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "public" + }, + { + "id": 60645, + "nodeType": "VariableDeclaration", + "src": "3077:64:71", + "nodes": [], + "baseFunctions": [ + 62042 + ], + "constant": false, + "documentation": { + "id": 60641, + "nodeType": "StructuredDocumentation", + "src": "3037:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "7ecebe00", + "mutability": "mutable", + "name": "nonces", + "nameLocation": "3135:6:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "typeName": { + "id": 60644, + "keyName": "teeAddress", + "keyNameLocation": "3093:10:71", + "keyType": { + "id": 60642, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3085:7:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "3077:50:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "valueName": "permitNonce", + "valueNameLocation": "3115:11:71", + "valueType": { + "id": 60643, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3107:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + "visibility": "public" + }, + { + "id": 60651, + "nodeType": "VariableDeclaration", + "src": "3308:69:71", + "nodes": [], + "constant": false, + "documentation": { + "id": 60646, + "nodeType": "StructuredDocumentation", + "src": "3148:155:71", + "text": "@notice Cache of computed workloadIds to avoid expensive recomputation\n @dev Maps teeAddress to cached workload information for gas optimization" + }, + "mutability": "mutable", + "name": "cachedWorkloads", + "nameLocation": "3362:15:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_CachedWorkload_$60611_storage_$", + "typeString": "mapping(address => struct CachedWorkload)" + }, + "typeName": { + "id": 60650, + "keyName": "teeAddress", + "keyNameLocation": "3324:10:71", + "keyType": { + "id": 60647, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3316:7:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "3308:45:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_CachedWorkload_$60611_storage_$", + "typeString": "mapping(address => struct CachedWorkload)" + }, + "valueName": "", + "valueNameLocation": "-1:-1:-1", + "valueType": { + "id": 60649, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60648, + "name": "CachedWorkload", + "nameLocations": [ + "3338:14:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 60611, + "src": "3338:14:71" + }, + "referencedDeclaration": 60611, + "src": "3338:14:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage_ptr", + "typeString": "struct CachedWorkload" + } + } + }, + "visibility": "private" + }, + { + "id": 60656, + "nodeType": "VariableDeclaration", + "src": "3600:17:71", + "nodes": [], + "constant": false, + "documentation": { + "id": 60652, + "nodeType": "StructuredDocumentation", + "src": "3384:211:71", + "text": "@dev Storage gap to allow for future storage variable additions in upgrades\n @dev This reserves 46 storage slots (out of 50 total - 4 used for approvedWorkloads, registry, nonces, and cachedWorkloads)" + }, + "mutability": "mutable", + "name": "__gap", + "nameLocation": "3612:5:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$46_storage", + "typeString": "uint256[46]" + }, + "typeName": { + "baseType": { + "id": 60653, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3600:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 60655, + "length": { + "hexValue": "3436", + "id": 60654, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3608:2:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_46_by_1", + "typeString": "int_const 46" + }, + "value": "46" + }, + "nodeType": "ArrayTypeName", + "src": "3600:11:71", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$46_storage_ptr", + "typeString": "uint256[46]" + } + }, + "visibility": "internal" + }, + { + "id": 60699, + "nodeType": "FunctionDefinition", + "src": "3664:351:71", + "nodes": [], + "body": { + "id": 60698, + "nodeType": "Block", + "src": "3756:259:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "id": 60668, + "name": "_initialOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60659, + "src": "3781:13:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60667, + "name": "__Ownable_init", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 47984, + "src": "3766:14:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 60669, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3766:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60670, + "nodeType": "ExpressionStatement", + "src": "3766:29:71" + }, + { + "expression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 60671, + "name": "__UUPSUpgradeable_init", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48446, + "src": "3805:22:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$__$returns$__$", + "typeString": "function ()" + } + }, + "id": 60672, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3805:24:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60673, + "nodeType": "ExpressionStatement", + "src": "3805:24:71" + }, + { + "expression": { + "arguments": [ + { + "hexValue": "426c6f636b4275696c646572506f6c696379", + "id": 60675, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3853:20:71", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_cbeb49ca47be8803ce2a0d04c03e1532e2fe91bbb42a2a5d9922693dc31e4646", + "typeString": "literal_string \"BlockBuilderPolicy\"" + }, + "value": "BlockBuilderPolicy" + }, + { + "hexValue": "31", + "id": 60676, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3875:3:71", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", + "typeString": "literal_string \"1\"" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_cbeb49ca47be8803ce2a0d04c03e1532e2fe91bbb42a2a5d9922693dc31e4646", + "typeString": "literal_string \"BlockBuilderPolicy\"" + }, + { + "typeIdentifier": "t_stringliteral_c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", + "typeString": "literal_string \"1\"" + } + ], + "id": 60674, + "name": "__EIP712_init", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48762, + "src": "3839:13:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (string memory,string memory)" + } + }, + "id": 60677, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3839:40:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60678, + "nodeType": "ExpressionStatement", + "src": "3839:40:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 60685, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 60680, + "name": "_registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60661, + "src": "3897:9:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "arguments": [ + { + "hexValue": "30", + "id": 60683, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3918:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 60682, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3910:7:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 60681, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3910:7:71", + "typeDescriptions": {} + } + }, + "id": 60684, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3910:10:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "3897:23:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 60686, + "name": "InvalidRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61900, + "src": "3922:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 60687, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3922:17:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 60679, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "3889:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 60688, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3889:51:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60689, + "nodeType": "ExpressionStatement", + "src": "3889:51:71" + }, + { + "expression": { + "id": 60692, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 60690, + "name": "registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60640, + "src": "3951:8:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 60691, + "name": "_registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60661, + "src": "3962:9:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "3951:20:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 60693, + "nodeType": "ExpressionStatement", + "src": "3951:20:71" + }, + { + "eventCall": { + "arguments": [ + { + "id": 60695, + "name": "_registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60661, + "src": "3998:9:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60694, + "name": "RegistrySet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61884, + "src": "3986:11:71", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 60696, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3986:22:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60697, + "nodeType": "EmitStatement", + "src": "3981:27:71" + } + ] + }, + "baseFunctions": [ + 61932 + ], + "documentation": { + "id": 60657, + "nodeType": "StructuredDocumentation", + "src": "3624:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "485cc955", + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 60665, + "kind": "modifierInvocation", + "modifierName": { + "id": 60664, + "name": "initializer", + "nameLocations": [ + "3744:11:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48232, + "src": "3744:11:71" + }, + "nodeType": "ModifierInvocation", + "src": "3744:11:71" + } + ], + "name": "initialize", + "nameLocation": "3673:10:71", + "overrides": { + "id": 60663, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "3735:8:71" + }, + "parameters": { + "id": 60662, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60659, + "mutability": "mutable", + "name": "_initialOwner", + "nameLocation": "3692:13:71", + "nodeType": "VariableDeclaration", + "scope": 60699, + "src": "3684:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60658, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3684:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60661, + "mutability": "mutable", + "name": "_registry", + "nameLocation": "3715:9:71", + "nodeType": "VariableDeclaration", + "scope": 60699, + "src": "3707:17:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60660, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3707:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "3683:42:71" + }, + "returnParameters": { + "id": 60666, + "nodeType": "ParameterList", + "parameters": [], + "src": "3756:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 60709, + "nodeType": "FunctionDefinition", + "src": "4150:84:71", + "nodes": [], + "body": { + "id": 60708, + "nodeType": "Block", + "src": "4232:2:71", + "nodes": [], + "statements": [] + }, + "baseFunctions": [ + 48528 + ], + "documentation": { + "id": 60700, + "nodeType": "StructuredDocumentation", + "src": "4021:124:71", + "text": "@notice Restricts upgrades to owner only\n @param newImplementation The address of the new implementation contract" + }, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 60706, + "kind": "modifierInvocation", + "modifierName": { + "id": 60705, + "name": "onlyOwner", + "nameLocations": [ + "4222:9:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48019, + "src": "4222:9:71" + }, + "nodeType": "ModifierInvocation", + "src": "4222:9:71" + } + ], + "name": "_authorizeUpgrade", + "nameLocation": "4159:17:71", + "overrides": { + "id": 60704, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "4213:8:71" + }, + "parameters": { + "id": 60703, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60702, + "mutability": "mutable", + "name": "newImplementation", + "nameLocation": "4185:17:71", + "nodeType": "VariableDeclaration", + "scope": 60709, + "src": "4177:25:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60701, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4177:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "4176:27:71" + }, + "returnParameters": { + "id": 60707, + "nodeType": "ParameterList", + "parameters": [], + "src": "4232:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "internal" + }, + { + "id": 60726, + "nodeType": "FunctionDefinition", + "src": "4280:172:71", + "nodes": [], + "body": { + "id": 60725, + "nodeType": "Block", + "src": "4372:80:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "expression": { + "id": 60719, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "4407:3:71", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 60720, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "4411:6:71", + "memberName": "sender", + "nodeType": "MemberAccess", + "src": "4407:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "id": 60721, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60712, + "src": "4419:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 60722, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60714, + "src": "4428:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 60718, + "name": "_verifyBlockBuilderProof", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60833, + "src": "4382:24:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint8_$_t_bytes32_$returns$__$", + "typeString": "function (address,uint8,bytes32)" + } + }, + "id": 60723, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4382:63:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60724, + "nodeType": "ExpressionStatement", + "src": "4382:63:71" + } + ] + }, + "baseFunctions": [ + 61940 + ], + "documentation": { + "id": 60710, + "nodeType": "StructuredDocumentation", + "src": "4240:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "b33d59da", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "verifyBlockBuilderProof", + "nameLocation": "4289:23:71", + "overrides": { + "id": 60716, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "4363:8:71" + }, + "parameters": { + "id": 60715, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60712, + "mutability": "mutable", + "name": "version", + "nameLocation": "4319:7:71", + "nodeType": "VariableDeclaration", + "scope": 60726, + "src": "4313:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 60711, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "4313:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60714, + "mutability": "mutable", + "name": "blockContentHash", + "nameLocation": "4336:16:71", + "nodeType": "VariableDeclaration", + "scope": 60726, + "src": "4328:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60713, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "4328:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "4312:41:71" + }, + "returnParameters": { + "id": 60717, + "nodeType": "ParameterList", + "parameters": [], + "src": "4372:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 60784, + "nodeType": "FunctionDefinition", + "src": "4498:725:71", + "nodes": [], + "body": { + "id": 60783, + "nodeType": "Block", + "src": "4675:548:71", + "nodes": [], + "statements": [ + { + "assignments": [ + 60740 + ], + "declarations": [ + { + "constant": false, + "id": 60740, + "mutability": "mutable", + "name": "digest", + "nameLocation": "4743:6:71", + "nodeType": "VariableDeclaration", + "scope": 60783, + "src": "4735:14:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60739, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "4735:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 60748, + "initialValue": { + "arguments": [ + { + "arguments": [ + { + "id": 60743, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60729, + "src": "4790:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 60744, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60731, + "src": "4799:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 60745, + "name": "nonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60733, + "src": "4817:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 60742, + "name": "computeStructHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61228, + "src": "4772:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint8_$_t_bytes32_$_t_uint256_$returns$_t_bytes32_$", + "typeString": "function (uint8,bytes32,uint256) pure returns (bytes32)" + } + }, + "id": 60746, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4772:51:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 60741, + "name": "getHashedTypeDataV4", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61205, + "src": "4752:19:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_bytes32_$returns$_t_bytes32_$", + "typeString": "function (bytes32) view returns (bytes32)" + } + }, + "id": 60747, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4752:72:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4735:89:71" + }, + { + "assignments": [ + 60750 + ], + "declarations": [ + { + "constant": false, + "id": 60750, + "mutability": "mutable", + "name": "teeAddress", + "nameLocation": "4842:10:71", + "nodeType": "VariableDeclaration", + "scope": 60783, + "src": "4834:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60749, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4834:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "id": 60755, + "initialValue": { + "arguments": [ + { + "id": 60753, + "name": "eip712Sig", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60735, + "src": "4870:9:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_calldata_ptr", + "typeString": "bytes calldata" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_calldata_ptr", + "typeString": "bytes calldata" + } + ], + "expression": { + "id": 60751, + "name": "digest", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60740, + "src": "4855:6:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "id": 60752, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "4862:7:71", + "memberName": "recover", + "nodeType": "MemberAccess", + "referencedDeclaration": 52154, + "src": "4855:14:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_bytes32_$_t_bytes_memory_ptr_$returns$_t_address_$attached_to$_t_bytes32_$", + "typeString": "function (bytes32,bytes memory) pure returns (address)" + } + }, + "id": 60754, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4855:25:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4834:46:71" + }, + { + "assignments": [ + 60757 + ], + "declarations": [ + { + "constant": false, + "id": 60757, + "mutability": "mutable", + "name": "expectedNonce", + "nameLocation": "4927:13:71", + "nodeType": "VariableDeclaration", + "scope": 60783, + "src": "4919:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 60756, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4919:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "id": 60761, + "initialValue": { + "baseExpression": { + "id": 60758, + "name": "nonces", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60645, + "src": "4943:6:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 60760, + "indexExpression": { + "id": 60759, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60750, + "src": "4950:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "4943:18:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4919:42:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 60765, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 60763, + "name": "nonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60733, + "src": "4979:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "id": 60764, + "name": "expectedNonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60757, + "src": "4988:13:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "4979:22:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [ + { + "id": 60767, + "name": "expectedNonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60757, + "src": "5016:13:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "id": 60768, + "name": "nonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60733, + "src": "5031:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 60766, + "name": "InvalidNonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61918, + "src": "5003:12:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$_t_uint256_$_t_uint256_$returns$_t_error_$", + "typeString": "function (uint256,uint256) pure returns (error)" + } + }, + "id": 60769, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5003:34:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 60762, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "4971:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 60770, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4971:67:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60771, + "nodeType": "ExpressionStatement", + "src": "4971:67:71" + }, + { + "expression": { + "id": 60775, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "5080:20:71", + "subExpression": { + "baseExpression": { + "id": 60772, + "name": "nonces", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60645, + "src": "5080:6:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 60774, + "indexExpression": { + "id": 60773, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60750, + "src": "5087:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "5080:18:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 60776, + "nodeType": "ExpressionStatement", + "src": "5080:20:71" + }, + { + "expression": { + "arguments": [ + { + "id": 60778, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60750, + "src": "5178:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "id": 60779, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60729, + "src": "5190:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 60780, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60731, + "src": "5199:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 60777, + "name": "_verifyBlockBuilderProof", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60833, + "src": "5153:24:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint8_$_t_bytes32_$returns$__$", + "typeString": "function (address,uint8,bytes32)" + } + }, + "id": 60781, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5153:63:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60782, + "nodeType": "ExpressionStatement", + "src": "5153:63:71" + } + ] + }, + "baseFunctions": [ + 61952 + ], + "documentation": { + "id": 60727, + "nodeType": "StructuredDocumentation", + "src": "4458:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "2dd8abfe", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "permitVerifyBlockBuilderProof", + "nameLocation": "4507:29:71", + "overrides": { + "id": 60737, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "4666:8:71" + }, + "parameters": { + "id": 60736, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60729, + "mutability": "mutable", + "name": "version", + "nameLocation": "4552:7:71", + "nodeType": "VariableDeclaration", + "scope": 60784, + "src": "4546:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 60728, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "4546:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60731, + "mutability": "mutable", + "name": "blockContentHash", + "nameLocation": "4577:16:71", + "nodeType": "VariableDeclaration", + "scope": 60784, + "src": "4569:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60730, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "4569:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60733, + "mutability": "mutable", + "name": "nonce", + "nameLocation": "4611:5:71", + "nodeType": "VariableDeclaration", + "scope": 60784, + "src": "4603:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 60732, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4603:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60735, + "mutability": "mutable", + "name": "eip712Sig", + "nameLocation": "4641:9:71", + "nodeType": "VariableDeclaration", + "scope": 60784, + "src": "4626:24:71", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_bytes_calldata_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 60734, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "4626:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "visibility": "internal" + } + ], + "src": "4536:120:71" + }, + "returnParameters": { + "id": 60738, + "nodeType": "ParameterList", + "parameters": [], + "src": "4675:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 60833, + "nodeType": "FunctionDefinition", + "src": "5648:1056:71", + "nodes": [], + "body": { + "id": 60832, + "nodeType": "Block", + "src": "5752:952:71", + "nodes": [], + "statements": [ + { + "assignments": [ + 60795, + 60798 + ], + "declarations": [ + { + "constant": false, + "id": 60795, + "mutability": "mutable", + "name": "allowed", + "nameLocation": "5866:7:71", + "nodeType": "VariableDeclaration", + "scope": 60832, + "src": "5861:12:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60794, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "5861:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60798, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "5886:10:71", + "nodeType": "VariableDeclaration", + "scope": 60832, + "src": "5875:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60797, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60796, + "name": "WorkloadId", + "nameLocations": [ + "5875:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "5875:10:71" + }, + "referencedDeclaration": 61861, + "src": "5875:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "id": 60802, + "initialValue": { + "arguments": [ + { + "id": 60800, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60787, + "src": "5923:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60799, + "name": "_cachedIsAllowedPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61013, + "src": "5900:22:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (address) returns (bool,WorkloadId)" + } + }, + "id": 60801, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5900:34:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5860:74:71" + }, + { + "expression": { + "arguments": [ + { + "id": 60804, + "name": "allowed", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60795, + "src": "5952:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [ + { + "id": 60806, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60787, + "src": "5986:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60805, + "name": "UnauthorizedBlockBuilder", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61911, + "src": "5961:24:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$_t_address_$returns$_t_error_$", + "typeString": "function (address) pure returns (error)" + } + }, + "id": 60807, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5961:36:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 60803, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5944:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 60808, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5944:54:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60809, + "nodeType": "ExpressionStatement", + "src": "5944:54:71" + }, + { + "assignments": [ + 60811 + ], + "declarations": [ + { + "constant": false, + "id": 60811, + "mutability": "mutable", + "name": "workloadKey", + "nameLocation": "6472:11:71", + "nodeType": "VariableDeclaration", + "scope": 60832, + "src": "6464:19:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60810, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "6464:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 60816, + "initialValue": { + "arguments": [ + { + "id": 60814, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60798, + "src": "6504:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 60812, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "6486:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60813, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "6497:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "6486:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 60815, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "6486:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6464:51:71" + }, + { + "assignments": [ + 60818 + ], + "declarations": [ + { + "constant": false, + "id": 60818, + "mutability": "mutable", + "name": "commitHash", + "nameLocation": "6539:10:71", + "nodeType": "VariableDeclaration", + "scope": 60832, + "src": "6525:24:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 60817, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "6525:6:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "id": 60823, + "initialValue": { + "expression": { + "baseExpression": { + "id": 60819, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "6552:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 60821, + "indexExpression": { + "id": 60820, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60811, + "src": "6570:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "6552:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 60822, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "6583:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "6552:41:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6525:68:71" + }, + { + "eventCall": { + "arguments": [ + { + "id": 60825, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60787, + "src": "6634:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "id": 60826, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60811, + "src": "6646:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 60827, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60789, + "src": "6659:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 60828, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60791, + "src": "6668:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 60829, + "name": "commitHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60818, + "src": "6686:10:71", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + ], + "id": 60824, + "name": "BlockBuilderProofVerified", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61897, + "src": "6608:25:71", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_bytes32_$_t_uint8_$_t_bytes32_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (address,bytes32,uint8,bytes32,string memory)" + } + }, + "id": 60830, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "6608:89:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60831, + "nodeType": "EmitStatement", + "src": "6603:94:71" + } + ] + }, + "documentation": { + "id": 60785, + "nodeType": "StructuredDocumentation", + "src": "5229:414:71", + "text": "@notice Internal function to verify a block builder proof\n @dev This function is internal because it is only used by the permitVerifyBlockBuilderProof function\n and it is not needed to be called by other contracts\n @param teeAddress The TEE-controlled address\n @param version The version of the flashtestation's protocol\n @param blockContentHash The hash of the block content" + }, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "_verifyBlockBuilderProof", + "nameLocation": "5657:24:71", + "parameters": { + "id": 60792, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60787, + "mutability": "mutable", + "name": "teeAddress", + "nameLocation": "5690:10:71", + "nodeType": "VariableDeclaration", + "scope": 60833, + "src": "5682:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60786, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5682:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60789, + "mutability": "mutable", + "name": "version", + "nameLocation": "5708:7:71", + "nodeType": "VariableDeclaration", + "scope": 60833, + "src": "5702:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 60788, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "5702:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60791, + "mutability": "mutable", + "name": "blockContentHash", + "nameLocation": "5725:16:71", + "nodeType": "VariableDeclaration", + "scope": 60833, + "src": "5717:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60790, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "5717:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "5681:61:71" + }, + "returnParameters": { + "id": 60793, + "nodeType": "ParameterList", + "parameters": [], + "src": "5752:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "internal" + }, + { + "id": 60903, + "nodeType": "FunctionDefinition", + "src": "6750:906:71", + "nodes": [], + "body": { + "id": 60902, + "nodeType": "Block", + "src": "6851:805:71", + "nodes": [], + "statements": [ + { + "assignments": [ + null, + 60849 + ], + "declarations": [ + null, + { + "constant": false, + "id": 60849, + "mutability": "mutable", + "name": "registration", + "nameLocation": "6971:12:71", + "nodeType": "VariableDeclaration", + "scope": 60902, + "src": "6926:57:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE" + }, + "typeName": { + "id": 60848, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60847, + "name": "IFlashtestationRegistry.RegisteredTEE", + "nameLocations": [ + "6926:23:71", + "6950:13:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 62069, + "src": "6926:37:71" + }, + "referencedDeclaration": 62069, + "src": "6926:37:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_storage_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE" + } + }, + "visibility": "internal" + } + ], + "id": 60856, + "initialValue": { + "arguments": [ + { + "id": 60854, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60836, + "src": "7048:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "arguments": [ + { + "id": 60851, + "name": "registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60640, + "src": "7022:8:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60850, + "name": "FlashtestationRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61842, + "src": "6999:22:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_FlashtestationRegistry_$61842_$", + "typeString": "type(contract FlashtestationRegistry)" + } + }, + "id": 60852, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "6999:32:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_FlashtestationRegistry_$61842", + "typeString": "contract FlashtestationRegistry" + } + }, + "id": 60853, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "7032:15:71", + "memberName": "getRegistration", + "nodeType": "MemberAccess", + "referencedDeclaration": 61657, + "src": "6999:48:71", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_bool_$_t_struct$_RegisteredTEE_$62069_memory_ptr_$", + "typeString": "function (address) view external returns (bool,struct IFlashtestationRegistry.RegisteredTEE memory)" + } + }, + "id": 60855, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "6999:60:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_struct$_RegisteredTEE_$62069_memory_ptr_$", + "typeString": "tuple(bool,struct IFlashtestationRegistry.RegisteredTEE memory)" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6923:136:71" + }, + { + "condition": { + "id": 60859, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "!", + "prefix": true, + "src": "7230:21:71", + "subExpression": { + "expression": { + "id": 60857, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60849, + "src": "7231:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 60858, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "7244:7:71", + "memberName": "isValid", + "nodeType": "MemberAccess", + "referencedDeclaration": 62059, + "src": "7231:20:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 60868, + "nodeType": "IfStatement", + "src": "7226:86:71", + "trueBody": { + "id": 60867, + "nodeType": "Block", + "src": "7253:59:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "66616c7365", + "id": 60860, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7275:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + { + "arguments": [ + { + "hexValue": "30", + "id": 60863, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7298:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "id": 60861, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "7282:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60862, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "7293:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "7282:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 60864, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7282:18:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60865, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "7274:27:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60844, + "id": 60866, + "nodeType": "Return", + "src": "7267:34:71" + } + ] + } + }, + { + "assignments": [ + 60871 + ], + "declarations": [ + { + "constant": false, + "id": 60871, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "7333:10:71", + "nodeType": "VariableDeclaration", + "scope": 60902, + "src": "7322:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60870, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60869, + "name": "WorkloadId", + "nameLocations": [ + "7322:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "7322:10:71" + }, + "referencedDeclaration": 61861, + "src": "7322:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "id": 60875, + "initialValue": { + "arguments": [ + { + "id": 60873, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60849, + "src": "7374:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + ], + "id": 60872, + "name": "workloadIdForTDRegistration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61059, + "src": "7346:27:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_struct$_RegisteredTEE_$62069_memory_ptr_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (struct IFlashtestationRegistry.RegisteredTEE memory) pure returns (WorkloadId)" + } + }, + "id": 60874, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7346:41:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "7322:65:71" + }, + { + "condition": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 60888, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "expression": { + "baseExpression": { + "id": 60878, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "7482:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 60883, + "indexExpression": { + "arguments": [ + { + "id": 60881, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60871, + "src": "7518:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 60879, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "7500:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60880, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "7511:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "7500:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 60882, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7500:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7482:48:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 60884, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "7531:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "7482:59:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + ], + "id": 60877, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "7476:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 60876, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "7476:5:71", + "typeDescriptions": {} + } + }, + "id": 60885, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7476:66:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes storage pointer" + } + }, + "id": 60886, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "7543:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "7476:73:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 60887, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7552:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "7476:77:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 60894, + "nodeType": "IfStatement", + "src": "7472:133:71", + "trueBody": { + "id": 60893, + "nodeType": "Block", + "src": "7555:50:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "74727565", + "id": 60889, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7577:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + { + "id": 60890, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60871, + "src": "7583:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60891, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "7576:18:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60844, + "id": 60892, + "nodeType": "Return", + "src": "7569:25:71" + } + ] + } + }, + { + "expression": { + "components": [ + { + "hexValue": "66616c7365", + "id": 60895, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7623:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + { + "arguments": [ + { + "hexValue": "30", + "id": 60898, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7646:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "id": 60896, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "7630:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60897, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "7641:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "7630:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 60899, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7630:18:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60900, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "7622:27:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60844, + "id": 60901, + "nodeType": "Return", + "src": "7615:34:71" + } + ] + }, + "baseFunctions": [ + 61963 + ], + "documentation": { + "id": 60834, + "nodeType": "StructuredDocumentation", + "src": "6710:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "d2753561", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isAllowedPolicy", + "nameLocation": "6759:15:71", + "overrides": { + "id": 60838, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "6807:8:71" + }, + "parameters": { + "id": 60837, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60836, + "mutability": "mutable", + "name": "teeAddress", + "nameLocation": "6783:10:71", + "nodeType": "VariableDeclaration", + "scope": 60903, + "src": "6775:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60835, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "6775:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "6774:20:71" + }, + "returnParameters": { + "id": 60844, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60840, + "mutability": "mutable", + "name": "allowed", + "nameLocation": "6830:7:71", + "nodeType": "VariableDeclaration", + "scope": 60903, + "src": "6825:12:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60839, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "6825:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60843, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 60903, + "src": "6839:10:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60842, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60841, + "name": "WorkloadId", + "nameLocations": [ + "6839:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "6839:10:71" + }, + "referencedDeclaration": 61861, + "src": "6839:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "6824:26:71" + }, + "scope": 61239, + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "id": 61013, + "nodeType": "FunctionDefinition", + "src": "8892:1574:71", + "nodes": [], + "body": { + "id": 61012, + "nodeType": "Block", + "src": "8979:1487:71", + "nodes": [], + "statements": [ + { + "assignments": [ + 60915, + 60917 + ], + "declarations": [ + { + "constant": false, + "id": 60915, + "mutability": "mutable", + "name": "isValid", + "nameLocation": "9054:7:71", + "nodeType": "VariableDeclaration", + "scope": 61012, + "src": "9049:12:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60914, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "9049:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60917, + "mutability": "mutable", + "name": "quoteHash", + "nameLocation": "9071:9:71", + "nodeType": "VariableDeclaration", + "scope": 61012, + "src": "9063:17:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60916, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "9063:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 60924, + "initialValue": { + "arguments": [ + { + "id": 60922, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60906, + "src": "9139:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "arguments": [ + { + "id": 60919, + "name": "registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60640, + "src": "9107:8:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60918, + "name": "FlashtestationRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61842, + "src": "9084:22:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_FlashtestationRegistry_$61842_$", + "typeString": "type(contract FlashtestationRegistry)" + } + }, + "id": 60920, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9084:32:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_FlashtestationRegistry_$61842", + "typeString": "contract FlashtestationRegistry" + } + }, + "id": 60921, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9117:21:71", + "memberName": "getRegistrationStatus", + "nodeType": "MemberAccess", + "referencedDeclaration": 61682, + "src": "9084:54:71", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_bool_$_t_bytes32_$", + "typeString": "function (address) view external returns (bool,bytes32)" + } + }, + "id": 60923, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9084:66:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_bytes32_$", + "typeString": "tuple(bool,bytes32)" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9048:102:71" + }, + { + "condition": { + "id": 60926, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "!", + "prefix": true, + "src": "9164:8:71", + "subExpression": { + "id": 60925, + "name": "isValid", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60915, + "src": "9165:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 60935, + "nodeType": "IfStatement", + "src": "9160:73:71", + "trueBody": { + "id": 60934, + "nodeType": "Block", + "src": "9174:59:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "66616c7365", + "id": 60927, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9196:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + { + "arguments": [ + { + "hexValue": "30", + "id": 60930, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9219:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "id": 60928, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "9203:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60929, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "9214:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "9203:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 60931, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9203:18:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60932, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "9195:27:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60913, + "id": 60933, + "nodeType": "Return", + "src": "9188:34:71" + } + ] + } + }, + { + "assignments": [ + 60938 + ], + "declarations": [ + { + "constant": false, + "id": 60938, + "mutability": "mutable", + "name": "cached", + "nameLocation": "9329:6:71", + "nodeType": "VariableDeclaration", + "scope": 61012, + "src": "9307:28:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload" + }, + "typeName": { + "id": 60937, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60936, + "name": "CachedWorkload", + "nameLocations": [ + "9307:14:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 60611, + "src": "9307:14:71" + }, + "referencedDeclaration": 60611, + "src": "9307:14:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage_ptr", + "typeString": "struct CachedWorkload" + } + }, + "visibility": "internal" + } + ], + "id": 60942, + "initialValue": { + "baseExpression": { + "id": 60939, + "name": "cachedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60651, + "src": "9338:15:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_CachedWorkload_$60611_storage_$", + "typeString": "mapping(address => struct CachedWorkload storage ref)" + } + }, + "id": 60941, + "indexExpression": { + "id": 60940, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60906, + "src": "9354:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "9338:27:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage", + "typeString": "struct CachedWorkload storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9307:58:71" + }, + { + "assignments": [ + 60944 + ], + "declarations": [ + { + "constant": false, + "id": 60944, + "mutability": "mutable", + "name": "cachedWorkloadId", + "nameLocation": "9467:16:71", + "nodeType": "VariableDeclaration", + "scope": 61012, + "src": "9459:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60943, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "9459:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 60950, + "initialValue": { + "arguments": [ + { + "expression": { + "id": 60947, + "name": "cached", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60938, + "src": "9504:6:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload memory" + } + }, + "id": 60948, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9511:10:71", + "memberName": "workloadId", + "nodeType": "MemberAccess", + "referencedDeclaration": 60607, + "src": "9504:17:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 60945, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "9486:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60946, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "9497:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "9486:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 60949, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9486:36:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9459:63:71" + }, + { + "condition": { + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 60958, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "commonType": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "id": 60953, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 60951, + "name": "cachedWorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60944, + "src": "9536:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "hexValue": "30", + "id": 60952, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9556:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "9536:21:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "commonType": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "id": 60957, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "id": 60954, + "name": "cached", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60938, + "src": "9561:6:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload memory" + } + }, + "id": 60955, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9568:9:71", + "memberName": "quoteHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 60610, + "src": "9561:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "id": 60956, + "name": "quoteHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60917, + "src": "9581:9:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "src": "9561:29:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "9536:54:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "id": 61010, + "nodeType": "Block", + "src": "10031:429:71", + "statements": [ + { + "assignments": [ + 60986, + 60989 + ], + "declarations": [ + { + "constant": false, + "id": 60986, + "mutability": "mutable", + "name": "allowed", + "nameLocation": "10136:7:71", + "nodeType": "VariableDeclaration", + "scope": 61010, + "src": "10131:12:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60985, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10131:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60989, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "10156:10:71", + "nodeType": "VariableDeclaration", + "scope": 61010, + "src": "10145:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60988, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60987, + "name": "WorkloadId", + "nameLocations": [ + "10145:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "10145:10:71" + }, + "referencedDeclaration": 61861, + "src": "10145:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "id": 60993, + "initialValue": { + "arguments": [ + { + "id": 60991, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60906, + "src": "10186:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60990, + "name": "isAllowedPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60903, + "src": "10170:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (address) view returns (bool,WorkloadId)" + } + }, + "id": 60992, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "10170:27:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "10130:67:71" + }, + { + "condition": { + "id": 60994, + "name": "allowed", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60986, + "src": "10216:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 61005, + "nodeType": "IfStatement", + "src": "10212:195:71", + "trueBody": { + "id": 61004, + "nodeType": "Block", + "src": "10225:182:71", + "statements": [ + { + "expression": { + "id": 61002, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "baseExpression": { + "id": 60995, + "name": "cachedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60651, + "src": "10300:15:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_CachedWorkload_$60611_storage_$", + "typeString": "mapping(address => struct CachedWorkload storage ref)" + } + }, + "id": 60997, + "indexExpression": { + "id": 60996, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60906, + "src": "10316:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "10300:27:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage", + "typeString": "struct CachedWorkload storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "arguments": [ + { + "id": 60999, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60989, + "src": "10358:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + { + "id": 61000, + "name": "quoteHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60917, + "src": "10381:9:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 60998, + "name": "CachedWorkload", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60611, + "src": "10330:14:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_struct$_CachedWorkload_$60611_storage_ptr_$", + "typeString": "type(struct CachedWorkload storage pointer)" + } + }, + "id": 61001, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "structConstructorCall", + "lValueRequested": false, + "nameLocations": [ + "10346:10:71", + "10370:9:71" + ], + "names": [ + "workloadId", + "quoteHash" + ], + "nodeType": "FunctionCall", + "src": "10330:62:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload memory" + } + }, + "src": "10300:92:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage", + "typeString": "struct CachedWorkload storage ref" + } + }, + "id": 61003, + "nodeType": "ExpressionStatement", + "src": "10300:92:71" + } + ] + } + }, + { + "expression": { + "components": [ + { + "id": 61006, + "name": "allowed", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60986, + "src": "10429:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "id": 61007, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60989, + "src": "10438:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 61008, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "10428:21:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60913, + "id": 61009, + "nodeType": "Return", + "src": "10421:28:71" + } + ] + }, + "id": 61011, + "nodeType": "IfStatement", + "src": "9532:928:71", + "trueBody": { + "id": 60984, + "nodeType": "Block", + "src": "9592:433:71", + "statements": [ + { + "condition": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 60968, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "expression": { + "baseExpression": { + "id": 60961, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "9715:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 60963, + "indexExpression": { + "id": 60962, + "name": "cachedWorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60944, + "src": "9733:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "9715:35:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 60964, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9751:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "9715:46:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + ], + "id": 60960, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "9709:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 60959, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "9709:5:71", + "typeDescriptions": {} + } + }, + "id": 60965, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9709:53:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes storage pointer" + } + }, + "id": 60966, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9763:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "9709:60:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 60967, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9772:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "9709:64:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "id": 60982, + "nodeType": "Block", + "src": "9846:169:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "66616c7365", + "id": 60975, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9974:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + { + "arguments": [ + { + "hexValue": "30", + "id": 60978, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9997:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "id": 60976, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "9981:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60977, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "9992:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "9981:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 60979, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9981:18:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60980, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "9973:27:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60913, + "id": 60981, + "nodeType": "Return", + "src": "9966:34:71" + } + ] + }, + "id": 60983, + "nodeType": "IfStatement", + "src": "9705:310:71", + "trueBody": { + "id": 60974, + "nodeType": "Block", + "src": "9775:65:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "74727565", + "id": 60969, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9801:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + { + "expression": { + "id": 60970, + "name": "cached", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60938, + "src": "9807:6:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload memory" + } + }, + "id": 60971, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9814:10:71", + "memberName": "workloadId", + "nodeType": "MemberAccess", + "referencedDeclaration": 60607, + "src": "9807:17:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60972, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "9800:25:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60913, + "id": 60973, + "nodeType": "Return", + "src": "9793:32:71" + } + ] + } + } + ] + } + } + ] + }, + "documentation": { + "id": 60904, + "nodeType": "StructuredDocumentation", + "src": "7662:1225:71", + "text": "@notice isAllowedPolicy but with caching to reduce gas costs\n @dev This function is only used by the verifyBlockBuilderProof function, which needs to be as efficient as possible\n because it is called onchain for every flashblock. The workloadId is cached to avoid expensive recomputation\n @dev A careful reader will notice that this function does not delete stale cache entries. It overwrites them\n if the underlying TEE registration is still valid. But for stale cache entries in every other scenario, the\n cache entry persists indefinitely. This is because every other instance results in a return value of (false, 0)\n to the caller (which is always the verifyBlockBuilderProof function) and it immediately reverts. This is an unfortunate\n consequence of our need to make this function as gas-efficient as possible, otherwise we would try to cleanup\n stale cache entries\n @param teeAddress The TEE-controlled address\n @return True if the TEE is using an approved workload in the policy\n @return The workloadId of the TEE that is using an approved workload in the policy, or 0 if\n the TEE is not using an approved workload in the policy" + }, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "_cachedIsAllowedPolicy", + "nameLocation": "8901:22:71", + "parameters": { + "id": 60907, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60906, + "mutability": "mutable", + "name": "teeAddress", + "nameLocation": "8932:10:71", + "nodeType": "VariableDeclaration", + "scope": 61013, + "src": "8924:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60905, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8924:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "8923:20:71" + }, + "returnParameters": { + "id": 60913, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60909, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61013, + "src": "8961:4:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60908, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "8961:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60912, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61013, + "src": "8967:10:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60911, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60910, + "name": "WorkloadId", + "nameLocations": [ + "8967:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "8967:10:71" + }, + "referencedDeclaration": 61861, + "src": "8967:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "8960:18:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "private" + }, + { + "id": 61059, + "nodeType": "FunctionDefinition", + "src": "10512:815:71", + "nodes": [], + "body": { + "id": 61058, + "nodeType": "Block", + "src": "10686:641:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "arguments": [ + { + "arguments": [ + { + "expression": { + "expression": { + "id": 61030, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "10793:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61031, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10806:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "10793:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61032, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10823:4:71", + "memberName": "mrTd", + "nodeType": "MemberAccess", + "referencedDeclaration": 146, + "src": "10793:34:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61033, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "10849:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61034, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10862:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "10849:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61035, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10879:5:71", + "memberName": "rtMr0", + "nodeType": "MemberAccess", + "referencedDeclaration": 154, + "src": "10849:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61036, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "10906:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61037, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10919:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "10906:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61038, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10936:5:71", + "memberName": "rtMr1", + "nodeType": "MemberAccess", + "referencedDeclaration": 156, + "src": "10906:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61039, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "10963:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61040, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10976:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "10963:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61041, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10993:5:71", + "memberName": "rtMr2", + "nodeType": "MemberAccess", + "referencedDeclaration": 158, + "src": "10963:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61042, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "11020:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61043, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11033:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "11020:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61044, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11050:5:71", + "memberName": "rtMr3", + "nodeType": "MemberAccess", + "referencedDeclaration": 160, + "src": "11020:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61045, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "11118:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61046, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11131:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "11118:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61047, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11148:10:71", + "memberName": "mrConfigId", + "nodeType": "MemberAccess", + "referencedDeclaration": 148, + "src": "11118:40:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61048, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "11180:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61049, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11193:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "11180:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61050, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11210:4:71", + "memberName": "xFAM", + "nodeType": "MemberAccess", + "referencedDeclaration": 144, + "src": "11180:34:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes8", + "typeString": "bytes8" + } + }, + { + "expression": { + "expression": { + "id": 61051, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "11236:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61052, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11249:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "11236:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61053, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11266:12:71", + "memberName": "tdAttributes", + "nodeType": "MemberAccess", + "referencedDeclaration": 142, + "src": "11236:42:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes8", + "typeString": "bytes8" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes8", + "typeString": "bytes8" + }, + { + "typeIdentifier": "t_bytes8", + "typeString": "bytes8" + } + ], + "expression": { + "id": 61028, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "10759:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 61027, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "10759:5:71", + "typeDescriptions": {} + } + }, + "id": 61029, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10765:6:71", + "memberName": "concat", + "nodeType": "MemberAccess", + "src": "10759:12:71", + "typeDescriptions": { + "typeIdentifier": "t_function_bytesconcat_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 61054, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "10759:537:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 61026, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "10732:9:71", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 61055, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "10732:578:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "id": 61024, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "10703:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 61025, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "10714:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "10703:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 61056, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "10703:617:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "functionReturnParameters": 61023, + "id": 61057, + "nodeType": "Return", + "src": "10696:624:71" + } + ] + }, + "baseFunctions": [ + 61973 + ], + "documentation": { + "id": 61014, + "nodeType": "StructuredDocumentation", + "src": "10472:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "4d37fc7a", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "workloadIdForTDRegistration", + "nameLocation": "10521:27:71", + "overrides": { + "id": 61019, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "10644:8:71" + }, + "parameters": { + "id": 61018, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61017, + "mutability": "mutable", + "name": "registration", + "nameLocation": "10594:12:71", + "nodeType": "VariableDeclaration", + "scope": 61059, + "src": "10549:57:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE" + }, + "typeName": { + "id": 61016, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61015, + "name": "IFlashtestationRegistry.RegisteredTEE", + "nameLocations": [ + "10549:23:71", + "10573:13:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 62069, + "src": "10549:37:71" + }, + "referencedDeclaration": 62069, + "src": "10549:37:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_storage_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE" + } + }, + "visibility": "internal" + } + ], + "src": "10548:59:71" + }, + "returnParameters": { + "id": 61023, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61022, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61059, + "src": "10670:10:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 61021, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61020, + "name": "WorkloadId", + "nameLocations": [ + "10670:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "10670:10:71" + }, + "referencedDeclaration": 61861, + "src": "10670:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "10669:12:71" + }, + "scope": 61239, + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "id": 61131, + "nodeType": "FunctionDefinition", + "src": "11373:730:71", + "nodes": [], + "body": { + "id": 61130, + "nodeType": "Block", + "src": "11543:560:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 61081, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "id": 61077, + "name": "commitHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61065, + "src": "11567:10:71", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + ], + "id": 61076, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "11561:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 61075, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "11561:5:71", + "typeDescriptions": {} + } + }, + "id": 61078, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11561:17:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_calldata_ptr", + "typeString": "bytes calldata" + } + }, + "id": 61079, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11579:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "11561:24:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 61080, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "11588:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "11561:28:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61082, + "name": "EmptyCommitHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61921, + "src": "11591:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 61083, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11591:17:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 61074, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "11553:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 61084, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11553:56:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61085, + "nodeType": "ExpressionStatement", + "src": "11553:56:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 61090, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "id": 61087, + "name": "sourceLocators", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61068, + "src": "11627:14:71", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_string_calldata_ptr_$dyn_calldata_ptr", + "typeString": "string calldata[] calldata" + } + }, + "id": 61088, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11642:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "11627:21:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 61089, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "11651:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "11627:25:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61091, + "name": "EmptySourceLocators", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61924, + "src": "11654:19:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 61092, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11654:21:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 61086, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "11619:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 61093, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11619:57:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61094, + "nodeType": "ExpressionStatement", + "src": "11619:57:71" + }, + { + "assignments": [ + 61096 + ], + "declarations": [ + { + "constant": false, + "id": 61096, + "mutability": "mutable", + "name": "workloadKey", + "nameLocation": "11695:11:71", + "nodeType": "VariableDeclaration", + "scope": 61130, + "src": "11687:19:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61095, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "11687:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 61101, + "initialValue": { + "arguments": [ + { + "id": 61099, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61063, + "src": "11727:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 61097, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "11709:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 61098, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "11720:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "11709:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 61100, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11709:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "11687:51:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 61112, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "expression": { + "baseExpression": { + "id": 61105, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "11807:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61107, + "indexExpression": { + "id": 61106, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61096, + "src": "11825:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "11807:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 61108, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11838:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "11807:41:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + ], + "id": 61104, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "11801:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 61103, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "11801:5:71", + "typeDescriptions": {} + } + }, + "id": 61109, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11801:48:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes storage pointer" + } + }, + "id": 61110, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11850:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "11801:55:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "hexValue": "30", + "id": 61111, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "11860:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "11801:60:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61113, + "name": "WorkloadAlreadyInPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61903, + "src": "11863:23:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 61114, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11863:25:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 61102, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "11793:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 61115, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11793:96:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61116, + "nodeType": "ExpressionStatement", + "src": "11793:96:71" + }, + { + "expression": { + "id": 61124, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "baseExpression": { + "id": 61117, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "11939:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61119, + "indexExpression": { + "id": 61118, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61096, + "src": "11957:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "11939:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "arguments": [ + { + "id": 61121, + "name": "commitHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61065, + "src": "12002:10:71", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + { + "id": 61122, + "name": "sourceLocators", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61068, + "src": "12030:14:71", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_string_calldata_ptr_$dyn_calldata_ptr", + "typeString": "string calldata[] calldata" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + }, + { + "typeIdentifier": "t_array$_t_string_calldata_ptr_$dyn_calldata_ptr", + "typeString": "string calldata[] calldata" + } + ], + "id": 61120, + "name": "WorkloadMetadata", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61869, + "src": "11972:16:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_struct$_WorkloadMetadata_$61869_storage_ptr_$", + "typeString": "type(struct IBlockBuilderPolicy.WorkloadMetadata storage pointer)" + } + }, + "id": 61123, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "structConstructorCall", + "lValueRequested": false, + "nameLocations": [ + "11990:10:71", + "12014:14:71" + ], + "names": [ + "commitHash", + "sourceLocators" + ], + "nodeType": "FunctionCall", + "src": "11972:74:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_memory_ptr", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata memory" + } + }, + "src": "11939:107:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 61125, + "nodeType": "ExpressionStatement", + "src": "11939:107:71" + }, + { + "eventCall": { + "arguments": [ + { + "id": 61127, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61096, + "src": "12084:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 61126, + "name": "WorkloadAddedToPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61874, + "src": "12062:21:71", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_bytes32_$returns$__$", + "typeString": "function (bytes32)" + } + }, + "id": 61128, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12062:34:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61129, + "nodeType": "EmitStatement", + "src": "12057:39:71" + } + ] + }, + "baseFunctions": [ + 61985 + ], + "documentation": { + "id": 61060, + "nodeType": "StructuredDocumentation", + "src": "11333:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "4f3a415a", + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 61072, + "kind": "modifierInvocation", + "modifierName": { + "id": 61071, + "name": "onlyOwner", + "nameLocations": [ + "11529:9:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48019, + "src": "11529:9:71" + }, + "nodeType": "ModifierInvocation", + "src": "11529:9:71" + } + ], + "name": "addWorkloadToPolicy", + "nameLocation": "11382:19:71", + "overrides": { + "id": 61070, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "11512:8:71" + }, + "parameters": { + "id": 61069, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61063, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "11413:10:71", + "nodeType": "VariableDeclaration", + "scope": 61131, + "src": "11402:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 61062, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61061, + "name": "WorkloadId", + "nameLocations": [ + "11402:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "11402:10:71" + }, + "referencedDeclaration": 61861, + "src": "11402:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61065, + "mutability": "mutable", + "name": "commitHash", + "nameLocation": "11441:10:71", + "nodeType": "VariableDeclaration", + "scope": 61131, + "src": "11425:26:71", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 61064, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "11425:6:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61068, + "mutability": "mutable", + "name": "sourceLocators", + "nameLocation": "11471:14:71", + "nodeType": "VariableDeclaration", + "scope": 61131, + "src": "11453:32:71", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_string_calldata_ptr_$dyn_calldata_ptr", + "typeString": "string[]" + }, + "typeName": { + "baseType": { + "id": 61066, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "11453:6:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "id": 61067, + "nodeType": "ArrayTypeName", + "src": "11453:8:71", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_string_storage_$dyn_storage_ptr", + "typeString": "string[]" + } + }, + "visibility": "internal" + } + ], + "src": "11401:85:71" + }, + "returnParameters": { + "id": 61073, + "nodeType": "ParameterList", + "parameters": [], + "src": "11543:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 61173, + "nodeType": "FunctionDefinition", + "src": "12149:433:71", + "nodes": [], + "body": { + "id": 61172, + "nodeType": "Block", + "src": "12234:348:71", + "nodes": [], + "statements": [ + { + "assignments": [ + 61142 + ], + "declarations": [ + { + "constant": false, + "id": 61142, + "mutability": "mutable", + "name": "workloadKey", + "nameLocation": "12252:11:71", + "nodeType": "VariableDeclaration", + "scope": 61172, + "src": "12244:19:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61141, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12244:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 61147, + "initialValue": { + "arguments": [ + { + "id": 61145, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61135, + "src": "12284:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 61143, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "12266:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 61144, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "12277:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "12266:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 61146, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12266:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "12244:51:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 61158, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "expression": { + "baseExpression": { + "id": 61151, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "12356:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61153, + "indexExpression": { + "id": 61152, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61142, + "src": "12374:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "12356:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 61154, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "12387:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "12356:41:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + ], + "id": 61150, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "12350:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 61149, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "12350:5:71", + "typeDescriptions": {} + } + }, + "id": 61155, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12350:48:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes storage pointer" + } + }, + "id": 61156, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "12399:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "12350:55:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 61157, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12408:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "12350:59:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61159, + "name": "WorkloadNotInPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61906, + "src": "12411:19:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 61160, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12411:21:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 61148, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "12342:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 61161, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12342:91:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61162, + "nodeType": "ExpressionStatement", + "src": "12342:91:71" + }, + { + "expression": { + "id": 61166, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "delete", + "prefix": true, + "src": "12484:37:71", + "subExpression": { + "baseExpression": { + "id": 61163, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "12491:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61165, + "indexExpression": { + "id": 61164, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61142, + "src": "12509:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "12491:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61167, + "nodeType": "ExpressionStatement", + "src": "12484:37:71" + }, + { + "eventCall": { + "arguments": [ + { + "id": 61169, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61142, + "src": "12563:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 61168, + "name": "WorkloadRemovedFromPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61879, + "src": "12537:25:71", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_bytes32_$returns$__$", + "typeString": "function (bytes32)" + } + }, + "id": 61170, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12537:38:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61171, + "nodeType": "EmitStatement", + "src": "12532:43:71" + } + ] + }, + "baseFunctions": [ + 61992 + ], + "documentation": { + "id": 61132, + "nodeType": "StructuredDocumentation", + "src": "12109:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "5c40e542", + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 61139, + "kind": "modifierInvocation", + "modifierName": { + "id": 61138, + "name": "onlyOwner", + "nameLocations": [ + "12224:9:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48019, + "src": "12224:9:71" + }, + "nodeType": "ModifierInvocation", + "src": "12224:9:71" + } + ], + "name": "removeWorkloadFromPolicy", + "nameLocation": "12158:24:71", + "overrides": { + "id": 61137, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "12215:8:71" + }, + "parameters": { + "id": 61136, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61135, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "12194:10:71", + "nodeType": "VariableDeclaration", + "scope": 61173, + "src": "12183:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 61134, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61133, + "name": "WorkloadId", + "nameLocations": [ + "12183:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "12183:10:71" + }, + "referencedDeclaration": 61861, + "src": "12183:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "12182:23:71" + }, + "returnParameters": { + "id": 61140, + "nodeType": "ParameterList", + "parameters": [], + "src": "12234:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 61192, + "nodeType": "FunctionDefinition", + "src": "12628:181:71", + "nodes": [], + "body": { + "id": 61191, + "nodeType": "Block", + "src": "12737:72:71", + "nodes": [], + "statements": [ + { + "expression": { + "baseExpression": { + "id": 61184, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "12754:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61189, + "indexExpression": { + "arguments": [ + { + "id": 61187, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61177, + "src": "12790:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 61185, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "12772:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 61186, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "12783:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "12772:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 61188, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12772:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "12754:48:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "functionReturnParameters": 61183, + "id": 61190, + "nodeType": "Return", + "src": "12747:55:71" + } + ] + }, + "baseFunctions": [ + 62002 + ], + "documentation": { + "id": 61174, + "nodeType": "StructuredDocumentation", + "src": "12588:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "abd45d21", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getWorkloadMetadata", + "nameLocation": "12637:19:71", + "overrides": { + "id": 61179, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "12694:8:71" + }, + "parameters": { + "id": 61178, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61177, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "12668:10:71", + "nodeType": "VariableDeclaration", + "scope": 61192, + "src": "12657:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 61176, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61175, + "name": "WorkloadId", + "nameLocations": [ + "12657:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "12657:10:71" + }, + "referencedDeclaration": 61861, + "src": "12657:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "12656:23:71" + }, + "returnParameters": { + "id": 61183, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61182, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61192, + "src": "12712:23:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_memory_ptr", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata" + }, + "typeName": { + "id": 61181, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61180, + "name": "WorkloadMetadata", + "nameLocations": [ + "12712:16:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61869, + "src": "12712:16:71" + }, + "referencedDeclaration": 61869, + "src": "12712:16:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage_ptr", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata" + } + }, + "visibility": "internal" + } + ], + "src": "12711:25:71" + }, + "scope": 61239, + "stateMutability": "view", + "virtual": false, + "visibility": "external" + }, + { + "id": 61205, + "nodeType": "FunctionDefinition", + "src": "12855:131:71", + "nodes": [], + "body": { + "id": 61204, + "nodeType": "Block", + "src": "12934:52:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "id": 61201, + "name": "structHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61195, + "src": "12968:10:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 61200, + "name": "_hashTypedDataV4", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48851, + "src": "12951:16:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_bytes32_$returns$_t_bytes32_$", + "typeString": "function (bytes32) view returns (bytes32)" + } + }, + "id": 61202, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12951:28:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "functionReturnParameters": 61199, + "id": 61203, + "nodeType": "Return", + "src": "12944:35:71" + } + ] + }, + "baseFunctions": [ + 62010 + ], + "documentation": { + "id": 61193, + "nodeType": "StructuredDocumentation", + "src": "12815:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "6931164e", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getHashedTypeDataV4", + "nameLocation": "12864:19:71", + "parameters": { + "id": 61196, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61195, + "mutability": "mutable", + "name": "structHash", + "nameLocation": "12892:10:71", + "nodeType": "VariableDeclaration", + "scope": 61205, + "src": "12884:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61194, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12884:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "12883:20:71" + }, + "returnParameters": { + "id": 61199, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61198, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61205, + "src": "12925:7:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61197, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12925:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "12924:9:71" + }, + "scope": 61239, + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "id": 61228, + "nodeType": "FunctionDefinition", + "src": "13032:229:71", + "nodes": [], + "body": { + "id": 61227, + "nodeType": "Block", + "src": "13145:116:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "arguments": [ + { + "id": 61220, + "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60631, + "src": "13183:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 61221, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61208, + "src": "13220:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 61222, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61210, + "src": "13229:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 61223, + "name": "nonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61212, + "src": "13247:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "id": 61218, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "13172:3:71", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 61219, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "13176:6:71", + "memberName": "encode", + "nodeType": "MemberAccess", + "src": "13172:10:71", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencode_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 61224, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "13172:81:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 61217, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "13162:9:71", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 61225, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "13162:92:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "functionReturnParameters": 61216, + "id": 61226, + "nodeType": "Return", + "src": "13155:99:71" + } + ] + }, + "baseFunctions": [ + 62022 + ], + "documentation": { + "id": 61206, + "nodeType": "StructuredDocumentation", + "src": "12992:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "7dec71a9", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "computeStructHash", + "nameLocation": "13041:17:71", + "parameters": { + "id": 61213, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61208, + "mutability": "mutable", + "name": "version", + "nameLocation": "13065:7:71", + "nodeType": "VariableDeclaration", + "scope": 61228, + "src": "13059:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 61207, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "13059:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61210, + "mutability": "mutable", + "name": "blockContentHash", + "nameLocation": "13082:16:71", + "nodeType": "VariableDeclaration", + "scope": 61228, + "src": "13074:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61209, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "13074:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61212, + "mutability": "mutable", + "name": "nonce", + "nameLocation": "13108:5:71", + "nodeType": "VariableDeclaration", + "scope": 61228, + "src": "13100:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 61211, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "13100:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "13058:56:71" + }, + "returnParameters": { + "id": 61216, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61215, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61228, + "src": "13136:7:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61214, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "13136:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "13135:9:71" + }, + "scope": 61239, + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "id": 61238, + "nodeType": "FunctionDefinition", + "src": "13307:103:71", + "nodes": [], + "body": { + "id": 61237, + "nodeType": "Block", + "src": "13366:44:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61234, + "name": "_domainSeparatorV4", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48812, + "src": "13383:18:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_bytes32_$", + "typeString": "function () view returns (bytes32)" + } + }, + "id": 61235, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "13383:20:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "functionReturnParameters": 61233, + "id": 61236, + "nodeType": "Return", + "src": "13376:27:71" + } + ] + }, + "baseFunctions": [ + 62028 + ], + "documentation": { + "id": 61229, + "nodeType": "StructuredDocumentation", + "src": "13267:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "f698da25", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "domainSeparator", + "nameLocation": "13316:15:71", + "parameters": { + "id": 61230, + "nodeType": "ParameterList", + "parameters": [], + "src": "13331:2:71" + }, + "returnParameters": { + "id": 61233, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61232, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61238, + "src": "13357:7:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61231, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "13357:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "13356:9:71" + }, + "scope": 61239, + "stateMutability": "view", + "virtual": false, + "visibility": "external" + } + ], + "abstract": false, + "baseContracts": [ + { + "baseName": { + "id": 60613, + "name": "Initializable", + "nameLocations": [ + "2056:13:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48392, + "src": "2056:13:71" + }, + "id": 60614, + "nodeType": "InheritanceSpecifier", + "src": "2056:13:71" + }, + { + "baseName": { + "id": 60615, + "name": "UUPSUpgradeable", + "nameLocations": [ + "2075:15:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48574, + "src": "2075:15:71" + }, + "id": 60616, + "nodeType": "InheritanceSpecifier", + "src": "2075:15:71" + }, + { + "baseName": { + "id": 60617, + "name": "OwnableUpgradeable", + "nameLocations": [ + "2096:18:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48124, + "src": "2096:18:71" + }, + "id": 60618, + "nodeType": "InheritanceSpecifier", + "src": "2096:18:71" + }, + { + "baseName": { + "id": 60619, + "name": "EIP712Upgradeable", + "nameLocations": [ + "2120:17:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 49049, + "src": "2120:17:71" + }, + "id": 60620, + "nodeType": "InheritanceSpecifier", + "src": "2120:17:71" + }, + { + "baseName": { + "id": 60621, + "name": "IBlockBuilderPolicy", + "nameLocations": [ + "2143:19:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 62049, + "src": "2143:19:71" + }, + "id": 60622, + "nodeType": "InheritanceSpecifier", + "src": "2143:19:71" + } + ], + "canonicalName": "BlockBuilderPolicy", + "contractDependencies": [], + "contractKind": "contract", + "documentation": { + "id": 60612, + "nodeType": "StructuredDocumentation", + "src": "1154:866:71", + "text": " @title BlockBuilderPolicy\n @notice A reference implementation of a policy contract for the FlashtestationRegistry\n @notice A Policy is a collection of related WorkloadIds. A Policy exists to specify which\n WorkloadIds are valid for a particular purpose, in this case for remote block building. It also\n exists to handle the problem that TEE workloads will need to change multiple times a year, either because\n of Intel DCAP Endorsement updates or updates to the TEE configuration (and thus its WorkloadId). Without\n Policies, consumer contracts that makes use of Flashtestations would need to be updated every time a TEE workload\n changes, which is a costly and error-prone process. Instead, consumer contracts need only check if a TEE address\n is allowed under any workload in a Policy, and the FlashtestationRegistry will handle the rest" + }, + "fullyImplemented": true, + "linearizedBaseContracts": [ + 61239, + 62049, + 49049, + 49243, + 48124, + 48620, + 48574, + 49253, + 48392 + ], + "name": "BlockBuilderPolicy", + "nameLocation": "2030:18:71", + "scope": 61240, + "usedErrors": [ + 47960, + 47965, + 48141, + 48144, + 48419, + 48424, + 49311, + 49324, + 49971, + 50264, + 52061, + 52066, + 52071, + 61900, + 61903, + 61906, + 61911, + 61918, + 61921, + 61924 + ], + "usedEvents": [ + 47971, + 48149, + 49205, + 49223, + 61874, + 61879, + 61884, + 61897 + ] + } + ], + "license": "MIT" + }, + "id": 71 +} \ No newline at end of file diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json new file mode 100644 index 00000000000..1a3b1e6c705 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json @@ -0,0 +1,450 @@ +{ + "abi": [ + { + "type": "function", + "name": "PERMIT_INCREMENT_TYPEHASH", + "inputs": [], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { "name": "", "type": "string", "internalType": "string" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addBuilder", + "inputs": [ + { + "name": "builder", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "computeStructHash", + "inputs": [ + { + "name": "currentFlashblockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { + "name": "fields", + "type": "bytes1", + "internalType": "bytes1" + }, + { "name": "name", "type": "string", "internalType": "string" }, + { + "name": "version", + "type": "string", + "internalType": "string" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "flashblockNumber", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getFlashblockNumber", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "hashTypedDataV4", + "inputs": [ + { + "name": "structHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "incrementFlashblockNumber", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + }, + { + "name": "_initialBuilders", + "type": "address[]", + "internalType": "address[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "isBuilder", + "inputs": [ + { "name": "", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { "name": "", "type": "address", "internalType": "address" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permitIncrementFlashblockNumber", + "inputs": [ + { + "name": "currentFlashblockNumber", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeBuilder", + "inputs": [ + { + "name": "builder", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "BuilderAdded", + "inputs": [ + { + "name": "builder", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BuilderRemoved", + "inputs": [ + { + "name": "builder", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "FlashblockIncremented", + "inputs": [ + { + "name": "newFlashblockIndex", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "AddressIsAlreadyABuilder", + "inputs": [ + { "name": "addr", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "BuilderDoesNotExist", + "inputs": [ + { "name": "addr", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [ + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "ERC1967NonPayable", "inputs": [] }, + { "type": "error", "name": "FailedCall", "inputs": [] }, + { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { + "type": "error", + "name": "MismatchedFlashblockNumber", + "inputs": [ + { + "name": "expectedFlashblockNumber", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actualFlashblockNumber", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NonBuilderAddress", + "inputs": [ + { "name": "addr", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "NotInitializing", "inputs": [] }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { "name": "slot", "type": "bytes32", "internalType": "bytes32" } + ] + } + ], + "bytecode": { + "object": "0x60a0806040523460295730608052611f7d908161002e8239608051818181611013015261114c0152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806325f5ffa41461147c5780633fd553e8146114455780634980f288146114095780634f1ef286146110e35780634f66ee131461108b57806352d1902d14610fce578063715018a614610ef457806384b0196e14610d4f5780638da5cb5b14610cdf578063946d920414610439578063a0a78ce01461025d578063ad3cb1cc146103b8578063b6b6b47514610350578063c91d762514610297578063e5b37c5d1461025d578063e7e3a27114610219578063ec9693861461012c5763f2fde38b146100df575f80fd5b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285761012661011961156b565b6101216118a2565b6117b5565b005b5f80fd5b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285773ffffffffffffffffffffffffffffffffffffffff61017861156b565b6101806118a2565b16805f52600160205260ff60405f205416156101ee57805f52600160205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541690557fc5a4a43135540d5e0967677a1ed86bf147f7c0e7dd757a109f4cff74c945f92e5f80a2005b7f677fda95000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128576020610255600435611770565b604051908152f35b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285760205f54604051908152f35b346101285760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285760043560243567ffffffffffffffff8111610128576102e9903690600401611609565b905f548082036103215761012661031c6103138561030e61030987611770565b611692565b611c7c565b90929192611cb6565b61190e565b907f53b6b59a000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285773ffffffffffffffffffffffffffffffffffffffff61039c61156b565b165f526001602052602060ff60405f2054166040519015158152f35b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128576104356040516103f760408261158e565b600581527f352e302e30000000000000000000000000000000000000000000000000000000602082015260405191829160208352602083019061164f565b0390f35b346101285760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285761047061156b565b6024359067ffffffffffffffff821161012857366023830112156101285781600401359167ffffffffffffffff8311610a76578260051b9060208201936104ba604051958661158e565b8452602460208501928201019036821161012857602401915b818310610cb2575050507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c16159167ffffffffffffffff821680159081610caa575b6001149081610ca0575b159081610c97575b50610c6f57818360017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006105969516177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055610c1a575b5061058e611c25565b610121611c25565b61059e611c25565b6040908151926105ae838561158e565b601084527f466c617368626c6f636b4e756d6265720000000000000000000000000000000060208501528251936105e5848661158e565b600185527f31000000000000000000000000000000000000000000000000000000000000006020860152610617611c25565b61061f611c25565b80519067ffffffffffffffff8211610a7657819061065d7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546119ee565b601f8111610b8d575b50602090601f8311600114610aae575f92610aa3575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c1916177fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102555b835167ffffffffffffffff8111610a76576107097fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546119ee565b601f81116109f4575b50602094601f8211600114610916579481929394955f9261090b575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c1916177fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103555b5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100555f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101555f5b8151811015610871578073ffffffffffffffffffffffffffffffffffffffff6107f36001938561172f565b51165f5281602052845f20827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082541617905573ffffffffffffffffffffffffffffffffffffffff610845828561172f565b51167fc2dabef8a63ab48fcf728bbe8864a3fe01a9e19addc6ce314abd5b6f9a1dce665f80a2016107c8565b505061087957005b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2917fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00555160018152a1005b01519050858061072e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52805f20915f5b8881106109dc575083600195969798106109a5575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10355610780565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080610978565b91926020600181928685015181550194019201610963565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f527f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f830160051c81019160208410610a6c575b601f0160051c01905b818110610a615750610712565b5f8155600101610a54565b9091508190610a4b565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b01519050868061067c565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52815f20925f5b818110610b755750908460019594939210610b3e575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102556106ce565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080610b11565b92936020600181928786015181550195019301610afb565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f529091507f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f840160051c81019160208510610c10575b90601f859493920160051c01905b818110610c025750610666565b5f8155849350600101610bf5565b9091508190610be7565b7fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005584610585565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050158561052e565b303b159150610526565b84915061051c565b823573ffffffffffffffffffffffffffffffffffffffff81168103610128578152602092830192016104d3565b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012857602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100541580610ecb575b15610e6d57610e11610db6611a3f565b610dbe611b52565b6020610e1f60405192610dd1838561158e565b5f84525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e087019061164f565b90858203604087015261164f565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b828110610e5657505050500390f35b835185528695509381019392810192600101610e47565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415610da6565b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012857610f2a6118a2565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285773ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001630036110635760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285760206040517f51de50fd99b637d778db8fe7a8a1966ddae5530dd6e4d8f693ffb315c812a24d8152f35b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285761111561156b565b60243567ffffffffffffffff811161012857611135903690600401611609565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168030149081156113c7575b50611063576111846118a2565b73ffffffffffffffffffffffffffffffffffffffff8216916040517f52d1902d000000000000000000000000000000000000000000000000000000008152602081600481875afa5f9181611393575b5061120457837f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8592036113685750813b1561133d57807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a281511561130c575f8083602061012695519101845af43d15611304573d916112e8836115cf565b926112f6604051948561158e565b83523d5f602085013e611ed7565b606091611ed7565b50503461131557005b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7faa1d49a4000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091506020813d6020116113bf575b816113af6020938361158e565b81010312610128575190856111d3565b3d91506113a2565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416141583611177565b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128576020610255600435611692565b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128576101263361190e565b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285773ffffffffffffffffffffffffffffffffffffffff6114c861156b565b6114d06118a2565b16805f52600160205260ff60405f20541661154057805f52600160205260405f2060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790557fc2dabef8a63ab48fcf728bbe8864a3fe01a9e19addc6ce314abd5b6f9a1dce665f80a2005b7fc0b0858c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361012857565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610a7657604052565b67ffffffffffffffff8111610a7657601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b81601f8201121561012857803590611620826115cf565b9261162e604051948561158e565b8284526020838301011161012857815f926020809301838601378301015290565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b60429061169d611e28565b6116a5611e92565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526116f660c08261158e565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b80518210156117435760209160051b010190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b60405160208101917f51de50fd99b637d778db8fe7a8a1966ddae5530dd6e4d8f693ffb315c812a24d83526040820152604081526117af60608261158e565b51902090565b73ffffffffffffffffffffffffffffffffffffffff1680156118765773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300541633036118e257565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff16805f52600160205260ff60405f205416156119c357505f547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461199657602060017ffaddd0b06e793a583e92393ad3f98637e560462ee98db1f2888f141124ee64ca9201805f55604051908152a1565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fdd9dbe80000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90600182811c92168015611a35575b6020831014611a0857565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916119fd565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025491611a71836119ee565b8083529260018116908115611b155750600114611a97575b611a959250038361158e565b565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b818310611af9575050906020611a9592820101611a89565b6020919350806001915483858901015201910190918492611ae1565b60209250611a959491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b820101611a89565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035491611b84836119ee565b8083529260018116908115611b155750600114611ba757611a959250038361158e565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b818310611c09575050906020611a9592820101611a89565b6020919350806001915483858901015201910190918492611bf1565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c1615611c5457565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b8151919060418303611cac57611ca59250602082015190606060408401519301515f1a90611d8e565b9192909190565b50505f9160029190565b6004811015611d615780611cc8575050565b60018103611cf8577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b60028103611d2c57507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600314611d365750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411611e1d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15611e12575f5173ffffffffffffffffffffffffffffffffffffffff811615611e0857905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f9160039190565b611e30611a3f565b8051908115611e40576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100548015611e6d5790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b611e9a611b52565b8051908115611eaa576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101548015611e6d5790565b90611f145750805115611eec57602081519101fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580611f67575b611f25575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b15611f1d56fea164736f6c634300081c000a", + "sourceMap": "1449:6259:60:-:0;;;;;;;1171:4:22;1163:13;;1449:6259:60;;;;;;1163:13:22;1449:6259:60;;;;;;;;;;;;;;", + "linkReferences": {} + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json new file mode 100644 index 00000000000..d823e0c3719 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json @@ -0,0 +1,641 @@ +{ + "abi": [ + { + "type": "function", + "name": "MAX_BYTES_SIZE", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "REGISTER_TYPEHASH", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "TD_REPORTDATA_LENGTH", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "attestationContract", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IAttestation" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "computeStructHash", + "inputs": [ + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "domainSeparator", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { "name": "fields", "type": "bytes1", "internalType": "bytes1" }, + { "name": "name", "type": "string", "internalType": "string" }, + { "name": "version", "type": "string", "internalType": "string" }, + { "name": "chainId", "type": "uint256", "internalType": "uint256" }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { "name": "salt", "type": "bytes32", "internalType": "bytes32" }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRegistration", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "", "type": "bool", "internalType": "bool" }, + { + "name": "", + "type": "tuple", + "internalType": "struct IFlashtestationRegistry.RegisteredTEE", + "components": [ + { "name": "isValid", "type": "bool", "internalType": "bool" }, + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "parsedReportBody", + "type": "tuple", + "internalType": "struct TD10ReportBody", + "components": [ + { + "name": "teeTcbSvn", + "type": "bytes16", + "internalType": "bytes16" + }, + { "name": "mrSeam", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrsignerSeam", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "seamAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "tdAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { "name": "xFAM", "type": "bytes8", "internalType": "bytes8" }, + { "name": "mrTd", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrConfigId", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "mrOwner", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrOwnerConfig", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "rtMr0", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr1", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr2", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr3", "type": "bytes", "internalType": "bytes" }, + { + "name": "reportData", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "quoteHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRegistrationStatus", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "isValid", "type": "bool", "internalType": "bool" }, + { "name": "quoteHash", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "hashTypedDataV4", + "inputs": [ + { "name": "structHash", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { + "name": "_attestationContract", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "invalidateAttestation", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "invalidatePreviousSignature", + "inputs": [ + { "name": "_nonce", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "permitNonce", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permitRegisterTEEService", + "inputs": [ + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "registerTEEService", + "inputs": [ + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "registeredTEEs", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "isValid", "type": "bool", "internalType": "bool" }, + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "parsedReportBody", + "type": "tuple", + "internalType": "struct TD10ReportBody", + "components": [ + { + "name": "teeTcbSvn", + "type": "bytes16", + "internalType": "bytes16" + }, + { "name": "mrSeam", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrsignerSeam", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "seamAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "tdAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { "name": "xFAM", "type": "bytes8", "internalType": "bytes8" }, + { "name": "mrTd", "type": "bytes", "internalType": "bytes" }, + { "name": "mrConfigId", "type": "bytes", "internalType": "bytes" }, + { "name": "mrOwner", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrOwnerConfig", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "rtMr0", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr1", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr2", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr3", "type": "bytes", "internalType": "bytes" }, + { "name": "reportData", "type": "bytes", "internalType": "bytes" } + ] + }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "quoteHash", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { "name": "newOwner", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PreviousSignatureInvalidated", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "invalidatedNonce", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "TEEServiceInvalidated", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "TEEServiceRegistered", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "rawQuote", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "alreadyExists", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { "name": "target", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ByteSizeExceeded", + "inputs": [ + { "name": "size", "type": "uint256", "internalType": "uint256" } + ] + }, + { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { "name": "length", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [{ "name": "s", "type": "bytes32", "internalType": "bytes32" }] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "ERC1967NonPayable", "inputs": [] }, + { + "type": "error", + "name": "ExpiredSignature", + "inputs": [ + { "name": "deadline", "type": "uint256", "internalType": "uint256" } + ] + }, + { "type": "error", "name": "FailedCall", "inputs": [] }, + { "type": "error", "name": "InvalidAttestationContract", "inputs": [] }, + { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { + "type": "error", + "name": "InvalidNonce", + "inputs": [ + { "name": "expected", "type": "uint256", "internalType": "uint256" }, + { "name": "provided", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "InvalidQuote", + "inputs": [{ "name": "output", "type": "bytes", "internalType": "bytes" }] + }, + { + "type": "error", + "name": "InvalidQuoteLength", + "inputs": [ + { "name": "length", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "InvalidRegistrationDataHash", + "inputs": [ + { "name": "expected", "type": "bytes32", "internalType": "bytes32" }, + { "name": "received", "type": "bytes32", "internalType": "bytes32" } + ] + }, + { + "type": "error", + "name": "InvalidReportDataLength", + "inputs": [ + { "name": "length", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "InvalidTEEType", + "inputs": [ + { "name": "teeType", "type": "bytes4", "internalType": "bytes4" } + ] + }, + { + "type": "error", + "name": "InvalidTEEVersion", + "inputs": [ + { "name": "version", "type": "uint16", "internalType": "uint16" } + ] + }, + { "type": "error", "name": "NotInitializing", "inputs": [] }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "ReentrancyGuardReentrantCall", "inputs": [] }, + { + "type": "error", + "name": "SignerMustMatchTEEAddress", + "inputs": [ + { "name": "signer", "type": "address", "internalType": "address" }, + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "TEEIsStillValid", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "TEEServiceAlreadyInvalid", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "TEEServiceAlreadyRegistered", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "TEEServiceNotRegistered", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "UUPSUnauthorizedCallContext", "inputs": [] }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { "name": "slot", "type": "bytes32", "internalType": "bytes32" } + ] + } + ], + "bytecode": { + "object": "0x60a0806040523460295730608052614e62908161002e8239608051818181610a320152610c3e0152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80630634434a146101845780630ac3302b1461017f57806322ba2bbf1461017a578063485cc955146101755780634980f288146101705780634f1ef2861461016b57806352d1902d146101665780636a5306a314610161578063715018a61461015c57806372731062146101575780637ecebe001461015257806384b0196e1461014d578063878111121461014857806387be6d4e146101435780638da5cb5b1461013e578063a8af4ff514610139578063aaae748e14610134578063ad3cb1cc1461012f578063e41689521461012a578063f2fde38b14610125578063f698da2514610120578063f745cb301461011b5763f9b68b3114610116575f80fd5b611ac7565b611a11565b61163a565b6115f3565b6115ba565b61153d565b611503565b611492565b611422565b6113d2565b611340565b611221565b611110565b61105d565b610ce6565b610c8e565b610bf9565b6109b7565b61083e565b6105bd565b6102e9565b610248565b6101bb565b9181601f840112156101b75782359167ffffffffffffffff83116101b757602083818601950101116101b757565b5f80fd5b346101b75760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760043567ffffffffffffffff81116101b75761020a903690600401610189565b906024359067ffffffffffffffff82116101b757602092610232610240933690600401610189565b906044359260643594611c82565b604051908152f35b60a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760043567ffffffffffffffff81116101b757610292903690600401610189565b60243567ffffffffffffffff81116101b7576102b2903690600401610189565b6084359391606435916044359167ffffffffffffffff87116101b7576102df6102e7973690600401610189565b969095611d01565b005b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760043567ffffffffffffffff81116101b757610333903690600401610189565b60243567ffffffffffffffff81116101b757610353903690600401610189565b91909261035e612781565b61037861036c368484610981565b5161500081111561281a565b61038661036c368587610981565b6103c06103a75f5473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b5f60405180927f38d8480a00000000000000000000000000000000000000000000000000000000825281806103f988886004840161288b565b039134905af1908115610572575f905f9261054b575b508161041a9161289c565b61042390613349565b6101c081018051516104399060348110156128e5565b5161044390613526565b73ffffffffffffffffffffffffffffffffffffffff821696906104698333808b14612918565b610474368884610981565b805190602001208181149161048892612969565b610493368686610981565b80519060200120906104a582846136e1565b966104ae610926565b60018152946104be368989610981565b6020870152604086015236906104d392610981565b606084015260808301526105059073ffffffffffffffffffffffffffffffffffffffff165f52600160205260405f2090565b9061050f91612dce565b60405192839261051f9284612f62565b037f206fdb1a74851a8542447b8b6704db24a36b906a7297cc23c2b984dc357b997891a26102e76127f5565b61041a925061056c91503d805f833e61056481836108e5565b8101906126a7565b9161040f565b61272c565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101b757565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036101b757565b346101b75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b7576105f4610577565b6105fc61059a565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054919067ffffffffffffffff61064360ff604086901c16159467ffffffffffffffff1690565b1680159081610836575b600114908161082c575b159081610823575b506107fb576106e091836106d760017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000007ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005416177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055565b61078057612071565b6106e657005b6107517fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055565b604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a1005b6107f6680100000000000000007fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005416177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055565b612071565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050155f61065f565b303b159150610657565b84915061064d565b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760206102406004356123af565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176108c357604052565b61087a565b6101e0810190811067ffffffffffffffff8211176108c357604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176108c357604052565b6040519061093560a0836108e5565b565b604051906109356101e0836108e5565b67ffffffffffffffff81116108c357601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b92919261098d82610947565b9161099b60405193846108e5565b8294818452818301116101b7578281602093845f960137010152565b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b7576109e9610577565b60243567ffffffffffffffff81116101b757366023820112156101b757610a1a903690602481600401359101610981565b9073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803014908115610bb7575b50610b8f57610a6a612f92565b604051917f52d1902d00000000000000000000000000000000000000000000000000000000835260208360048173ffffffffffffffffffffffffffffffffffffffff86165afa5f9381610b5e575b50610aff577f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5273ffffffffffffffffffffffffffffffffffffffff821660045260245ffd5b907f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8303610b31576102e79250613964565b7faa1d49a4000000000000000000000000000000000000000000000000000000005f52600483905260245ffd5b610b8191945060203d602011610b88575b610b7981836108e5565b810190612f83565b925f610ab8565b503d610b6f565b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc541614155f610a5d565b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163003610b8f5760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760206040517f95b0f36aa3383d49f247995a06db7a3bd7d07a2e7fe943cfdfc72b826979736a8152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757610d1c612f92565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b816101c0610fa0610f8c610f78610f64610f50610f3c610f28610f16610e80610e6d610fb29d6101e060208f8190610e5d8582517fffffffffffffffffffffffffffffffff00000000000000000000000000000000169052565b01519201526101e08d0190610dc0565b60408d01518c6040818403910152610dc0565b60608c8101517fffffffffffffffff00000000000000000000000000000000000000000000000016908c015260808c8101517fffffffffffffffff00000000000000000000000000000000000000000000000016908c015260a08c8101517fffffffffffffffff00000000000000000000000000000000000000000000000016908c015260c08c01518b820360c08d0152610dc0565b60e08b01518a820360e08c0152610dc0565b6101008a01518982036101008b0152610dc0565b6101208901518882036101208a0152610dc0565b610140880151878203610140890152610dc0565b610160870151868203610160880152610dc0565b610180860151858203610180870152610dc0565b6101a08501518482036101a0860152610dc0565b920151906101c0818403910152610dc0565b90565b90151581526040602082015281511515604082015260c06080611054611020610fed602087015160a0606088015260e0870190610dc0565b60408701517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08783030185880152610e03565b60608601517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08683030160a0870152610dc0565b93015191015290565b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff6110a9610577565b5f60806040516110b8816108a7565b828152606060208201526110ca61244c565b6040820152606080820152015216805f52600160205260ff60405f205416905f5260016020526110fc60405f206124b9565b9061110c60405192839283610fb5565b0390f35b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff61115c610577565b165f526002602052602060405f2054604051908152f35b92939073ffffffffffffffffffffffffffffffffffffffff926111c76111d5927f0f00000000000000000000000000000000000000000000000000000000000000875260e0602088015260e0870190610dc0565b908582036040870152610dc0565b9360608401521660808201525f60a082015260c0818303910152602080835192838152019201905f5b81811061120b5750505090565b82518452602093840193909201916001016111fe565b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b7577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100541580611317575b156112b957611285612ffe565b61128d61310d565b9061110c60405161129f6020826108e5565b5f8082523660208301376040519384933091469186611173565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415611278565b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757600435335f52600260205261138c60405f20549182808214611fac565b335f52600260205260405f206113a28154612010565b90556040519081527faba960b001cf41ae7d1278e08bf0afa5081bfad043326cfe1e1d5ee266c9ac5260203392a2005b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757602073ffffffffffffffffffffffffffffffffffffffff5f5416604051908152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff6114de610577565b165f5260016020526040805f20601060ff825416910154825191151582526020820152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760206040516150008152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75761110c60405161157c6040826108e5565b600581527f352e302e300000000000000000000000000000000000000000000000000000006020820152604051918291602083526020830190610dc0565b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757602060405160348152f35b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b7576102e761162d610577565b611635612f92565b612526565b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757611670614cc3565b611678614d2d565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526116c960c0826108e5565b519020604051908152602090f35b90600182811c9216801561171e575b60208310146116f157565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916116e6565b5f9291815491611737836116d7565b808352926001811690811561178c575060011461175357505050565b5f9081526020812093945091925b838310611772575060209250010190565b600181602092949394548385870101520191019190611761565b905060209495507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091509291921683830152151560051b010190565b906109356117dc9260405193848092611728565b03836108e5565b906119c1600c6117f1610937565b93611826611800825460801b90565b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000168652565b611832600182016117c8565b6020860152611843600282016117c8565b604086015261192d61190460038301546118896118608260c01b90565b7fffffffffffffffff0000000000000000000000000000000000000000000000001660608a0152565b6118dc608082901b7fffffffffffffffff000000000000000000000000000000000000000000000000167fffffffffffffffff0000000000000000000000000000000000000000000000001660808a0152565b60401b7fffffffffffffffff0000000000000000000000000000000000000000000000001690565b7fffffffffffffffff0000000000000000000000000000000000000000000000001660a0870152565b611939600482016117c8565b60c086015261194a600582016117c8565b60e086015261195b600682016117c8565b61010086015261196d600782016117c8565b61012086015261197f600882016117c8565b610140860152611991600982016117c8565b6101608601526119a3600a82016117c8565b6101808601526119b5600b82016117c8565b6101a0860152016117c8565b6101c0830152565b959493906080936119f0611a0c946119fe9315158a5260a060208b015260a08a0190610dc0565b9088820360408a0152610e03565b908682036060880152610dc0565b930152565b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff611a5d610577565b165f52600160205260405f2060ff81541661110c60405192611a8d84611a868160018501611728565b03856108e5565b611a99600282016117e3565b90601060405191611ab883611ab181600f8501611728565b03846108e5565b015491604051958695866119c9565b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757611bb2611afc610577565b611b04612781565b5f611b35611b308373ffffffffffffffffffffffffffffffffffffffff165f52600160205260405f2090565b6124b9565b611b5a83611b556020840193611b4f838651511515612613565b51151590565b61265d565b611b7b6103a7835473ffffffffffffffffffffffffffffffffffffffff1690565b90519060405180809681947f38d8480a0000000000000000000000000000000000000000000000000000000083526004830161271b565b039134905af180156105725781611be79173ffffffffffffffffffffffffffffffffffffffff945f91611c67575b5015612737565b611c3a611c128273ffffffffffffffffffffffffffffffffffffffff165f52600160205260405f2090565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008154169055565b167f5bb0bbb0993a623e10dd3579bf5b9403deba943e0bfe950b740d60209c9135ef5f80a26102e76127f5565b611c7b91503d805f833e61056481836108e5565b505f611be0565b611c9590611ca493929694963691610981565b60208151910120943691610981565b60208151910120916040519260208401947f95b0f36aa3383d49f247995a06db7a3bd7d07a2e7fe943cfdfc72b826979736a865260408501526060840152608083015260a082015260a08152611cfb60c0826108e5565b51902090565b9396611d3e90611d388598611d30611d2b8a8d899a611d479a8a9e9a8e611d26612781565b611c82565b6123af565b923691610981565b906131de565b9093919361324f565b611d7773ffffffffffffffffffffffffffffffffffffffff831697885f52600260205260405f2054808214611fac565b804211611f815750855f52600260205260405f20611d958154612010565b9055611da561036c368686610981565b611db361036c368785610981565b611dd46103a75f5473ffffffffffffffffffffffffffffffffffffffff1690565b5f60405180927f38d8480a0000000000000000000000000000000000000000000000000000000082528180611e0d8a8a6004840161288b565b039134905af1908115610572575f905f92611f62575b5081611e2e9161289c565b611e3790613349565b916101c08301805151603481101590611e4f916128e5565b51611e5990613526565b90928373ffffffffffffffffffffffffffffffffffffffff8116809a1491611e8092612918565b611e8b368884610981565b8051906020012081811491611e9f92612969565b611eaa368686610981565b8051906020012090611ebc82846136e1565b96611ec5610926565b6001815294611ed5368989610981565b602087015260408601523690611eea92610981565b60608401526080830152611f1c9073ffffffffffffffffffffffffffffffffffffffff165f52600160205260405f2090565b90611f2691612dce565b604051928392611f369284612f62565b037f206fdb1a74851a8542447b8b6704db24a36b906a7297cc23c2b984dc357b997891a26109356127f5565b611e2e9250611f7b91503d805f833e61056481836108e5565b91611e23565b7fbd2a913c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b15611fb5575050565b7f06427aeb000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461203d5760010190565b611fe3565b1561204957565b7f72cd95d7000000000000000000000000000000000000000000000000000000005f5260045ffd5b6120889092919261208061373f565b61163561373f565b60409182519261209881856108e5565b601684527f466c617368746573746174696f6e52656769737472790000000000000000000060208501526120ce815191826108e5565b600181527f3100000000000000000000000000000000000000000000000000000000000000602082015261210061373f565b61210861373f565b835167ffffffffffffffff81116108c35761214c816121477fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546116d7565b6129b6565b6020601f821160011461229457916121b7826121de9373ffffffffffffffffffffffffffffffffffffffff969561093598995f92612289575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10255613796565b6122065f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10055565b61222e5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10155565b61223661373f565b61223e61373f565b1661224a811515612042565b73ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000005f5416175f55565b015190505f80612185565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d965f5b818110612397575092610935969773ffffffffffffffffffffffffffffffffffffffff969593600193836121de9710612360575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10255613796565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080612333565b838301518955600190980197602093840193016122ff565b6042906123ba614cc3565b6123c2614d2d565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261241360c0826108e5565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b60405190612459826108c8565b60606101c0835f81528260208201528260408201525f838201525f60808201525f60a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a08201520152565b906040516124c6816108a7565b60806010829460ff815416151584526040516124f0816124e98160018601611728565b03826108e5565b6020850152612501600282016117e3565b604085015260405161251a816124e981600f8601611728565b60608501520154910152565b73ffffffffffffffffffffffffffffffffffffffff1680156125e75773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b1561261b5750565b73ffffffffffffffffffffffffffffffffffffffff907fbb527454000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b156126655750565b73ffffffffffffffffffffffffffffffffffffffff907f138c0ee8000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b91906040838203126101b757825180151581036101b7579260208101519067ffffffffffffffff82116101b7570181601f820112156101b7578051906126ec82610947565b926126fa60405194856108e5565b828452602083830101116101b757815f9260208093018386015e8301015290565b906020610fb2928181520190610dc0565b6040513d5f823e3d90fd5b1561273f5750565b73ffffffffffffffffffffffffffffffffffffffff907f927b3443000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005c6127cd5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b156128225750565b7f9e24c2f6000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b916020610fb293818152019161284d565b156128a45750565b6128e1906040519182917f64d10cb10000000000000000000000000000000000000000000000000000000083526020600484018181520190610dc0565b0390fd5b156128ed5750565b7f4fe16298000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b15612921575050565b9073ffffffffffffffffffffffffffffffffffffffff80927f38e0a7e5000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15612972575050565b7fcc14da59000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b8181106129ab575050565b5f81556001016129a0565b90601f82116129c3575050565b610935917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f5260205f20906020601f840160051c83019310612a0e575b601f0160051c01906129a0565b9091508190612a01565b9190601f8111612a2757505050565b610935925f5260205f20906020601f840160051c83019310612a0e57601f0160051c01906129a0565b919091825167ffffffffffffffff81116108c357612a7881612a7284546116d7565b84612a18565b6020601f8211600114612ac9578190612ac59394955f926122895750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b9055565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821690612afa845f5260205f2090565b915f5b818110612b5357509583600195969710612b1c575b505050811b019055565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080612b12565b9192602060018192868b015181550194019201612afd565b600c6101c061093593612bcf612ba182517fffffffffffffffffffffffffffffffff000000000000000000000000000000001690565b859060801c7fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055565b612be0602082015160018601612a50565b612bf1604082015160028601612a50565b612d3760038501612c56612c2860608501517fffffffffffffffff0000000000000000000000000000000000000000000000001690565b829060c01c7fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000825416179055565b612cc7612c8660808501517fffffffffffffffff0000000000000000000000000000000000000000000000001690565b82547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff1660809190911c6fffffffffffffffff000000000000000016178255565b60a08301517fffffffffffffffff0000000000000000000000000000000000000000000000001681547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff1660409190911c77ffffffffffffffff0000000000000000000000000000000016179055565b612d4860c082015160048601612a50565b612d5960e082015160058601612a50565b612d6b61010082015160068601612a50565b612d7d61012082015160078601612a50565b612d8f61014082015160088601612a50565b612da161016082015160098601612a50565b612db3610180820151600a8601612a50565b612dc56101a0820151600b8601612a50565b01519101612a50565b90612e0781511515839060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b60018201602082015180519067ffffffffffffffff82116108c357612e3682612e3085546116d7565b85612a18565b602090601f8311600114612eb557826010959360809593612e89935f926122895750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b90555b612e9d604082015160028601612b6b565b612eae6060820151600f8601612a50565b0151910155565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0831691612ee7855f5260205f2090565b925f5b818110612f4a5750926001928592601098966080989610612f13575b505050811b019055612e8c565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080612f06565b92936020600181928786015181550195019301612eea565b91602091612f7b9195949560408552604085019161284d565b931515910152565b908160209103126101b7575190565b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303612fd257565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b6040517fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10254815f61302e836116d7565b80835292600181169081156130d05750600114613052575b610fb2925003826108e5565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b8183106130b4575050906020610fb292820101613046565b602091935080600191548385880101520191019091839261309c565b60209250610fb29491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b820101613046565b6040517fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10354815f61313d836116d7565b80835292600181169081156130d0575060011461316057610fb2925003826108e5565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b8183106131c2575050906020610fb292820101613046565b60209193508060019154838588010152019101909183926131aa565b815191906041830361320e576132079250602082015190606060408401519301515f1a90613a95565b9192909190565b50505f9160029190565b6004111561322257565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b61325881613218565b80613261575050565b61326a81613218565b6001810361329a577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b6132a381613218565b600281036132d757507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b806132e3600392613218565b146132eb5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b1561331e5750565b7fd915602a000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b61335161244c565b5080516002116101b7576133656002610947565b61337260405191826108e5565b600281526133806002610947565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602082019201368337816020840160025b60208110156134d857806134b957507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b51825182169119161790525190517fffff00000000000000000000000000000000000000000000000000000000000081169160028110613484575b505060f01c6004810361345957506134548161343e610fb293613b24565b805161344f90610255811015613316565b613cc9565b614af4565b7f940a5ec6000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7fffff0000000000000000000000000000000000000000000000000000000000009250829060020360031b1b16165f80613420565b6134cd6134c86134d292614d72565b614dad565b614d80565b906133e5565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156133b357611fe3565b80516014116101b7576135396014610947565b9161354760405193846108e5565b601483526135556014610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208501910136823760149381602085015b60208710156136945761360994959680155f1461367f57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b51825182169119161790525190517fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116916014811061364a575b505060601c92613dd1565b60208151910151906020811061361d575090565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060200360031b1b1690565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009250829060140360031b1b16165f806135fe565b6134cd6134c861368e92614d72565b906135c3565b90815181526020810180911161203d57906020810180911161203d57957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811161203d5795613588565b73ffffffffffffffffffffffffffffffffffffffff16805f526001602052601060405f2001548092146137145750151590565b7ffb5bab5b000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561376e57565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b90815167ffffffffffffffff81116108c3576137fc816137d67fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546116d7565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103612a18565b602092601f821160011461386d57613849929382915f926122895750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10355565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216937f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75915f5b86811061394c5750836001959610613915575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10355565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f80806138eb565b919260206001819286850151815501940192016138d8565b90813b15613a535773ffffffffffffffffffffffffffffffffffffffff8216807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2805115613a2257613a1f91614c7f565b50565b505034613a2b57565b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff827f4c9c8ce3000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411613b19579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610572575f5173ffffffffffffffffffffffffffffffffffffffff811615613b0f57905f905f90565b505f906001905f90565b5050505f9160039190565b9081516006116101b757613b386004610947565b613b4560405191826108e5565b60048152613b536004610947565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208201920136833760226004940182905b6020861015613c7c577fffffffff00000000000000000000000000000000000000000000000000000000949580155f14613c6757507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b51825182169119161790525190518281169160048110613c52575b5050167f81000000000000000000000000000000000000000000000000000000000000008103613c275750565b7fea75591a000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b839250829060040360031b1b16165f80613bfa565b6134cd6134c8613c7692614d72565b90613bdf565b90815181526020810180911161203d57906020810180911161203d57947fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811161203d5794613b87565b8051610255116101b757613cde610248610947565b90613cec60405192836108e5565b6102488252613cfc610248610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208401910136823790602d016102485b6020811015613d835780613d6e57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b518251821691191617905290565b6134cd6134c8613d7d92614d72565b90613d60565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811115613d2e57611fe3565b80516034116101b757613de46020610947565b90613df260405192836108e5565b60208252613e006020610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060340160205b6020811015613e475780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811115613e3157611fe3565b80516010116101b757613ea86010610947565b90613eb660405192836108e5565b60108252613ec46010610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060200160105b6020811015613f0b5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811115613ef557611fe3565b80516040116101b757613f6c6030610947565b90613f7a60405192836108e5565b60308252613f886030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237603080920190915b6020811015613fd05780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811115613fba57611fe3565b80516070116101b7576140316030610947565b9061403f60405192836108e5565b6030825261404d6030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060600160305b60208110156140945780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561407e57611fe3565b80516078116101b7576140f56008610947565b9061410360405192836108e5565b600882526141116008610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060900160085b60208110156141585780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561414257611fe3565b80516080116101b7576141b96008610947565b906141c760405192836108e5565b600882526141d56008610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060980160085b602081101561421c5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561420657611fe3565b80516088116101b75761427d6008610947565b9061428b60405192836108e5565b600882526142996008610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060a00160085b60208110156142e05780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156142ca57611fe3565b805160b8116101b7576143416030610947565b9061434f60405192836108e5565b6030825261435d6030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060a80160305b60208110156143a45780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561438e57611fe3565b805160e8116101b7576144056030610947565b9061441360405192836108e5565b603082526144216030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060d80160305b60208110156144685780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561445257611fe3565b8051610118116101b7576144ca6030610947565b906144d860405192836108e5565b603082526144e66030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101080160305b602081101561452e5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561451857611fe3565b8051610148116101b7576145906030610947565b9061459e60405192836108e5565b603082526145ac6030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101380160305b60208110156145f45780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156145de57611fe3565b8051610178116101b7576146566030610947565b9061466460405192836108e5565b603082526146726030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101680160305b60208110156146ba5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156146a457611fe3565b80516101a8116101b75761471c6030610947565b9061472a60405192836108e5565b603082526147386030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101980160305b60208110156147805780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561476a57611fe3565b80516101d8116101b7576147e26030610947565b906147f060405192836108e5565b603082526147fe6030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101c80160305b60208110156148465780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561483057611fe3565b8051610208116101b7576148a86030610947565b906148b660405192836108e5565b603082526148c46030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101f80160305b602081101561490c5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156148f657611fe3565b8051610248116101b75761496e6040610947565b9061497c60405192836108e5565b6040825261498a6040610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906102280160405b60208110156149d25780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156149bc57611fe3565b90602082519201517fffffffffffffffffffffffffffffffff0000000000000000000000000000000081169260108110614a58575050565b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000929350829060100360031b1b161690565b90602082519201517fffffffffffffffff00000000000000000000000000000000000000000000000081169260088110614ac2575050565b7fffffffffffffffff000000000000000000000000000000000000000000000000929350829060080360031b1b161690565b906119c1614b0061244c565b92614b3b614b15614b1083613e95565b614a20565b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000168552565b614b4481613f59565b6020850152614b528161401e565b6040850152614b94614b6b614b66836140e2565b614a8a565b7fffffffffffffffff000000000000000000000000000000000000000000000000166060860152565b614bcc614ba3614b66836141a6565b7fffffffffffffffff000000000000000000000000000000000000000000000000166080860152565b614c04614bdb614b668361426a565b7fffffffffffffffff0000000000000000000000000000000000000000000000001660a0860152565b614c0d8161432e565b60c0850152614c1b816143f2565b60e0850152614c29816144b6565b610100850152614c388161457c565b610120850152614c4781614642565b610140850152614c5681614708565b610160850152614c65816147ce565b610180850152614c7481614894565b6101a085015261495a565b5f80610fb293602081519101845af43d15614cbb573d91614c9f83610947565b92614cad60405194856108e5565b83523d5f602085013e614dbc565b606091614dbc565b614ccb612ffe565b8051908115614cdb576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100548015614d085790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b614d3561310d565b8051908115614d45576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101548015614d085790565b602003906020821161203d57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161203d57565b601f811161203d576101000a90565b90614df95750805115614dd157805190602001fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580614e4c575b614e0a575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b15614e0256fea164736f6c634300081c000a", + "sourceMap": "1080:11972:97:-:0;;;;;;;1171:4:51;1163:13;;1080:11972:97;;;;;;1163:13:51;1080:11972:97;;;;;;;;;;;;;;", + "linkReferences": {} + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json new file mode 100644 index 00000000000..1ccf04a535f --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json @@ -0,0 +1,86 @@ +{ + "abi": [ + { + "type": "function", + "name": "baseFee", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "quoteResults", + "inputs": [ + { "name": "", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { "name": "success", "type": "bool", "internalType": "bool" }, + { "name": "output", "type": "bytes", "internalType": "bytes" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setBaseFee", + "inputs": [ + { + "name": "_baseFee", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setQuoteResult", + "inputs": [ + { + "name": "rawQuote", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "_success", "type": "bool", "internalType": "bool" }, + { "name": "_output", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "verifyAndAttestOnChain", + "inputs": [ + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { "name": "", "type": "bool", "internalType": "bool" }, + { "name": "", "type": "bytes", "internalType": "bytes" } + ], + "stateMutability": "payable" + }, + { + "type": "error", + "name": "InsufficientFee", + "inputs": [ + { + "name": "required", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "provided", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "bytecode": { + "object": "0x608080604052346015576106fa908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806338d8480a146103b957806346860698146103825780636ef25c3a1461034757806389e7a16d146102b957639321d73e14610050575f80fd5b346102b55760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b55760043567ffffffffffffffff81116102b55761009f90369060040161048d565b6024358015158091036102b5576044359167ffffffffffffffff83116102b55760206100d1600194369060040161056a565b94604051936100df8561050d565b845281840195865282604051938492833781015f8152030190209051151560ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00835416911617815501905190815167ffffffffffffffff81116102885761014782546105de565b601f8111610243575b50602092601f82116001146101aa57928192935f9261019f575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790555f80f35b015190505f8061016a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821693835f52805f20915f5b86811061022b57508360019596106101f4575b505050811b019055005b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f80806101ea565b919260206001819286850151815501940192016101d7565b825f5260205f20601f830160051c8101916020841061027e575b601f0160051c01905b8181106102735750610150565b5f8155600101610266565b909150819061025d565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f80fd5b346102b55760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b55760043567ffffffffffffffff81116102b557602061030b8192369060040161056a565b604051928184925191829101835e81015f815203019020610333600160ff835416920161062f565b90610343604051928392836104bb565b0390f35b346102b5575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b5576020600154604051908152f35b346102b55760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b557600435600155005b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b55760043567ffffffffffffffff81116102b55761040390369060040161048d565b9060015480341061045e575060209082604051938492833781015f8152030190206104456001604051926104368461050d565b60ff815416151584520161062f565b90816020820152511515610343604051928392836104bb565b7fa458261b000000000000000000000000000000000000000000000000000000005f526004523460245260445ffd5b9181601f840112156102b55782359167ffffffffffffffff83116102b557602083818601950101116102b557565b90601f60206060947fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093151585526040828601528051918291826040880152018686015e5f8582860101520116010190565b6040810190811067ffffffffffffffff82111761028857604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761028857604052565b81601f820112156102b55780359067ffffffffffffffff821161028857604051926105bd60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8601160185610529565b828452602083830101116102b557815f926020809301838601378301015290565b90600182811c92168015610625575b60208310146105f857565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916105ed565b9060405191825f825492610642846105de565b80845293600181169081156106ad5750600114610669575b5061066792500383610529565b565b90505f9291925260205f20905f915b818310610691575050906020610667928201015f61065a565b6020919350806001915483858901015201910190918492610678565b602093506106679592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201015f61065a56fea164736f6c634300081c000a", + "sourceMap": "865:844:109:-:0;;;;;;;;;;;;;;;;;", + "linkReferences": {} + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl new file mode 100644 index 00000000000..824b494e5f1 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl @@ -0,0 +1,903 @@ +{ + "config": { + "chainId": 901, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "mergeNetsplitBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "bedrockBlock": 0, + "regolithTime": 0, + "canyonTime": 0, + "ecotoneTime": 0, + "fjordTime": 0, + "graniteTime": 0, + "holoceneTime": 0, + "isthmusTime": 0, + "jovianTime": 0, + "pragueTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "depositContractAddress": "0x0000000000000000000000000000000000000000", + "optimism": { + "eip1559Elasticity": 6, + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250 + } + }, + "nonce": "0x0", + "timestamp": "", + "extraData": "0x0100000032000000020000000000000000", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x4200000000000000000000000000000000000011", + "alloc": { + "4200000000000000000000000000000000000015": { + "balance": "0x1", + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106dd565b610224565b6100a86100a33660046106f8565b610296565b6040516100b5919061077b565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106dd565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ee565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060c565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81815560405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a25050565b60006106367fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038381556040805173ffffffffffffffffffffffffffffffffffffffff80851682528616602082015292935090917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d857600080fd5b919050565b6000602082840312156106ef57600080fd5b610412826106b4565b60008060006040848603121561070d57600080fd5b610716846106b4565b9250602084013567ffffffffffffffff8082111561073357600080fd5b818601915086601f83011261074757600080fd5b81358181111561075657600080fd5b87602082850101111561076857600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a85785810183015185820160400152820161078c565b818111156107ba576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a", + "storage": { + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000003ba4007f5c922fbb33c454b41ea7a1f11e83df2c" + } + }, + "3Ba4007f5C922FBb33C454B41ea7a1f11E83df2C": { + "balance": "0x1", + "code": "0x608060405234801561001057600080fd5b50600436106101985760003560e01c806364ca23ef116100e3578063c59859181161008c578063e81b2c6d11610066578063e81b2c6d146103f0578063f8206140146103f9578063fe3d57101461040257600080fd5b8063c598591814610375578063d844471514610395578063e591b282146103ce57600080fd5b80638b239f73116100bd5780638b239f73146103435780639e8c49661461034c578063b80777ea1461035557600080fd5b806364ca23ef146102ff57806368d5dca6146103135780638381f58a1461032f57600080fd5b80634397dfef1161014557806354fd4d501161011f57806354fd4d501461027b578063550fcdc9146102bd5780635cf24969146102f657600080fd5b80634397dfef1461021a578063440a5e20146102425780634d5d9a2a1461024a57600080fd5b806316d3bc7f1161017657806316d3bc7f146101d657806321326849146102035780633db6be2b1461021257600080fd5b8063015d8eb91461019d578063098999be146101b257806309bd5a60146101ba575b600080fd5b6101b06101ab366004610623565b610433565b005b6101b0610572565b6101c360025481565b6040519081526020015b60405180910390f35b6008546101ea9067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101cd565b604051600081526020016101cd565b6101b0610585565b6040805173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815260126020820152016101cd565b6101b06105af565b6008546102669068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101cd565b60408051808201909152600581527f312e372e3000000000000000000000000000000000000000000000000000000060208201525b6040516101cd9190610695565b60408051808201909152600381527f455448000000000000000000000000000000000000000000000000000000000060208201526102b0565b6101c360015481565b6003546101ea9067ffffffffffffffff1681565b6003546102669068010000000000000000900463ffffffff1681565b6000546101ea9067ffffffffffffffff1681565b6101c360055481565b6101c360065481565b6000546101ea9068010000000000000000900467ffffffffffffffff1681565b600354610266906c01000000000000000000000000900463ffffffff1681565b60408051808201909152600581527f457468657200000000000000000000000000000000000000000000000000000060208201526102b0565b60405173deaddeaddeaddeaddeaddeaddeaddeaddead000181526020016101cd565b6101c360045481565b6101c360075481565b600854610420906c01000000000000000000000000900461ffff1681565b60405161ffff90911681526020016101cd565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104da576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61057a6105af565b60a43560a01c600855565b61058d6105af565b6dffff00000000000000000000000060b03560901c1660a43560a01c17600855565b73deaddeaddeaddeaddeaddeaddeaddeaddead00013381146105d957633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b803567ffffffffffffffff8116811461061e57600080fd5b919050565b600080600080600080600080610100898b03121561064057600080fd5b61064989610606565b975061065760208a01610606565b9650604089013595506060890135945061067360808a01610606565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b818110156106c2578581018301518582016040015282016106a6565b818111156106d4576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a" + }, + "70997970C51812dc3A010C7d01b50e0d17dc79C8": { + "balance": "0x21e19e0c9bab2400000" + }, + "0000000000000000000000000000000000000000": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000001": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000002": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000003": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000004": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000005": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000006": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000007": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000008": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000009": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000010": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000011": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000012": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000013": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000014": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000015": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000016": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000017": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000018": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000019": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000020": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000021": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000022": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000023": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000024": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000025": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000026": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000027": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000028": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000029": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000030": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000031": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000032": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000033": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000034": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000035": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000036": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000037": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000038": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000039": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000040": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000041": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000042": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000043": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000044": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000045": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000046": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000047": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000048": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000049": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000050": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000051": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000052": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000053": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000054": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000055": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000056": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000057": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000058": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000059": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000060": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000061": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000062": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000063": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000064": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000065": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000066": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000067": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000068": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000069": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000070": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000071": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000072": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000073": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000074": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000075": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000076": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000077": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000078": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000079": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000080": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000081": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000082": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000083": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000084": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000085": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000086": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000087": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000088": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000089": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000090": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000091": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000092": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000093": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000094": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000095": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000096": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000097": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000098": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000099": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009f": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000aa": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ab": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ac": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ad": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ae": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000af": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ba": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000be": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bf": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ca": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ce": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cf": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000da": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000db": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000dc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000dd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000de": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000df": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ea": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000eb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ec": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ed": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ee": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ef": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fa": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fe": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ff": { + "balance": "0x1" + }, + "000000000022d473030f116ddee9f6b43ac78ba3": { + "code": "0x6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000038503611b69577f48deb34b39fb4b41f5c195008940d5ef510cdd7853eba5807b2fa08dfd58647590565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a", + "balance": "0x0", + "nonce": "0x1" + }, + "0000000071727de22e5e9d8baf0edac6f37da032": { + "code": "0x60806040526004361015610024575b361561001957600080fd5b61002233612748565b005b60003560e01c806242dc5314611b0057806301ffc9a7146119ae5780630396cb60146116765780630bd28e3b146115fa5780631b2e01b814611566578063205c2878146113d157806322cdde4c1461136b57806335567e1a146112b35780635287ce12146111a557806370a0823114611140578063765e827f14610e82578063850aaf6214610dc35780639b249f6914610c74578063b760faf914610c3a578063bb9fe6bf14610a68578063c23a5cea146107c4578063dbed18e0146101a15763fc7e286d0361000e573461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5773ffffffffffffffffffffffffffffffffffffffff61013a61229f565b16600052600060205260a0604060002065ffffffffffff6001825492015460405192835260ff8116151560208401526dffffffffffffffffffffffffffff8160081c16604084015263ffffffff8160781c16606084015260981c166080820152f35b600080fd5b3461019c576101af36612317565b906101b86129bd565b60009160005b82811061056f57506101d08493612588565b6000805b8481106102fc5750507fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972600080a16000809360005b81811061024757610240868660007f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d8180a2613ba7565b6001600255005b6102a261025582848a612796565b73ffffffffffffffffffffffffffffffffffffffff6102766020830161282a565b167f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d600080a2806127d6565b906000915b8083106102b957505050600101610209565b909194976102f36102ed6001926102e78c8b6102e0826102da8e8b8d61269d565b9261265a565b5191613597565b90612409565b99612416565b950191906102a7565b6020610309828789612796565b61031f61031682806127d6565b9390920161282a565b9160009273ffffffffffffffffffffffffffffffffffffffff8091165b8285106103505750505050506001016101d4565b909192939561037f83610378610366848c61265a565b516103728b898b61269d565b856129f6565b9290613dd7565b9116840361050a576104a5576103958491613dd7565b9116610440576103b5576103aa600191612416565b96019392919061033c565b60a487604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f65000000000000000000000000000000000000000000000000000000000000006084820152fd5b608488604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413334207369676e6174757265206572726f720000000000000000000000006064820152fd5b608488604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f414132322065787069726564206f72206e6f74206475650000000000000000006064820152fd5b608489604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413234207369676e6174757265206572726f720000000000000000000000006064820152fd5b61057a818487612796565b9361058585806127d6565b919095602073ffffffffffffffffffffffffffffffffffffffff6105aa82840161282a565b1697600192838a1461076657896105da575b5050505060019293949550906105d191612409565b939291016101be565b8060406105e892019061284b565b918a3b1561019c57929391906040519485937f2dd8113300000000000000000000000000000000000000000000000000000000855288604486016040600488015252606490818601918a60051b8701019680936000915b8c83106106e657505050505050838392610684927ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8560009803016024860152612709565b03818a5afa90816106d7575b506106c657602486604051907f86a9f7500000000000000000000000000000000000000000000000000000000082526004820152fd5b93945084936105d1600189806105bc565b6106e0906121bd565b88610690565b91939596977fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c908a9294969a0301865288357ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee18336030181121561019c57836107538793858394016128ec565b9a0196019301909189979695949261063f565b606483604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601760248201527f4141393620696e76616c69642061676772656761746f720000000000000000006044820152fd5b3461019c576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c576107fc61229f565b33600052600082526001604060002001908154916dffffffffffffffffffffffffffff8360081c16928315610a0a5765ffffffffffff8160981c1680156109ac57421061094e5760009373ffffffffffffffffffffffffffffffffffffffff859485947fffffffffffffff000000000000000000000000000000000000000000000000ff86951690556040517fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda33391806108da8786836020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b0390a2165af16108e8612450565b50156108f057005b606490604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601860248201527f6661696c656420746f207769746864726177207374616b6500000000000000006044820152fd5b606485604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601b60248201527f5374616b65207769746864726177616c206973206e6f742064756500000000006044820152fd5b606486604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601d60248201527f6d7573742063616c6c20756e6c6f636b5374616b6528292066697273740000006044820152fd5b606485604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601460248201527f4e6f207374616b6520746f2077697468647261770000000000000000000000006044820152fd5b3461019c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c573360005260006020526001604060002001805463ffffffff8160781c16908115610bdc5760ff1615610b7e5765ffffffffffff908142160191818311610b4f5780547fffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff001678ffffffffffff00000000000000000000000000000000000000609885901b161790556040519116815233907ffa9b3c14cc825c412c9ed81b3ba365a5b459439403f18829e572ed53a4180f0a90602090a2005b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f616c726561647920756e7374616b696e670000000000000000000000000000006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f6e6f74207374616b6564000000000000000000000000000000000000000000006044820152fd5b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c57610022610c6f61229f565b612748565b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5760043567ffffffffffffffff811161019c576020610cc8610d1b9236906004016122c2565b919073ffffffffffffffffffffffffffffffffffffffff9260405194859283927f570e1a360000000000000000000000000000000000000000000000000000000084528560048501526024840191612709565b03816000857f000000000000000000000000efc2c1444ebcc4db75e7613d20c6a62ff67a167c165af1908115610db757602492600092610d86575b50604051917f6ca7b806000000000000000000000000000000000000000000000000000000008352166004820152fd5b610da991925060203d602011610db0575b610da181836121ed565b8101906126dd565b9083610d56565b503d610d97565b6040513d6000823e3d90fd5b3461019c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c57610dfa61229f565b60243567ffffffffffffffff811161019c57600091610e1e839236906004016122c2565b90816040519283928337810184815203915af4610e39612450565b90610e7e6040519283927f99410554000000000000000000000000000000000000000000000000000000008452151560048401526040602484015260448301906123c6565b0390fd5b3461019c57610e9036612317565b610e9b9291926129bd565b610ea483612588565b60005b848110610f1c57506000927fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972600080a16000915b858310610eec576102408585613ba7565b909193600190610f12610f0087898761269d565b610f0a888661265a565b519088613597565b0194019190610edb565b610f47610f40610f2e8385979561265a565b51610f3a84898761269d565b846129f6565b9190613dd7565b73ffffffffffffffffffffffffffffffffffffffff929183166110db5761107657610f7190613dd7565b911661101157610f8657600101929092610ea7565b60a490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f65000000000000000000000000000000000000000000000000000000000000006084820152fd5b608482604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413334207369676e6174757265206572726f720000000000000000000000006064820152fd5b608483604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f414132322065787069726564206f72206e6f74206475650000000000000000006064820152fd5b608484604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413234207369676e6174757265206572726f720000000000000000000000006064820152fd5b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5773ffffffffffffffffffffffffffffffffffffffff61118c61229f565b1660005260006020526020604060002054604051908152f35b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5773ffffffffffffffffffffffffffffffffffffffff6111f161229f565b6000608060405161120181612155565b828152826020820152826040820152826060820152015216600052600060205260a06040600020608060405161123681612155565b6001835493848352015490602081019060ff8316151582526dffffffffffffffffffffffffffff60408201818560081c16815263ffffffff936060840193858760781c16855265ffffffffffff978891019660981c1686526040519788525115156020880152511660408601525116606084015251166080820152f35b3461019c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5760206112ec61229f565b73ffffffffffffffffffffffffffffffffffffffff6113096122f0565b911660005260018252604060002077ffffffffffffffffffffffffffffffffffffffffffffffff821660005282526040600020547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006040519260401b16178152f35b3461019c577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60208136011261019c576004359067ffffffffffffffff821161019c5761012090823603011261019c576113c9602091600401612480565b604051908152f35b3461019c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5761140861229f565b60243590336000526000602052604060002090815491828411611508576000808573ffffffffffffffffffffffffffffffffffffffff8295839561144c848a612443565b90556040805173ffffffffffffffffffffffffffffffffffffffff831681526020810185905233917fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb91a2165af16114a2612450565b50156114aa57005b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6661696c656420746f20776974686472617700000000000000000000000000006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f576974686472617720616d6f756e7420746f6f206c61726765000000000000006044820152fd5b3461019c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5761159d61229f565b73ffffffffffffffffffffffffffffffffffffffff6115ba6122f0565b9116600052600160205277ffffffffffffffffffffffffffffffffffffffffffffffff604060002091166000526020526020604060002054604051908152f35b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5760043577ffffffffffffffffffffffffffffffffffffffffffffffff811680910361019c5733600052600160205260406000209060005260205260406000206116728154612416565b9055005b6020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5760043563ffffffff9182821680920361019c5733600052600081526040600020928215611950576001840154908160781c1683106118f2576116f86dffffffffffffffffffffffffffff9182349160081c16612409565b93841561189457818511611836579065ffffffffffff61180592546040519061172082612155565b8152848101926001845260408201908816815260608201878152600160808401936000855233600052600089526040600020905181550194511515917fffffffffffffffffffffffffff0000000000000000000000000000000000000060ff72ffffffff0000000000000000000000000000006effffffffffffffffffffffffffff008954945160081b16945160781b1694169116171717835551167fffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffff78ffffffffffff0000000000000000000000000000000000000083549260981b169116179055565b6040519283528201527fa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c0160403392a2005b606483604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152600e60248201527f7374616b65206f766572666c6f770000000000000000000000000000000000006044820152fd5b606483604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601260248201527f6e6f207374616b652073706563696669656400000000000000000000000000006044820152fd5b606482604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601c60248201527f63616e6e6f7420646563726561736520756e7374616b652074696d65000000006044820152fd5b606482604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601a60248201527f6d757374207370656369667920756e7374616b652064656c61790000000000006044820152fd5b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c576004357fffffffff00000000000000000000000000000000000000000000000000000000811680910361019c57807f60fc6b6e0000000000000000000000000000000000000000000000000000000060209214908115611ad6575b8115611aac575b8115611a82575b8115611a58575b506040519015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501482611a4d565b7f3e84f0210000000000000000000000000000000000000000000000000000000081149150611a46565b7fcf28ef970000000000000000000000000000000000000000000000000000000081149150611a3f565b7f915074d80000000000000000000000000000000000000000000000000000000081149150611a38565b3461019c576102007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5767ffffffffffffffff60043581811161019c573660238201121561019c57611b62903690602481600401359101612268565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc36016101c0811261019c5761014060405191611b9e83612155565b1261019c5760405192611bb0846121a0565b60243573ffffffffffffffffffffffffffffffffffffffff8116810361019c578452602093604435858201526064356040820152608435606082015260a435608082015260c43560a082015260e43560c08201526101043573ffffffffffffffffffffffffffffffffffffffff8116810361019c5760e08201526101243561010082015261014435610120820152825261016435848301526101843560408301526101a43560608301526101c43560808301526101e43590811161019c57611c7c9036906004016122c2565b905a3033036120f7578351606081015195603f5a0260061c61271060a0840151890101116120ce5760009681519182611ff0575b5050505090611cca915a9003608085015101923691612268565b925a90600094845193611cdc85613ccc565b9173ffffffffffffffffffffffffffffffffffffffff60e0870151168015600014611ea957505073ffffffffffffffffffffffffffffffffffffffff855116935b5a9003019360a06060820151910151016080860151850390818111611e95575b50508302604085015192818410600014611dce5750506003811015611da157600203611d79576113c99293508093611d7481613d65565b613cf6565b5050507fdeadaa51000000000000000000000000000000000000000000000000000000008152fd5b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b81611dde92979396940390613c98565b506003841015611e6857507f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f60808683015192519473ffffffffffffffffffffffffffffffffffffffff865116948873ffffffffffffffffffffffffffffffffffffffff60e0890151169701519160405192835215898301528760408301526060820152a46113c9565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526021600452fd5b6064919003600a0204909301928780611d3d565b8095918051611eba575b5050611d1d565b6003861015611fc1576002860315611eb35760a088015190823b1561019c57600091611f2491836040519586809581947f7c627b210000000000000000000000000000000000000000000000000000000083528d60048401526080602484015260848301906123c6565b8b8b0260448301528b60648301520393f19081611fad575b50611fa65787893d610800808211611f9e575b506040519282828501016040528184528284013e610e7e6040519283927fad7954bc000000000000000000000000000000000000000000000000000000008452600484015260248301906123c6565b905083611f4f565b8980611eb3565b611fb89199506121bd565b6000978a611f3c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91600092918380938c73ffffffffffffffffffffffffffffffffffffffff885116910192f115612023575b808080611cb0565b611cca929195503d6108008082116120c6575b5060405190888183010160405280825260008983013e805161205f575b5050600194909161201b565b7f1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a20188870151918973ffffffffffffffffffffffffffffffffffffffff8551169401516120bc604051928392835260408d84015260408301906123c6565b0390a38680612053565b905088612036565b877fdeaddead000000000000000000000000000000000000000000000000000000006000526000fd5b606486604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601760248201527f4141393220696e7465726e616c2063616c6c206f6e6c790000000000000000006044820152fd5b60a0810190811067ffffffffffffffff82111761217157604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610140810190811067ffffffffffffffff82111761217157604052565b67ffffffffffffffff811161217157604052565b6060810190811067ffffffffffffffff82111761217157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761217157604052565b67ffffffffffffffff811161217157601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926122748261222e565b9161228260405193846121ed565b82948184528183011161019c578281602093846000960137010152565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361019c57565b9181601f8401121561019c5782359167ffffffffffffffff831161019c576020838186019501011161019c57565b6024359077ffffffffffffffffffffffffffffffffffffffffffffffff8216820361019c57565b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc83011261019c5760043567ffffffffffffffff9283821161019c578060238301121561019c57816004013593841161019c5760248460051b8301011161019c57602401919060243573ffffffffffffffffffffffffffffffffffffffff8116810361019c5790565b60005b8381106123b65750506000910152565b81810151838201526020016123a6565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602093612402815180928187528780880191016123a3565b0116010190565b91908201809211610b4f57565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610b4f5760010190565b91908203918211610b4f57565b3d1561247b573d906124618261222e565b9161246f60405193846121ed565b82523d6000602084013e565b606090565b604061248e8183018361284b565b90818351918237206124a3606084018461284b565b90818451918237209260c06124bb60e083018361284b565b908186519182372091845195602087019473ffffffffffffffffffffffffffffffffffffffff833516865260208301358789015260608801526080870152608081013560a087015260a081013582870152013560e08501526101009081850152835261012083019167ffffffffffffffff918484108385111761217157838252845190206101408501908152306101608601524661018086015260608452936101a00191821183831017612171575251902090565b67ffffffffffffffff81116121715760051b60200190565b9061259282612570565b6040906125a260405191826121ed565b8381527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06125d08295612570565b019160005b8381106125e25750505050565b60209082516125f081612155565b83516125fb816121a0565b600081526000849181838201528187820152816060818184015260809282848201528260a08201528260c08201528260e082015282610100820152826101208201528652818587015281898701528501528301528286010152016125d5565b805182101561266e5760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b919081101561266e5760051b810135907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee18136030182121561019c570190565b9081602091031261019c575173ffffffffffffffffffffffffffffffffffffffff8116810361019c5790565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b7f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4602073ffffffffffffffffffffffffffffffffffffffff61278a3485613c98565b936040519485521692a2565b919081101561266e5760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18136030182121561019c570190565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561019c570180359067ffffffffffffffff821161019c57602001918160051b3603831361019c57565b3573ffffffffffffffffffffffffffffffffffffffff8116810361019c5790565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561019c570180359067ffffffffffffffff821161019c5760200191813603831361019c57565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561019c57016020813591019167ffffffffffffffff821161019c57813603831361019c57565b61012091813573ffffffffffffffffffffffffffffffffffffffff811680910361019c576129626129476129ba9561299b93855260208601356020860152612937604087018761289c565b9091806040880152860191612709565b612954606086018661289c565b908583036060870152612709565b6080840135608084015260a084013560a084015260c084013560c084015261298d60e085018561289c565b9084830360e0860152612709565b916129ac610100918281019061289c565b929091818503910152612709565b90565b60028054146129cc5760028055565b60046040517f3ee5aeb5000000000000000000000000000000000000000000000000000000008152fd5b926000905a93805194843573ffffffffffffffffffffffffffffffffffffffff811680910361019c5786526020850135602087015260808501356fffffffffffffffffffffffffffffffff90818116606089015260801c604088015260a086013560c088015260c086013590811661010088015260801c610120870152612a8060e086018661284b565b801561357b576034811061351d578060141161019c578060241161019c5760341161019c57602481013560801c60a0880152601481013560801c60808801523560601c60e08701525b612ad285612480565b60208301526040860151946effffffffffffffffffffffffffffff8660c08901511760608901511760808901511760a0890151176101008901511761012089015117116134bf57604087015160608801510160808801510160a08801510160c0880151016101008801510296835173ffffffffffffffffffffffffffffffffffffffff81511690612b66604085018561284b565b806131e4575b505060e0015173ffffffffffffffffffffffffffffffffffffffff1690600082156131ac575b6020612bd7918b828a01516000868a604051978896879586937f19822f7c00000000000000000000000000000000000000000000000000000000855260048501613db5565b0393f160009181613178575b50612c8b573d8c610800808311612c83575b50604051916020818401016040528083526000602084013e610e7e6040519283927f65c8fd4d000000000000000000000000000000000000000000000000000000008452600484015260606024840152600d60648401527f4141323320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a48301906123c6565b915082612bf5565b9a92939495969798999a91156130f2575b509773ffffffffffffffffffffffffffffffffffffffff835116602084015190600052600160205260406000208160401c60005260205267ffffffffffffffff604060002091825492612cee84612416565b9055160361308d575a8503116130285773ffffffffffffffffffffffffffffffffffffffff60e0606093015116612d42575b509060a09184959697986040608096015260608601520135905a900301910152565b969550505a9683519773ffffffffffffffffffffffffffffffffffffffff60e08a01511680600052600060205260406000208054848110612fc3576080612dcd9a9b9c600093878094039055015192602089015183604051809d819582947f52b7512c0000000000000000000000000000000000000000000000000000000084528c60048501613db5565b039286f1978860009160009a612f36575b50612e86573d8b610800808311612e7e575b50604051916020818401016040528083526000602084013e610e7e6040519283927f65c8fd4d000000000000000000000000000000000000000000000000000000008452600484015260606024840152600d60648401527f4141333320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a48301906123c6565b915082612df0565b9991929394959697989998925a900311612eab57509096959094939291906080612d20565b60a490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602760448201527f41413336206f766572207061796d6173746572566572696669636174696f6e4760648201527f61734c696d6974000000000000000000000000000000000000000000000000006084820152fd5b915098503d90816000823e612f4b82826121ed565b604081838101031261019c5780519067ffffffffffffffff821161019c57828101601f83830101121561019c578181015191612f868361222e565b93612f9460405195866121ed565b838552820160208483850101011161019c57602092612fba9184808701918501016123a3565b01519838612dde565b60848b604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413331207061796d6173746572206465706f73697420746f6f206c6f7700006064820152fd5b608490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413236206f76657220766572696669636174696f6e4761734c696d697400006064820152fd5b608482604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601a60448201527f4141323520696e76616c6964206163636f756e74206e6f6e63650000000000006064820152fd5b600052600060205260406000208054808c11613113578b9003905538612c9c565b608484604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152fd5b9091506020813d6020116131a4575b81613194602093836121ed565b8101031261019c57519038612be3565b3d9150613187565b508060005260006020526040600020548a81116000146131d75750612bd7602060005b915050612b92565b6020612bd7918c036131cf565b833b61345a57604088510151602060405180927f570e1a360000000000000000000000000000000000000000000000000000000082528260048301528160008161323260248201898b612709565b039273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000efc2c1444ebcc4db75e7613d20c6a62ff67a167c1690f1908115610db75760009161343b575b5073ffffffffffffffffffffffffffffffffffffffff811680156133d6578503613371573b1561330c5760141161019c5773ffffffffffffffffffffffffffffffffffffffff9183887fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d604060e0958787602086015195510151168251913560601c82526020820152a391612b6c565b60848d604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f4141313520696e6974436f6465206d757374206372656174652073656e6465726064820152fd5b60848e604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f4141313420696e6974436f6465206d7573742072657475726e2073656e6465726064820152fd5b60848f604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601b60448201527f4141313320696e6974436f6465206661696c6564206f72204f4f4700000000006064820152fd5b613454915060203d602011610db057610da181836121ed565b3861327c565b60848d604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601f60448201527f414131302073656e64657220616c726561647920636f6e7374727563746564006064820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f41413934206761732076616c756573206f766572666c6f7700000000000000006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4141393320696e76616c6964207061796d6173746572416e64446174610000006044820152fd5b5050600060e087015260006080870152600060a0870152612ac9565b9092915a906060810151916040928351967fffffffff00000000000000000000000000000000000000000000000000000000886135d7606084018461284b565b600060038211613b9f575b7f8dd7712f0000000000000000000000000000000000000000000000000000000094168403613a445750505061379d6000926136b292602088015161363a8a5193849360208501528b602485015260648401906128ec565b90604483015203906136727fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0928381018352826121ed565b61379189519485927e42dc5300000000000000000000000000000000000000000000000000000000602085015261020060248501526102248401906123c6565b613760604484018b60806101a091805173ffffffffffffffffffffffffffffffffffffffff808251168652602082015160208701526040820151604087015260608201516060870152838201518487015260a082015160a087015260c082015160c087015260e08201511660e0860152610100808201519086015261012080910151908501526020810151610140850152604081015161016085015260608101516101808501520151910152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83820301610204840152876123c6565b039081018352826121ed565b6020918183809351910182305af1600051988652156137bf575b505050505050565b909192939495965060003d8214613a3a575b7fdeaddead00000000000000000000000000000000000000000000000000000000810361385b57608487878051917f220266b600000000000000000000000000000000000000000000000000000000835260048301526024820152600f60448201527f41413935206f7574206f662067617300000000000000000000000000000000006064820152fd5b7fdeadaa510000000000000000000000000000000000000000000000000000000091929395949650146000146138c55750506138a961389e6138b8935a90612443565b608085015190612409565b9083015183611d748295613d65565b905b3880808080806137b7565b909261395290828601518651907ff62676f440ff169a3a9afdbf812e89e7f95975ee8e5c31214ffdef631c5f479273ffffffffffffffffffffffffffffffffffffffff9580878551169401516139483d610800808211613a32575b508a519084818301018c5280825260008583013e8a805194859485528401528a8301906123c6565b0390a35a90612443565b916139636080860193845190612409565b926000905a94829488519761397789613ccc565b948260e08b0151168015600014613a1857505050875116955b5a9003019560a06060820151910151019051860390818111613a04575b5050840290850151928184106000146139de57505080611e68575090816139d89293611d7481613d65565b906138ba565b6139ee9082849397950390613c98565b50611e68575090826139ff92613cf6565b6139d8565b6064919003600a02049094019338806139ad565b90919892509751613a2a575b50613990565b955038613a24565b905038613920565b8181803e516137d1565b613b97945082935090613a8c917e42dc53000000000000000000000000000000000000000000000000000000006020613b6b9501526102006024860152610224850191612709565b613b3a604484018860806101a091805173ffffffffffffffffffffffffffffffffffffffff808251168652602082015160208701526040820151604087015260608201516060870152838201518487015260a082015160a087015260c082015160c087015260e08201511660e0860152610100808201519086015261012080910151908501526020810151610140850152604081015161016085015260608101516101808501520151910152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83820301610204840152846123c6565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018952886121ed565b60008761379d565b5081356135e2565b73ffffffffffffffffffffffffffffffffffffffff168015613c3a57600080809381935af1613bd4612450565b5015613bdc57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f41413931206661696c65642073656e6420746f2062656e6566696369617279006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4141393020696e76616c69642062656e656669636961727900000000000000006044820152fd5b73ffffffffffffffffffffffffffffffffffffffff166000526000602052613cc66040600020918254612409565b80915590565b610120610100820151910151808214613cf257480180821015613ced575090565b905090565b5090565b9190917f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f6080602083015192519473ffffffffffffffffffffffffffffffffffffffff946020868851169660e089015116970151916040519283526000602084015260408301526060820152a4565b60208101519051907f67b4fa9642f42120bf031f3051d1824b0fe25627945b27b8a6a65d5761d5482e60208073ffffffffffffffffffffffffffffffffffffffff855116940151604051908152a3565b613dcd604092959493956060835260608301906128ec565b9460208201520152565b8015613e6457600060408051613dec816121d1565b828152826020820152015273ffffffffffffffffffffffffffffffffffffffff811690604065ffffffffffff91828160a01c16908115613e5c575b60d01c92825191613e37836121d1565b8583528460208401521691829101524211908115613e5457509091565b905042109091565b839150613e27565b5060009060009056fea2646970667358221220b094fd69f04977ae9458e5ba422d01cd2d20dbcfca0992ff37f19aa07deec25464736f6c63430008170033", + "balance": "0x0", + "nonce": "0x1" + }, + "000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "balance": "0x0", + "nonce": "0x1" + }, + "02484cb50aac86eae85610d6f4bf026f30f6627d": { + "balance": "0x21e19e0c9bab2400000" + }, + "08135da0a343e492fa2d4282f2ae34c6c5cc1bbe": { + "balance": "0x21e19e0c9bab2400000" + }, + "09db0a93b389bef724429898f539aeb7ac2dd55f": { + "balance": "0x21e19e0c9bab2400000" + }, + "0b799c86a49deeb90402691f1041aa3af2d3c875": { + "balance": "0x0", + "nonce": "0x1" + }, + "13b0d85ccb8bf860b6b79af3029fca081ae9bef2": { + "code": "0x6080604052600436106100435760003560e01c8063076c37b21461004f578063481286e61461007157806356299481146100ba57806366cfa057146100da57600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5061006f61006a366004610327565b6100fa565b005b34801561007d57600080fd5b5061009161008c366004610327565b61014a565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100c657600080fd5b506100916100d5366004610349565b61015d565b3480156100e657600080fd5b5061006f6100f53660046103ca565b610172565b61014582826040518060200161010f9061031a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082820381018352601f90910116604052610183565b505050565b600061015683836102e7565b9392505050565b600061016a8484846102f0565b949350505050565b61017d838383610183565b50505050565b6000834710156101f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e636500000060448201526064015b60405180910390fd5b815160000361025f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f60448201526064016101eb565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116610156576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f790000000000000060448201526064016101eb565b60006101568383305b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b61014e806104ad83390190565b6000806040838503121561033a57600080fd5b50508035926020909101359150565b60008060006060848603121561035e57600080fd5b8335925060208401359150604084013573ffffffffffffffffffffffffffffffffffffffff8116811461039057600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156103df57600080fd5b8335925060208401359150604084013567ffffffffffffffff8082111561040557600080fd5b818601915086601f83011261041957600080fd5b81358181111561042b5761042b61039b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104715761047161039b565b8160405282815289602084870101111561048a57600080fd5b826020860160208301376000602084830101528095505050505050925092509256fe608060405234801561001057600080fd5b5061012e806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063249cb3fa14602d575b600080fd5b603c603836600460b1565b604e565b60405190815260200160405180910390f35b60008281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516845290915281205460ff16608857600060aa565b7fa2ef4600d742022d532d4747cb3547474667d6f13804902513b2ec01c848f4b45b9392505050565b6000806040838503121560c357600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811460ed57600080fd5b80915050925092905056fea26469706673582212205ffd4e6cede7d06a5daf93d48d0541fc68189eeb16608c1999a82063b666eb1164736f6c63430008130033a2646970667358221220fdc4a0fe96e3b21c108ca155438d37c9143fb01278a3c1d274948bad89c564ba64736f6c63430008130033", + "balance": "0x0", + "nonce": "0x1" + }, + "cd3b766ccdd6ae721141f452c550ca635964ce71": { + "balance": "0x21e19e0c9bab2400000" + }, + "dd2fd4581271e230360230f9337d5c0430bf44c0": { + "balance": "0x21e19e0c9bab2400000" + }, + "df37f81daad2b0327a0a50003740e1c935c70913": { + "balance": "0x21e19e0c9bab2400000" + }, + "df3e18d64bc6a983f673ab319ccae4f1a57c7097": { + "balance": "0x21e19e0c9bab2400000" + }, + "efc2c1444ebcc4db75e7613d20c6a62ff67a167c": { + "code": "0x6080600436101561000f57600080fd5b6000803560e01c63570e1a361461002557600080fd5b3461018a5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261018a576004359167ffffffffffffffff9081841161018657366023850112156101865783600401358281116101825736602482870101116101825780601411610182577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec810192808411610155577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f81600b8501160116830190838210908211176101555792846024819482600c60209a968b9960405286845289840196603889018837830101525193013560601c5af1908051911561014d575b5073ffffffffffffffffffffffffffffffffffffffff60405191168152f35b90503861012e565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b8380fd5b8280fd5b80fdfea26469706673582212207adef8895ad3393b02fab10a111d85ea80ff35366aa43995f4ea20e67f29200664736f6c63430008170033", + "balance": "0x0", + "nonce": "0x1" + }, + "f39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "balance": "0x21e19e0c9bab2400000" + }, + "fabb0ac9d68b0b445fb7357272ff202c5651694a": { + "balance": "0x21e19e0c9bab2400000" + }, + "23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "balance": "0x21e19e0c9bab2400000" + }, + "a0ee7a142d267c1f36714e4a8f75612f20a79720": { + "balance": "0x21e19e0c9bab2400000" + }, + "fb1bffc9d739b8d520daf37df666da4c687191ea": { + "code": "0x6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611647578063f698da2514611698578063f8dc5dd9146116c3578063ffa1ad741461173e57610231565b8063e19a9dd91461139b578063e318b52b146113ec578063e75235b81461147d578063e86637db146114a857610231565b8063cc2f8452116100d1578063cc2f8452146110e8578063d4d9bdcd146111b5578063d8d11f78146111f0578063e009cfde1461132a57610231565b8063affed0e014610d94578063b4faba0914610dbf578063b63e800d14610ea7578063c4ca3a9c1461101757610231565b80635624b25b1161017a5780636a761202116101495780636a761202146109945780637d83297414610b50578063934f3a1114610bbf578063a0e67e2b14610d2857610231565b80635624b25b146107fb5780635ae6bd37146108b9578063610b592514610908578063694e80c31461095957610231565b80632f54bf6e116101b65780632f54bf6e146104d35780633408e4701461053a578063468721a7146105655780635229073f1461067a57610231565b80630d582f131461029e57806312fb68e0146102f95780632d9ad53d1461046c57610231565b36610231573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023d57600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061027257600080f35b36600080373360601b365260008060143601600080855af13d6000803e80610299573d6000fd5b3d6000f35b3480156102aa57600080fd5b506102f7600480360360408110156102c157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506117ce565b005b34801561030557600080fd5b5061046a6004803603608081101561031c57600080fd5b81019080803590602001909291908035906020019064010000000081111561034357600080fd5b82018360208201111561035557600080fd5b8035906020019184600183028401116401000000008311171561037757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103da57600080fd5b8201836020820111156103ec57600080fd5b8035906020019184600183028401116401000000008311171561040e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611bbe565b005b34801561047857600080fd5b506104bb6004803603602081101561048f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612440565b60405180821515815260200191505060405180910390f35b3480156104df57600080fd5b50610522600480360360208110156104f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612512565b60405180821515815260200191505060405180910390f35b34801561054657600080fd5b5061054f6125e4565b6040518082815260200191505060405180910390f35b34801561057157600080fd5b506106626004803603608081101561058857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105cf57600080fd5b8201836020820111156105e157600080fd5b8035906020019184600183028401116401000000008311171561060357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506125f1565b60405180821515815260200191505060405180910390f35b34801561068657600080fd5b506107776004803603608081101561069d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106e457600080fd5b8201836020820111156106f657600080fd5b8035906020019184600183028401116401000000008311171561071857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506126fc565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107bf5780820151818401526020810190506107a4565b50505050905090810190601f1680156107ec5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561080757600080fd5b5061083e6004803603604081101561081e57600080fd5b810190808035906020019092919080359060200190929190505050612732565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561087e578082015181840152602081019050610863565b50505050905090810190601f1680156108ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108c557600080fd5b506108f2600480360360208110156108dc57600080fd5b81019080803590602001909291905050506127b9565b6040518082815260200191505060405180910390f35b34801561091457600080fd5b506109576004803603602081101561092b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506127d1565b005b34801561096557600080fd5b506109926004803603602081101561097c57600080fd5b8101908080359060200190929190505050612b63565b005b610b3860048036036101408110156109ab57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109f257600080fd5b820183602082011115610a0457600080fd5b80359060200191846001830284011164010000000083111715610a2657600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610ab257600080fd5b820183602082011115610ac457600080fd5b80359060200191846001830284011164010000000083111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612c9d565b60405180821515815260200191505060405180910390f35b348015610b5c57600080fd5b50610ba960048036036040811015610b7357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050612edc565b6040518082815260200191505060405180910390f35b348015610bcb57600080fd5b50610d2660048036036060811015610be257600080fd5b810190808035906020019092919080359060200190640100000000811115610c0957600080fd5b820183602082011115610c1b57600080fd5b80359060200191846001830284011164010000000083111715610c3d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610ca057600080fd5b820183602082011115610cb257600080fd5b80359060200191846001830284011164010000000083111715610cd457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612f01565b005b348015610d3457600080fd5b50610d3d612f90565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d80578082015181840152602081019050610d65565b505050509050019250505060405180910390f35b348015610da057600080fd5b50610da9613139565b6040518082815260200191505060405180910390f35b348015610dcb57600080fd5b50610ea560048036036040811015610de257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1f57600080fd5b820183602082011115610e3157600080fd5b80359060200191846001830284011164010000000083111715610e5357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061313f565b005b348015610eb357600080fd5b506110156004803603610100811015610ecb57600080fd5b8101908080359060200190640100000000811115610ee857600080fd5b820183602082011115610efa57600080fd5b80359060200191846020830284011164010000000083111715610f1c57600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f6757600080fd5b820183602082011115610f7957600080fd5b80359060200191846001830284011164010000000083111715610f9b57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613161565b005b34801561102357600080fd5b506110d26004803603608081101561103a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561108157600080fd5b82018360208201111561109357600080fd5b803590602001918460018302840111640100000000831117156110b557600080fd5b9091929391929390803560ff16906020019092919050505061331f565b6040518082815260200191505060405180910390f35b3480156110f457600080fd5b506111416004803603604081101561110b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613447565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156111a0578082015181840152602081019050611185565b50505050905001935050505060405180910390f35b3480156111c157600080fd5b506111ee600480360360208110156111d857600080fd5b8101908080359060200190929190505050613639565b005b3480156111fc57600080fd5b50611314600480360361014081101561121457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561125b57600080fd5b82018360208201111561126d57600080fd5b8035906020019184600183028401116401000000008311171561128f57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506137d8565b6040518082815260200191505060405180910390f35b34801561133657600080fd5b506113996004803603604081101561134d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613805565b005b3480156113a757600080fd5b506113ea600480360360208110156113be57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613b96565b005b3480156113f857600080fd5b5061147b6004803603606081101561140f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613c1a565b005b34801561148957600080fd5b5061149261428c565b6040518082815260200191505060405180910390f35b3480156114b457600080fd5b506115cc60048036036101408110156114cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561151357600080fd5b82018360208201111561152557600080fd5b8035906020019184600183028401116401000000008311171561154757600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050614296565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561160c5780820151818401526020810190506115f1565b50505050905090810190601f1680156116395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561165357600080fd5b506116966004803603602081101561166a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061443e565b005b3480156116a457600080fd5b506116ad61449f565b6040518082815260200191505060405180910390f35b3480156116cf57600080fd5b5061173c600480360360608110156116e657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061451d565b005b34801561174a57600080fd5b50611753614950565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611793578082015181840152602081019050611778565b50505050905090810190601f1680156117c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6117d6614989565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156118405750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561187857503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6118ea576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146119eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055507f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2682604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414611bba57611bb981612b63565b5b5050565b611bd2604182614a2c90919063ffffffff16565b82511015611c48576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b8681101561243457611c648882614a66565b80945081955082965050505060008460ff16141561206d578260001c9450611c96604188614a2c90919063ffffffff16565b8260001c1015611d0e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611d2760208460001c614a9590919063ffffffff16565b1115611d9b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611dd182611dc360208760001c614a9590919063ffffffff16565b614a9590919063ffffffff16565b1115611e45576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611ee7578082015181840152602081019050611ecc565b50505050905090810190601f168015611f145780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611f4d578082015181840152602081019050611f32565b50505050905090810190601f168015611f7a5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f9957600080fd5b505afa158015611fad573d6000803e3d6000fd5b505050506040513d6020811015611fc357600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612066576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b50506122b2565b60018460ff161415612181578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061210a57506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b61217c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6122b1565b601e8460ff1611156122495760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612238573d6000803e3d6000fd5b5050506020604051035194506122b0565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156122a3573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161180156123795750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123b25750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b612424576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611c52565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff161415801561250b5750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125dd5750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e3386868686604051808673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018060200183600181111561266b57fe5b8152602001828103825284818151815260200191508051906020019080838360005b838110156126a857808201518184015260208101905061268d565b50505050905090810190601f1680156126d55780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a16126f285858585614ab4565b9050949350505050565b6000606061270c868686866125f1565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff8111801561275057600080fd5b506040519080825280601f01601f1916602001820160405280156127835781602001600182028036833780820191505090505b50905060005b838110156127ae57808501548060208302602085010152508080600101915050612789565b508091505092915050565b60076020528060005260406000206000915090505481565b6127d9614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156128435750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6128b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146129b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b612b6b614989565b600354811115612be3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612c5a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000606060055433600454604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c604051808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612d5057fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018060200184810384528e8e82818152602001925080828437600081840152601f19601f820116905080830192505050848103835286818151815260200191508051906020019080838360005b83811015612e0a578082015181840152602081019050612def565b50505050905090810190601f168015612e375780820380516001836020036101000a031916815260200191505b50848103825285818151815260200191508051906020019080838360005b83811015612e70578082015181840152602081019050612e55565b50505050905090810190601f168015612e9d5780820380516001836020036101000a031916815260200191505b509f5050505050505050505050505050505060405180910390a1612eca8d8d8d8d8d8d8d8d8d8d8d614c9a565b9150509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b6000600454905060008111612f7e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b612f8a84848484611bbe565b50505050565b6060600060035467ffffffffffffffff81118015612fad57600080fd5b50604051908082528060200260200182016040528015612fdc5781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613130578083838151811061308757fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508180600101925050613046565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6131ac8a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050896151d7565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146131ea576131e9846156d7565b5b6132388787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050615706565b60008211156132525761325082600060018685615941565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b6000805a9050613376878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050865a615b47565b61337f57600080fd5b60005a8203905080604051602001808281526020019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561340c5780820151818401526020810190506133f1565b50505050905090810190601f1680156134395780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561346257600080fd5b506040519080825280602002602001820160405280156134915781602001602082028036833780820191505090505b509150600080600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156135645750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561356f57508482105b1561362a578084838151811061358157fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081806001019250506134fa565b80925081845250509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561373b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b60006137ed8c8c8c8c8c8c8c8c8c8c8c614296565b8051906020012090509b9a5050505050505050505050565b61380d614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156138775750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6138e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146139e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613b9e614989565b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181557f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa282604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613c22614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613c8c5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015613cc457503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613d36576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613e37576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614158015613ea15750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b613f13576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614013576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a17f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561432757fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b6143b361449f565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b614446614989565b61444f816156d7565b7f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6144cd6125e4565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b614525614989565b8060016003540310156145a0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561460a5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b61467c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461477c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1806004541461494b5761494a81612b63565b5b505050565b6040518060400160405280600581526020017f312e332e3000000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614a2a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614a3f5760009050614a60565b6000828402905082848281614a5057fe5b0414614a5b57600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614aaa57600080fd5b8091505092915050565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614158015614b7f5750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b614bf1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b614bfe858585855a615b47565b90508015614c4e573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a2614c92565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b6000806000614cb48e8e8e8e8e8e8e8e8e8e600554614296565b905060056000815480929190600101919050555080805190602001209150614cdd828286612f01565b506000614ce8615b93565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614614ece578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115614d8b57fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015614e5d578082015181840152602081019050614e42565b50505050905090810190601f168015614e8a5780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015614eb557600080fd5b505af1158015614ec9573d6000803e3d6000fd5b505050505b6101f4614ef56109c48b01603f60408d0281614ee657fe5b04615bc490919063ffffffff16565b015a1015614f6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a9050614fd48f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d14614fc9578e614fcf565b6109c45a035b615b47565b9350614fe95a82615bde90919063ffffffff16565b90508380614ff8575060008a14155b80615004575060008814155b615076576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808911156150905761508d828b8b8b8b615941565b90505b84156150da577f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e8482604051808381526020018281526020019250505060405180910390a161511a565b7f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d238482604051808381526020018281526020019250505060405180910390a15b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146151c6578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b1580156151ad57600080fd5b505af11580156151c1573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b60006004541461524f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b81518111156152c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600181101561533d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b835181101561564357600084828151811061535d57fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156153d15750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561540957503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561544157508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b6154b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146155b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550809250508080600101915050615346565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614615808576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161461593d576158ca8260008360015a615b47565b61593c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461597e5782615980565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415615a98576159ea3a86106159c7573a6159c9565b855b6159dc888a614a9590919063ffffffff16565b614a2c90919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050615a93576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615b3d565b615abd85615aaf888a614a9590919063ffffffff16565b614a2c90919063ffffffff16565b9150615aca848284615bfe565b615b3c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600180811115615b5557fe5b836001811115615b6157fe5b1415615b7a576000808551602087018986f49050615b8a565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b600081831015615bd45781615bd6565b825b905092915050565b600082821115615bed57600080fd5b600082840390508091505092915050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d60008114615ca55760208114615cad5760009350615cb8565b819350615cb8565b600051158215171593505b505050939250505056fea2646970667358221220047fac33099ca576d1c4f1ac6a8abdb0396e42ad6a397d2cb2f4dc1624cc0c5b64736f6c63430007060033", + "balance": "0x0", + "nonce": "0x1" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": "0x1", + "excessBlobGas": "0x0", + "blobGasUsed": "0x0" +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/quote-output.bin b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/quote-output.bin new file mode 100644 index 00000000000..70a42bbf551 Binary files /dev/null and b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/quote-output.bin differ diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/test-jwt-secret.txt b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/test-jwt-secret.txt new file mode 100644 index 00000000000..6e72091cdd5 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/test-jwt-secret.txt @@ -0,0 +1 @@ +688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/test-quote.bin b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/test-quote.bin new file mode 100644 index 00000000000..aea0077d4f1 Binary files /dev/null and b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/artifacts/test-quote.bin differ diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/contracts.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/contracts.rs new file mode 100644 index 00000000000..5f9b2449347 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/contracts.rs @@ -0,0 +1,43 @@ +pub mod flashtestation_registry { + use crate::tests::framework::contracts::block_builder_policy::BlockBuilderPolicy::TD10ReportBody; + use alloy_sol_types::sol; + + sol!( + // https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216 + #[sol(rpc, abi)] + FlashtestationRegistry, + "src/tests/framework/artifacts/contracts/FlashtestationRegistry.json", + ); +} + +pub mod block_builder_policy { + use crate::tests::framework::contracts::block_builder_policy::BlockBuilderPolicy::TD10ReportBody; + use alloy_sol_types::sol; + + sol!( + // https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216 + #[sol(rpc, abi)] + BlockBuilderPolicy, + "src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json", + ); +} + +pub mod flashblocks_number_contract { + use alloy_sol_types::sol; + sol!( + // https://github.com/Uniswap/flashblocks_number_contract/tree/c21ca0aedc3ff4d1eecf20cd55abeb984080bc78 + #[sol(rpc, abi)] + FlashblocksNumber, + "src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json", + ); +} + +pub mod mock_dcap_attestation { + use alloy_sol_types::sol; + sol!( + // https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216 + #[sol(rpc, abi)] + MockAutomataDcapAttestationFee, + "src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json", + ); +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/driver.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/driver.rs new file mode 100644 index 00000000000..a56f030822d --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/driver.rs @@ -0,0 +1,363 @@ +use core::time::Duration; + +use alloy_eips::{BlockNumberOrTag, Encodable2718, eip7685::Requests}; +use alloy_primitives::{B64, B256, Bytes, TxKind, U256, address, hex}; +use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadAttributes, PayloadStatusEnum}; +use alloy_rpc_types_eth::Block; +use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; +use op_alloy_network::Optimism; +use op_alloy_rpc_types::Transaction; +use reth_optimism_node::OpPayloadAttributes; +use reth_optimism_payload_builder::OpPayloadAttrs; +use rollup_boost::OpExecutionPayloadEnvelope; + +use super::{EngineApi, Ipc, LocalInstance, TransactionBuilder}; +use crate::{ + args::OpRbuilderArgs, + tests::{ + DEFAULT_DENOMINATOR, DEFAULT_ELASTICITY, ExternalNode, Protocol, + framework::DEFAULT_GAS_LIMIT, + }, + tx_signer::Signer, +}; + +/// The ChainDriver is a type that allows driving the op builder node to build new blocks manually +/// by calling the `build_new_block` method. It uses the Engine API to interact with the node +/// and the provider to fetch blocks and transactions. +pub struct ChainDriver { + engine_api: EngineApi, + provider: RootProvider, + signer: Option, + gas_limit: Option, + args: OpRbuilderArgs, + validation_nodes: Vec, +} + +// instantiation and configuration +impl ChainDriver { + const MIN_BLOCK_TIME: Duration = Duration::from_secs(1); + + /// Creates a new ChainDriver instance for a local instance of RBuilder running in-process + /// communicating over IPC. + pub async fn local(instance: &LocalInstance) -> eyre::Result> { + Ok(ChainDriver:: { + engine_api: instance.engine_api(), + provider: instance.provider().await?, + signer: Default::default(), + gas_limit: None, + args: instance.args().clone(), + validation_nodes: vec![], + }) + } + + /// Creates a new ChainDriver for some EL node instance. + pub fn remote( + provider: RootProvider, + engine_api: EngineApi, + ) -> ChainDriver { + ChainDriver { + engine_api, + provider, + signer: Default::default(), + gas_limit: None, + args: OpRbuilderArgs::default(), + validation_nodes: vec![], + } + } + + /// Specifies the block builder signing key used to sign builder transactions. + /// If not specified, a random signer will be used. + pub fn with_signer(mut self, signer: Signer) -> Self { + self.signer = Some(signer); + self + } + + /// Specifies a custom gas limit for blocks being built, otherwise the limit is + /// set to a default value of 10_000_000. + pub fn with_gas_limit(mut self, gas_limit: u64) -> Self { + self.gas_limit = Some(gas_limit); + self + } + + /// Adds an external Optimism execution client node that will receive all newly built + /// blocks by this driver and ensure that they are valid. This validation process is + /// transparent and happens in the background when building new blocks. + /// + /// If there are validation nodes specified any newly built block will be submitted to + /// the validation EL and the driver will fail if the block is rejected by the + /// validation node. + pub async fn with_validation_node(mut self, node: ExternalNode) -> eyre::Result { + node.catch_up_with(self.provider()).await?; + self.validation_nodes.push(node); + Ok(self) + } +} + +// public test api +impl ChainDriver { + pub async fn build_new_block_with_no_tx_pool(&self) -> eyre::Result> { + self.build_new_block_with_txs_timestamp(vec![], Some(true), None, None, Some(0)) + .await + } + + /// Builds a new block using the current state of the chain and the transactions in the pool. + pub async fn build_new_block(&self) -> eyre::Result> { + self.build_new_block_with_txs(vec![]).await + } + + /// Builds a new block with block_timestamp calculated as block time right before sending FCU + pub async fn build_new_block_with_current_timestamp( + &self, + timestamp_jitter: Option, + ) -> eyre::Result> { + self.build_new_block_with_txs_timestamp(vec![], None, None, timestamp_jitter, Some(0)) + .await + } + + /// Builds a new block with provided txs and timestamp + pub async fn build_new_block_with_txs_timestamp( + &self, + txs: Vec, + no_tx_pool: Option, + block_timestamp: Option, + // Amount of time to lag before sending FCU. This tests late FCU scenarios + timestamp_jitter: Option, + min_base_fee: Option, + ) -> eyre::Result> { + let latest = self.latest().await?; + + // Add L1 block info as the first transaction in every L2 block + // This deposit transaction contains L1 block metadata required by the L2 chain + // Currently using hardcoded data from L1 block 124665056 + // If this info is not provided, Reth cannot decode the receipt for any transaction + // in the block since it also includes this info as part of the result. + // It does not matter if the to address (4200000000000000000000000000000000000015) is + // not deployed on the L2 chain since Reth queries the block to get the info and not the contract. + let block_info_tx: Bytes = { + let deposit_tx = TxDeposit { + source_hash: B256::default(), + from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"), + to: TxKind::Call(address!("4200000000000000000000000000000000000015")), + mint: 0, + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: JOVIAN_DATA.into(), + }; + + // Create a temporary signer for the deposit + let signer = self.signer.unwrap_or_else(Signer::random); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; + signed_tx.encoded_2718().into() + }; + + let mut wait_until = None; + // If block_timestamp we need to produce new timestamp according to current clocks + let block_timestamp = if let Some(block_timestamp) = block_timestamp { + block_timestamp.as_secs() + } else { + // We take the following second, until which we will need to wait before issuing FCU + let latest_timestamp = (chrono::Utc::now().timestamp() + 1) as u64; + wait_until = Some(latest_timestamp); + latest_timestamp + + Duration::from_millis(self.args.chain_block_time) + .as_secs() + .max(Self::MIN_BLOCK_TIME.as_secs()) + }; + + // This step will alight time at which we send FCU. ideally we must send FCU and the beginning of the second. + if let Some(wait_until) = wait_until { + let sleep_time = Duration::from_secs(wait_until).saturating_sub(Duration::from_millis( + chrono::Utc::now().timestamp_millis() as u64, + )); + if let Some(timestamp_jitter) = timestamp_jitter { + tokio::time::sleep(sleep_time + timestamp_jitter).await; + } else { + tokio::time::sleep(sleep_time).await; + } + } + + let eip_1559_params: u64 = + ((DEFAULT_DENOMINATOR as u64) << 32) | (DEFAULT_ELASTICITY as u64); + + let fcu_result = self + .fcu(OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: block_timestamp, + parent_beacon_block_root: Some(B256::ZERO), + withdrawals: Some(vec![]), + ..Default::default() + }, + transactions: Some(vec![block_info_tx].into_iter().chain(txs).collect()), + gas_limit: Some(self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)), + no_tx_pool, + min_base_fee, + eip_1559_params: Some(B64::from(eip_1559_params)), + }) + .await?; + + if fcu_result.payload_status.is_invalid() { + return Err(eyre::eyre!("Forkchoice update failed: {fcu_result:?}")); + } + + let payload_id = fcu_result + .payload_id + .ok_or_else(|| eyre::eyre!("Forkchoice update did not return a payload ID"))?; + + // wait for the block to be built for the specified chain block time + if let Some(timestamp_jitter) = timestamp_jitter { + tokio::time::sleep( + Duration::from_millis(self.args.chain_block_time) + .max(Self::MIN_BLOCK_TIME) + .saturating_sub(timestamp_jitter), + ) + .await; + } else { + tokio::time::sleep( + Duration::from_millis(self.args.chain_block_time).max(Self::MIN_BLOCK_TIME), + ) + .await; + } + + let payload = + OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?); + + let OpExecutionPayloadEnvelope::V4(payload) = payload else { + return Err(eyre::eyre!("Expected V4 payload, got something else")); + }; + + let payload = payload.execution_payload; + + if self + .engine_api + .new_payload(payload.clone(), vec![], B256::ZERO, Requests::default()) + .await? + .status + != PayloadStatusEnum::Valid + { + return Err(eyre::eyre!("Invalid validation status from builder")); + } + + let new_block_hash = payload.payload_inner.payload_inner.payload_inner.block_hash; + + self.engine_api + .update_forkchoice(latest.header.hash, new_block_hash, None) + .await?; + + let block = self + .provider + .get_block_by_number(BlockNumberOrTag::Latest) + .full() + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest block after building new block"))?; + + assert_eq!( + block.header.hash, new_block_hash, + "New block hash does not match expected hash" + ); + + for validation_node in &self.validation_nodes { + // optionally for each external validation node, ensure + // that they consider the block valid before returning it + // to the test author. + validation_node.post_block(&payload).await?; + } + + Ok(block) + } + + /// Builds a new block using the current state of the chain and the transactions in the pool with a list + /// of mandatory builder transactions. Those are usually deposit transactions. + pub async fn build_new_block_with_txs( + &self, + txs: Vec, + ) -> eyre::Result> { + let latest = self.latest().await?; + let latest_timestamp = Duration::from_secs(latest.header.timestamp); + let block_timestamp = latest_timestamp + Self::MIN_BLOCK_TIME; + + self.build_new_block_with_txs_timestamp(txs, None, Some(block_timestamp), None, Some(0)) + .await + } + + /// Retreives the latest built block and returns only a list of transaction + /// hashes from its body. + pub async fn latest(&self) -> eyre::Result> { + self.provider + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest block")) + } + + /// Retreives the latest built block and returns a list of full transaction + /// contents in its body. + pub async fn latest_full(&self) -> eyre::Result> { + self.provider + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .full() + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest full block")) + } + + /// retreives a specific block by its number or tag and returns a list of transaction + /// hashes from its body. + pub async fn get_block( + &self, + number: BlockNumberOrTag, + ) -> eyre::Result>> { + Ok(self.provider.get_block_by_number(number).await?) + } + + /// retreives a specific block by its number or tag and returns a list of full transaction + /// contents in its body. + pub async fn get_block_full( + &self, + number: BlockNumberOrTag, + ) -> eyre::Result>> { + Ok(self.provider.get_block_by_number(number).full().await?) + } + + /// Returns a transaction builder that can be used to create and send transactions. + pub fn create_transaction(&self) -> TransactionBuilder { + TransactionBuilder::new(self.provider.clone()) + } + + /// Returns a reference to the underlying alloy provider that is used to + /// interact with the chain. + pub const fn provider(&self) -> &RootProvider { + &self.provider + } +} + +// internal methods +impl ChainDriver { + async fn fcu(&self, attribs: OpPayloadAttributes) -> eyre::Result { + let latest = self.latest().await?.header.hash; + let response = self + .engine_api + .update_forkchoice(latest, latest, Some(OpPayloadAttrs(attribs))) + .await?; + + Ok(response) + } +} + +// L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) +// https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1 +// It has the following modifications: +// 1. Function signature support Jovian: cast sig "setL1BlockValuesJovian()" => 0x3db6be2b +// 2. Zero operator fee scalar +// 3. Zero operator fee constant +// 4. DA footprint of 400 applied +// See: // https://specs.optimism.io/protocol/jovian/l1-attributes.html for Jovian specs. +const JOVIAN_DATA: &[u8] = &hex!( + "3db6be2b0000146b000f79c500000000000000040000000066d052e700000000013ad8a + 3000000000000000000000000000000000000000000000000000000003ef12787000000 + 00000000000000000000000000000000000000000000000000000000012fdf87b89884a + 61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a5900000000000000000000 + 00006887246668a3b87f54deb3b94ba47a6f63f32985 + 00000000 + 0000000000000000 + 0190" +); diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/external.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/external.rs new file mode 100644 index 00000000000..8bb99edad23 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/external.rs @@ -0,0 +1,532 @@ +use alloy_consensus::constants::EMPTY_WITHDRAWALS; +use alloy_eips::{BlockNumberOrTag, Encodable2718, eip7685::Requests}; +use alloy_primitives::{B256, U256, keccak256, private::alloy_rlp::Encodable}; +use alloy_provider::{Identity, Provider, ProviderBuilder, RootProvider}; +use alloy_rpc_types_engine::{ + ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadStatusEnum, +}; +use futures::{StreamExt, TryStreamExt}; +use op_alloy_network::Optimism; +use op_alloy_rpc_types_engine::OpExecutionPayloadV4; +use std::path::{Path, PathBuf}; +use testcontainers::bollard::{ + Docker, + container::{ + AttachContainerOptions, Config, CreateContainerOptions, RemoveContainerOptions, + StartContainerOptions, StopContainerOptions, + }, + exec::{CreateExecOptions, StartExecResults}, + image::CreateImageOptions, + secret::{ContainerCreateResponse, HostConfig}, +}; +use tokio::signal; +use tracing::{debug, warn}; + +use crate::tests::{EngineApi, Ipc}; + +const AUTH_CONTAINER_IPC_PATH: &str = "/home/op-reth-shared/auth.ipc"; +const RPC_CONTAINER_IPC_PATH: &str = "/home/op-reth-shared/rpc.ipc"; + +/// This type represents an Optimism execution client node that is running inside a +/// docker container. This node is used to validate the correctness of the blocks built +/// by op-rbuilder. +/// +/// When this node is attached to a `ChainDriver`, it will automatically catch up with the +/// provided chain and will transparently ingest all newly built blocks by the driver. +/// +/// If the built payload fails to validate, then the driver block production function will +/// return an error during `ChainDriver::build_new_block`. +pub struct ExternalNode { + engine_api: EngineApi, + provider: RootProvider, + docker: Docker, + tempdir: PathBuf, + container_id: String, +} + +impl ExternalNode { + /// Creates a new instance of `ExternalNode` that runs the `op-reth` client in a Docker container + /// using the specified version tag. + pub async fn reth_version(version_tag: &str) -> eyre::Result { + let docker = Docker::connect_with_local_defaults()?; + + let tempdir = std::env::var("TESTS_TEMP_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::temp_dir()); + + let tempdir = tempdir.join(format!("reth-shared-{}", nanoid::nanoid!())); + let auth_ipc = tempdir.join("auth.ipc").to_string_lossy().to_string(); + let rpc_ipc = tempdir.join("rpc.ipc").to_string_lossy().to_string(); + + std::fs::create_dir_all(&tempdir) + .map_err(|_| eyre::eyre!("Failed to create temporary directory"))?; + + std::fs::write( + tempdir.join("genesis.json"), + include_str!("./artifacts/genesis.json.tmpl"), + ) + .map_err(|_| eyre::eyre!("Failed to write genesis file"))?; + + // Create Docker container with reth EL client + let container = create_container(&tempdir, &docker, version_tag).await?; + + docker + .start_container(&container.id, None::>) + .await?; + + // Wait for the container to be ready and IPCs to be created + await_ipc_readiness(&docker, &container.id).await?; + + // IPC files created by the container have restrictive permissions, + // so we need to relax them to allow the host to access them. + relax_permissions(&docker, &container.id, AUTH_CONTAINER_IPC_PATH).await?; + relax_permissions(&docker, &container.id, RPC_CONTAINER_IPC_PATH).await?; + + // Connect to the IPCs + let engine_api = EngineApi::with_ipc(&auth_ipc); + let provider = ProviderBuilder::::default() + .connect_ipc(rpc_ipc.into()) + .await?; + + // spin up a task that will clean up the container on ctrl-c + tokio::spawn({ + let docker = docker.clone(); + let container_id = container.id.clone(); + let tempdir = tempdir.clone(); + + async move { + if signal::ctrl_c().await.is_ok() { + cleanup(tempdir.clone(), docker.clone(), container_id.clone()).await; + } + } + }); + + Ok(Self { + engine_api, + provider, + docker, + tempdir, + container_id: container.id, + }) + } + + /// Creates a new instance of `ExternalNode` that runs the `op-reth` client in a Docker container + /// using the latest version. + pub async fn reth() -> eyre::Result { + Self::reth_version("latest").await + } +} + +impl ExternalNode { + /// Access to the RPC API of the validation node. + pub fn provider(&self) -> &RootProvider { + &self.provider + } + + /// Access to the Engine API of the validation node. + pub fn engine_api(&self) -> &EngineApi { + &self.engine_api + } +} + +impl ExternalNode { + /// Catches up this node with another node. + /// + /// This method will fail if this node is ahead of the provided chain or they do not + /// share the same genesis block. + pub async fn catch_up_with(&self, chain: &RootProvider) -> eyre::Result<()> { + // check if we need to catch up + let (latest_hash, latest_number) = chain.latest_block_hash_and_number().await?; + let (our_latest_hash, our_latest_number) = + self.provider.latest_block_hash_and_number().await?; + + // check if we can sync in the first place + match (our_latest_number, latest_number) { + (we, them) if we == them && our_latest_hash == latest_hash => { + // we are already caught up and in sync with the provided chain + return Ok(()); + } + (we, them) if we == them && our_latest_hash != latest_hash => { + // divergent histories, can't sync + return Err(eyre::eyre!( + "External node is at the same height but has a different latest block hash: \ + {we} == {them}, {our_latest_hash} != {latest_hash}", + )); + } + (we, them) if we > them => { + return Err(eyre::eyre!( + "External node is ahead of the provided chain: {we} > {them}", + )); + } + (we, them) if we < them => { + debug!("external node is behind the local chain: {we} < {them}, catching up..."); + + // make sure that we share common history with the provided chain + let hash_at_height = chain.hash_at_height(we).await?; + if hash_at_height != our_latest_hash { + return Err(eyre::eyre!( + "External node does not share the same genesis block or history with \ + the provided chain: {} != {} at height {}", + hash_at_height, + our_latest_hash, + we + )); + } + } + _ => {} + }; + + // we are behind, let's catch up + let mut our_current_height = our_latest_number + 1; + + while our_current_height <= latest_number { + let payload = chain + .execution_payload_for_block(our_current_height) + .await?; + + let (latest_hash, _) = self.provider().latest_block_hash_and_number().await?; + + let status = self + .engine_api() + .new_payload(payload, vec![], B256::ZERO, Requests::default()) + .await?; + + if status.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!( + "Failed to import block at height {our_current_height} into external validation node: {:?}", + status.status + )); + } + + let new_chain_hash = status.latest_valid_hash.unwrap_or_default(); + self.engine_api() + .update_forkchoice(latest_hash, new_chain_hash, None) + .await?; + + our_current_height += 1; + } + + // sync complete, double check that we are in sync + let (final_hash, final_number) = self.provider().latest_block_hash_and_number().await?; + + if final_hash != latest_hash || final_number != latest_number { + return Err(eyre::eyre!( + "Failed to sync external validation node: {:?} != {:?}, {:?} != {:?}", + final_hash, + latest_hash, + final_number, + latest_number + )); + } + + Ok(()) + } + + /// Posts a block to the external validation node for validation and sets it as the latest block + /// in the fork choice. + pub async fn post_block(&self, payload: &OpExecutionPayloadV4) -> eyre::Result<()> { + let result = self + .engine_api + .new_payload(payload.clone(), vec![], B256::ZERO, Requests::default()) + .await?; + + let new_block_hash = payload.payload_inner.payload_inner.payload_inner.block_hash; + debug!( + "external validation node payload status for block {new_block_hash}: {:?}", + result.status + ); + + if result.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!( + "Failed to validate block {new_block_hash} with external validation node." + )); + } + + let (latest_hash, _) = self.provider.latest_block_hash_and_number().await?; + + self.engine_api + .update_forkchoice(latest_hash, new_block_hash, None) + .await?; + + Ok(()) + } +} + +impl Drop for ExternalNode { + fn drop(&mut self) { + // Block on cleaning up the container + let docker = self.docker.clone(); + let container_id = self.container_id.clone(); + let tempdir = self.tempdir.clone(); + tokio::spawn(async move { + cleanup(tempdir, docker, container_id).await; + }); + } +} + +async fn create_container( + tempdir: &Path, + docker: &Docker, + version_tag: &str, +) -> eyre::Result { + let host_config = HostConfig { + binds: Some(vec![format!( + "{}:/home/op-reth-shared:rw", + tempdir.display() + )]), + ..Default::default() + }; + + // first pull the image locally + let mut pull_stream = docker.create_image( + Some(CreateImageOptions { + from_image: "ghcr.io/paradigmxyz/op-reth".to_string(), + tag: version_tag.into(), + ..Default::default() + }), + None, + None, + ); + + while let Some(pull_result) = pull_stream.try_next().await? { + debug!( + "Pulling 'ghcr.io/paradigmxyz/op-reth:{version_tag}' locally: {:?}", + pull_result + ); + } + + // Don't expose any ports, as we will only use IPC for communication. + let container_config = Config { + image: Some(format!("ghcr.io/paradigmxyz/op-reth:{version_tag}")), + entrypoint: Some(vec!["op-reth".to_string()]), + cmd: Some( + vec![ + "node", + "--chain=/home/op-reth-shared/genesis.json", + "--auth-ipc", + &format!("--auth-ipc.path={AUTH_CONTAINER_IPC_PATH}"), + &format!("--ipcpath={RPC_CONTAINER_IPC_PATH}"), + "--disable-discovery", + "--no-persist-peers", + "--max-outbound-peers=0", + "--max-inbound-peers=0", + "--trusted-only", + ] + .into_iter() + .map(String::from) + .collect(), + ), + host_config: Some(host_config), + ..Default::default() + }; + + Ok(docker + .create_container( + Some(CreateContainerOptions::::default()), + container_config, + ) + .await?) +} + +async fn relax_permissions(docker: &Docker, container: &str, path: &str) -> eyre::Result<()> { + let exec = docker + .create_exec( + container, + CreateExecOptions { + cmd: Some(vec!["chmod", "777", path]), + attach_stdout: Some(true), + attach_stderr: Some(true), + ..Default::default() + }, + ) + .await?; + + let StartExecResults::Attached { mut output, .. } = docker.start_exec(&exec.id, None).await? + else { + return Err(eyre::eyre!("Failed to start exec for relaxing permissions")); + }; + + while let Some(Ok(output)) = output.next().await { + use testcontainers::bollard::container::LogOutput::*; + match output { + StdErr { message } => { + return Err(eyre::eyre!( + "Failed to relax permissions for {path}: {}", + String::from_utf8_lossy(&message) + )); + } + _ => continue, + }; + } + + Ok(()) +} + +async fn await_ipc_readiness(docker: &Docker, container: &str) -> eyre::Result<()> { + let mut attach_stream = docker + .attach_container( + container, + Some(AttachContainerOptions:: { + stdout: Some(true), + stderr: Some(true), + stream: Some(true), + logs: Some(true), + ..Default::default() + }), + ) + .await?; + + let mut rpc_ipc_started = false; + let mut auth_ipc_started = false; + + // wait for the node to start and signal that IPCs are ready + while let Some(Ok(output)) = attach_stream.output.next().await { + use testcontainers::bollard::container::LogOutput; + match output { + LogOutput::StdOut { message } | LogOutput::StdErr { message } => { + let message = String::from_utf8_lossy(&message); + if message.contains(AUTH_CONTAINER_IPC_PATH) { + auth_ipc_started = true; + } + + if message.contains(RPC_CONTAINER_IPC_PATH) { + rpc_ipc_started = true; + } + + if message.to_lowercase().contains("error") { + return Err(eyre::eyre!("Failed to start op-reth container: {message}.")); + } + } + LogOutput::StdIn { .. } | LogOutput::Console { .. } => {} + } + + if auth_ipc_started && rpc_ipc_started { + break; + } + } + + if !auth_ipc_started || !rpc_ipc_started { + return Err(eyre::eyre!( + "Failed to start op-reth container: IPCs not ready" + )); + } + + Ok(()) +} + +async fn cleanup(tempdir: PathBuf, docker: Docker, container_id: String) { + // This is a no-op function that will be spawned to clean up the container on ctrl-c + // or Drop. + debug!( + "Cleaning up external node resources at {} [{container_id}]...", + tempdir.display() + ); + + if !tempdir.exists() { + return; // If the tempdir does not exist, there's nothing to clean up. + } + + // Block on cleaning up the container + if let Err(e) = docker + .stop_container(&container_id, None::) + .await + { + warn!("Failed to stop container {}: {}", container_id, e); + } + + if let Err(e) = docker + .remove_container( + &container_id, + Some(RemoveContainerOptions { + force: true, + ..Default::default() + }), + ) + .await + { + warn!("Failed to remove container {}: {}", container_id, e); + } + + // Clean up the temporary directory + std::fs::remove_dir_all(&tempdir).expect("Failed to remove temporary directory"); +} + +trait OptimismProviderExt { + async fn hash_at_height(&self, height: u64) -> eyre::Result; + async fn latest_block_hash_and_number(&self) -> eyre::Result<(B256, u64)>; + async fn execution_payload_for_block(&self, number: u64) -> eyre::Result; +} + +impl OptimismProviderExt for RootProvider { + async fn hash_at_height(&self, height: u64) -> eyre::Result { + let block = self + .get_block_by_number(BlockNumberOrTag::Number(height)) + .await? + .ok_or_else(|| eyre::eyre!("No block found at height {}", height))?; + Ok(block.header.hash) + } + + async fn latest_block_hash_and_number(&self) -> eyre::Result<(B256, u64)> { + let block = self + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .ok_or_else(|| eyre::eyre!("No latest block found"))?; + Ok((block.header.hash, block.header.number)) + } + + async fn execution_payload_for_block(&self, number: u64) -> eyre::Result { + let block = self + .get_block_by_number(BlockNumberOrTag::Number(number)) + .full() + .await? + .ok_or_else(|| eyre::eyre!("No block found at height {}", number))?; + + let withdrawals = block.withdrawals.clone().unwrap_or_default(); + + // Calculate the withdrawals root properly + let withdrawals_root = if withdrawals.is_empty() { + EMPTY_WITHDRAWALS + } else { + // Calculate the Merkle Patricia Trie root of the withdrawals + let mut buf = Vec::new(); + withdrawals.encode(&mut buf); + keccak256(&buf) + }; + + let payload = OpExecutionPayloadV4 { + payload_inner: ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: block.header.parent_hash, + fee_recipient: block.header.beneficiary, + state_root: block.header.state_root, + receipts_root: block.header.receipts_root, + logs_bloom: block.header.logs_bloom, + prev_randao: block.header.mix_hash, + block_number: block.header.number, + gas_limit: block.header.gas_limit, + gas_used: block.header.gas_used, + timestamp: block.header.timestamp, + extra_data: block.header.extra_data.clone(), + base_fee_per_gas: U256::from( + block.header.base_fee_per_gas.unwrap_or_default(), + ), + block_hash: block.header.hash, + transactions: block + .transactions + .into_transactions_vec() + .into_iter() + .map(|tx| tx.as_ref().encoded_2718().into()) + .collect(), + }, + withdrawals: block.withdrawals.unwrap_or_default().to_vec(), + }, + blob_gas_used: block.header.inner.blob_gas_used.unwrap_or_default(), + excess_blob_gas: block.header.inner.excess_blob_gas.unwrap_or_default(), + }, + withdrawals_root, + }; + + Ok(payload) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/instance.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/instance.rs new file mode 100644 index 00000000000..1956f97321b --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/instance.rs @@ -0,0 +1,591 @@ +use crate::{ + args::OpRbuilderArgs, + builders::{BuilderConfig, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, + primitives::reth::engine_api_builder::OpEngineApiBuilder, + revert_protection::{EthApiExtServer, RevertProtectionExt}, + tests::{ + EngineApi, Ipc, TEE_DEBUG_ADDRESS, TransactionPoolObserver, builder_signer, create_test_db, + framework::driver::ChainDriver, get_available_port, + }, + tx::FBPooledTransaction, + tx_signer::Signer, +}; +use alloy_primitives::{Address, B256, Bytes, hex, keccak256}; +use alloy_provider::{Identity, ProviderBuilder, RootProvider}; +use clap::Parser; +use core::{ + any::Any, + future::Future, + net::Ipv4Addr, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; +use futures::{FutureExt, StreamExt}; +use http::{Request, Response, StatusCode}; +use http_body_util::Full; +use hyper::{body::Bytes as HyperBytes, server::conn::http1, service::service_fn}; +use hyper_util::rt::TokioIo; +use moka::future::Cache; +use nanoid::nanoid; +use op_alloy_network::Optimism; +use parking_lot::Mutex; +use reth::{ + args::{DatadirArgs, NetworkArgs, RpcServerArgs}, + core::exit::NodeExitFuture, + tasks::Runtime, +}; +use reth_node_builder::{NodeBuilder, NodeConfig}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_cli::commands::Commands; +use reth_optimism_node::{ + OpNode, + node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, +}; +use reth_optimism_rpc::OpEthApiBuilder; +use reth_transaction_pool::{AllTransactionsEvents, TransactionPool}; +use rollup_boost::FlashblocksPayloadV1; +use std::{ + net::SocketAddr, + sync::{Arc, LazyLock}, +}; +use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; +use tokio_tungstenite::{connect_async, tungstenite::Message}; +use tokio_util::sync::CancellationToken; + +/// Represents a type that emulates a local in-process instance of the OP builder node. +/// This node uses IPC as the communication channel for the RPC server Engine API. +pub struct LocalInstance { + signer: Signer, + config: NodeConfig, + args: OpRbuilderArgs, + task_manager: Option, + exit_future: NodeExitFuture, + _node_handle: Box, + pool_observer: TransactionPoolObserver, + attestation_server: Option, +} + +impl LocalInstance { + /// Creates a new local instance of the OP builder node with the given arguments, + /// with the default Reth node configuration. + /// + /// This method does not prefund any accounts, so before sending any transactions + /// make sure that sender accounts are funded. + pub async fn new(args: OpRbuilderArgs) -> eyre::Result { + Box::pin(Self::new_with_config::

(args, default_node_config())).await + } + + /// Creates a new local instance of the OP builder node with the given arguments, + /// with a given Reth node configuration. + /// + /// This method does not prefund any accounts, so before sending any transactions + /// make sure that sender accounts are funded. + pub async fn new_with_config( + args: OpRbuilderArgs, + config: NodeConfig, + ) -> eyre::Result { + let mut args = args; + let task_manager = task_manager(); + let op_node = OpNode::new(args.rollup_args.clone()); + let reverted_cache = Cache::builder().max_capacity(100).build(); + let reverted_cache_clone = reverted_cache.clone(); + + let (rpc_ready_tx, rpc_ready_rx) = oneshot::channel::<()>(); + let (txpool_ready_tx, txpool_ready_rx) = + oneshot::channel::>(); + + let signer = args.builder_signer.unwrap_or(builder_signer()); + args.builder_signer = Some(signer); + args.rollup_args.enable_tx_conditional = true; + + let attestation_server = if args.flashtestations.flashtestations_enabled { + let server = spawn_attestation_provider().await?; + args.flashtestations.quote_provider = Some(server.url()); + tracing::info!("Started attestation server at {}", server.url()); + Some(server) + } else { + None + }; + + let builder_config = BuilderConfig::::try_from(args.clone()) + .expect("Failed to convert rollup args to builder config"); + let da_config = builder_config.da_config.clone(); + let gas_limit_config = builder_config.gas_limit_config.clone(); + + let addons: OpAddOns< + _, + OpEthApiBuilder, + OpEngineValidatorBuilder, + OpEngineApiBuilder, + > = OpAddOnsBuilder::default() + .with_sequencer(args.rollup_args.sequencer.clone()) + .with_enable_tx_conditional(args.rollup_args.enable_tx_conditional) + .with_da_config(da_config) + .with_gas_limit_config(gas_limit_config) + .build(); + + let node_builder = NodeBuilder::<_, OpChainSpec>::new(config.clone()) + .with_database(create_test_db(config.clone())) + .with_launch_context(task_manager.clone()) + .with_types::() + .with_components( + op_node + .components() + .pool(pool_component(&args)) + .payload(P::new_service(builder_config)?), + ) + .with_add_ons(addons) + .extend_rpc_modules(move |ctx| { + if args.enable_revert_protection { + tracing::info!("Revert protection enabled"); + + let pool = ctx.pool().clone(); + let provider = ctx.provider().clone(); + let revert_protection_ext = RevertProtectionExt::new( + pool, + provider, + ctx.registry.eth_api().clone(), + reverted_cache, + ); + + ctx.modules + .add_or_replace_configured(revert_protection_ext.into_rpc())?; + } + + Ok(()) + }) + .on_rpc_started(move |_, _| { + let _ = rpc_ready_tx.send(()); + Ok(()) + }) + .on_node_started(move |ctx| { + txpool_ready_tx + .send(ctx.pool.all_transactions_event_listener()) + .expect("Failed to send txpool ready signal"); + + Ok(()) + }); + + let node_handle = node_builder.launch().await?; + let exit_future = node_handle.node_exit_future; + let boxed_handle = Box::new(node_handle.node); + let node_handle: Box = boxed_handle; + + // Wait for all required components to be ready + rpc_ready_rx.await.expect("Failed to receive ready signal"); + let pool_monitor = txpool_ready_rx + .await + .expect("Failed to receive txpool ready signal"); + + Ok(Self { + args, + signer, + config, + exit_future, + _node_handle: node_handle, + task_manager: Some(task_manager), + pool_observer: TransactionPoolObserver::new(pool_monitor, reverted_cache_clone), + attestation_server, + }) + } + + /// Creates new local instance of the OP builder node with the standard builder configuration. + /// This method prefunds the default accounts with 1 ETH each. + pub async fn standard() -> eyre::Result { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(ref node_command) = args.command else { + unreachable!() + }; + Self::new::(node_command.ext.clone()).await + } + + /// Creates new local instance of the OP builder node with the flashblocks builder configuration. + /// This method prefunds the default accounts with 1 ETH each. + pub async fn flashblocks() -> eyre::Result { + let mut args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(ref mut node_command) = args.command else { + unreachable!() + }; + node_command.ext.flashblocks.enabled = true; + node_command.ext.flashblocks.flashblocks_port = 0; // use random os assigned port + Self::new::(node_command.ext.clone()).await + } + + pub const fn config(&self) -> &NodeConfig { + &self.config + } + + pub const fn args(&self) -> &OpRbuilderArgs { + &self.args + } + + pub const fn signer(&self) -> &Signer { + &self.signer + } + + pub fn flashblocks_ws_url(&self) -> String { + let ipaddr: Ipv4Addr = self + .args + .flashblocks + .flashblocks_addr + .parse() + .expect("Failed to parse flashblocks IP address"); + + let ipaddr = if ipaddr.is_unspecified() { + Ipv4Addr::LOCALHOST + } else { + ipaddr + }; + + let port = self.args.flashblocks.flashblocks_port; + + format!("ws://{ipaddr}:{port}/") + } + + pub fn spawn_flashblocks_listener(&self) -> FlashblocksListener { + FlashblocksListener::new(self.flashblocks_ws_url()) + } + + pub fn rpc_ipc(&self) -> &str { + &self.config.rpc.ipcpath + } + + pub fn auth_ipc(&self) -> &str { + &self.config.rpc.auth_ipc_path + } + + pub fn engine_api(&self) -> EngineApi { + EngineApi::::with_ipc(self.auth_ipc()) + } + + pub const fn pool(&self) -> &TransactionPoolObserver { + &self.pool_observer + } + + pub const fn attestation_server(&self) -> &Option { + &self.attestation_server + } + + pub async fn driver(&self) -> eyre::Result> { + ChainDriver::::local(self).await + } + + pub async fn provider(&self) -> eyre::Result> { + ProviderBuilder::::default() + .connect_ipc(self.rpc_ipc().to_string().into()) + .await + .map_err(|e| eyre::eyre!("Failed to connect to provider: {e}")) + } +} + +impl Drop for LocalInstance { + fn drop(&mut self) { + if let Some(task_manager) = self.task_manager.take() { + task_manager.graceful_shutdown_with_timeout(Duration::from_secs(3)); + std::fs::remove_dir_all(self.config().datadir().to_string()).unwrap_or_else(|e| { + panic!( + "Failed to remove temporary data directory {}: {e}", + self.config().datadir() + ) + }); + } + } +} + +impl Future for LocalInstance { + type Output = eyre::Result<()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.get_mut().exit_future.poll_unpin(cx) + } +} + +pub fn default_node_config() -> NodeConfig { + let tempdir = std::env::temp_dir(); + let random_id = nanoid!(); + + let data_path = tempdir + .join(format!("rbuilder.{random_id}.datadir")) + .to_path_buf(); + + std::fs::create_dir_all(&data_path).expect("Failed to create temporary data directory"); + + let rpc_ipc_path = tempdir + .join(format!("rbuilder.{random_id}.rpc-ipc")) + .to_path_buf(); + + let auth_ipc_path = tempdir + .join(format!("rbuilder.{random_id}.auth-ipc")) + .to_path_buf(); + + let mut rpc = RpcServerArgs::default().with_auth_ipc(); + rpc.ws = false; + rpc.http = false; + rpc.auth_port = 0; + rpc.ipcpath = rpc_ipc_path.to_string_lossy().into(); + rpc.auth_ipc_path = auth_ipc_path.to_string_lossy().into(); + + let mut network = NetworkArgs::default().with_unused_ports(); + network.discovery.disable_discovery = true; + + let datadir = DatadirArgs { + datadir: data_path + .to_string_lossy() + .parse() + .expect("Failed to parse data dir path"), + static_files_path: None, + rocksdb_path: None, + pprof_dumps_path: None, + }; + + NodeConfig::::new(chain_spec()) + .with_datadir_args(datadir) + .with_rpc(rpc) + .with_network(network) +} + +fn chain_spec() -> Arc { + static CHAIN_SPEC: LazyLock> = LazyLock::new(|| { + let genesis = include_str!("./artifacts/genesis.json.tmpl"); + let genesis = serde_json::from_str(genesis).expect("invalid genesis JSON"); + let chain_spec = OpChainSpec::from_genesis(genesis); + Arc::new(chain_spec) + }); + + CHAIN_SPEC.clone() +} + +fn task_manager() -> Runtime { + Runtime::test() +} + +fn pool_component(args: &OpRbuilderArgs) -> OpPoolBuilder { + let rollup_args = &args.rollup_args; + OpPoolBuilder::::default() + .with_enable_tx_conditional( + // Revert protection uses the same internal pool logic as conditional transactions + // to garbage collect transactions out of the bundle range. + rollup_args.enable_tx_conditional || args.enable_revert_protection, + ) + .with_supervisor( + rollup_args.supervisor_http.clone(), + rollup_args.supervisor_safety_level, + ) +} + +async fn spawn_attestation_provider() -> eyre::Result { + let quote = include_bytes!("./artifacts/test-quote.bin"); + let mut service = AttestationServer::new(TEE_DEBUG_ADDRESS, Bytes::new(), quote.into()); + service.start().await?; + Ok(service) +} + +/// A utility for listening to flashblocks WebSocket messages during tests. +/// +/// This provides a reusable way to capture and inspect flashblocks that are produced +/// during test execution, eliminating the need for duplicate WebSocket listening code. +pub struct FlashblocksListener { + pub flashblocks: Arc>>, + pub cancellation_token: CancellationToken, + pub handle: JoinHandle>, +} + +impl FlashblocksListener { + /// Create a new flashblocks listener that connects to the given WebSocket URL. + /// + /// The listener will automatically parse incoming messages as FlashblocksPayloadV1. + fn new(flashblocks_ws_url: String) -> Self { + let flashblocks = Arc::new(Mutex::new(Vec::new())); + let cancellation_token = CancellationToken::new(); + + let flashblocks_clone = flashblocks.clone(); + let cancellation_token_clone = cancellation_token.clone(); + + let handle = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + let fb = serde_json::from_str(&text).unwrap(); + flashblocks_clone.lock().push(fb); + } + } + } + }); + + Self { + flashblocks, + cancellation_token, + handle, + } + } + + /// Get a snapshot of all received flashblocks + pub fn get_flashblocks(&self) -> Vec { + self.flashblocks.lock().clone() + } + + /// Find a flashblock by index + pub fn find_flashblock(&self, index: u64) -> Option { + self.flashblocks + .lock() + .iter() + .find(|fb| fb.index == index) + .cloned() + } + + /// Check if any flashblock contains the given transaction hash + pub fn contains_transaction(&self, tx_hash: &B256) -> bool { + let tx_hash_str = format!("{tx_hash:#x}"); + self.flashblocks.lock().iter().any(|fb| { + if let Some(receipts) = fb.metadata.get("receipts") + && let Some(receipts_obj) = receipts.as_object() + { + return receipts_obj.contains_key(&tx_hash_str); + } + false + }) + } + + /// Find which flashblock index contains the given transaction hash + pub fn find_transaction_flashblock(&self, tx_hash: &B256) -> Option { + let tx_hash_str = format!("{tx_hash:#x}"); + self.flashblocks.lock().iter().find_map(|fb| { + if let Some(receipts) = fb.metadata.get("receipts") + && let Some(receipts_obj) = receipts.as_object() + && receipts_obj.contains_key(&tx_hash_str) + { + return Some(fb.index); + } + None + }) + } + + /// Stop the listener and wait for it to complete + pub async fn stop(self) -> eyre::Result<()> { + self.cancellation_token.cancel(); + self.handle.await? + } +} + +/// A utility service to spawn a server that returns a mock quote for an attestation request +pub struct AttestationServer { + tee_address: Address, + extra_registration_data: Bytes, + mock_attestation: Bytes, + server_handle: Option>, + shutdown_tx: Option>, + port: u16, + error_on_request: bool, +} + +impl AttestationServer { + pub fn new( + tee_address: Address, + extra_registration_data: Bytes, + mock_attestation: Bytes, + ) -> Self { + AttestationServer { + tee_address, + extra_registration_data, + mock_attestation, + server_handle: None, + shutdown_tx: None, + port: 0, + error_on_request: false, + } + } + + pub fn set_error(&mut self, error: bool) { + self.error_on_request = error; + } + + pub async fn start(&mut self) -> eyre::Result { + self.port = get_available_port(); + let addr = SocketAddr::from(([127, 0, 0, 1], self.port)); + let listener = TcpListener::bind(addr).await?; + + let mock_attestation = self.mock_attestation.clone(); + // Concatenate tee_address bytes and extra_registration_data bytes, then hex encode + let combined = [ + self.tee_address.as_slice(), // 20 bytes address + keccak256(self.extra_registration_data.clone()).as_slice(), // 32 byte hash + &[0u8; 12], // padding to 64 bytes + ] + .concat(); + let set_error = self.error_on_request; + + let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); + self.shutdown_tx = Some(shutdown_tx); + + // Create the service + self.server_handle = Some(tokio::spawn(async move { + loop { + let mock_attestation = mock_attestation.clone(); + let expected_path = format!("/{}", hex::encode(&combined)); + tokio::select! { + // Handle shutdown signal + _ = &mut shutdown_rx => { + break; + } + result = listener.accept() => { + let (stream, _) = result.expect("failed to accept attestation request"); + + tokio::task::spawn(async move { + let service = service_fn(move |req: Request| { + let response = + if set_error { + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Full::new(HyperBytes::new())) + .unwrap() + } + else if req.uri().path() == expected_path { + Response::builder() + .header("content-type", "application/octet-stream") + .body(Full::new(mock_attestation.clone().into())) + .unwrap() + } else { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(HyperBytes::new())) + .unwrap() + }; + async { Ok::<_, hyper::Error>(response) } + }); + + let io = TokioIo::new(stream); + if let Err(err) = http1::Builder::new().serve_connection(io, service).await { + tracing::error!(message = "Error serving attestations", error = %err); + } + }); + } + } + } + })); + + // Give the spawned task a chance to start + tokio::task::yield_now().await; + + Ok(self.port) + } + + pub fn url(&self) -> String { + format!("http://127.0.0.1:{}", self.port) + } +} + +impl Drop for AttestationServer { + fn drop(&mut self) { + if let Some(tx) = self.shutdown_tx.take() { + let _ = tx.send(()); + } + tracing::info!("AttestationServer dropped, terminating server"); + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/macros/Cargo.toml b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/macros/Cargo.toml new file mode 100644 index 00000000000..c131b63bff1 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "macros" +version = "0.1.0" +edition = "2024" +description = "Macros supporting the tests infrastructure for op-rbuilder" + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0" +quote = "1.0" +proc-macro2 = "1.0" +paste = "1.0" diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/macros/src/lib.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/macros/src/lib.rs new file mode 100644 index 00000000000..daa88c7e715 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/macros/src/lib.rs @@ -0,0 +1,297 @@ +use proc_macro::TokenStream; +use quote::{ToTokens, quote}; +use syn::{Expr, ItemFn, Meta, Token, parse_macro_input, punctuated::Punctuated}; + +// Define all variant information in one place +struct VariantInfo { + name: &'static str, + builder_type: &'static str, + default_instance_call: &'static str, + args_modifier: fn(&proc_macro2::TokenStream) -> proc_macro2::TokenStream, + default_args_factory: fn() -> proc_macro2::TokenStream, +} + +const BUILDER_VARIANTS: &[VariantInfo] = &[ + VariantInfo { + name: "standard", + builder_type: "crate::builders::StandardBuilder", + default_instance_call: "crate::tests::LocalInstance::standard().await?", + args_modifier: |args| quote! { #args }, + default_args_factory: || quote! { Default::default() }, + }, + VariantInfo { + name: "flashblocks", + builder_type: "crate::builders::FlashblocksBuilder", + default_instance_call: "crate::tests::LocalInstance::flashblocks().await?", + args_modifier: |args| { + quote! { + { + let mut args = #args; + args.flashblocks.enabled = true; + args.flashblocks.flashblocks_port = crate::tests::get_available_port(); + args + } + } + }, + default_args_factory: || { + quote! { + { + let mut args = crate::args::OpRbuilderArgs::default(); + args.flashblocks.enabled = true; + args.flashblocks.flashblocks_port = crate::tests::get_available_port(); + args + } + } + }, + }, +]; + +fn get_variant_info(variant: &str) -> Option<&'static VariantInfo> { + BUILDER_VARIANTS.iter().find(|v| v.name == variant) +} + +fn get_variant_names() -> Vec<&'static str> { + BUILDER_VARIANTS.iter().map(|v| v.name).collect() +} + +struct TestConfig { + variants: std::collections::HashMap>, // variant name -> custom expression (None = default) + args: Option, // Expression to pass to LocalInstance::new() + config: Option, // NodeConfig for new_with_config + multi_threaded: bool, // Whether to use multi_thread flavor +} + +impl syn::parse::Parse for TestConfig { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut config = TestConfig { + variants: std::collections::HashMap::new(), + args: None, + config: None, + multi_threaded: false, + }; + + if input.is_empty() { + // No arguments provided, generate all variants with defaults + for variant in BUILDER_VARIANTS { + config.variants.insert(variant.name.to_string(), None); + } + return Ok(config); + } + + let args: Punctuated = input.parse_terminated(Meta::parse, Token![,])?; + let variant_names = get_variant_names(); + + for arg in args { + match arg { + Meta::Path(path) => { + if let Some(ident) = path.get_ident() { + let name = ident.to_string(); + if variant_names.contains(&name.as_str()) { + config.variants.insert(name, None); + } else if name == "multi_threaded" { + config.multi_threaded = true; + } else { + return Err(syn::Error::new_spanned( + path, + format!( + "Unknown variant '{}'. Use one of: {}, 'multi_threaded', 'args', or 'config'", + name, + variant_names.join(", ") + ), + )); + } + } + } + Meta::NameValue(nv) => { + if let Some(ident) = nv.path.get_ident() { + let name = ident.to_string(); + if variant_names.contains(&name.as_str()) { + config.variants.insert(name, Some(nv.value)); + } else if name == "args" { + config.args = Some(nv.value); + } else if name == "config" { + config.config = Some(nv.value); + } else { + return Err(syn::Error::new_spanned( + nv.path, + format!( + "Unknown attribute '{}'. Use one of: {}, 'multi_threaded', 'args', or 'config'", + name, + variant_names.join(", ") + ), + )); + } + } + } + _ => { + return Err(syn::Error::new_spanned( + arg, + format!( + "Invalid attribute format. Use one of: {}, 'multi_threaded', 'args', or 'config'", + variant_names.join(", ") + ), + )); + } + } + } + + // Validate that custom expressions and args/config are not used together + for (variant, custom_expr) in &config.variants { + if custom_expr.is_some() && (config.args.is_some() || config.config.is_some()) { + return Err(syn::Error::new_spanned( + config.args.as_ref().or(config.config.as_ref()).unwrap(), + format!( + "Cannot use 'args' or 'config' with custom '{variant}' expression. Use either '{variant} = expression' or 'args/config' parameters, not both." + ), + )); + } + } + + // If only args/config/multi_threaded is specified, generate all variants + if config.variants.is_empty() + && (config.args.is_some() || config.config.is_some() || config.multi_threaded) + { + for variant in BUILDER_VARIANTS { + config.variants.insert(variant.name.to_string(), None); + } + } + + Ok(config) + } +} + +fn generate_instance_init( + variant: &str, + custom_expr: Option<&Expr>, + args: &Option, + config: &Option, +) -> proc_macro2::TokenStream { + if let Some(expr) = custom_expr { + return quote! { #expr }; + } + + let variant_info = + get_variant_info(variant).unwrap_or_else(|| panic!("Unknown variant: {variant}")); + + let builder_type: proc_macro2::TokenStream = variant_info.builder_type.parse().unwrap(); + let default_call: proc_macro2::TokenStream = + variant_info.default_instance_call.parse().unwrap(); + + match (args, config) { + (None, None) => default_call, + (Some(args_expr), None) => { + let modified_args = (variant_info.args_modifier)("e! { #args_expr }); + quote! { crate::tests::LocalInstance::new::<#builder_type>(#modified_args).await? } + } + (None, Some(config_expr)) => { + let default_args = (variant_info.default_args_factory)(); + quote! { + crate::tests::LocalInstance::new_with_config::<#builder_type>(#default_args, #config_expr).await? + } + } + (Some(args_expr), Some(config_expr)) => { + let modified_args = (variant_info.args_modifier)("e! { #args_expr }); + quote! { + crate::tests::LocalInstance::new_with_config::<#builder_type>(#modified_args, #config_expr).await? + } + } + } +} + +#[proc_macro_attribute] +pub fn rb_test(args: TokenStream, input: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(input as ItemFn); + let config = parse_macro_input!(args as TestConfig); + + validate_signature(&input_fn); + + // Create the original function without test attributes (helper function) + let mut helper_fn = input_fn.clone(); + helper_fn + .attrs + .retain(|attr| !attr.path().is_ident("test") && !attr.path().is_ident("tokio")); + + let original_name = &input_fn.sig.ident; + let mut generated_functions = vec![quote! { #helper_fn }]; + + // Generate test for each requested variant + for (variant, custom_expr) in &config.variants { + let test_name = + syn::Ident::new(&format!("{original_name}_{variant}"), original_name.span()); + + let instance_init = + generate_instance_init(variant, custom_expr.as_ref(), &config.args, &config.config); + + let test_attribute = if config.multi_threaded { + quote! { #[tokio::test(flavor = "multi_thread")] } + } else { + quote! { #[tokio::test] } + }; + + generated_functions.push(quote! { + #test_attribute + async fn #test_name() -> eyre::Result<()> { + let subscriber = tracing_subscriber::fmt() + .with_env_filter(std::env::var("RUST_LOG") + .unwrap_or_else(|_| "info".to_string())) + .with_file(true) + .with_line_number(true) + .with_test_writer() + .finish(); + let _guard = tracing::subscriber::set_global_default(subscriber); + tracing::info!("{} start", stringify!(#test_name)); + + let instance = #instance_init; + #original_name(instance).await + } + }); + } + + TokenStream::from(quote! { + #(#generated_functions)* + }) +} + +fn validate_signature(item_fn: &ItemFn) { + if item_fn.sig.asyncness.is_none() { + panic!("Function must be async."); + } + if item_fn.sig.inputs.len() != 1 { + panic!("Function must have exactly one parameter of type LocalInstance."); + } + + let output_types = item_fn + .sig + .output + .to_token_stream() + .to_string() + .replace(" ", ""); + + if output_types != "->eyre::Result<()>" { + panic!("Function must return Result<(), eyre::Error>. Actual: {output_types}",); + } +} + +// Generate conditional execution macros for each variant +macro_rules! generate_if_variant_macros { + ($($variant_name:ident),*) => { + $( + paste::paste! { + #[proc_macro] + pub fn [](input: TokenStream) -> TokenStream { + let input = proc_macro2::TokenStream::from(input); + let suffix = concat!("_", stringify!($variant_name)); + + TokenStream::from(quote! { + if std::thread::current().name().unwrap_or("").ends_with(#suffix) { + #input + } + }) + } + } + )* + }; +} + +// Generate macros for all variants +generate_if_variant_macros!(standard, flashblocks); diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/mod.rs new file mode 100644 index 00000000000..b259215bdbb --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/mod.rs @@ -0,0 +1,86 @@ +mod apis; +mod contracts; +mod driver; +mod external; +mod instance; +mod txs; +mod utils; + +use alloy_primitives::{B256, b256}; +pub use apis::*; +pub use contracts::*; +pub use driver::*; +pub use external::*; +pub use instance::*; +pub use txs::*; +pub use utils::*; + +// anvil default key[1] +pub const BUILDER_PRIVATE_KEY: &str = + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; +// anvil default key[0] +pub const FUNDED_PRIVATE_KEY: &str = + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; +// anvil default key[8] +pub const FLASHBLOCKS_DEPLOY_KEY: &str = + "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97"; +// anvil default key[9] +pub const FLASHTESTATION_DEPLOY_KEY: &str = + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"; + +pub const DEFAULT_GAS_LIMIT: u64 = 10_000_000; + +pub const DEFAULT_DENOMINATOR: u32 = 50; + +pub const DEFAULT_ELASTICITY: u32 = 2; +pub const DEFAULT_JWT_TOKEN: &str = + "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; + +pub const ONE_ETH: u128 = 1_000_000_000_000_000_000; + +// flashtestations constants +pub const TEE_DEBUG_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("6Af149F267e1e62dFc431F2de6deeEC7224746f4"); + +pub const WORKLOAD_ID: B256 = + b256!("952569f637f3f7e36cd8f5a7578ae4d03a1cb05ddaf33b35d3054464bb1c862e"); + +pub const SOURCE_LOCATORS: &[&str] = &[ + "https://github.com/flashbots/flashbots-images/commit/53d431f58a0d1a76f6711518ef8d876ce8181fc2", +]; + +pub const COMMIT_HASH: &str = "53d431f58a0d1a76f6711518ef8d876ce8181fc2"; + +/// This gets invoked before any tests, when the cargo test framework loads the test library. +/// It injects itself into +#[ctor::ctor] +fn init_tests() { + use tracing_subscriber::{filter::filter_fn, prelude::*}; + if let Ok(v) = std::env::var("TEST_TRACE") { + let level = match v.as_str() { + "false" | "off" => return, + "true" | "debug" | "on" => tracing::Level::DEBUG, + "trace" => tracing::Level::TRACE, + "info" => tracing::Level::INFO, + "warn" => tracing::Level::WARN, + "error" => tracing::Level::ERROR, + _ => return, + }; + + // let prefix_blacklist = &["alloy_transport_ipc", "storage::db::mdbx"]; + let prefix_blacklist = &["storage::db::mdbx"]; + + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with(filter_fn(move |metadata| { + metadata.level() <= &level + && !prefix_blacklist + .iter() + .any(|prefix| metadata.target().starts_with(prefix)) + })) + .init(); + } + + #[cfg(not(windows))] + let _ = rlimit::setrlimit(rlimit::Resource::NOFILE, 500_000, 500_000); +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/txs.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/txs.rs new file mode 100644 index 00000000000..cd477530af6 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/txs.rs @@ -0,0 +1,379 @@ +use crate::{ + primitives::bundle::{Bundle, BundleResult}, + tests::funded_signer, + tx::FBPooledTransaction, + tx_signer::Signer, +}; +use alloy_consensus::TxEip1559; +use alloy_eips::{BlockNumberOrTag, eip2718::Encodable2718}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, hex}; +use alloy_provider::{PendingTransactionBuilder, Provider, RootProvider}; +use core::cmp::max; +use dashmap::DashMap; +use futures::StreamExt; +use moka::future::Cache; +use op_alloy_consensus::{OpTxEnvelope, OpTypedTransaction}; +use op_alloy_network::Optimism; +use reth_primitives_traits::Recovered; +use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent, TransactionEvent}; +use std::{collections::VecDeque, sync::Arc}; +use tokio::sync::watch; +use tracing::debug; + +use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; + +#[derive(Clone, Copy, Default)] +pub struct BundleOpts { + block_number_min: Option, + block_number_max: Option, + flashblock_number_min: Option, + flashblock_number_max: Option, + min_timestamp: Option, + max_timestamp: Option, +} + +impl BundleOpts { + pub fn with_block_number_min(mut self, block_number_min: u64) -> Self { + self.block_number_min = Some(block_number_min); + self + } + + pub fn with_block_number_max(mut self, block_number_max: u64) -> Self { + self.block_number_max = Some(block_number_max); + self + } + + pub fn with_flashblock_number_min(mut self, flashblock_number_min: u64) -> Self { + self.flashblock_number_min = Some(flashblock_number_min); + self + } + + pub fn with_flashblock_number_max(mut self, flashblock_number_max: u64) -> Self { + self.flashblock_number_max = Some(flashblock_number_max); + self + } + + pub fn with_min_timestamp(mut self, min_timestamp: u64) -> Self { + self.min_timestamp = Some(min_timestamp); + self + } + + pub fn with_max_timestamp(mut self, max_timestamp: u64) -> Self { + self.max_timestamp = Some(max_timestamp); + self + } +} + +#[derive(Clone)] +pub struct TransactionBuilder { + provider: RootProvider, + signer: Option, + nonce: Option, + base_fee: Option, + tx: TxEip1559, + bundle_opts: Option, + with_reverted_hash: bool, +} + +impl TransactionBuilder { + pub fn new(provider: RootProvider) -> Self { + Self { + provider, + signer: None, + nonce: None, + base_fee: None, + tx: TxEip1559 { + chain_id: 901, + gas_limit: 210000, + ..Default::default() + }, + bundle_opts: None, + with_reverted_hash: false, + } + } + + pub fn with_to(mut self, to: Address) -> Self { + self.tx.to = TxKind::Call(to); + self + } + + pub fn with_create(mut self) -> Self { + self.tx.to = TxKind::Create; + self + } + + pub fn with_value(mut self, value: u128) -> Self { + self.tx.value = U256::from(value); + self + } + + pub fn with_signer(mut self, signer: Signer) -> Self { + self.signer = Some(signer); + self + } + + pub fn with_chain_id(mut self, chain_id: u64) -> Self { + self.tx.chain_id = chain_id; + self + } + + pub fn with_nonce(mut self, nonce: u64) -> Self { + self.tx.nonce = nonce; + self + } + + pub fn with_gas_limit(mut self, gas_limit: u64) -> Self { + self.tx.gas_limit = gas_limit; + self + } + + pub fn with_max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self { + self.tx.max_fee_per_gas = max_fee_per_gas; + self + } + + pub fn with_max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self { + self.tx.max_priority_fee_per_gas = max_priority_fee_per_gas; + self + } + + pub fn with_input(mut self, input: Bytes) -> Self { + self.tx.input = input; + self + } + + pub fn with_bundle(mut self, bundle_opts: BundleOpts) -> Self { + self.bundle_opts = Some(bundle_opts); + self + } + + pub fn with_reverted_hash(mut self) -> Self { + self.with_reverted_hash = true; + self + } + + pub fn with_revert(mut self) -> Self { + self.tx.input = hex!("60006000fd").into(); + self + } + + pub async fn build(mut self) -> Recovered { + let signer = self.signer.unwrap_or(funded_signer()); + + let nonce = match self.nonce { + Some(nonce) => nonce, + None => self + .provider + .get_transaction_count(signer.address) + .pending() + .await + .expect("Failed to get transaction count"), + }; + + let base_fee = match self.base_fee { + Some(base_fee) => base_fee, + None => { + let previous_base_fee = self + .provider + .get_block_by_number(BlockNumberOrTag::Latest) + .await + .expect("failed to get latest block") + .expect("latest block should exist") + .header + .base_fee_per_gas + .expect("base fee should be present in latest block"); + + max(previous_base_fee as u128, MIN_PROTOCOL_BASE_FEE as u128) + } + }; + + self.tx.nonce = nonce; + + if self.tx.max_fee_per_gas == 0 { + self.tx.max_fee_per_gas = base_fee + self.tx.max_priority_fee_per_gas; + } + + signer + .sign_tx(OpTypedTransaction::Eip1559(self.tx)) + .expect("Failed to sign transaction") + } + + pub async fn send(self) -> eyre::Result> { + let with_reverted_hash = self.with_reverted_hash; + let bundle_opts = self.bundle_opts; + let provider = self.provider.clone(); + let transaction = self.build().await; + let txn_hash = transaction.tx_hash(); + let transaction_encoded = transaction.encoded_2718(); + + if let Some(bundle_opts) = bundle_opts { + // Send the transaction as a bundle with the bundle options + let bundle = Bundle { + transactions: vec![transaction_encoded.into()], + reverting_hashes: if with_reverted_hash { + Some(vec![txn_hash]) + } else { + None + }, + block_number_min: bundle_opts.block_number_min, + block_number_max: bundle_opts.block_number_max, + flashblock_number_min: bundle_opts.flashblock_number_min, + flashblock_number_max: bundle_opts.flashblock_number_max, + min_timestamp: bundle_opts.min_timestamp, + max_timestamp: bundle_opts.max_timestamp, + }; + + let result: BundleResult = provider + .client() + .request("eth_sendBundle", (bundle,)) + .await?; + + return Ok(PendingTransactionBuilder::new( + provider.root().clone(), + result.bundle_hash, + )); + } + + Ok(provider + .send_raw_transaction(transaction_encoded.as_slice()) + .await?) + } +} + +type ObservationsMap = DashMap>; + +pub struct TransactionPoolObserver { + /// Stores a mapping of all observed transactions to their history of events. + observations: Arc, + + /// Fired when this type is dropped, giving a signal to the listener loop + /// to stop listening for events. + term: Option>, +} + +impl Drop for TransactionPoolObserver { + fn drop(&mut self) { + // Signal the listener loop to stop listening for events + if let Some(term) = self.term.take() { + let _ = term.send(true); + } + } +} + +impl TransactionPoolObserver { + pub fn new( + stream: AllTransactionsEvents, + reverts: Cache, + ) -> Self { + let mut stream = stream; + let observations = Arc::new(ObservationsMap::new()); + let observations_clone = Arc::clone(&observations); + let (term, mut term_rx) = watch::channel(false); + + tokio::spawn(async move { + let observations = observations_clone; + + loop { + tokio::select! { + _ = term_rx.changed() => { + if *term_rx.borrow() { + debug!("Transaction pool observer terminated."); + return; + } + } + tx_event = stream.next() => { + match tx_event { + Some(FullTransactionEvent::Pending(hash)) => { + tracing::debug!("Transaction pending: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Pending); + }, + Some(FullTransactionEvent::Queued(hash, _)) => { + tracing::debug!("Transaction queued: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Queued); + }, + Some(FullTransactionEvent::Mined { tx_hash, block_hash }) => { + tracing::debug!("Transaction mined: {tx_hash} in block {block_hash}"); + observations.entry(tx_hash).or_default().push_back(TransactionEvent::Mined(block_hash)); + }, + Some(FullTransactionEvent::Replaced { transaction, replaced_by }) => { + tracing::debug!("Transaction replaced: {transaction:?} by {replaced_by}"); + observations.entry(*transaction.hash()).or_default().push_back(TransactionEvent::Replaced(replaced_by)); + }, + Some(FullTransactionEvent::Discarded(hash)) => { + tracing::debug!("Transaction discarded: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Discarded); + reverts.insert(hash, ()).await; + }, + Some(FullTransactionEvent::Invalid(hash)) => { + tracing::debug!("Transaction invalid: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Invalid); + }, + Some(FullTransactionEvent::Propagated(_)) => {}, + None => {}, + } + } + } + } + }); + + Self { + observations, + term: Some(term), + } + } + + pub fn tx_status(&self, txhash: TxHash) -> Option { + self.observations + .get(&txhash) + .and_then(|history| history.back().cloned()) + } + + pub fn is_pending(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Pending)) + } + + pub fn is_queued(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Queued)) + } + + pub fn is_dropped(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Discarded)) + } + + pub fn count(&self, status: TransactionEvent) -> usize { + self.observations + .iter() + .filter(|tx| tx.value().back() == Some(&status)) + .count() + } + + pub fn pending_count(&self) -> usize { + self.count(TransactionEvent::Pending) + } + + pub fn queued_count(&self) -> usize { + self.count(TransactionEvent::Queued) + } + + pub fn dropped_count(&self) -> usize { + self.count(TransactionEvent::Discarded) + } + + /// Returns the history of pool events for a transaction. + pub fn history(&self, txhash: TxHash) -> Option> { + self.observations + .get(&txhash) + .map(|history| history.iter().cloned().collect()) + } + + pub fn print_all(&self) { + tracing::debug!("TxPool {:#?}", self.observations); + } + + pub fn exists(&self, txhash: TxHash) -> bool { + matches!( + self.tx_status(txhash), + Some(TransactionEvent::Pending) | Some(TransactionEvent::Queued) + ) + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/utils.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/utils.rs new file mode 100644 index 00000000000..99772de19eb --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/framework/utils.rs @@ -0,0 +1,390 @@ +use crate::{ + tests::{ + BUILDER_PRIVATE_KEY, COMMIT_HASH, FLASHBLOCKS_DEPLOY_KEY, FLASHTESTATION_DEPLOY_KEY, + Protocol, SOURCE_LOCATORS, WORKLOAD_ID, block_builder_policy::BlockBuilderPolicy, + flashblocks_number_contract::FlashblocksNumber, + flashtestation_registry::FlashtestationRegistry, framework::driver::ChainDriver, + mock_dcap_attestation::MockAutomataDcapAttestationFee, + }, + tx_signer::Signer, +}; +use alloy_eips::Encodable2718; +use alloy_primitives::{Address, B256, BlockHash, TxHash, TxKind, U256, hex}; +use alloy_rpc_types_eth::{Block, BlockTransactionHashes}; +use alloy_sol_types::SolCall; +use core::future::Future; +use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; +use op_alloy_rpc_types::Transaction; +use reth_db::{ + ClientVersion, DatabaseEnv, init_db, + mdbx::{DatabaseArguments, KILOBYTE, MEGABYTE, MaxReadTransactionDuration}, + test_utils::{ERROR_DB_CREATION, TempDatabase}, +}; +use reth_node_core::{args::DatadirArgs, dirs::DataDirPath, node_config::NodeConfig}; +use reth_optimism_chainspec::OpChainSpec; +use std::{net::TcpListener, sync::Arc}; + +use super::{FUNDED_PRIVATE_KEY, TransactionBuilder}; + +pub trait TransactionBuilderExt { + fn random_valid_transfer(self) -> Self; + fn random_reverting_transaction(self) -> Self; + fn random_big_transaction(self) -> Self; + // flashblocks number methods + fn deploy_flashblock_number_contract(self) -> Self; + fn init_flashblock_number_contract(self, register_builder: bool) -> Self; + fn add_authorized_builder(self, builder: Address) -> Self; + // flashtestations methods + fn deploy_flashtestation_registry_contract(self) -> Self; + fn init_flashtestation_registry_contract(self, dcap_address: Address) -> Self; + fn deploy_builder_policy_contract(self) -> Self; + fn init_builder_policy_contract(self, registry_address: Address) -> Self; + fn add_workload_to_policy(self) -> Self; + fn deploy_mock_dcap_contract(self) -> Self; + fn add_mock_quote(self) -> Self; +} + +impl TransactionBuilderExt for TransactionBuilder { + fn random_valid_transfer(self) -> Self { + self.with_to(rand::random::

()).with_value(1) + } + + fn random_reverting_transaction(self) -> Self { + self.with_create().with_input(hex!("60006000fd").into()) // PUSH1 0x00 PUSH1 0x00 REVERT + } + + // This transaction is big in the sense that it uses a lot of gas. The exact + // amount it uses is 86220 gas. + fn random_big_transaction(self) -> Self { + // PUSH13 0x63ffffffff60005260046000f3 PUSH1 0x00 MSTORE PUSH1 0x02 PUSH1 0x0d PUSH1 0x13 PUSH1 0x00 CREATE2 + self.with_create() + .with_input(hex!("6c63ffffffff60005260046000f36000526002600d60136000f5").into()) + } + + fn deploy_flashblock_number_contract(self) -> Self { + self.with_create() + .with_input(FlashblocksNumber::BYTECODE.clone()) + .with_gas_limit(2_000_000) // deployment costs ~1.6 million gas + .with_signer(flashblocks_number_signer()) + } + + fn init_flashblock_number_contract(self, register_builder: bool) -> Self { + let builder_signer = builder_signer(); + let owner = flashblocks_number_signer(); + + let init_data = FlashblocksNumber::initializeCall { + _owner: owner.address, + _initialBuilders: if register_builder { + vec![builder_signer.address] + } else { + vec![] + }, + } + .abi_encode(); + + self.with_input(init_data.into()) + .with_signer(flashblocks_number_signer()) + } + + fn add_authorized_builder(self, builder: Address) -> Self { + let calldata = FlashblocksNumber::addBuilderCall { builder }.abi_encode(); + + self.with_input(calldata.into()) + .with_signer(flashblocks_number_signer()) + } + + fn deploy_flashtestation_registry_contract(self) -> Self { + self.with_create() + .with_input(FlashtestationRegistry::BYTECODE.clone()) + .with_gas_limit(5_000_000) + .with_signer(flashtestations_signer()) + } + + fn init_flashtestation_registry_contract(self, dcap_address: Address) -> Self { + let owner = flashtestations_signer(); + + let init_data = FlashtestationRegistry::initializeCall { + owner: owner.address, + _attestationContract: dcap_address, + } + .abi_encode(); + + self.with_input(init_data.into()).with_signer(owner) + } + + fn deploy_builder_policy_contract(self) -> Self { + self.with_create() + .with_input(BlockBuilderPolicy::BYTECODE.clone()) + .with_gas_limit(3_000_000) + .with_signer(flashtestations_signer()) + } + + fn init_builder_policy_contract(self, registry_address: Address) -> Self { + let owner = flashtestations_signer(); + + let init_data = BlockBuilderPolicy::initializeCall { + _initialOwner: owner.address, + _registry: registry_address, + } + .abi_encode(); + + self.with_input(init_data.into()) + .with_signer(flashtestations_signer()) + } + + fn add_workload_to_policy(self) -> Self { + let workload = BlockBuilderPolicy::addWorkloadToPolicyCall { + workloadId: WORKLOAD_ID, + commitHash: COMMIT_HASH.to_string(), + sourceLocators: SOURCE_LOCATORS + .iter() + .map(|source| source.to_string()) + .collect(), + } + .abi_encode(); + + self.with_input(workload.into()) + .with_signer(flashtestations_signer()) + } + + fn deploy_mock_dcap_contract(self) -> Self { + self.with_create() + .with_input(MockAutomataDcapAttestationFee::BYTECODE.clone()) + .with_gas_limit(1_000_000) + .with_signer(flashtestations_signer()) + } + + fn add_mock_quote(self) -> Self { + let quote = MockAutomataDcapAttestationFee::setQuoteResultCall { + // quote from http://ns31695324.ip-141-94-163.eu:10080/attest for builder key + rawQuote: include_bytes!("./artifacts/test-quote.bin").into(), + _success: true, + // response from verifyAndAttestOnChain from the real automata dcap contract on + // unichain sepolia 0x95175096a9B74165BE0ac84260cc14Fc1c0EF5FF + _output: include_bytes!("./artifacts/quote-output.bin").into(), + } + .abi_encode(); + self.with_input(quote.into()) + .with_gas_limit(500_000) + .with_signer(flashtestations_signer()) + } +} + +pub trait ChainDriverExt { + fn fund_many( + &self, + addresses: Vec
, + amount: u128, + ) -> impl Future>; + fn fund(&self, address: Address, amount: u128) + -> impl Future>; + + fn fund_accounts( + &self, + count: usize, + amount: u128, + ) -> impl Future>> { + async move { + let accounts = (0..count).map(|_| Signer::random()).collect::>(); + self.fund_many(accounts.iter().map(|a| a.address).collect(), amount) + .await?; + Ok(accounts) + } + } + + fn build_new_block_with_valid_transaction( + &self, + ) -> impl Future)>>; + + fn build_new_block_with_reverting_transaction( + &self, + ) -> impl Future)>>; +} + +impl ChainDriverExt for ChainDriver

{ + async fn fund_many(&self, addresses: Vec

, amount: u128) -> eyre::Result { + let mut txs = Vec::with_capacity(addresses.len()); + + for address in addresses { + let deposit = TxDeposit { + source_hash: B256::default(), + from: address, // Set the sender to the address of the account to seed + to: TxKind::Create, + mint: amount, // Amount to deposit + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: Default::default(), // No input data for the deposit + }; + + let signer = Signer::random(); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit))?; + let signed_tx_rlp = signed_tx.encoded_2718(); + txs.push(signed_tx_rlp.into()); + } + + Ok(self.build_new_block_with_txs(txs).await?.header.hash) + } + + async fn fund(&self, address: Address, amount: u128) -> eyre::Result { + let deposit = TxDeposit { + source_hash: B256::default(), + from: address, // Set the sender to the address of the account to seed + to: TxKind::Create, + mint: amount, // Amount to deposit + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: Default::default(), // No input data for the deposit + }; + + let signer = Signer::random(); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit))?; + let signed_tx_rlp = signed_tx.encoded_2718(); + Ok(self + .build_new_block_with_txs(vec![signed_tx_rlp.into()]) + .await? + .header + .hash) + } + + async fn build_new_block_with_valid_transaction( + &self, + ) -> eyre::Result<(TxHash, Block)> { + let tx = self + .create_transaction() + .random_valid_transfer() + .send() + .await?; + Ok((*tx.tx_hash(), self.build_new_block().await?)) + } + + async fn build_new_block_with_reverting_transaction( + &self, + ) -> eyre::Result<(TxHash, Block)> { + let tx = self + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + + Ok((*tx.tx_hash(), self.build_new_block().await?)) + } +} + +pub trait BlockTransactionsExt { + fn includes(&self, txs: &impl AsTxs) -> bool; +} + +impl BlockTransactionsExt for Block { + fn includes(&self, txs: &impl AsTxs) -> bool { + txs.as_txs() + .into_iter() + .all(|tx| self.transactions.hashes().any(|included| included == tx)) + } +} + +impl BlockTransactionsExt for BlockTransactionHashes<'_, Transaction> { + fn includes(&self, txs: &impl AsTxs) -> bool { + let mut included_tx_iter = self.clone(); + txs.as_txs() + .iter() + .all(|tx| included_tx_iter.any(|included| included == *tx)) + } +} + +pub trait OpRbuilderArgsTestExt { + fn test_default() -> Self; +} + +impl OpRbuilderArgsTestExt for crate::args::OpRbuilderArgs { + fn test_default() -> Self { + let mut default = Self::default(); + default.flashblocks.flashblocks_port = 0; // randomize port + default + } +} + +pub trait AsTxs { + fn as_txs(&self) -> Vec; +} + +impl AsTxs for TxHash { + fn as_txs(&self) -> Vec { + vec![*self] + } +} + +impl AsTxs for Vec { + fn as_txs(&self) -> Vec { + self.clone() + } +} + +pub fn create_test_db(config: NodeConfig) -> Arc> { + let path = reth_node_core::dirs::MaybePlatformPath::::from( + reth_db::test_utils::tempdir_path(), + ); + let db_config = config.with_datadir_args(DatadirArgs { + datadir: path.clone(), + ..Default::default() + }); + let data_dir = path.unwrap_or_chain_default(db_config.chain.chain(), db_config.datadir.clone()); + let path = data_dir.db(); + let db = init_db( + path.as_path(), + DatabaseArguments::new(ClientVersion::default()) + .with_max_read_transaction_duration(Some(MaxReadTransactionDuration::Unbounded)) + .with_geometry_max_size(Some(4 * MEGABYTE)) + .with_growth_step(Some(4 * KILOBYTE)), + ) + .expect(ERROR_DB_CREATION); + Arc::new(TempDatabase::new(db, path)) +} + +/// Gets an available port by first binding to port 0 -- instructing the OS to +/// find and assign one. Then the listener is dropped when this goes out of +/// scope, freeing the port for the next time this function is called. +pub fn get_available_port() -> u16 { + TcpListener::bind("127.0.0.1:0") + .expect("Failed to bind to random port") + .local_addr() + .expect("Failed to get local address") + .port() +} + +pub fn builder_signer() -> Signer { + Signer::try_from_secret( + BUILDER_PRIVATE_KEY + .parse() + .expect("invalid hardcoded builder private key"), + ) + .expect("Failed to create signer from hardcoded builder private key") +} + +pub fn funded_signer() -> Signer { + Signer::try_from_secret( + FUNDED_PRIVATE_KEY + .parse() + .expect("invalid hardcoded funded private key"), + ) + .expect("Failed to create signer from hardcoded funded private key") +} + +pub fn flashblocks_number_signer() -> Signer { + Signer::try_from_secret( + FLASHBLOCKS_DEPLOY_KEY + .parse() + .expect("invalid hardcoded flashblocks number deployer private key"), + ) + .expect("Failed to create signer from hardcoded flashblocks number deployer private key") +} + +pub fn flashtestations_signer() -> Signer { + Signer::try_from_secret( + FLASHTESTATION_DEPLOY_KEY + .parse() + .expect("invalid hardcoded flashtestations deployer private key"), + ) + .expect("Failed to create signer from hardcoded flashtestations deployer private key") +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/gas_limiter.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/gas_limiter.rs new file mode 100644 index 00000000000..00dfb03538e --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/gas_limiter.rs @@ -0,0 +1,121 @@ +use crate::{ + args::OpRbuilderArgs, + gas_limiter::args::GasLimiterArgs, + tests::{ChainDriverExt, LocalInstance, TransactionBuilderExt}, +}; +use macros::rb_test; +use std::collections::HashSet; +use tracing::info; + +/// Integration test for the gas limiter functionality. +/// Tests that gas limits are properly enforced during actual block building +/// and transaction execution. +#[rb_test(args = OpRbuilderArgs { + gas_limiter: GasLimiterArgs { + gas_limiter_enabled: true, + max_gas_per_address: 200000, // 200k gas per address - low for testing + refill_rate_per_block: 100000, // 100k gas refill per block + cleanup_interval: 100, + }, + ..Default::default() +})] +async fn gas_limiter_blocks_excessive_usage(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // Fund some accounts for testing + let funded_accounts = driver + .fund_accounts(2, 10_000_000_000_000_000_000u128) + .await?; // 10 ETH each + + // These transactions should not be throttled + let tx1 = driver + .create_transaction() + .with_signer(funded_accounts[0]) + .random_valid_transfer() + .send() + .await?; + + let tx2 = driver + .create_transaction() + .with_signer(funded_accounts[1]) + .random_valid_transfer() + .send() + .await?; + + // Build block and verify inclusion + let block = driver.build_new_block_with_current_timestamp(None).await?; + let tx_hashes: HashSet<_> = block.transactions.hashes().collect(); + + assert!(tx_hashes.contains(tx1.tx_hash()), "tx1 should be included"); + assert!(tx_hashes.contains(tx2.tx_hash()), "tx2 should be included"); + + // Send multiple big transactions from the same address - these should hit the gas limiter + let mut sent_txs = Vec::new(); + for i in 0..5 { + let big_tx = driver + .create_transaction() + .with_signer(funded_accounts[0]) + .random_big_transaction() + .send() + .await?; + sent_txs.push(*big_tx.tx_hash()); + info!( + "Sent big transaction {} from address {}", + i + 1, + funded_accounts[0].address + ); + } + + // Meanwhile, the other address should not be throttled + let legit_tx = driver + .create_transaction() + .with_signer(funded_accounts[1]) + .random_big_transaction() + .send() + .await?; + + let block = driver.build_new_block_with_current_timestamp(None).await?; + let tx_hashes: HashSet<_> = block.transactions.hashes().collect(); + + let included_count = sent_txs.iter().filter(|tx| tx_hashes.contains(*tx)).count(); + + // With gas limiting, we shouldn't get all 5 big transactions from the same + // address. We do this imprecise count because we haven't built a way of + // sending a tx that uses an exact amount of gas. + assert!( + included_count < 5, + "Gas limiter should have rejected some transactions, included: {}/5", + included_count + ); + assert!( + included_count > 0, + "Gas limiter should have allowed at least one transaction" + ); + + assert!( + tx_hashes.contains(legit_tx.tx_hash()), + "Transaction from different address should be included" + ); + + // After building new blocks, the limited address should get more capacity + for _ in 0..3 { + let _block = driver.build_new_block_with_current_timestamp(None).await?; + } + + let tx_after_refill = driver + .create_transaction() + .with_signer(funded_accounts[0]) + .random_valid_transfer() + .send() + .await?; + + let refill_block = driver.build_new_block_with_current_timestamp(None).await?; + let refill_tx_hashes: HashSet<_> = refill_block.transactions.hashes().collect(); + + assert!( + refill_tx_hashes.contains(tx_after_refill.tx_hash()), + "Transaction should succeed after refill" + ); + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/miner_gas_limit.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/miner_gas_limit.rs new file mode 100644 index 00000000000..619509db4a0 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/miner_gas_limit.rs @@ -0,0 +1,138 @@ +use crate::tests::{BlockTransactionsExt, LocalInstance}; +use alloy_provider::Provider; +use macros::{if_flashblocks, if_standard, rb_test}; + +/// This test ensures that the miner gas limit is respected +/// We will set the limit to 60,000 and see that the builder will not include any transactions +#[rb_test] +async fn miner_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + let call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (60000,)) + .await?; + assert!(call, "miner_setGasLimit should be executed successfully"); + + let unfit_tx = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + // tx should not be included because the gas limit is less than the transaction gas + assert!( + !block.includes(unfit_tx.tx_hash()), + "transaction should not be included in the block" + ); + + Ok(()) +} + +/// This test ensures that block will fill up to the limit, each transaction is 53,000 gas +/// We will set our limit to 1Mgas and ensure that throttling occurs +/// There is a deposit transaction for 182,706 gas, and builder transactions are 21,600 gas +/// +/// Standard = (785,000 - 182,706 - 21,600) / 53,000 = 10.95 = 10 transactions can fit +/// Flashblocks = (785,000 - 182,706 - 21,600 - 21,600) / 53,000 = 10.54 = 10 transactions can fit +#[rb_test] +async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + let call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (785_000,)) + .await?; + assert!(call, "miner_setGasLimit should be executed successfully"); + + let mut tx_hashes = Vec::new(); + for _ in 0..10 { + let tx = driver + .create_transaction() + .with_gas_limit(53000) + .with_max_priority_fee_per_gas(100) + .send() + .await?; + tx_hashes.push(tx.tx_hash().clone()); + } + let unfit_tx = driver + .create_transaction() + .with_gas_limit(53000) + .with_max_priority_fee_per_gas(50) + .send() + .await?; + + let block = driver.build_new_block().await?; + + for (i, tx_hash) in tx_hashes.iter().enumerate() { + assert!( + block.includes(tx_hash), + "tx i={} hash={} should be in block", + i, + tx_hash + ); + } + assert!( + !block.includes(unfit_tx.tx_hash()), + "unfit tx should not be in block" + ); + + if_standard! { + assert_eq!( + block.transactions.len(), + 12, + "deposit + builder + 15 valid txs should be in the block" + ); + } + + if_flashblocks! { + assert_eq!( + block.transactions.len(), + 13, + "deposit + builder + 15 valid txs should be in the block" + ); + } + + Ok(()) +} + +/// This test ensures that the gasLimit can be reset to the default value +/// by setting it to 0 +#[rb_test] +async fn reset_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + let call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (60000,)) + .await?; + assert!(call, "miner_setGasLimit should be executed successfully"); + + let unfit_tx = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + // tx should not be included because the gas limit is less than the transaction gas + assert!( + !block.includes(unfit_tx.tx_hash()), + "transaction should not be included in the block" + ); + + let reset_call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (0,)) + .await?; + assert!( + reset_call, + "miner_setGasLimit should be executed successfully" + ); + + let _ = driver.build_new_block().await?; + + let fit_tx = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + // tx should be included because the gas limit is reset to the default value + assert!( + block.includes(fit_tx.tx_hash()), + "transaction should be in block" + ); + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/mod.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/mod.rs new file mode 100644 index 00000000000..fd202a89bc1 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/mod.rs @@ -0,0 +1,46 @@ +// base +mod framework; +pub use framework::*; + +#[cfg(test)] +mod flashblocks; + +#[cfg(test)] +mod flashtestations; + +#[cfg(test)] +mod data_availability; + +#[cfg(test)] +mod miner_gas_limit; + +#[cfg(test)] +mod gas_limiter; + +#[cfg(test)] +mod ordering; + +#[cfg(test)] +mod revert; + +#[cfg(test)] +mod smoke; + +#[cfg(test)] +mod txpool; + +#[cfg(test)] +mod forks; +// If the order of deployment from the signer changes the address will change +#[cfg(test)] +const FLASHBLOCKS_NUMBER_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("95bd8d42f30351685e96c62eddc0d0613bf9a87a"); +#[cfg(test)] +const MOCK_DCAP_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("700b6a60ce7eaaea56f065753d8dcb9653dbad35"); +#[cfg(test)] +const FLASHTESTATION_REGISTRY_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("a15bb66138824a1c7167f5e85b957d04dd34e468"); +#[cfg(test)] +const BLOCK_BUILDER_POLICY_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("8ce361602b935680e8dec218b820ff5056beb7af"); diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/ordering.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/ordering.rs new file mode 100644 index 00000000000..bba31b0ef94 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/ordering.rs @@ -0,0 +1,72 @@ +use crate::tests::{ChainDriverExt, LocalInstance, framework::ONE_ETH}; +use alloy_consensus::Transaction; +use futures::{StreamExt, future::join_all, stream}; +use macros::rb_test; + +/// This test ensures that the transactions are ordered by fee priority in the block. +/// This version of the test is only applicable to the standard builder because in flashblocks +/// the transaction order is commited by the block after each flashblock is produced, +/// so the order is only going to hold within one flashblock, but not the entire block. +#[rb_test(standard)] +async fn fee_priority_ordering(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(10, ONE_ETH).await?; + + let latest_block = driver.latest().await?; + let base_fee = latest_block + .header + .base_fee_per_gas + .expect("Base fee should be present in the latest block"); + + // generate transactions with randomized tips + let txs = join_all(accounts.iter().map(|signer| { + driver + .create_transaction() + .with_signer(*signer) + .with_max_priority_fee_per_gas(rand::random_range(1..50)) + .send() + })) + .await + .into_iter() + .collect::>>()? + .into_iter() + .map(|tx| *tx.tx_hash()) + .collect::>(); + + driver.build_new_block().await?; + + // verify all transactions are included in the block + assert!( + stream::iter(txs.iter()) + .all(|tx_hash| async { + driver + .latest_full() + .await + .expect("Failed to fetch latest block") + .transactions + .hashes() + .any(|hash| hash == *tx_hash) + }) + .await, + "not all transactions included in the block" + ); + + // verify all transactions are ordered by fee priority + let txs_tips = driver + .latest_full() + .await? + .into_transactions_vec() + .into_iter() + .skip(1) // skip the deposit transaction + .take(txs.len()) // skip the last builder transaction + .map(|tx| tx.effective_tip_per_gas(base_fee as u64)) + .rev() // we want to check descending order + .collect::>(); + + assert!( + txs_tips.is_sorted(), + "Transactions not ordered by fee priority" + ); + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/revert.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/revert.rs new file mode 100644 index 00000000000..42185900b32 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/revert.rs @@ -0,0 +1,451 @@ +use alloy_provider::{PendingTransactionBuilder, Provider}; +use macros::{if_flashblocks, if_standard, rb_test}; +use op_alloy_network::Optimism; + +use crate::{ + args::OpRbuilderArgs, + primitives::bundle::MAX_BLOCK_RANGE_BLOCKS, + tests::{ + BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, LocalInstance, ONE_ETH, + OpRbuilderArgsTestExt, TransactionBuilderExt, + }, +}; + +/// This test ensures that the transactions that get reverted and not included in the block, +/// are eventually dropped from the pool once their block range is reached. +/// This test creates N transactions with different block ranges. +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn monitor_transaction_gc(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(10, ONE_ETH).await?; + let latest_block_number = driver.latest().await?.header.number; + + // send 10 bundles with different block ranges + let mut pending_txn = Vec::new(); + + for i in 0..accounts.len() { + let txn = driver + .create_transaction() + .random_reverting_transaction() + .with_signer(accounts[i].clone()) + .with_bundle( + BundleOpts::default().with_block_number_max(latest_block_number + i as u64 + 1), + ) + .send() + .await?; + pending_txn.push(txn); + } + + // generate 10 blocks + for i in 0..10 { + let generated_block = driver.build_new_block_with_current_timestamp(None).await?; + + if_standard! { + // standard builder blocks should only include two transactions (deposit + builder) + assert_eq!(generated_block.transactions.len(), 2); + } + + if_flashblocks! { + // flashblocks should include three transactions (deposit + 2 builder txs) + assert_eq!(generated_block.transactions.len(), 3); + } + + // since we created the 10 transactions with increasing block ranges, as we generate blocks + // one transaction will be gc on each block. + // The pool considers a bundle "exceeded" when current_block > block_number_max (strict + // greater than), so after the i-th iteration (current block = latest + i + 1) the bundles + // with max in [latest+1, latest+i] are dropped and the rest are still pending. + for j in 0..i { + assert!(rbuilder.pool().is_dropped(*pending_txn[j].tx_hash())); + } + for j in i..10 { + assert!(rbuilder.pool().is_pending(*pending_txn[j].tx_hash())); + } + } + + Ok(()) +} + +/// If revert protection is disabled, the transactions that revert are included in the block. +#[rb_test] +async fn disabled(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + for _ in 0..10 { + let valid_tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + + let reverting_tx = driver + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + let block = driver.build_new_block().await?; + + assert!(block.includes(valid_tx.tx_hash())); + assert!(block.includes(reverting_tx.tx_hash())); + } + + Ok(()) +} + +/// If revert protection is disabled, it should not be possible to send a revert bundle +/// since the revert RPC endpoint is not available. +#[rb_test] +async fn disabled_bundle_endpoint_error(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + let res = driver + .create_transaction() + .with_bundle(BundleOpts::default()) + .send() + .await; + + assert!( + res.is_err(), + "Expected error because method is not available" + ); + Ok(()) +} + +/// Test the behaviour of the revert protection bundle, if the bundle **does not** revert +/// the transaction is included in the block. If the bundle reverts, the transaction +/// is not included in the block and tried again for the next bundle range blocks +/// when it will be dropped from the pool. +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn bundle(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let _ = driver.build_new_block().await?; // Block 1 + + // Test 1: Bundle does not revert + let valid_bundle = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + let block2 = driver.build_new_block().await?; // Block 2 + assert!( + block2 + .transactions + .hashes() + .includes(valid_bundle.tx_hash()) + ); + + let bundle_opts = BundleOpts::default().with_block_number_max(4); + + let reverted_bundle = driver + .create_transaction() + .random_reverting_transaction() + .with_bundle(bundle_opts) + .send() + .await?; + + // Test 2: Bundle reverts. It is not included in the block + let block3 = driver.build_new_block().await?; // Block 3 + assert!(!block3.includes(reverted_bundle.tx_hash())); + + // After the block the transaction is still pending in the pool + assert!(rbuilder.pool().is_pending(*reverted_bundle.tx_hash())); + + // Test 3: Chain reaches the bundle's max block. The bundle is still in range (current == max) + // and therefore still pending in the pool. + driver.build_new_block().await?; // Block 4 + assert!(rbuilder.pool().is_pending(*reverted_bundle.tx_hash())); + + // Test 4: Chain progresses beyond the bundle range. The transaction is dropped from the pool. + driver.build_new_block().await?; // Block 5 + assert!(rbuilder.pool().is_dropped(*reverted_bundle.tx_hash())); + + driver.build_new_block().await?; // Block 6 + assert!(rbuilder.pool().is_dropped(*reverted_bundle.tx_hash())); + + Ok(()) +} + +/// Test the behaviour of the revert protection bundle with a min block number. +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn bundle_min_block_number(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // The bundle is valid when the min block number is equal to the current block + let bundle_with_min_block = driver + .create_transaction() + .with_revert() // the transaction reverts but it is included in the block + .with_reverted_hash() + .with_bundle(BundleOpts::default().with_block_number_min(2)) + .send() + .await?; + + let block = driver.build_new_block().await?; // Block 1, bundle still not valid + assert!(!block.includes(bundle_with_min_block.tx_hash())); + + let block = driver.build_new_block().await?; // Block 2, bundle is valid + assert!(block.includes(bundle_with_min_block.tx_hash())); + + // Send a bundle with a match of min and max block number + let bundle_with_min_and_max_block = driver + .create_transaction() + .with_revert() + .with_reverted_hash() + .with_bundle( + BundleOpts::default() + .with_block_number_max(4) + .with_block_number_min(4), + ) + .send() + .await?; + + let block = driver.build_new_block().await?; // Block 3, bundle still not valid + assert!(!block.includes(bundle_with_min_and_max_block.tx_hash())); + + let block = driver.build_new_block().await?; // Block 4, bundle is valid + assert!(block.includes(bundle_with_min_and_max_block.tx_hash())); + + Ok(()) +} + +/// Test the behaviour of the revert protection bundle with a min timestamp. +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn bundle_min_timestamp(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let initial_timestamp = driver.latest().await?.header.timestamp; + + // The bundle is valid when the min timestamp is equal to the current block's timestamp + let bundle_with_min_timestamp = driver + .create_transaction() + .with_revert() // the transaction reverts but it is included in the block + .with_reverted_hash() + .with_bundle(BundleOpts::default().with_min_timestamp(initial_timestamp + 2)) + .send() + .await?; + + // Each block advances the timestamp by block_time_secs which is 1 when chain_block_time isn't set + let block = driver.build_new_block().await?; // Block 1, initial_timestamp + 1 + assert!(!block.includes(bundle_with_min_timestamp.tx_hash())); + + let block = driver.build_new_block().await?; // Block 2, initial_timestamp + 2, so bundle is valid + assert!(block.includes(bundle_with_min_timestamp.tx_hash())); + + Ok(()) +} + +/// Test the range limits for the revert protection bundle. +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn bundle_range_limits(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let _ = driver.build_new_block().await?; // Block 1 + let _ = driver.build_new_block().await?; // Block 2 + + async fn send_bundle( + driver: &ChainDriver, + bundle: BundleOpts, + ) -> eyre::Result> { + driver.create_transaction().with_bundle(bundle).send().await + } + + // Max block cannot be a past block + assert!( + send_bundle(&driver, BundleOpts::default().with_block_number_max(1)) + .await + .is_err() + ); + + // Bundles are valid if their max block in in between the current block and the max block range + let current_block = 2; + let next_valid_block = current_block + 1; + + for i in next_valid_block..next_valid_block + MAX_BLOCK_RANGE_BLOCKS { + assert!( + send_bundle(&driver, BundleOpts::default().with_block_number_max(i)) + .await + .is_ok() + ); + } + + // A bundle with a block out of range is invalid + assert!( + send_bundle( + &driver, + BundleOpts::default() + .with_block_number_max(next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1) + ) + .await + .is_err() + ); + + // A bundle with a min block number higher than the max block is invalid + assert!( + send_bundle( + &driver, + BundleOpts::default() + .with_block_number_max(1) + .with_block_number_min(2) + ) + .await + .is_err() + ); + + // A bundle with a min block number lower or equal to the current block is valid + assert!( + send_bundle( + &driver, + BundleOpts::default() + .with_block_number_max(next_valid_block) + .with_block_number_min(current_block) + ) + .await + .is_ok() + ); + assert!( + send_bundle( + &driver, + BundleOpts::default().with_block_number_max(next_valid_block) + ) + .await + .is_ok() + ); + + // A bundle with a min block equal to max block is valid + assert!( + send_bundle( + &driver, + BundleOpts::default() + .with_block_number_max(next_valid_block) + .with_block_number_min(next_valid_block) + ) + .await + .is_ok() + ); + + // Test min-only cases (no max specified) + // A bundle with only min block that's within the default range is valid + let default_max = current_block + MAX_BLOCK_RANGE_BLOCKS; + assert!( + send_bundle( + &driver, + BundleOpts::default().with_block_number_min(current_block) + ) + .await + .is_ok() + ); + assert!( + send_bundle( + &driver, + BundleOpts::default().with_block_number_min(default_max - 1) + ) + .await + .is_ok() + ); + assert!( + send_bundle( + &driver, + BundleOpts::default().with_block_number_min(default_max) + ) + .await + .is_ok() + ); + + // A bundle with only min block that exceeds the default max range is invalid + assert!( + send_bundle( + &driver, + BundleOpts::default().with_block_number_min(default_max + 1) + ) + .await + .is_err() + ); + + Ok(()) +} + +/// If a transaction reverts and was sent as a normal transaction through the eth_sendRawTransaction +/// bundle, the transaction should be included in the block. +/// This behaviour is the same as the 'disabled' test. +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn allow_reverted_transactions_without_bundle(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + for _ in 0..10 { + let valid_tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + let reverting_tx = driver + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + let block = driver.build_new_block().await?; + + assert!(block.includes(valid_tx.tx_hash())); + assert!(block.includes(reverting_tx.tx_hash())); + } + + Ok(()) +} + +/// If a transaction reverts and gets dropped it, the eth_getTransactionReceipt should return +/// an error message that it was dropped. +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..OpRbuilderArgs::test_default() +})] +async fn check_transaction_receipt_status_message(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + + let reverting_tx = driver + .create_transaction() + .random_reverting_transaction() + .with_bundle(BundleOpts::default().with_block_number_max(3)) + .send() + .await?; + let tx_hash = reverting_tx.tx_hash(); + + let _ = driver.build_new_block().await?; // Block 1 + let receipt = provider.get_transaction_receipt(*tx_hash).await?; + assert!(receipt.is_none()); + + let _ = driver.build_new_block().await?; // Block 2 + let receipt = provider.get_transaction_receipt(*tx_hash).await?; + assert!(receipt.is_none()); + + // Block 3 is the bundle's max block. The bundle is still in range (current == max) so it is + // still pending and the receipt is not yet a hard error. + let _ = driver.build_new_block().await?; + let receipt = provider.get_transaction_receipt(*tx_hash).await?; + assert!(receipt.is_none()); + + // Block 4 advances past the bundle's max block, the pool drops the transaction and the + // receipt RPC reports it as dropped. + let _ = driver.build_new_block().await?; + let receipt = provider.get_transaction_receipt(*tx_hash).await; + + assert!(receipt.is_err()); + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/smoke.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/smoke.rs new file mode 100644 index 00000000000..e2fe8eef79f --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/smoke.rs @@ -0,0 +1,304 @@ +use crate::{ + args::OpRbuilderArgs, + tests::{LocalInstance, TransactionBuilderExt}, +}; +use alloy_primitives::TxHash; + +use core::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; +use macros::{if_flashblocks, if_standard, rb_test}; +use std::collections::HashSet; +use tokio::{join, task::yield_now}; +use tracing::info; + +/// This is a smoke test that ensures that transactions are included in blocks +/// and that the block generator is functioning correctly. +/// +/// Generated blocks are also validated against an external op-reth node to +/// ensure their correctness. Gated on `docker-tests` because that validation +/// step needs `/var/run/docker.sock`. +#[cfg(feature = "docker-tests")] +#[rb_test] +async fn chain_produces_blocks(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + #[cfg(target_os = "linux")] + let driver = driver + .with_validation_node(crate::tests::ExternalNode::reth().await?) + .await?; + + const SAMPLE_SIZE: usize = 10; + + // ensure that each block has at least two transactions when + // no user transactions are sent. + // the deposit transaction and the block generator's transaction + for _ in 0..SAMPLE_SIZE { + let block = driver.build_new_block_with_current_timestamp(None).await?; + let transactions = block.transactions; + + if_standard! { + assert_eq!( + transactions.len(), + 2, + "Empty blocks should have exactly two transactions" + ); + } + + if_flashblocks! { + // in flashblocks we add an additional transaction on the first + // flashblocks and then one on the last flashblock + assert_eq!( + transactions.len(), + 3, + "Empty blocks should have exactly three transactions" + ); + } + } + + // ensure that transactions are included in blocks and each block has all the transactions + // sent to it during its block time + the two mandatory transactions + for _ in 0..SAMPLE_SIZE { + let count = rand::random_range(1..8); + let mut tx_hashes = HashSet::::default(); + + for _ in 0..count { + let tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await + .expect("Failed to send transaction"); + tx_hashes.insert(*tx.tx_hash()); + } + + let block = driver.build_new_block_with_current_timestamp(None).await?; + + let txs = block.transactions; + + if_standard! { + assert_eq!( + txs.len(), + 2 + count, + "Block should have {} transactions", + 2 + count + ); + } + + if_flashblocks! { + // in flashblocks we add an additional transaction on the first + // flashblocks and then one on the last flashblock, so it will have + // one more transaction than the standard builder + assert_eq!( + txs.len(), + 3 + count, + "Block should have {} transactions", + 3 + count + ); + } + + for tx_hash in tx_hashes { + assert!( + txs.hashes().any(|hash| hash == tx_hash), + "Transaction {} should be included in the block", + tx_hash + ); + } + } + Ok(()) +} + +/// Ensures that payloads are generated correctly even when the builder is busy +/// with other requests, such as fcu or getPayload. +#[rb_test(multi_threaded)] +async fn produces_blocks_under_load_within_deadline(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?.with_gas_limit(10_00_000); + + let done = AtomicBool::new(false); + + let (populate, produce) = join!( + async { + // Keep the builder busy with new transactions. + loop { + match driver + .create_transaction() + .random_valid_transfer() + .send() + .await + { + Ok(_) => {} + Err(e) if e.to_string().contains("txpool is full") => { + // If the txpool is full, give it a short break + tokio::time::sleep(Duration::from_millis(100)).await; + } + Err(e) => return Err(e), + }; + + if done.load(Ordering::Relaxed) { + break; + } + + yield_now().await; + } + Ok::<(), eyre::Error>(()) + }, + async { + // Wait for a short time to allow the transaction population to start + // and fill up the txpool. + tokio::time::sleep(Duration::from_secs(1)).await; + + // Now, start producing blocks under load. + for _ in 0..10 { + // Ensure that the builder can still produce blocks under + // heavy load of incoming transactions. + let block = tokio::time::timeout( + Duration::from_secs(rbuilder.args().chain_block_time) + + Duration::from_millis(500), + driver.build_new_block_with_current_timestamp(None), + ) + .await + .expect("Timeout while waiting for block production") + .expect("Failed to produce block under load"); + + info!("Produced a block under load: {block:#?}"); + + yield_now().await; + } + + // we're happy with one block produced under load + // set the done flag to true to stop the transaction population + done.store(true, Ordering::Relaxed); + info!("All blocks produced under load"); + + Ok::<(), eyre::Error>(()) + } + ); + + populate.unwrap(); + + //assert!(populate.is_ok(), "Failed to populate transactions"); + assert!(produce.is_ok(), "Failed to produce block under load"); + + Ok(()) +} + +#[rb_test] +async fn test_no_tx_pool(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // make sure we can build a couple of blocks first + let _ = driver.build_new_block().await?; + + // now lets try to build a block with no transactions + let _ = driver.build_new_block_with_no_tx_pool().await?; + + Ok(()) +} + +// Cross-validates against an external op-reth — gated on `docker-tests`. +#[cfg(feature = "docker-tests")] +#[rb_test(args = OpRbuilderArgs { + max_gas_per_txn: Some(25000), + ..Default::default() +})] +async fn chain_produces_big_tx_with_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + #[cfg(target_os = "linux")] + let driver = driver + .with_validation_node(crate::tests::ExternalNode::reth().await?) + .await?; + + // insert valid txn under limit + let tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await + .expect("Failed to send transaction"); + + // insert txn with gas usage above limit + let tx_high_gas = driver + .create_transaction() + .random_big_transaction() + .send() + .await + .expect("Failed to send transaction"); + + let block = driver.build_new_block_with_current_timestamp(None).await?; + let txs = block.transactions; + + if_standard! { + assert_eq!( + txs.len(), + 3, + "Should have 3 transactions" + ); + } + + if_flashblocks! { + assert_eq!( + txs.len(), + 4, + "Should have 4 transactions" + ); + } + + // assert we included the tx with gas under limit + let inclusion_result = txs.hashes().find(|hash| hash == tx.tx_hash()); + assert!(inclusion_result.is_some()); + + // assert we do not include the tx with gas above limit + let exclusion_result = txs.hashes().find(|hash| hash == tx_high_gas.tx_hash()); + assert!(exclusion_result.is_none()); + + Ok(()) +} + +// Cross-validates against an external op-reth — gated on `docker-tests`. +#[cfg(feature = "docker-tests")] +#[rb_test(args = OpRbuilderArgs { + ..Default::default() +})] +async fn chain_produces_big_tx_without_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + #[cfg(target_os = "linux")] + let driver = driver + .with_validation_node(crate::tests::ExternalNode::reth().await?) + .await?; + + // insert txn with gas usage but there is no limit + let tx = driver + .create_transaction() + .random_big_transaction() + .send() + .await + .expect("Failed to send transaction"); + + let block = driver.build_new_block_with_current_timestamp(None).await?; + let txs = block.transactions; + + // assert we included the tx + let inclusion_result = txs.hashes().find(|hash| hash == tx.tx_hash()); + assert!(inclusion_result.is_some()); + + if_standard! { + assert_eq!( + txs.len(), + 3, + "Should have 3 transactions" + ); + } + + if_flashblocks! { + assert_eq!( + txs.len(), + 4, + "Should have 4 transactions" + ); + } + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tests/txpool.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tests/txpool.rs new file mode 100644 index 00000000000..2b0e4e23f94 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tests/txpool.rs @@ -0,0 +1,73 @@ +use crate::tests::{ + BlockTransactionsExt, ChainDriverExt, LocalInstance, ONE_ETH, default_node_config, +}; +use macros::rb_test; +use reth::args::TxPoolArgs; +use reth_node_builder::NodeConfig; +use reth_optimism_chainspec::OpChainSpec; + +/// This test ensures that pending pool custom limit is respected and priority tx would be included even when pool if full. +#[rb_test( + config = NodeConfig:: { + txpool: TxPoolArgs { + pending_max_count: 50, + // Allow a single sender to occupy the full pending sub-pool so the test can + // saturate the pool from one signer and then exercise priority-tx eviction + // from a different signer. + max_account_slots: 50, + ..Default::default() + }, + ..default_node_config() + }, + standard +)] +async fn pending_pool_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(50, ONE_ETH).await?; + + // Send 50 txs from different addrs + let acc_no_priority = accounts.first().unwrap(); + let acc_with_priority = accounts.last().unwrap(); + + for _ in 0..50 { + let _ = driver + .create_transaction() + .with_signer(*acc_no_priority) + .send() + .await?; + } + + assert_eq!( + rbuilder.pool().pending_count(), + 50, + "Pending pool must contain at max 50 txs {:?}", + rbuilder.pool().pending_count() + ); + + // Send 10 txs that should be included in the block + let mut txs = Vec::new(); + for _ in 0..10 { + let tx = driver + .create_transaction() + .with_signer(*acc_with_priority) + .with_max_priority_fee_per_gas(10) + .send() + .await?; + txs.push(*tx.tx_hash()); + } + + assert_eq!( + rbuilder.pool().pending_count(), + 50, + "Pending pool must contain at max 50 txs {:?}", + rbuilder.pool().pending_count() + ); + + // After we try building block our reverting tx would be removed and other tx will move to queue pool + let block = driver.build_new_block().await?; + + // Ensure that 10 extra txs got included + assert!(block.includes(&txs)); + + Ok(()) +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/traits.rs b/rust/op-rbuilder/crates/op-rbuilder/src/traits.rs new file mode 100644 index 00000000000..ef9dde45f93 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/traits.rs @@ -0,0 +1,88 @@ +use alloy_consensus::Header; +use reth_node_api::{FullNodeComponents, FullNodeTypes, NodeTypes}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_node::OpEngineTypes; +use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; +use reth_payload_util::PayloadTransactions; +use reth_provider::{BlockReaderIdExt, ChainSpecProvider, StateProviderFactory}; +use reth_transaction_pool::TransactionPool; + +use crate::tx::FBPoolTransaction; + +pub trait NodeBounds: + FullNodeTypes< + Types: NodeTypes, +> +{ +} + +impl NodeBounds for T where + T: FullNodeTypes< + Types: NodeTypes< + Payload = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + > +{ +} + +pub trait NodeComponents: + FullNodeComponents< + Types: NodeTypes, +> +{ +} + +impl NodeComponents for T where + T: FullNodeComponents< + Types: NodeTypes< + Payload = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + > +{ +} + +pub trait PoolBounds: + TransactionPool> + Unpin + 'static +where + ::Transaction: FBPoolTransaction, +{ +} + +impl PoolBounds for T +where + T: TransactionPool> + + Unpin + + 'static, + ::Transaction: FBPoolTransaction, +{ +} + +pub trait ClientBounds: + StateProviderFactory + + ChainSpecProvider + + BlockReaderIdExt
+ + Clone +{ +} + +impl ClientBounds for T where + T: StateProviderFactory + + ChainSpecProvider + + BlockReaderIdExt
+ + Clone +{ +} + +pub trait PayloadTxsBounds: + PayloadTransactions> +{ +} + +impl PayloadTxsBounds for T where + T: PayloadTransactions> +{ +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tx.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tx.rs new file mode 100644 index 00000000000..e15fcfb0be1 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tx.rs @@ -0,0 +1,306 @@ +use std::{borrow::Cow, sync::Arc}; + +use alloy_consensus::{BlobTransactionValidationError, conditional::BlockConditionalAttributes}; +use alloy_eips::{ + Typed2718, eip4844::env_settings::KzgSettings, eip7594::BlobTransactionSidecarVariant, + eip7702::SignedAuthorization, +}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256}; +use alloy_rpc_types_eth::{AccessList, erc4337::TransactionConditional}; +use reth_optimism_primitives::OpTransactionSigned; +use reth_optimism_txpool::{ + OpPooledTransaction, OpPooledTx, conditional::MaybeConditionalTransaction, + estimated_da_size::DataAvailabilitySized, interop::MaybeInteropTransaction, +}; +use reth_primitives_traits::{InMemorySize, Recovered}; +use reth_transaction_pool::{EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction}; + +pub trait FBPoolTransaction: + MaybeRevertingTransaction + OpPooledTx + MaybeFlashblockFilter +{ +} + +#[derive(Clone, Debug)] +pub struct FBPooledTransaction { + pub inner: OpPooledTransaction, + + /// reverted hashes for the transaction. If the transaction is a bundle, + /// this is the list of hashes of the transactions that reverted. If the + /// transaction is not a bundle, this is `None`. + pub reverted_hashes: Option>, + + pub flashblock_number_min: Option, + pub flashblock_number_max: Option, +} + +impl FBPoolTransaction for FBPooledTransaction {} + +impl OpPooledTx for FBPooledTransaction { + fn encoded_2718(&self) -> Cow<'_, Bytes> { + Cow::Borrowed(self.inner.encoded_2718()) + } +} + +pub trait MaybeRevertingTransaction { + fn with_reverted_hashes(self, reverted_hashes: Vec) -> Self; + fn reverted_hashes(&self) -> Option>; +} + +impl MaybeRevertingTransaction for FBPooledTransaction { + fn with_reverted_hashes(mut self, reverted_hashes: Vec) -> Self { + self.reverted_hashes = Some(reverted_hashes); + self + } + + fn reverted_hashes(&self) -> Option> { + self.reverted_hashes.clone() + } +} + +pub trait MaybeFlashblockFilter { + fn with_flashblock_number_min(self, flashblock_number_min: Option) -> Self; + fn with_flashblock_number_max(self, flashblock_number_max: Option) -> Self; + fn flashblock_number_min(&self) -> Option; + fn flashblock_number_max(&self) -> Option; +} + +impl MaybeFlashblockFilter for FBPooledTransaction { + fn with_flashblock_number_min(mut self, flashblock_number_min: Option) -> Self { + self.flashblock_number_min = flashblock_number_min; + self + } + + fn with_flashblock_number_max(mut self, flashblock_number_max: Option) -> Self { + self.flashblock_number_max = flashblock_number_max; + self + } + + fn flashblock_number_min(&self) -> Option { + self.flashblock_number_min + } + + fn flashblock_number_max(&self) -> Option { + self.flashblock_number_max + } +} + +impl InMemorySize for FBPooledTransaction { + fn size(&self) -> usize { + self.inner.size() + core::mem::size_of::() + } +} + +impl PoolTransaction for FBPooledTransaction { + type TryFromConsensusError = + >::Error; + type Consensus = OpTransactionSigned; + type Pooled = op_alloy_consensus::OpPooledTransaction; + + fn clone_into_consensus(&self) -> Recovered { + self.inner.clone_into_consensus() + } + + fn consensus_ref(&self) -> Recovered<&Self::Consensus> { + self.inner.consensus_ref() + } + + fn into_consensus(self) -> Recovered { + self.inner.into_consensus() + } + + fn from_pooled(tx: Recovered) -> Self { + let inner = OpPooledTransaction::from_pooled(tx); + Self { + inner, + reverted_hashes: None, + flashblock_number_min: None, + flashblock_number_max: None, + } + } + + fn hash(&self) -> &TxHash { + self.inner.hash() + } + + fn sender(&self) -> Address { + self.inner.sender() + } + + fn sender_ref(&self) -> &Address { + self.inner.sender_ref() + } + + fn cost(&self) -> &U256 { + self.inner.cost() + } + + fn encoded_length(&self) -> usize { + self.inner.encoded_length() + } +} + +impl Typed2718 for FBPooledTransaction { + fn ty(&self) -> u8 { + self.inner.ty() + } +} + +impl alloy_consensus::Transaction for FBPooledTransaction { + fn chain_id(&self) -> Option { + self.inner.chain_id() + } + + fn nonce(&self) -> u64 { + self.inner.nonce() + } + + fn gas_limit(&self) -> u64 { + self.inner.gas_limit() + } + + fn gas_price(&self) -> Option { + self.inner.gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.inner.max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.inner.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.inner.max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.inner.priority_fee_or_price() + } + + fn effective_gas_price(&self, base_fee: Option) -> u128 { + self.inner.effective_gas_price(base_fee) + } + + fn is_dynamic_fee(&self) -> bool { + self.inner.is_dynamic_fee() + } + + fn kind(&self) -> TxKind { + self.inner.kind() + } + + fn is_create(&self) -> bool { + self.inner.is_create() + } + + fn value(&self) -> U256 { + self.inner.value() + } + + fn input(&self) -> &Bytes { + self.inner.input() + } + + fn access_list(&self) -> Option<&AccessList> { + self.inner.access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.inner.blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.inner.authorization_list() + } +} + +impl EthPoolTransaction for FBPooledTransaction { + fn take_blob(&mut self) -> EthBlobTransactionSidecar { + EthBlobTransactionSidecar::None + } + + fn try_into_pooled_eip4844( + self, + sidecar: Arc, + ) -> Option> { + self.inner.try_into_pooled_eip4844(sidecar) + } + + fn try_from_eip4844( + _tx: Recovered, + _sidecar: BlobTransactionSidecarVariant, + ) -> Option { + None + } + + fn validate_blob( + &self, + _sidecar: &BlobTransactionSidecarVariant, + _settings: &KzgSettings, + ) -> Result<(), BlobTransactionValidationError> { + Err(BlobTransactionValidationError::NotBlobTransaction( + self.ty(), + )) + } +} + +impl MaybeInteropTransaction for FBPooledTransaction { + fn interop_deadline(&self) -> Option { + self.inner.interop_deadline() + } + + fn set_interop_deadline(&self, deadline: u64) { + self.inner.set_interop_deadline(deadline); + } + + fn with_interop_deadline(self, interop: u64) -> Self + where + Self: Sized, + { + self.inner.with_interop_deadline(interop).into() + } +} + +impl DataAvailabilitySized for FBPooledTransaction { + fn estimated_da_size(&self) -> u64 { + self.inner.estimated_da_size() + } +} + +impl From for FBPooledTransaction { + fn from(tx: OpPooledTransaction) -> Self { + Self { + inner: tx, + reverted_hashes: None, + flashblock_number_min: None, + flashblock_number_max: None, + } + } +} + +impl MaybeConditionalTransaction for FBPooledTransaction { + fn set_conditional(&mut self, conditional: TransactionConditional) { + self.inner.set_conditional(conditional); + } + + fn conditional(&self) -> Option<&TransactionConditional> { + self.inner.conditional() + } + + fn has_exceeded_block_attributes(&self, block_attr: &BlockConditionalAttributes) -> bool { + self.inner.has_exceeded_block_attributes(block_attr) + } + + fn with_conditional(self, conditional: TransactionConditional) -> Self + where + Self: Sized, + { + FBPooledTransaction { + inner: self.inner.with_conditional(conditional), + reverted_hashes: self.reverted_hashes, + flashblock_number_min: self.flashblock_number_min, + flashblock_number_max: self.flashblock_number_max, + } + } +} diff --git a/rust/op-rbuilder/crates/op-rbuilder/src/tx_signer.rs b/rust/op-rbuilder/crates/op-rbuilder/src/tx_signer.rs new file mode 100644 index 00000000000..3d4207b9130 --- /dev/null +++ b/rust/op-rbuilder/crates/op-rbuilder/src/tx_signer.rs @@ -0,0 +1,204 @@ +use std::str::FromStr; + +use alloy_consensus::SignableTransaction; +use alloy_primitives::{Address, B256, Signature, U256}; +use k256::sha2::Sha256; +use op_alloy_consensus::OpTypedTransaction; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives_traits::Recovered; +use secp256k1::{Message, PublicKey, SECP256K1, Secp256k1, SecretKey, rand::rngs::OsRng}; +use sha3::{Digest, Keccak256}; + +/// Simple struct to sign txs/messages. +/// Mainly used to sign payout txs from the builder and to create test data. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Signer { + pub address: Address, + pub pubkey: PublicKey, + pub secret: SecretKey, +} + +impl Signer { + pub fn try_from_secret(secret: B256) -> Result { + let secret = SecretKey::from_slice(secret.as_ref())?; + let pubkey = secret.public_key(SECP256K1); + let address = public_key_to_address(&pubkey); + + Ok(Self { + address, + pubkey, + secret, + }) + } + + pub fn sign_message(&self, message: B256) -> Result { + let s = SECP256K1 + .sign_ecdsa_recoverable(&Message::from_digest_slice(&message[..])?, &self.secret); + let (rec_id, data) = s.serialize_compact(); + + let signature = Signature::new( + U256::try_from_be_slice(&data[..32]).expect("The slice has at most 32 bytes"), + U256::try_from_be_slice(&data[32..64]).expect("The slice has at most 32 bytes"), + i32::from(rec_id) != 0, + ); + Ok(signature) + } + + pub fn sign_tx( + &self, + tx: OpTypedTransaction, + ) -> Result, secp256k1::Error> { + let signature_hash = match &tx { + OpTypedTransaction::Legacy(tx) => tx.signature_hash(), + OpTypedTransaction::Eip2930(tx) => tx.signature_hash(), + OpTypedTransaction::Eip1559(tx) => tx.signature_hash(), + OpTypedTransaction::Eip7702(tx) => tx.signature_hash(), + OpTypedTransaction::Deposit(_) => B256::ZERO, + OpTypedTransaction::PostExec(_) => B256::ZERO, + }; + let signature = self.sign_message(signature_hash)?; + let signed = OpTransactionSigned::new_unhashed(tx, signature); + Ok(Recovered::new_unchecked(signed, self.address)) + } + + pub fn random() -> Self { + Self::try_from_secret(B256::random()).expect("failed to create random signer") + } +} + +impl FromStr for Signer { + type Err = eyre::Error; + + fn from_str(s: &str) -> Result { + Self::try_from_secret(B256::from_str(s)?) + .map_err(|e| eyre::eyre!("invalid secret key {:?}", e.to_string())) + } +} + +pub fn generate_signer() -> Signer { + let secp = Secp256k1::new(); + + // Generate cryptographically secure random private key + let private_key = SecretKey::new(&mut OsRng); + + // Derive public key + let public_key = PublicKey::from_secret_key(&secp, &private_key); + + // Derive Ethereum address + let address = public_key_to_address(&public_key); + + Signer { + address, + pubkey: public_key, + secret: private_key, + } +} + +/// Converts a public key to an Ethereum address +pub fn public_key_to_address(public_key: &PublicKey) -> Address { + // Get uncompressed public key (65 bytes: 0x04 + 64 bytes) + let pubkey_bytes = public_key.serialize_uncompressed(); + + // Skip the 0x04 prefix and hash the remaining 64 bytes + let hash = Keccak256::digest(&pubkey_bytes[1..65]); + + // Take last 20 bytes as address + Address::from_slice(&hash[12..32]) +} + +// Generate a key deterministically from a seed for debug and testing +// Do not use in production +pub fn generate_key_from_seed(seed: &str) -> Signer { + // Hash the seed + let mut hasher = Sha256::new(); + hasher.update(seed.as_bytes()); + let hash = hasher.finalize(); + + // Create signing key + let secp = Secp256k1::new(); + let private_key = SecretKey::from_slice(&hash).expect("Failed to create private key"); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + let address = public_key_to_address(&public_key); + + Signer { + address, + pubkey: public_key, + secret: private_key, + } +} + +#[cfg(test)] +mod test { + use super::*; + use alloy_consensus::{TxEip1559, transaction::SignerRecoverable}; + use alloy_primitives::{TxKind as TransactionKind, address, fixed_bytes}; + #[test] + fn test_sign_transaction() { + let secret = + fixed_bytes!("7a3233fcd52c19f9ffce062fd620a8888930b086fba48cfea8fc14aac98a4dce"); + let address = address!("B2B9609c200CA9b7708c2a130b911dabf8B49B20"); + let signer = Signer::try_from_secret(secret).expect("signer creation"); + assert_eq!(signer.address, address); + + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: 1, + nonce: 2, + gas_limit: 21000, + max_fee_per_gas: 1000, + max_priority_fee_per_gas: 20000, + to: TransactionKind::Call(address), + value: U256::from(3000u128), + ..Default::default() + }); + + let signed_tx = signer.sign_tx(tx).expect("sign tx"); + assert_eq!(signed_tx.signer(), address); + + let signed = signed_tx.into_inner(); + assert_eq!(signed.recover_signer().ok(), Some(address)); + } + + #[test] + fn test_public_key_format() { + let secp = Secp256k1::new(); + let private_key = SecretKey::new(&mut OsRng); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + + let pubkey_bytes = public_key.serialize_uncompressed(); + + // Verify the public key format + assert_eq!( + pubkey_bytes.len(), + 65, + "Uncompressed public key should be 65 bytes" + ); + assert_eq!( + pubkey_bytes[0], 0x04, + "Uncompressed public key should start with 0x04" + ); + + // Verify report data would be 64 bytes + let report_data = &pubkey_bytes[1..65]; + assert_eq!( + report_data.len(), + 64, + "Report data should be exactly 64 bytes" + ); + } + + #[test] + fn test_deterministic_address_derivation() { + // Test with a known private key to ensure deterministic results + let secp = Secp256k1::new(); + let private_key = SecretKey::from_slice(&[0x42; 32]).unwrap(); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + + let address1 = public_key_to_address(&public_key); + let address2 = public_key_to_address(&public_key); + + assert_eq!( + address1, address2, + "Address derivation should be deterministic" + ); + } +} diff --git a/rust/op-rbuilder/crates/p2p/Cargo.toml b/rust/op-rbuilder/crates/p2p/Cargo.toml new file mode 100644 index 00000000000..5f512c4a2a7 --- /dev/null +++ b/rust/op-rbuilder/crates/p2p/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "p2p" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +libp2p = { version = "0.56", features = ["identify", "ping", "noise", "tcp", "autonat", "mdns", "tokio", "cbor", "macros", "yamux"] } +libp2p-stream = "0.4.0-alpha" +multiaddr = "0.18" + +derive_more = { workspace = true, features = ["from"] } +eyre = { workspace = true } +futures = { workspace = true} +futures-util = { workspace = true } +hex = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true, features = [ "macros" ] } +tokio-util = { workspace = true, features = [ "compat", "codec" ] } +tracing = { workspace = true } + +[lints] +workspace = true diff --git a/rust/op-rbuilder/crates/p2p/src/behaviour.rs b/rust/op-rbuilder/crates/p2p/src/behaviour.rs new file mode 100644 index 00000000000..bedfb78ecef --- /dev/null +++ b/rust/op-rbuilder/crates/p2p/src/behaviour.rs @@ -0,0 +1,116 @@ +use eyre::WrapErr as _; +use libp2p::{ + Swarm, autonat, + connection_limits::{self, ConnectionLimits}, + identify, identity, mdns, ping, + swarm::NetworkBehaviour, +}; +use std::{convert::Infallible, time::Duration}; + +const PROTOCOL_VERSION: &str = "1.0.0"; + +#[derive(NetworkBehaviour)] +#[behaviour(to_swarm = "BehaviourEvent")] +pub(crate) struct Behaviour { + // connection gating + connection_limits: connection_limits::Behaviour, + + // discovery + mdns: mdns::tokio::Behaviour, + + // protocols + identify: identify::Behaviour, + ping: ping::Behaviour, + stream: libp2p_stream::Behaviour, + + // nat traversal + autonat: autonat::Behaviour, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, derive_more::From)] +pub(crate) enum BehaviourEvent { + Autonat(autonat::Event), + Identify(identify::Event), + Mdns(mdns::Event), + Ping(ping::Event), +} + +impl From<()> for BehaviourEvent { + fn from(_: ()) -> Self { + unreachable!("() cannot be converted to BehaviourEvent") + } +} + +impl From for BehaviourEvent { + fn from(_: Infallible) -> Self { + unreachable!("Infallible cannot be converted to BehaviourEvent") + } +} + +impl Behaviour { + pub(crate) fn new( + keypair: &identity::Keypair, + agent_version: String, + max_peer_count: u32, + ) -> eyre::Result { + let peer_id = keypair.public().to_peer_id(); + + let autonat = autonat::Behaviour::new(peer_id, autonat::Config::default()); + let mdns = mdns::tokio::Behaviour::new(mdns::Config::default(), peer_id) + .wrap_err("failed to create mDNS behaviour")?; + let connection_limits = connection_limits::Behaviour::new( + ConnectionLimits::default().with_max_established(Some(max_peer_count)), + ); + + let identify = identify::Behaviour::new( + identify::Config::new(PROTOCOL_VERSION.to_string(), keypair.public()) + .with_agent_version(agent_version), + ); + let ping = ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(10))); + let stream = libp2p_stream::Behaviour::new(); + + Ok(Self { + autonat, + connection_limits, + identify, + ping, + mdns, + stream, + }) + } + + pub(crate) fn new_control(&mut self) -> libp2p_stream::Control { + self.stream.new_control() + } +} + +impl BehaviourEvent { + pub(crate) fn handle(self, swarm: &mut Swarm) { + match self { + BehaviourEvent::Autonat(_event) => {} + BehaviourEvent::Identify(_event) => {} + BehaviourEvent::Mdns(event) => match event { + mdns::Event::Discovered(list) => { + for (peer_id, multiaddr) in list { + if swarm.is_connected(&peer_id) { + continue; + } + + tracing::debug!("mDNS discovered peer {peer_id} at {multiaddr}"); + swarm.add_peer_address(peer_id, multiaddr); + swarm.dial(peer_id).unwrap_or_else(|e| { + tracing::error!("failed to dial mDNS discovered peer {peer_id}: {e}") + }); + } + } + mdns::Event::Expired(list) => { + for (peer_id, multiaddr) in list { + tracing::debug!("mDNS expired peer {peer_id} at {multiaddr}"); + } + } + }, + BehaviourEvent::Ping(_event) => {} + } + } +} diff --git a/rust/op-rbuilder/crates/p2p/src/lib.rs b/rust/op-rbuilder/crates/p2p/src/lib.rs new file mode 100644 index 00000000000..757be3b2540 --- /dev/null +++ b/rust/op-rbuilder/crates/p2p/src/lib.rs @@ -0,0 +1,577 @@ +mod behaviour; +mod outgoing; + +use behaviour::Behaviour; +use libp2p_stream::IncomingStreams; + +use eyre::Context; +use libp2p::{ + PeerId, Swarm, Transport as _, + identity::{self, ed25519}, + noise, + swarm::SwarmEvent, + tcp, yamux, +}; +use multiaddr::Protocol; +use std::{collections::HashMap, time::Duration}; +use tokio::sync::mpsc; +use tokio_util::sync::CancellationToken; +use tracing::{debug, warn}; + +pub use libp2p::{Multiaddr, StreamProtocol}; + +const DEFAULT_MAX_PEER_COUNT: u32 = 50; + +/// A message that can be sent between peers. +pub trait Message: + serde::Serialize + for<'de> serde::Deserialize<'de> + Send + Sync + Clone + std::fmt::Debug +{ + fn protocol(&self) -> StreamProtocol; + + fn to_string(&self) -> eyre::Result { + serde_json::to_string(self).wrap_err("failed to serialize message to string") + } + + fn from_str(s: &str) -> eyre::Result + where + Self: Sized, + { + serde_json::from_str(s).wrap_err("failed to deserialize message from string") + } +} + +/// The libp2p node. +/// +/// The current behaviour of the node regarding messaging protocols is as follows: +/// - for each supported protocol, the node will accept incoming streams from remote peers on that protocol. +/// - when a new connection is established with a peer, the node will open outbound streams to that peer for each supported protocol. +/// - when a new outgoing message is received on `outgoing_message_rx`, the node will broadcast that message to all connected peers that have an outbound stream open for the message's protocol. +/// - incoming messages received on incoming streams are handled by `IncomingStreamsHandler`, which reads messages from the stream and sends them to a channel for processing by the consumer of this library. +/// +/// Currently, there is no gossip implemented; messages are simply broadcast to connected peers. +pub struct Node { + /// The peer ID of this node. + peer_id: PeerId, + + /// The multiaddresses this node is listening on. + listen_addrs: Vec, + + /// The libp2p swarm, which contains the state of the network + /// and its behaviours. + swarm: Swarm, + + /// The multiaddresses of known peers to connect to on startup. + known_peers: Vec, + + /// Receiver for outgoing messages to be sent to peers. + outgoing_message_rx: mpsc::Receiver, + + /// Handler for managing outgoing streams to peers. + /// Used to determine what peers to broadcast to when a + /// new outgoing message is received on `outgoing_message_rx`. + outgoing_streams_handler: outgoing::StreamsHandler, + + /// Handlers for incoming streams (streams which remote peers have opened with us). + incoming_streams_handlers: Vec>, + + /// The protocols this node supports. + protocols: Vec, + + /// Cancellation token to shut down the node. + cancellation_token: CancellationToken, +} + +impl Node { + /// Returns the multiaddresses that this node is listening on, with the peer ID included. + pub fn multiaddrs(&self) -> Vec { + self.listen_addrs + .iter() + .map(|addr| { + addr.clone() + .with_p2p(self.peer_id) + .expect("can add peer ID to multiaddr") + }) + .collect() + } + + /// Runs the p2p node, dials known peers, and starts listening for incoming connections and messages. + /// + /// This function will run until the cancellation token is triggered. + /// If an error occurs, it will be logged, but the node will continue running. + pub async fn run(self) -> eyre::Result<()> { + use libp2p::futures::StreamExt as _; + + let Node { + peer_id: _, + listen_addrs, + mut swarm, + known_peers, + mut outgoing_message_rx, + mut outgoing_streams_handler, + cancellation_token, + incoming_streams_handlers, + protocols, + } = self; + + for addr in listen_addrs { + swarm + .listen_on(addr) + .wrap_err("swarm failed to listen on multiaddr")?; + } + + for mut address in known_peers { + let peer_id = match address.pop() { + Some(multiaddr::Protocol::P2p(peer_id)) => peer_id, + _ => { + eyre::bail!("no peer ID for known peer"); + } + }; + swarm.add_peer_address(peer_id, address.clone()); + swarm + .dial(address) + .wrap_err("swarm failed to dial known peer")?; + } + + let handles = incoming_streams_handlers + .into_iter() + .map(|handler| tokio::spawn(handler.run())) + .collect::>(); + + loop { + tokio::select! { + biased; + _ = cancellation_token.cancelled() => { + debug!("cancellation token triggered, shutting down node"); + handles.into_iter().for_each(|h| h.abort()); + break Ok(()); + } + Some(message) = outgoing_message_rx.recv() => { + let protocol = message.protocol(); + debug!("received message to broadcast on protocol {protocol}"); + if let Err(e) = outgoing_streams_handler.broadcast_message(message).await { + warn!("failed to broadcast message on protocol {protocol}: {e:?}"); + } + } + event = swarm.select_next_some() => { + match event { + SwarmEvent::NewListenAddr { + address, + .. + } => { + debug!("new listen address: {address}"); + } + SwarmEvent::ExternalAddrConfirmed { address } => { + debug!("external address confirmed: {address}"); + } + SwarmEvent::ConnectionEstablished { + peer_id, + connection_id, + .. + } => { + // when a new connection is established, open outbound streams for each protocol + // and add them to the outgoing streams handler. + debug!("connection established with peer {peer_id}"); + if !outgoing_streams_handler.has_peer(&peer_id) { + for protocol in &protocols { + match swarm + .behaviour_mut() + .new_control() + .open_stream(peer_id, protocol.clone()) + .await + { + Ok(stream) => { outgoing_streams_handler.insert_peer_and_stream(peer_id, protocol.clone(), stream); + debug!("opened outbound stream with peer {peer_id} with protocol {protocol} on connection {connection_id}"); + } + Err(e) => { + warn!("failed to open stream with peer {peer_id} on connection {connection_id}: {e:?}"); + } + } + } + } + } + SwarmEvent::ConnectionClosed { + peer_id, + cause, + .. + } => { + debug!("connection closed with peer {peer_id}: {cause:?}"); + outgoing_streams_handler.remove_peer(&peer_id); + } + SwarmEvent::Behaviour(event) => event.handle(&mut swarm), + _ => continue, + } + }, + } + } + } +} + +pub struct NodeBuildResult { + pub node: Node, + pub outgoing_message_tx: mpsc::Sender, + pub incoming_message_rxs: HashMap>, +} + +pub struct NodeBuilder { + port: Option, + listen_addrs: Vec, + keypair_hex: Option, + known_peers: Vec, + agent_version: Option, + protocols: Vec, + max_peer_count: Option, + cancellation_token: Option, +} + +impl Default for NodeBuilder { + fn default() -> Self { + Self::new() + } +} + +impl NodeBuilder { + pub fn new() -> Self { + Self { + port: None, + listen_addrs: Vec::new(), + keypair_hex: None, + known_peers: Vec::new(), + agent_version: None, + protocols: Vec::new(), + max_peer_count: None, + cancellation_token: None, + } + } + + pub fn with_port(mut self, port: u16) -> Self { + self.port = Some(port); + self + } + + pub fn with_listen_addr(mut self, addr: libp2p::Multiaddr) -> Self { + self.listen_addrs.push(addr); + self + } + + pub fn with_keypair_hex_string(mut self, keypair_hex: String) -> Self { + self.keypair_hex = Some(keypair_hex); + self + } + + pub fn with_agent_version(mut self, agent_version: String) -> Self { + self.agent_version = Some(agent_version); + self + } + + pub fn with_protocol(mut self, protocol: StreamProtocol) -> Self { + self.protocols.push(protocol); + self + } + + pub fn with_cancellation_token( + mut self, + cancellation_token: tokio_util::sync::CancellationToken, + ) -> Self { + self.cancellation_token = Some(cancellation_token); + self + } + + pub fn with_max_peer_count(mut self, max_peer_count: u32) -> Self { + self.max_peer_count = Some(max_peer_count); + self + } + + pub fn with_known_peers(mut self, addresses: I) -> Self + where + I: IntoIterator, + T: Into, + { + for address in addresses { + self.known_peers.push(address.into()); + } + self + } + + pub fn try_build(self) -> eyre::Result> { + let Self { + port, + listen_addrs, + keypair_hex, + known_peers, + agent_version, + protocols, + max_peer_count, + cancellation_token, + } = self; + + // TODO: caller should be forced to provide this + let cancellation_token = cancellation_token.unwrap_or_default(); + + let Some(agent_version) = agent_version else { + eyre::bail!("agent version must be set"); + }; + + let keypair = match keypair_hex { + Some(hex) => { + let mut bytes = hex::decode(hex).wrap_err("failed to decode hex string")?; + let keypair = ed25519::Keypair::try_from_bytes(&mut bytes) + .wrap_err("failed to create keypair from bytes")?; + Some(keypair.into()) + } + None => None, + }; + let keypair = keypair.unwrap_or(identity::Keypair::generate_ed25519()); + let peer_id = keypair.public().to_peer_id(); + + let transport = create_transport(&keypair).wrap_err("failed to create transport")?; + let max_peer_count = max_peer_count.unwrap_or(DEFAULT_MAX_PEER_COUNT); + let mut behaviour = Behaviour::new(&keypair, agent_version, max_peer_count) + .context("failed to create behaviour")?; + let mut control = behaviour.new_control(); + + let mut incoming_streams_handlers = Vec::new(); + let mut incoming_message_rxs = HashMap::new(); + for protocol in &protocols { + let incoming_streams = control + .accept(protocol.clone()) + .wrap_err("failed to subscribe to incoming streams for flashblocks protocol")?; + let (incoming_streams_handler, message_rx) = IncomingStreamsHandler::new( + protocol.clone(), + incoming_streams, + cancellation_token.clone(), + ); + incoming_streams_handlers.push(incoming_streams_handler); + incoming_message_rxs.insert(protocol.clone(), message_rx); + } + + let swarm = libp2p::SwarmBuilder::with_existing_identity(keypair) + .with_tokio() + .with_other_transport(|_| transport)? + .with_behaviour(|_| behaviour)? + .with_swarm_config(|cfg| { + cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX)) // don't disconnect from idle peers + }) + .build(); + + // disallow providing listen addresses that have a peer ID in them, + // as we've specified the peer ID for this node above. + let mut listen_addrs: Vec = listen_addrs + .into_iter() + .filter(|addr| { + for protocol in addr.iter() { + if protocol == Protocol::P2p(peer_id) { + return false; + } + } + true + }) + .collect(); + if listen_addrs.is_empty() { + let port = port.unwrap_or(0); + let listen_addr = format!("/ip4/0.0.0.0/tcp/{port}") + .parse() + .expect("can parse valid multiaddr"); + listen_addrs.push(listen_addr); + } + + let (outgoing_message_tx, outgoing_message_rx) = tokio::sync::mpsc::channel(100); + + Ok(NodeBuildResult { + node: Node { + peer_id, + swarm, + listen_addrs, + known_peers, + outgoing_message_rx, + outgoing_streams_handler: outgoing::StreamsHandler::new(), + cancellation_token, + incoming_streams_handlers, + protocols, + }, + outgoing_message_tx, + incoming_message_rxs, + }) + } +} + +struct IncomingStreamsHandler { + protocol: StreamProtocol, + incoming: IncomingStreams, + tx: mpsc::Sender, + cancellation_token: CancellationToken, +} + +impl IncomingStreamsHandler { + fn new( + protocol: StreamProtocol, + incoming: IncomingStreams, + cancellation_token: CancellationToken, + ) -> (Self, mpsc::Receiver) { + const CHANNEL_SIZE: usize = 100; + let (tx, rx) = mpsc::channel(CHANNEL_SIZE); + ( + Self { + protocol, + incoming, + tx, + cancellation_token, + }, + rx, + ) + } + + async fn run(self) { + use futures::StreamExt as _; + + let Self { + protocol, + mut incoming, + tx, + cancellation_token, + } = self; + let mut handle_stream_futures = futures::stream::FuturesUnordered::new(); + + loop { + tokio::select! { + _ = cancellation_token.cancelled() => { + debug!("cancellation token triggered, shutting down incoming streams handler for protocol {protocol}"); + return; + } + Some((from, stream)) = incoming.next() => { + debug!("new incoming stream on protocol {protocol} from peer {from}"); + handle_stream_futures.push(tokio::spawn(handle_incoming_stream(from, stream, tx.clone()))); + } + Some(res) = handle_stream_futures.next() => { + match res { + Ok(Ok(())) => {} + Ok(Err(e)) => { + warn!("error handling incoming stream: {e:?}"); + } + Err(e) => { + warn!("task handling incoming stream panicked: {e:?}"); + } + } + } + } + } + } +} + +async fn handle_incoming_stream( + peer_id: PeerId, + stream: libp2p::Stream, + payload_tx: mpsc::Sender, +) -> eyre::Result<()> { + use futures::StreamExt as _; + use tokio_util::{ + codec::{FramedRead, LinesCodec}, + compat::FuturesAsyncReadCompatExt as _, + }; + + let codec = LinesCodec::new(); + let mut reader = FramedRead::new(stream.compat(), codec); + + while let Some(res) = reader.next().await { + match res { + Ok(str) => { + let payload = M::from_str(&str).wrap_err("failed to decode stream message")?; + debug!("got message from peer {peer_id}: {payload:?}"); + let _ = payload_tx.send(payload).await; + } + Err(e) => { + return Err(e).wrap_err(format!("failed to read from stream of peer {peer_id}")); + } + } + } + + Ok(()) +} + +fn create_transport( + keypair: &identity::Keypair, +) -> eyre::Result> { + let transport = tcp::tokio::Transport::new(tcp::Config::default()) + .upgrade(libp2p::core::upgrade::Version::V1) + .authenticate(noise::Config::new(keypair)?) + .multiplex(yamux::Config::default()) + .timeout(Duration::from_secs(20)) + .boxed(); + + Ok(transport) +} + +#[cfg(test)] +mod test { + use super::*; + + const TEST_AGENT_VERSION: &str = "test/1.0.0"; + const TEST_PROTOCOL: StreamProtocol = StreamProtocol::new("/test/1.0.0"); + + #[derive(Debug, PartialEq, Eq, Clone)] + struct TestMessage { + content: String, + } + + impl Message for TestMessage { + fn protocol(&self) -> StreamProtocol { + TEST_PROTOCOL + } + } + + impl serde::Serialize for TestMessage { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.content) + } + } + + impl<'de> serde::Deserialize<'de> for TestMessage { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(TestMessage { content: s }) + } + } + + #[tokio::test] + async fn two_nodes_can_connect_and_message() { + let NodeBuildResult { + node: node1, + outgoing_message_tx: _, + incoming_message_rxs: mut rx1, + } = NodeBuilder::new() + .with_listen_addr("/ip4/127.0.0.1/tcp/9000".parse().unwrap()) + .with_agent_version(TEST_AGENT_VERSION.to_string()) + .with_protocol(TEST_PROTOCOL) + .try_build::() + .unwrap(); + let NodeBuildResult { + node: node2, + outgoing_message_tx: tx2, + incoming_message_rxs: _, + } = NodeBuilder::new() + .with_known_peers(node1.multiaddrs()) + .with_protocol(TEST_PROTOCOL) + .with_listen_addr("/ip4/127.0.0.1/tcp/9001".parse().unwrap()) + .with_agent_version(TEST_AGENT_VERSION.to_string()) + .try_build::() + .unwrap(); + + tokio::spawn(async move { node1.run().await }); + tokio::spawn(async move { node2.run().await }); + // sleep to allow nodes to connect + tokio::time::sleep(Duration::from_secs(2)).await; + + let message = TestMessage { + content: "message".to_string(), + }; + tx2.send(message.clone()).await.unwrap(); + + let recv_message: TestMessage = rx1.remove(&TEST_PROTOCOL).unwrap().recv().await.unwrap(); + assert_eq!(recv_message, message); + } +} diff --git a/rust/op-rbuilder/crates/p2p/src/outgoing.rs b/rust/op-rbuilder/crates/p2p/src/outgoing.rs new file mode 100644 index 00000000000..2440e0f72e1 --- /dev/null +++ b/rust/op-rbuilder/crates/p2p/src/outgoing.rs @@ -0,0 +1,100 @@ +use crate::Message; +use eyre::Context; +use futures::stream::FuturesUnordered; +use libp2p::{PeerId, StreamProtocol, swarm::Stream}; +use std::collections::HashMap; +use tracing::{debug, warn}; + +pub(crate) struct StreamsHandler { + peers_to_stream: HashMap>, +} + +impl StreamsHandler { + pub(crate) fn new() -> Self { + Self { + peers_to_stream: HashMap::new(), + } + } + + pub(crate) fn has_peer(&self, peer: &PeerId) -> bool { + self.peers_to_stream.contains_key(peer) + } + + pub(crate) fn insert_peer_and_stream( + &mut self, + peer: PeerId, + protocol: StreamProtocol, + stream: Stream, + ) { + self.peers_to_stream + .entry(peer) + .or_default() + .insert(protocol, stream); + } + + pub(crate) fn remove_peer(&mut self, peer: &PeerId) { + self.peers_to_stream.remove(peer); + } + + pub(crate) async fn broadcast_message(&mut self, message: M) -> eyre::Result<()> { + use futures::{SinkExt as _, StreamExt as _}; + use tokio_util::{ + codec::{FramedWrite, LinesCodec}, + compat::FuturesAsyncReadCompatExt as _, + }; + + let protocol = message.protocol(); + let payload = message + .to_string() + .wrap_err("failed to serialize payload")?; + + let peers = self.peers_to_stream.keys().cloned().collect::>(); + let mut futures = FuturesUnordered::new(); + for peer in peers { + let protocol_to_stream = self + .peers_to_stream + .get_mut(&peer) + .expect("stream map must exist for peer"); + let Some(stream) = protocol_to_stream.remove(&protocol) else { + warn!("no stream for protocol {protocol:?} to peer {peer}"); + continue; + }; + let stream = stream.compat(); + let payload = payload.clone(); + let fut = async move { + let mut writer = FramedWrite::new(stream, LinesCodec::new()); + writer + .send(payload) + .await + .wrap_err("failed to send message to peer")?; + Ok::<(PeerId, libp2p::swarm::Stream), eyre::ErrReport>(( + peer, + writer.into_inner().into_inner(), + )) + }; + futures.push(fut); + } + + while let Some(result) = futures.next().await { + match result { + Ok((peer, stream)) => { + let protocol_to_stream = self + .peers_to_stream + .get_mut(&peer) + .expect("stream map must exist for peer"); + protocol_to_stream.insert(protocol.clone(), stream); + } + Err(e) => { + warn!("failed to send payload to peer: {e:?}"); + } + } + } + + debug!( + "broadcasted message to {} peers", + self.peers_to_stream.len() + ); + + Ok(()) + } +} diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/.dockerignore b/rust/op-rbuilder/crates/tdx-quote-provider/.dockerignore new file mode 100644 index 00000000000..de51f7f60cf --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/.dockerignore @@ -0,0 +1,6 @@ +target/ +.git/ +.github/ +.gitignore +tests/ +README.md \ No newline at end of file diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/Cargo.toml b/rust/op-rbuilder/crates/tdx-quote-provider/Cargo.toml new file mode 100644 index 00000000000..5616cba840e --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "tdx-quote-provider" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "TDX quote provider API service" +readme = "README.md" + +[dependencies] +axum = { version = "0.8.1" } +thiserror.workspace = true +clap.workspace = true +tracing.workspace = true +tokio = { workspace = true, features = ["full", "signal"] } +eyre.workspace = true +metrics.workspace = true +metrics-derive = "0.1" +serde.workspace = true +serde_json.workspace = true + +hex = "0.4.3" +dotenvy = "0.15.4" +metrics-exporter-prometheus = { version = "0.17.0", features = [ + "http-listener", +] } +tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } +tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", features = ["configfs"], branch = "main"} + +[dev-dependencies] +reqwest.workspace = true + +[[bin]] +name = "tdx-quote-provider" +path = "src/main.rs" + +[lib] +name = "tdx_quote_provider" +path = "src/lib.rs" \ No newline at end of file diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/Dockerfile b/rust/op-rbuilder/crates/tdx-quote-provider/Dockerfile new file mode 100644 index 00000000000..a9c2b715b1a --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/Dockerfile @@ -0,0 +1,23 @@ +FROM rust:1.88 AS builder + +WORKDIR /app + +ARG BINARY="tdx-quote-provider" +ARG FEATURES + +COPY . . + +RUN apt-get update && apt-get install -y libssl3 libtss2-dev + +RUN cargo build --release --features="$FEATURES" --package=${BINARY} + +FROM debian:12-slim +WORKDIR /app + +# Install runtime dependencies +RUN apt-get update && apt-get install -y libssl3 libtss2-dev + +ARG BINARY="tdx-quote-provider" +COPY --from=builder /app/target/release/${BINARY} /usr/local/bin/ + +ENTRYPOINT ["/usr/local/bin/tdx-quote-provider"] diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/README.md b/rust/op-rbuilder/crates/tdx-quote-provider/README.md new file mode 100644 index 00000000000..205bdd67e6c --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/README.md @@ -0,0 +1,102 @@ +# TDX Quote Provider + +This crate is a service intended to be run alongside the same TDX VM as op-rbuilder for flashtestations. This is a simple HTTP server that generates and returns an attestation quote by the requesting service. + +TDX attestations uses configfs, which requires root access. Having a separate service allow applications like the op-rbuilder to request attestations without root access. + +## Usage + +You can run the server using [Cargo](https://doc.rust-lang.org/cargo/). To run the quote provider server: + +```bash +cargo run -p tdx-quote-provider --bin tdx-quote-provider -- +``` + +This will run a server that will generate and provide TDX attestation quotes on http:localhost:8181 by default. + +To run the server with a mock attestation quote for testing: + +```bash +cargo run -p tdx-quote-provider --bin tdx-quote-provider \ + --mock \ + --mock-attestation-path /path/to/mock/quote.bin +``` + +### Command-line Options + +| Flag | Environment Variable | Default | Description | +|------|---------------------|---------|-------------| +| `--service-host` | `SERVICE_HOST` | `127.0.0.1` | Host to run the HTTP server on | +| `--service-port` | `SERVICE_PORT` | `8181` | Port to run the HTTP server on | +| `--metrics` | `METRICS` | `false` | Enable Prometheus metrics | +| `--metrics-host` | `METRICS_HOST` | `127.0.0.1` | Host to run the metrics server on | +| `--metrics-port` | `METRICS_PORT` | `9090` | Port to run the metrics server on | +| `--mock` | `MOCK` | `false` | Use mock attestation for testing | +| `--mock-attestation-path` | `MOCK_ATTESTATION_PATH` | `""` | Path to the mock attestation file | +| `--log-level` | `LOG_LEVEL` | `info` | Log level (trace, debug, info, warn, error) | +| `--log-format` | `LOG_FORMAT` | `text` | Log format (text, json) | + +## Endpoints + +### `GET /healthz` +Health check endpoint that returns `200 OK` if the server is running. + +**Response:** +``` +HTTP/1.1 200 OK +``` + +### `GET /attest/{appdata}` +Generates and returns a TDX attestation quote for the provided report data. In the case of op-rbuilder, this is the public key of the ethereum key pair generated during the bootstrapping step for flashtestations. + +**Parameters:** +- `appdata` (path parameter): Hex-encoded 64-byte report data + +**Response:** +- **Success (200 OK):** Binary attestation quote with `Content-Type: application/octet-stream` + +**Example:** +```bash +curl -X GET "http://localhost:8181/attest/bbbbf586ac29a7b62fef9118e9d614179962d463419ebd905eb5ece84f2946dfccff93a66129a140ea49c49f7590c36143ad2aec7f8ed74aaa0ff479494a6493" \ # debug key for flashtestations + -H "Accept: application/octet-stream" \ + --output attestation.bin +``` + +### Metrics Endpoint + +When enabled with `--metrics`, Prometheus metrics are available at the configured metrics address (default: `http://localhost:9090`). + +## Contributing + +### Building & Testing + +All tests and test data with a mock attestation quote are in the `/tests` folder. + +```bash +# Build the project +cargo build -p tdx-quote-provider + +# Run all the tests +cargo test -p tdx-quote-provider +``` + +### Deployment + +To build a docker image: + +```bash +# Build from the workspace root +docker build -f crates/tdx-quote-provider/Dockerfile -t tdx-quote-provider . +``` + +Builds of the websocket proxy are provided on [Dockerhub](https://hub.docker.com/r/flashbots/tdx-quote-provider/tags). + +You can see a full list of parameters by running: + +`docker run flashbots/tdx-quote-provider:latest --help` + +Example: + +```bash +docker run flashbots/tdx-quote-provider:latest +``` \ No newline at end of file diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/src/lib.rs b/rust/op-rbuilder/crates/tdx-quote-provider/src/lib.rs new file mode 100644 index 00000000000..3aad8c3a49f --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/src/lib.rs @@ -0,0 +1,7 @@ +//! TDX Quote Provider +//! +//! This crate provides functionality for generating and managing TDX attestation quotes. + +pub mod metrics; +pub mod provider; +pub mod server; diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/src/main.rs b/rust/op-rbuilder/crates/tdx-quote-provider/src/main.rs new file mode 100644 index 00000000000..a820ce976d9 --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/src/main.rs @@ -0,0 +1,95 @@ +use clap::Parser; +use dotenvy::dotenv; +use metrics_exporter_prometheus::PrometheusBuilder; +use tracing::{Level, info}; +use tracing_subscriber::filter::EnvFilter; + +use crate::server::{Server, ServerConfig}; + +mod metrics; +mod provider; +mod server; + +#[derive(Clone, Parser, Debug)] +#[command(about = "TDX Quote Provider CLI")] +struct Args { + /// Host to run the http server on + #[arg(long, env, default_value = "127.0.0.1")] + pub service_host: String, + + /// Port to run the http server on + #[arg(long, env, default_value = "8181")] + pub service_port: u16, + + // Enable Prometheus metrics + #[arg(long, env, default_value = "false")] + pub metrics: bool, + + /// Host to run the metrics server on + #[arg(long, env, default_value = "127.0.0.1")] + pub metrics_host: String, + + /// Port to run the metrics server on + #[arg(long, env, default_value = "9090")] + pub metrics_port: u16, + + /// Use mock attestation for testing + #[arg(long, env, default_value = "false")] + pub mock: bool, + + /// Path to the mock attestation file + #[arg(long, env, default_value = "")] + pub mock_attestation_path: String, + + /// Log level + #[arg(long, env, default_value = "info")] + pub log_level: Level, + + /// Log format + #[arg(long, env, default_value = "text")] + pub log_format: String, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + dotenv().ok(); + + let args = Args::parse(); + + if args.log_format == "json" { + tracing_subscriber::fmt() + .json() + .with_env_filter(EnvFilter::new(args.log_level.to_string())) + .with_ansi(false) + .init(); + } else { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::new(args.log_level.to_string())) + .init(); + } + + info!("Starting TDX quote provider"); + + if args.metrics { + let metrics_addr = format!("{}:{}", args.metrics_host, args.metrics_port); + info!(message = "starting metrics server", address = metrics_addr); + let socket_addr: std::net::SocketAddr = + metrics_addr.parse().expect("invalid metrics address"); + let builder = PrometheusBuilder::new().with_http_listener(socket_addr); + + builder + .install() + .expect("failed to setup Prometheus endpoint") + } + + // Start the server + let server = Server::new(ServerConfig { + listen_addr: format!("{}:{}", args.service_host, args.service_port) + .parse() + .unwrap(), + use_mock: args.mock, + mock_attestation_path: args.mock_attestation_path, + }); + + server.listen().await +} diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/src/metrics.rs b/rust/op-rbuilder/crates/tdx-quote-provider/src/metrics.rs new file mode 100644 index 00000000000..695fad581cb --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/src/metrics.rs @@ -0,0 +1,9 @@ +use metrics::Histogram; +use metrics_derive::Metrics; + +#[derive(Metrics, Clone)] +#[metrics(scope = "tdx_quote_provider")] +pub struct Metrics { + /// Duration of attestation request + pub attest_duration: Histogram, +} diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/src/provider.rs b/rust/op-rbuilder/crates/tdx-quote-provider/src/provider.rs new file mode 100644 index 00000000000..0479edf8a7b --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/src/provider.rs @@ -0,0 +1,93 @@ +use std::{fs::File, io::Read, sync::Arc}; +use tdx::{Tdx, device::DeviceOptions, error::TdxError}; +use thiserror::Error; +use tracing::info; + +#[derive(Error, Debug)] +pub enum AttestationError { + #[error("Failed to get attestation: {0}")] + GetAttestationFailed(TdxError), + #[error("Failed to read mock attestation file: {0}")] + ReadMockAttestationFailed(std::io::Error), +} + +/// Configuration for attestation +#[derive(Default)] +pub struct AttestationConfig { + /// If true, uses the mock attestation provider instead of real TDX hardware + pub mock: bool, + /// Path to the mock attestation file + pub mock_attestation_path: String, +} + +/// Trait for attestation providers +pub trait AttestationProvider { + fn get_attestation(&self, report_data: [u8; 64]) -> Result, AttestationError>; +} + +/// Real TDX hardware attestation provider +pub struct TdxAttestationProvider { + tdx: Tdx, +} + +impl Default for TdxAttestationProvider { + fn default() -> Self { + Self::new() + } +} + +impl TdxAttestationProvider { + pub fn new() -> Self { + Self { tdx: Tdx::new() } + } +} + +impl AttestationProvider for TdxAttestationProvider { + fn get_attestation(&self, report_data: [u8; 64]) -> Result, AttestationError> { + self.tdx + .get_attestation_report_raw_with_options(DeviceOptions { + report_data: Some(report_data), + }) + .map(|(quote, _var_data)| quote) + .map_err(AttestationError::GetAttestationFailed) + } +} + +/// Mock attestation provider +pub struct MockAttestationProvider { + mock_attestation_path: String, +} + +impl MockAttestationProvider { + pub fn new(mock_attestation_path: String) -> Self { + Self { + mock_attestation_path, + } + } +} + +impl AttestationProvider for MockAttestationProvider { + fn get_attestation(&self, _report_data: [u8; 64]) -> Result, AttestationError> { + info!( + target: "tdx_quote_provider", + mock_attestation_path = self.mock_attestation_path, + "using mock attestation provider", + ); + let mut file = File::open(self.mock_attestation_path.clone()) + .map_err(AttestationError::ReadMockAttestationFailed)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer) + .map_err(AttestationError::ReadMockAttestationFailed)?; + Ok(buffer) + } +} + +pub fn get_attestation_provider( + config: AttestationConfig, +) -> Arc { + if config.mock { + Arc::new(MockAttestationProvider::new(config.mock_attestation_path)) + } else { + Arc::new(TdxAttestationProvider::new()) + } +} diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/src/server.rs b/rust/op-rbuilder/crates/tdx-quote-provider/src/server.rs new file mode 100644 index 00000000000..b8d5e295519 --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/src/server.rs @@ -0,0 +1,180 @@ +use std::{net::SocketAddr, sync::Arc, time::Instant}; + +use axum::{ + Router, + body::Body, + extract::{Path, State}, + http::{Response, StatusCode}, + response::IntoResponse, + routing::get, +}; +use serde_json::json; +use tokio::{net::TcpListener, signal}; +use tracing::info; + +use crate::{ + metrics::Metrics, + provider::{AttestationConfig, AttestationProvider, get_attestation_provider}, +}; + +/// Server configuration +#[derive(Debug, Clone)] +pub struct ServerConfig { + /// Address to listen on + pub listen_addr: SocketAddr, + /// Whether to use mock attestation + pub use_mock: bool, + /// Path to the mock attestation file + pub mock_attestation_path: String, +} + +impl Default for ServerConfig { + fn default() -> Self { + Self { + listen_addr: "127.0.0.1:8181".parse().unwrap(), + use_mock: false, + mock_attestation_path: "".to_string(), + } + } +} + +#[derive(Clone)] +struct ServerState { + quote_provider: Arc, + metrics: Metrics, +} + +#[derive(Clone)] +pub struct Server { + /// Quote provider + quote_provider: Arc, + /// Metrics for the server + metrics: Metrics, + /// Server configuration + config: ServerConfig, +} + +impl Server { + pub fn new(config: ServerConfig) -> Self { + let attestation_config = AttestationConfig { + mock: config.use_mock, + mock_attestation_path: config.mock_attestation_path.clone(), + }; + let quote_provider = get_attestation_provider(attestation_config); + Self { + quote_provider, + metrics: Metrics::default(), + config, + } + } + + pub async fn listen(self) -> eyre::Result<()> { + let router = Router::new() + .route("/healthz", get(healthz_handler)) + .route("/attest/{appdata}", get(attest_handler)) + .with_state(ServerState { + quote_provider: self.quote_provider, + metrics: self.metrics, + }); + + let listener = TcpListener::bind(self.config.listen_addr).await?; + info!( + message = "starting server", + address = listener.local_addr()?.to_string() + ); + + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(shutdown_signal()) + .await + .map_err(|e| eyre::eyre!("failed to start server: {}", e)) + } +} + +async fn healthz_handler() -> impl IntoResponse { + StatusCode::OK +} + +async fn attest_handler( + Path(appdata): Path, + State(server): State, +) -> impl IntoResponse { + let start = Instant::now(); + + // Decode hex + let appdata = match hex::decode(appdata) { + Ok(data) => data, + Err(e) => { + info!(target: "tdx_quote_provider", error = %e, "Invalid hex in report data for attestation"); + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from( + json!({"message": "Invalid hex in report data for attestation"}).to_string(), + )) + .unwrap() + .into_response(); + } + }; + + // Convert to report data + let report_data: [u8; 64] = match appdata.try_into() { + Ok(data) => data, + Err(e) => { + info!(target: "tdx_quote_provider", error = ?e, "Invalid report data length for attestation"); + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from( + json!({"message": "Invalid report data length for attestation"}).to_string(), + )) + .unwrap() + .into_response(); + } + }; + + // Get attestation + match server.quote_provider.get_attestation(report_data) { + Ok(attestation) => { + let duration = start.elapsed(); + server + .metrics + .attest_duration + .record(duration.as_secs_f64()); + + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/octet-stream") + .body(Body::from(attestation)) + .unwrap() + .into_response() + } + Err(e) => { + tracing::error!(target: "tdx_quote_provider", error = %e, "Failed to get TDX attestation"); + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from( + json!({"message": "Failed to get TDX attestation"}).to_string(), + )) + .unwrap() + .into_response() + } + } +} + +async fn shutdown_signal() { + let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate()).unwrap(); + let mut sigint = signal::unix::signal(signal::unix::SignalKind::interrupt()).unwrap(); + + tokio::select! { + _ = signal::ctrl_c() => { + info!("Received Ctrl+C, shutting down gracefully"); + } + _ = sigterm.recv() => { + info!("Received SIGTERM, shutting down gracefully"); + } + _ = sigint.recv() => { + info!("Received SIGINT, shutting down gracefully"); + } + } +} diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/tests/mod.rs b/rust/op-rbuilder/crates/tdx-quote-provider/tests/mod.rs new file mode 100644 index 00000000000..f4a70f9f552 --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/tests/mod.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +mod simple; diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/tests/simple.rs b/rust/op-rbuilder/crates/tdx-quote-provider/tests/simple.rs new file mode 100644 index 00000000000..afa060286fa --- /dev/null +++ b/rust/op-rbuilder/crates/tdx-quote-provider/tests/simple.rs @@ -0,0 +1,114 @@ +use axum::body::Bytes; +use std::{error::Error, net::SocketAddr, time::Duration}; +use tdx_quote_provider::server::{Server, ServerConfig}; +use tokio::net::TcpListener; + +struct TestHarness { + server: Server, + server_addr: SocketAddr, +} + +impl TestHarness { + async fn alloc_port() -> SocketAddr { + let address = SocketAddr::from(([127, 0, 0, 1], 0)); + let listener = TcpListener::bind(&address).await.unwrap(); + listener.local_addr().unwrap() + } + + fn new(addr: SocketAddr) -> TestHarness { + let path = format!("{}/tests/test_data/quote.bin", env!("CARGO_MANIFEST_DIR")); + Self { + server: Server::new(ServerConfig { + listen_addr: addr, + use_mock: true, + mock_attestation_path: path, + }), + server_addr: addr, + } + } + + async fn healthcheck(&self) -> Result<(), Box> { + let url = format!("http://{}/healthz", self.server_addr); + let response = reqwest::get(url).await?; + match response.error_for_status() { + Ok(_) => Ok(()), + Err(e) => Err(e.into()), + } + } + + async fn attest(&self, app_data: String) -> Result> { + let url = format!("http://{}/attest/{}", self.server_addr, app_data); + let response = reqwest::get(url).await?; + match response.error_for_status() { + Ok(response) => { + let body = response.bytes().await?; + Ok(body) + } + Err(e) => Err(e.into()), + } + } + + async fn start_server(&mut self) { + let mut healthy = true; + + let server = self.server.clone(); + let _server_handle = tokio::spawn(async move { + _ = server.listen().await; + }); + + for _ in 0..5 { + let resp = self.healthcheck().await; + match resp { + Ok(_) => { + healthy = true; + break; + } + Err(_) => { + tokio::time::sleep(Duration::from_millis(25)).await; + } + } + } + + assert!(healthy); + } +} + +#[tokio::test] +async fn test_healthcheck() { + let addr = TestHarness::alloc_port().await; + let mut harness = TestHarness::new(addr); + assert!(harness.healthcheck().await.is_err()); + harness.start_server().await; + assert!(harness.healthcheck().await.is_ok()); +} + +#[tokio::test] +async fn test_mock_attest() { + let addr = TestHarness::alloc_port().await; + + let mut harness = TestHarness::new(addr); + harness.start_server().await; + + let report_data = hex::encode(vec![0; 64]); + let response = harness.attest(report_data).await; + assert!(response.is_ok()); + let body = response.unwrap(); + assert_eq!( + body, + Bytes::from_static(include_bytes!("./test_data/quote.bin")) + ); +} + +#[tokio::test] +async fn test_attest_invalid_report_data() { + let addr = TestHarness::alloc_port().await; + + let mut harness = TestHarness::new(addr); + harness.start_server().await; + + let response = harness.attest("invalid".to_string()).await; + assert!(response.is_err()); + + let response = harness.attest("aede".to_string()).await; + assert!(response.is_err()); +} diff --git a/rust/op-rbuilder/crates/tdx-quote-provider/tests/test_data/quote.bin b/rust/op-rbuilder/crates/tdx-quote-provider/tests/test_data/quote.bin new file mode 100644 index 00000000000..ba753696270 Binary files /dev/null and b/rust/op-rbuilder/crates/tdx-quote-provider/tests/test_data/quote.bin differ diff --git a/rust/op-rbuilder/docs/eth_sendBundle.md b/rust/op-rbuilder/docs/eth_sendBundle.md new file mode 100644 index 00000000000..7dfa4daa111 --- /dev/null +++ b/rust/op-rbuilder/docs/eth_sendBundle.md @@ -0,0 +1,159 @@ +# eth_sendBundle API Reference + +## Overview + +The `eth_sendBundle` method is a JSON-RPC endpoint that allows searchers to submit transactions with advanced execution control. Unlike regular transaction submission via `eth_sendTransaction`, bundles allow for the following features: +- **Execution Timing**: Specify exact block ranges, flashblock ranges, or timestamps when transactions should execute +- **Revert Protection**: Reverting transactions don't land on-chain so they don't cost any gas + +## Prerequisites + +The `eth_sendBundle` endpoint is only available when revert protection is enabled with the `--builder.enable-revert-protection` flag. + +## Bundle Structure (JSON-RPC params) + +```json +{ + "txs": ["0x..."], // Array of raw transaction bytes + "revertingTxHashes": ["0x..."], // Optional: transactions allowed to revert + "minBlockNumber": "0x1", // Optional: minimum block number + "maxBlockNumber": "0xa", // Optional: maximum block number + "minFlashblockNumber": "0x64", // Optional: minimum flashblock number + "maxFlashblockNumber": "0x68", // Optional: maximum flashblock number + "minTimestamp": 1640995200, // Optional: minimum timestamp (Unix epoch) + "maxTimestamp": 1640995800 // Optional: maximum timestamp (Unix epoch) +} +``` + +### Parameters + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `txs` | `string[]` | ✅ | Array of RLP-encoded transaction data (exactly one transaction) | +| `revertingTxHashes` | `string[]` | ❌ | Transaction hashes allowed to revert without failing the bundle | +| `minBlockNumber` | `number` | ❌ | Earliest block number for execution | +| `maxBlockNumber` | `number` | ❌ | Latest block number for execution | +| `minFlashblockNumber` | `number` | ❌ | Earliest flashblock iteration for execution | +| `maxFlashblockNumber` | `number` | ❌ | Latest flashblock iteration for execution | +| `minTimestamp` | `number` | ❌ | Earliest timestamp for execution (Unix epoch seconds) | +| `maxTimestamp` | `number` | ❌ | Latest timestamp for execution (Unix epoch seconds) | + +## Response + +```json +{ + "bundleHash": "0x..." // Transaction hash of the submitted transaction +} +``` + +## Validation Rules + +### Block Number Validation + +1. **Range Validity**: If both `minBlockNumber` and `maxBlockNumber` are specified, min ≤ max +2. **Past Block Protection**: `maxBlockNumber` must be greater than the current block number +3. **Range Limits**: Block ranges cannot exceed 10 blocks (`MAX_BLOCK_RANGE_BLOCKS`) +4. **Default Maximum**: If no `maxBlockNumber` is specified, defaults to `current_block + 10` + +### Flashblock Number Validation + +If both `minFlashblockNumber` and `maxFlashblockNumber` are specified, min ≤ max. + +### Block Number + Flashblock Number interaction + +When both block number and flashblock number ranges are specified, they act independently of each other. For example, if the builder receives a bundle request with parameters like +``` +"minBlockNumber": 100, +"maxBlockNumber": 105, +"minFlashblockNumber": 1, +"maxFlashblockNumber": 3, +``` +Then the builder will only execute the bundle if the current block number is between 100 and 105 AND the current flashblock number is between 1 and 3. + +### Transaction Constraints + +1. **Single Transaction**: Bundles must contain exactly one transaction +2. **Valid Format**: Transaction must be properly RLP-encoded + +### Timestamp Constraints (⚠️ Caution) + +Timestamp-based constraints depend on the builder node's clock and may not be perfectly synchronized with network time. Block number or flashblock number constraints are preferred. + +## Error Responses + +| Error | Description | Solution | +|-------|-------------|----------| +| `bundle must contain exactly one transaction` | Bundle has 0 or >1 transactions | Include exactly one transaction | +| `block_number_max (X) is a past block` | Max block is ≤ current block | Use future block number | +| `block_number_max (X) is too high` | Block range exceeds 10 blocks | Reduce block range | +| `flashblock_number_min (X) is greater than flashblock_number_max (Y)` | Invalid flashblock range | Ensure min ≤ max | +| `method not found` | Revert protection disabled | Enable revert protection | + +## Usage Examples + +### Basic Bundle Submission + +```bash +curl -X POST http://localhost:8545 \ + -H "Content-Type: application/json" \ + -d '{ + "method": "eth_sendBundle", + "params": [{ + "txs": ["0x02f86c0182..."], // Raw transaction bytes + "maxBlockNumber": 10 // Execute within next 10 blocks + }], + "id": 1, + "jsonrpc": "2.0" + }' +``` + +### Bundle with Revert Protection + +```bash +curl -X POST http://localhost:8545 \ + -H "Content-Type: application/json" \ + -d '{ + "method": "eth_sendBundle", + "params": [{ + "txs": ["0x02f86c0182..."], + "revertingTxHashes": ["0xabc123..."], // Allow this tx to revert + "minBlockNumber": 5, + "maxBlockNumber": 10 + }], + "id": 1, + "jsonrpc": "2.0" + }' +``` + +### Flashblock Bundle + +```bash +curl -X POST http://localhost:8545 \ + -H "Content-Type: application/json" \ + -d '{ + "method": "eth_sendBundle", + "params": [{ + "txs": ["0x02f86c0182..."], + "minFlashblockNumber": 1, // Flashblock 1 + "maxFlashblockNumber": 4 // Flashblock 4 + }], + "id": 1, + "jsonrpc": "2.0" + }' +``` + +## Monitoring and Debugging + +### Check Bundle Status +Use `eth_getTransactionReceipt` to check if your bundle was included: + +```bash +curl -X POST http://localhost:8545 \ + -H "Content-Type: application/json" \ + -d '{ + "method": "eth_getTransactionReceipt", + "params": ["0x..."], // Bundle hash (is also tx hash) from response + "id": 1, + "jsonrpc": "2.0" + }' +``` \ No newline at end of file diff --git a/rust/op-rbuilder/docs/flashblocks.md b/rust/op-rbuilder/docs/flashblocks.md new file mode 100644 index 00000000000..af0980d86d2 --- /dev/null +++ b/rust/op-rbuilder/docs/flashblocks.md @@ -0,0 +1,226 @@ +# Flashblock flow details + +**FCU w/ attr - engine_forkchoiceUpdated with payloadAttributes**. This starts block building. + +**FCU w/o attr - engine_forkchoiceUpdated without payloadAttributes**. This progresses the unsafe/safe/finalized head. + +**newPayload - engine_newPayload**. This provides the execution client with new canonical block. + +**getPayload - engine_getPayload**. Collects block building results. In flashblocks, this is only used to terminate block building as flashblocks are assembled on rollup-boost side. + +Flashblocks uses similar to canonical mechanism of block building, with the exception that it current implementation does not rely on getPayload. + +It starts block building process on FCU w/ attr and try to time flashblocks, so all of them would be delivered before op-node issue getPayload to rollup-boost. + +## build_payload Function Overview +This is the function that produces the block and contains all logic responsible for building the block and producing flashblocks +```pseudocode +function build_payload(build_arguments, best_payload_cell): + // 1. Set up the state for block building + apply_pre_execution_changes() + // === FALLBACK BLOCK CREATION === + // Produces deposits only block + execute_sequencer_transactions() + // 2. Add builder transaction if we build block with txpool enabled (specified in FCU) + if transaction_pool_enabled: + add_builder_tx_after_deposits() + + // 3. Build and publish initial fallback block + fallback_payload = build_block(state, context, execution_info) + best_payload_cell.set(fallback_payload) + // 4. Store the block in trie cache. This cache would be looked up on the next FCU w/ attr + send_payload_to_engine(fallback_payload) + // 5. Send fb via websocket + publish_flashblock(fallback_payload) + + // 6. Return early if transaction pool is disabled + if no_transaction_pool: + record_metrics_and_return() + + // === FLASHBLOCKS TIMING CALCULATION === + // 7. Calculate dynamic flashblock timing and number of flashblocks we are able to produce based on the formula + // first_offset = (block timestamp - current timestamp) % flashblock_time + // flashblocks_count = (block timestamp - current timestamp) // flashblock_time + // Ex: If we started building flashblocks at 400ms (because of network latency) that means we have only + // 600ms to build the block (assuming block time is 1s) + // If we have 250ms flashblock time, we could build only 3 flashblocks, the first one must be sent after 100ms and others + // after 250ms each. + (flashblocks_count, first_offset) = calculate_flashblocks_timing(timestamp) + + // 8. Calculate resource limits per flashblock + gas_per_flashblock = total_gas_limit / flashblocks_count + da_per_flashblock = total_da_limit / flashblocks_count + + // === FLASHBLOCKS BUILDING LOOP === + + // 9. Setup timing coordination. This timer task produces a cancel token that would be cancelled when it's time to + // send the flashblock. + setup_timer_task(first_offset, flashblock_interval) + // 10. We use a custom wrapper around reth BestTransaction iterator. Our wrapper tracks committed transactions and skips + // them when we build a new flashblock. + create_best_transactions_iterator() + + // 10. Main flashblock building loop + loop: + // Wait for next flashblock timing signal + flashblock_cancel_token = wait_for_next_flashblock_signal() + + if flashblock_cancel_token is None: + break // All flashblocks completed or parent cancelled + + if reached_target_flashblock_count(): + continue // Skip if we've built enough flashblocks + + // === SINGLE FLASHBLOCK BUILDING === + + // 11. Provide a fresh BestTransaction iterator that would contain all transactions in mempool that could be + // included in the flashblock + refresh_transaction_iterator_for_current_flashblock() + + // 12. Execute transactions within limits + execute_best_transactions(): + while has_transactions() and within_limits(): + transaction = get_next_best_transaction() + if can_execute(transaction, gas_limit, da_limit): + execute_transaction(transaction) + update_cumulative_usage() + else: + skip_transaction() + // If we cannot fit a transaction, we will remove this tx and its ancestors from this round of + // flashblock building + mark_invalid() + + // 13. Add builder transaction to last flashblock + if is_last_flashblock(): + add_builder_tx_to_block() + + // 14. Build block and create flashblock payload + (new_payload, flashblock_payload) = build_block(state, context, info) + + // 15. Wait for timing coordination + wait_for_flashblock_timing_completion() + + if parent_build_cancelled(): + break // If the main cancel token is cancelled we must start a new block building job and all previous + // results are obsolete. + + // 16. Publish flashblock and update state + publish_flashblock(flashblock_payload) + best_payload_cell.set(new_payload) + send_payload_to_engine(new_payload) + + // 17. Update limits for next flashblock + increment_flashblock_index() + increase_gas_and_da_limits_for_next_flashblock() + mark_committed_transactions() + + // === CLEANUP PHASE === + + // 18. Record final metrics and cleanup + record_flashblocks_metrics() + return success +``` + +### Timing Coordination +- **Timer Task**: Spawned async task that sends timing signals at regular intervals +- **First Offset**: Timeout after which we must send the flashblock. We calculate it so we send flashblocks at `current time % flashblock_time == 0` +- **Cancellation Tokens**: When we cancel the flashblock token we stop this flashblock building process and publish it. + +### Caveats +If the system clock drifts too much we will print an error message and fallback to producing flashblocks with regular flashblock_time intervals, without adjusting anything. + +## Block building flow +These are sequence diagrams for flashblock building flow, rollup-boost, op-node and fallback sequencer interaction. + +There are 2 separate cases: for regular blocks and for blocks with the no-txpool flag. When building no-txpool blocks we are using only the fallback EL to construct the block. +This is done to rely on the canonical implementation for such blocks as they are not compute intensive. + +### Regular Block Building Flow (Mermaid) + +```mermaid +sequenceDiagram + participant OR as op-rbuilder + participant OG as op-geth + participant RB as rollup-boost + participant ON as op-node + participant WP as websocket-proxy + + rect rgba(230, 240, 250, 0.2) + note over ON, WP: Block building process + + ON->>RB: FCU w/ attr + RB->>OG: FCU w/ attr + RB->>OR: FCU w/ attr + OG->>RB: VALID/SYNCING + OR->>RB: VALID/SYNCING + RB->>RB: Mark that op-rbuilder is building this block + RB->>ON: op-geth VALID/SYNCING + OR->>OR: Start block building + OG->>OG: Build regular block + OR->>RB: Base flashblock + OR->>RB: FB 1 + RB->>WP: Propagate FB 1 + OR->>RB: ... + RB->>WP: ... + OR->>RB: FB N + RB->>WP: Propagate FB N + ON->>RB: getPayload + + alt There is in-mem flashblock + RB->>RB: Build payload from locally stored FBs + RB->>ON: Payload from local FBs + RB-->>OR: getPayload to stop building, without waiting for a response + else There is no in-mem flashblocks, but builder is marked as building payload + RB->>OG: getPayload + RB->>OR: getPayload + RB->>ON: op-rbuilder payload, if present, op-geth otherwise. Selection policy may be used if present + else There is no in-mem flashblocks, but builder is not marked as building payload + RB->>OG: getPayload + RB->>ON: op-geth payload + end + end + + rect rgba(240, 250, 240, 0.2) + note over ON, OR: Chain progression + + ON->>RB: FCU w/o attr + RB->>OG: FCU w/o attr + RB->>OR: FCU w/o attr + ON->>RB: newPayload + RB->>OG: newPayload + RB->>OR: newPayload + end +``` + +### No-Txpool Block Building Flow (Mermaid) + +```mermaid +sequenceDiagram + participant OR as op-rbuilder + participant OG as op-geth + participant RB as rollup-boost + participant ON as op-node + + rect rgba(255, 245, 230, 0.2) + note over ON, OR: Block building + + ON->>RB: FCU w/ attr + RB->>OG: FCU w/ attr + OG->>RB: VALID/SYNCING + RB->>ON: VALID/SYNCING + OG->>OG: Build block with deposit transaction only + ON->>RB: getPayload + RB->>OG: getPayload + OG->>RB: Payload + RB->>ON: Payload + end + + rect rgba(240, 250, 240, 0.2) + note over ON, OR: Chain progression + + ON->>RB: FCU w/o attr + RB->>OG: FCU w/o attr + ON->>RB: newPayload + RB->>OG: newPayload + end +``` \ No newline at end of file diff --git a/rust/op-rbuilder/justfile b/rust/op-rbuilder/justfile new file mode 100644 index 00000000000..07ae8507eb4 --- /dev/null +++ b/rust/op-rbuilder/justfile @@ -0,0 +1,32 @@ +# Run the linters +lint: + cargo +nightly fmt -- --check + cargo +nightly clippy --all-features -- -D warnings + +# Build and run op-rbuilder in playground mode for testing +run-playground: + cargo build --bin op-rbuilder -p op-rbuilder + ./target/debug/op-rbuilder node --builder.playground + +# Run the complete test suite (genesis generation, build, and tests) +run-tests: + just generate-test-genesis + just build-op-rbuilder + just run-tests-op-rbuilder + +# Download `op-reth` binary +download-op-reth: + ./scripts/ci/download-op-reth.sh + +# Generate a genesis file (for tests) +generate-test-genesis: + cargo run -p op-rbuilder --features="testing" --bin tester -- genesis --output genesis.json + + +# Build the op-rbuilder binary +build-op-rbuilder: + cargo build -p op-rbuilder --bin op-rbuilder + +# Run the integration tests +run-tests-op-rbuilder: + PATH=$PATH:$(pwd) cargo test --package op-rbuilder --lib diff --git a/rust/op-rbuilder/rust-toolchain.toml b/rust/op-rbuilder/rust-toolchain.toml new file mode 100644 index 00000000000..4559c5fcee9 --- /dev/null +++ b/rust/op-rbuilder/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +# Bumped from 1.88.0 to match the parent monorepo's rust/rust-toolchain.toml +# pin (1.94). Required by the alloy / op-revm / reth-trie bumps from commit +# 340b6cd onwards. +channel = "1.94.0" +components = ["rustfmt", "clippy"] diff --git a/rust/op-rbuilder/rustfmt.toml b/rust/op-rbuilder/rustfmt.toml new file mode 100644 index 00000000000..648e8368fdb --- /dev/null +++ b/rust/op-rbuilder/rustfmt.toml @@ -0,0 +1,2 @@ +reorder_imports = true +imports_granularity = "Crate" diff --git a/rust/op-rbuilder/scripts/ci/benchmark-in-ci.sh b/rust/op-rbuilder/scripts/ci/benchmark-in-ci.sh new file mode 100755 index 00000000000..27cc9f48d08 --- /dev/null +++ b/rust/op-rbuilder/scripts/ci/benchmark-in-ci.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# +# +# This script runs the benchmarks and prepares a customized report. +# +# Primary use-case is running in (GitHub) CI, but can also run locally. +# +# Benchmarks can be run in two modes: +# 1. Basic mode: Run the benchmarks for the current commit only (used if HEAD_SHA == BASE_SHA) +# 2. Comparison mode: Run the benchmarks for the current commit and a target commit, and compare them (to see the specific improvements) +# +# Important variables are sourced from ./env-vars.sh, which combines local git information with Github specific variables and methods. +# +# Useful environment variables (to possibly override): +# - X_HEAD_SHA: custom head sha (for dev purposes) +# - X_BASE_SHA: custom base sha (which is run first, and the head benchmark compared against) +# - SKIP_BENCH: skip running `cargo bench` (just generates the HTML based on the previous benchmark). +# +set -e + +script_dir=$( dirname -- "$0"; ) + +echo "Running benchmarks ..." + +# Gather env vars +# https://docs.github.com/en/actions/learn-github-actions/variables +source "${script_dir}/env-vars.sh" + +echo "DATE_UTC: ${DATE}" +echo "HEAD_SHA: ${HEAD_SHA}" +echo "HEAD_BRANCH: ${HEAD_BRANCH}" +echo "BASE_SHA: ${BASE_SHA}" +echo "BASE_REF: ${BASE_REF}" +echo "GITHUB_SHA: ${GITHUB_SHA}" +echo "GITHUB_REF_NAME: ${GITHUB_REF_NAME}" +echo "GITHUB_REF_TYPE: ${GITHUB_REF_TYPE}" +echo "GITHUB_HEAD_REF: ${GITHUB_HEAD_REF}" +echo "GITHUB_ACTOR: ${GITHUB_ACTOR}" +echo "PR_NUMBER: ${PR_NUMBER}" +echo "RUNNER_ARCH: ${RUNNER_ARCH}" +echo "RUNNER_OS: ${RUNNER_OS}" + +# +# RUN THE BENCHMARKS +# +cd $script_dir +cd ../.. + +function run_benchmark() { + if [ "$HEAD_SHA" == "$BASE_SHA" ]; then + # Benchmark only current commit, no comparison + echo "Running cargo bench ..." + cargo bench --workspace --features optimism + else + # Benchmark target commit first, and then benchmark current commit against that baseline + echo "Benchmarking ${HEAD_SHA_SHORT} against the target ${BASE_SHA_SHORT} ..." + + # Switch to target commit and run benchmarks + echo "Switching to $BASE_SHA_SHORT and starting benchmarks ..." + git checkout $BASE_SHA + cargo bench --workspace --features optimism + + # Switch back to current commit and run benchmarks again + echo "Switching back to $HEAD_SHA_SHORT and running benchmarks ..." + # Reset to ensure any changes (e.g. to Cargo.lock) are discarded before attempting to checkout. + git reset --hard + git checkout $HEAD_SHA + cargo bench --workspace --features optimism + fi +} + +# Run benchmarks now (if env var SKIP_BENCH is not set) +if [ -n "$SKIP_BENCH" ]; then + echo "SKIP_BENCH is set, skipping cargo bench." +else + run_benchmark +fi + +# +# Build the summary +# +# Grab the changes as markdown (used in templates for PR and job-summary comment) +export BENCH_CHANGES_MD=$( ./scripts/ci/criterion-get-changes.py target/criterion --output-format md ) +export BENCH_CHANGES_MD_ONLYSIGNIFICANT=$( ./scripts/ci/criterion-get-changes.py target/criterion --output-format md --only-significant ) +if [ -z "$BENCH_CHANGES_MD_ONLYSIGNIFICANT" ]; then + export BENCH_CHANGES_MD_ONLYSIGNIFICANT="None" +fi + +# Prettify criterion report +mkdir -p target/benchmark-in-ci +./scripts/ci/criterion-prettify-report.sh target/criterion target/benchmark-in-ci/benchmark-report +echo "Saved report: target/benchmark-in-ci/benchmark-report/report/index.html" + +# Create summary markdown +fn="target/benchmark-in-ci/benchmark-report/benchmark-summary.md" +envsubst < scripts/ci/templates/benchmark-summary.md > $fn +echo "Wrote summary: $fn" + +# Create summary pr comment +fn="target/benchmark-in-ci/benchmark-report/benchmark-pr-comment.md" +envsubst < scripts/ci/templates/benchmark-pr-comment.md > $fn +echo "Wrote PR comment: $fn" diff --git a/rust/op-rbuilder/scripts/ci/criterion-get-changes.py b/rust/op-rbuilder/scripts/ci/criterion-get-changes.py new file mode 100755 index 00000000000..f2f81559061 --- /dev/null +++ b/rust/op-rbuilder/scripts/ci/criterion-get-changes.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# +# This script extracts the specific changes from a Criterion-generated comparison report. +# + +# Require one argument +import sys +import os +import json +import argparse + +from urllib.parse import quote + +parser = argparse.ArgumentParser(description="Extract Criterion.rs benchmark changes.") +parser.add_argument( + "report_dir", type=str, help="The directory containing the Criterion.rs report." +) + +# add a --output-format argument to the parser +parser.add_argument( + "--output-format", + type=str, + help="The output format.", + default="txt", + choices=["md", "txt", "html"], +) + +# add a --only-significant argument to the parser +parser.add_argument( + "--only-significant", + action="store_true", + help="Only show significant changes.", +) + +args = parser.parse_args() +# print(args) + +report_dir = args.report_dir +output_format = args.output_format +only_significant = args.only_significant + +# Make sure the directory exists +if not os.path.isdir(report_dir): + print(f"Error: directory '{report_dir}' does not exist.") + sys.exit(1) + + +# Find and parse all benchmark changes (based on "change/estimates.json" files) +changes = [] +for root, dirs, files in os.walk(report_dir): + for file in files: + if root.endswith("/change") and file == "estimates.json": + change_estimates = json.load(open(os.path.join(root, file), "r")) + benchmark_info = json.load( + open(os.path.join(root, "../new/benchmark.json"), "r") + ) + changes.append( + { + "dir": root, + "file": file, + "estimates": change_estimates, + "benchmark": benchmark_info, + } + ) + +# Changes are considered non-noise if above a 2% default threshold. See also +# the Criterion docs: https://bheisler.github.io/criterion.rs/book/user_guide/command_line_output.html#change +# and the Criterion code: https://github.com/bheisler/criterion.rs/blob/master/src/report.rs#L596 +mean_p_noise_threshold = 0.02 + + +def has_improved(estimates_data): + """ + Compare the mean confidence to the noise threshold. See also Criterion reference code: + https://github.com/bheisler/criterion.rs/blob/master/src/report.rs#L779 + """ + confidence_interval = estimates_data["mean"]["confidence_interval"] + lower_bound = confidence_interval["lower_bound"] + upper_bound = confidence_interval["upper_bound"] + if lower_bound < -mean_p_noise_threshold and upper_bound < -mean_p_noise_threshold: + return True + elif lower_bound > mean_p_noise_threshold and upper_bound > mean_p_noise_threshold: + return False + return None + + +def estimate_to_str(estimates_data): + """ + Summarize whether an improvement or degradation has occurred. + Returns: (message, is_significant) + """ + mean_p = estimates_data["mean"]["point_estimate"] + if abs(mean_p) < mean_p_noise_threshold: + return "No change in performance detected.", False + has_improved_str = has_improved(estimates_data) + if has_improved_str is None: + return "Change within noise threshold.", False + elif has_improved_str: + return f"Performance has improved.", True + else: + return f"Performance has degraded.", True + + +# Prepare changes for printing +change_data = [] +for change in changes: + full_id = change["benchmark"]["full_id"] + url = f"{os.getenv('PUBLIC_URL_REPORT')}/{quote(full_id)}/report/index.html" + mean_p = change["estimates"]["mean"]["point_estimate"] + mean_p_fmt = f"{100 * mean_p:.2f}" + msg, is_significant = estimate_to_str(change["estimates"]) + if only_significant and not is_significant: + continue + change_data.append((full_id, url, mean_p_fmt, msg, is_significant)) + +if not change_data: + exit(0) + +# Print the changes +if output_format == "md": + print("| Benchmark | Mean | Status |") + print("| --- | --- | --- |") + for full_id, url, mean_fmt, msg, is_significant in change_data: + if is_significant: + msg = f"**{msg}**" + print(f"| [{full_id}]({url}) | {mean_fmt}% | {msg} |") +elif output_format == "html": + print( + "BenchmarkMeanStatus" + ) + for full_id, url, mean_fmt, msg, is_significant in change_data: + if is_significant: + msg = f"{msg}" + print( + f"{full_id}{mean_fmt}%{msg}" + ) + print("") +else: + print("Benchmark \t Mean \t Status \t Significant") + for full_id, url, mean_fmt, msg, is_significant in change_data: + print(f"{full_id} \t {mean_fmt}% \t {msg} \t {is_significant}") diff --git a/rust/op-rbuilder/scripts/ci/criterion-prettify-report.sh b/rust/op-rbuilder/scripts/ci/criterion-prettify-report.sh new file mode 100755 index 00000000000..aea9dbc7938 --- /dev/null +++ b/rust/op-rbuilder/scripts/ci/criterion-prettify-report.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Build a customized benchmark report directory (with HTML + images), using the original criterion report as input. +# +set -e + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +script_dir=$( dirname -- "$0"; ) +criterion_dir=$( realpath -s $1 ) +output_dir=$( realpath -s $2 ) + +source "${script_dir}/env-vars.sh" + +echo "Input directory: $criterion_dir" +echo "Output directory: $output_dir" + +# Error if input_dir doesn't exist +if [ ! -d "$criterion_dir" ]; then + echo "Error: input directory '$criterion_dir' does not exist." + exit 1 +fi + +cmd="${script_dir}/criterion-get-changes.py $criterion_dir --output-format html --only-significant" +export BENCH_CHANGES_HTML=$( $cmd ) + +# Clean up the output directory and create a copy of the input directory +rm -rf "$output_dir" +cp -r "$criterion_dir" "$output_dir" + +# Iterate over all html files in the input directory, recursively +find $output_dir -type f -name "*.html" | while read file; do + echo "- Updating $file ..." + python3 "${script_dir}/criterion-update-html.py" "$file" "$file" +done diff --git a/rust/op-rbuilder/scripts/ci/criterion-update-html.py b/rust/op-rbuilder/scripts/ci/criterion-update-html.py new file mode 100755 index 00000000000..f50298957b8 --- /dev/null +++ b/rust/op-rbuilder/scripts/ci/criterion-update-html.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# +# Customize the Criterion.rs report HTML files (adding style, links and other information) +# +# Usage: +# $ python3 criterion-update-html.py [] +# +# Criterion HTML templates: https://github.com/bheisler/criterion.rs/tree/master/src/html +# + +import sys +import re +import os +from datetime import datetime, timezone + +if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} []") + sys.exit(1) + +# Prepare variables +input_file = sys.argv[1] +output_file = sys.argv[2] if len(sys.argv) > 2 else None +script_dir = os.path.dirname(os.path.realpath(__file__)) + +# Prepare paths +fn_styles = f"{script_dir}/templates/report-styles.css" +fn_template_head = f"{script_dir}/templates/report-head.html" +fn_template_footer = f"{script_dir}/templates/report-footer.html" +fn_template_index = f"{script_dir}/templates/report-index.html" +fn_template_report = f"{script_dir}/templates/report-criterion-benchmark.html" +fn_partial_index_changes = f"{script_dir}/templates/partials/index-changes.html" + +# Prepare templates +template_head = open(fn_template_head, "r").read() +template_footer = open(fn_template_footer, "r").read() +template_index = open(fn_template_index, "r").read() +template_report = open(fn_template_report, "r").read() +partial_index_changes = open(fn_partial_index_changes, "r").read() + +# Setup template variables +PR_NUMBER = os.getenv("PR_NUMBER") +PUBLIC_URL_REPORT = os.getenv("PUBLIC_URL_REPORT") +CHANGES_TABLE = os.getenv("BENCH_CHANGES_HTML") + +# Template vars need to be tuple to ensure ordering +template_vars = [ + ("__TITLE__", "rbuilder benchmarks for __HEAD_SHA_SHORT__"), + ("__STYLE__", ""), + ("__DATE__", os.getenv("DATE") or datetime.now(timezone.utc)), + ("__HEAD_SHA__", os.getenv("HEAD_SHA")), + ("__HEAD_SHA_SHORT__", os.getenv("HEAD_SHA_SHORT")), + ("__HEAD_BRANCH__", os.getenv("HEAD_BRANCH")), + ("__BASE_SHA__", os.getenv("BASE_SHA")), + ("__BASE_REF__", os.getenv("BASE_REF")), + ("__GITHUB_SHA__", os.getenv("GITHUB_SHA")), + ("__GITHUB_REF_NAME__", os.getenv("GITHUB_REF_NAME")), + ("__GITHUB_REF_TYPE__", os.getenv("GITHUB_REF_TYPE")), + ("__GITHUB_HEAD_REF__", os.getenv("GITHUB_HEAD_REF")), + ("__GITHUB_ACTOR__", os.getenv("GITHUB_ACTOR")), + ("__RUNNER_ARCH__", os.getenv("RUNNER_ARCH")), + ("__RUNNER_OS__", os.getenv("RUNNER_OS")), + ("__PR_NUMBER__", PR_NUMBER), + ("__REPO_URL__", os.getenv("REPO_URL")), + ("__STATIC_URL__", os.getenv("PUBLIC_URL_STATIC")), +] + + +def remove_from_str(s, pattern): + return re.sub(pattern, "", s, flags=re.DOTALL) + + +def render_template(template_html_body, content, template_vars_extra=[]): + html = template_head + template_html_body + + core_content = [ + ("__FOOTER__", template_footer), + ("__CONTENT__", content), + ] + + # replace template variables + for key, value in core_content + template_vars + template_vars_extra: + html = re.sub(key, str(value), html) + + # some removals (i.e. if no PR number) + if not PR_NUMBER: + html = remove_from_str(html, r""".*?""") + + return html + + +def update_index(data): + url = f"{PUBLIC_URL_REPORT}/report/index.html" + template_vars = [("__URL__", url)] + + # extract original content + ul_element = re.search(r"
    (.*)
", data, re.DOTALL).group(1) + content = "
    " + ul_element + "
" + + changes = "" + if CHANGES_TABLE: + changes = re.sub(r"__CHANGES_TABLE__", CHANGES_TABLE, partial_index_changes) + template_vars.append(("__CHANGES__", changes)) + + # Render template + updated_html = render_template(template_index, content, template_vars) + return updated_html + + +def update_subpage(data): + # extract the body + content = re.search(r"(.*)", data, re.DOTALL).group(1) + + # get the benchmark name + benchmark_name = re.search(r"

(.*?)

", data, re.DOTALL).group(1) + + # remove