diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml new file mode 100644 index 00000000..97c47c2e --- /dev/null +++ b/.github/workflows/builder.yml @@ -0,0 +1,141 @@ +name: Builder CI + +on: + push: + pull_request: + merge_group: + +env: + CARGO_TERM_COLOR: always + # Zero out OTEL env vars to prevent local environment interference in tests + OTEL_EXPORTER_OTLP_ENDPOINT: "" + OTEL_EXPORTER_OTLP_HEADERS: "" + OTEL_SDK_DISABLED: "true" + +permissions: + contents: read + +jobs: + build-builder: + name: Build Builder Crates + runs-on: ubuntu-24.04 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable + with: + components: clippy + + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + cache-on-failure: true + add-rust-environment-hash-key: "false" + key: builder-stable-${{ hashFiles('Cargo.lock') }} + + - name: Install just + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 + + - name: Build builder crates + run: just build-builder + + test-builder: + name: Test Builder Crates + runs-on: ubuntu-24.04 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable + + - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + cache-on-failure: true + add-rust-environment-hash-key: "false" + key: builder-stable-${{ hashFiles('Cargo.lock') }} + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@50d5a8956f2e319df19e6b57539d7e2acb9f8c1e # v1.5.0 + with: + version: stable + + - name: Install just + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 + + - name: Run builder tests + run: just test-builder + env: + TESTS_TEMP_DIR: ${{ github.workspace }} + + clippy-builder: + name: Clippy Builder Crates + runs-on: ubuntu-24.04 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + + - uses: dtolnay/rust-toolchain@0c3131df9e5407c0c36352032d04af846dbe0fb7 # nightly + with: + components: clippy + + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + cache-on-failure: true + add-rust-environment-hash-key: "false" + key: builder-nightly-${{ hashFiles('Cargo.lock') }} + + - name: Install just + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 + + - name: Run clippy on builder crates + run: just check-clippy-builder diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96180292..8a3b224d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,19 @@ jobs: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: @@ -44,6 +57,19 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 @@ -96,6 +122,19 @@ jobs: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable with: components: clippy @@ -142,6 +181,19 @@ jobs: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: dtolnay/rust-toolchain@0c3131df9e5407c0c36352032d04af846dbe0fb7 # nightly - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 diff --git a/Cargo.lock b/Cargo.lock index 1b53991d..094985e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,13 +95,36 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c97aa0031055a663e364890f2bc15879d6ec38dae9fbeece68fcc82d9cdb81" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-trie", +] + [[package]] name = "alloy-chains" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd208e8a87fbc2ca1a3822dd1ea03b0a7a4a841e6fa70db2c236dd30ae2e7018" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "num_enum", "serde", @@ -115,7 +138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e30ab0d3e3c32976f67fc1a96179989e45a69594af42003a6663332f9b0bb9d" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", "alloy-trie", @@ -144,7 +167,7 @@ checksum = "c20736b1f9d927d875d8777ef0c2250d4c57ea828529a9dbfa2c628db57b911e" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", "arbitrary", @@ -159,13 +182,13 @@ checksum = "008aba161fce2a0d94956ae09d7d7a09f8fbdf18acbef921809ef126d6cdaf97" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-json-abi", + "alloy-json-abi 1.5.2", "alloy-network", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-types-eth", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "alloy-transport", "futures", "futures-util", @@ -173,16 +196,29 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "alloy-core" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4087016b0896051dd3d03e0bedda2f4d4d1689af8addc8450288c63a9e5f68" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi 1.5.2", + "alloy-primitives 1.5.2", + "alloy-rlp", + "alloy-sol-types 1.5.2", +] + [[package]] name = "alloy-dyn-abi" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "369f5707b958927176265e8a58627fc6195e5dfa5c55689396e68b241b3a72e6" dependencies = [ - "alloy-json-abi", - "alloy-primitives", - "alloy-sol-type-parser", - "alloy-sol-types", + "alloy-json-abi 1.5.2", + "alloy-primitives 1.5.2", + "alloy-sol-type-parser 1.5.2", + "alloy-sol-types 1.5.2", "derive_more", "itoa", "serde", @@ -196,7 +232,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "crc", @@ -211,7 +247,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "borsh", @@ -225,7 +261,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "borsh", @@ -242,7 +278,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6adac476434bf024279164dcdca299309f0c7d1e3557024eb7a83f8d9d01c6b5" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "borsh", "serde", @@ -257,7 +293,7 @@ dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", "arbitrary", @@ -284,10 +320,10 @@ dependencies = [ "alloy-eips", "alloy-hardforks", "alloy-op-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "auto_impl", "derive_more", "op-alloy-consensus", @@ -304,7 +340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a838301c4e2546c96db1848f18ffe9f722f2fccd9715b83d4bf269a2cf00b5a1" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-serde", "alloy-trie", "borsh", @@ -320,20 +356,32 @@ checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" dependencies = [ "alloy-chains", "alloy-eip2124", - "alloy-primitives", + "alloy-primitives 1.5.2", "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.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e3cf01219c966f95a460c95f1d4c30e12f6c18150c21a30b768af2a2a29142" dependencies = [ - "alloy-primitives", - "alloy-sol-type-parser", + "alloy-primitives 1.5.2", + "alloy-sol-type-parser 1.5.2", "serde", "serde_json", ] @@ -344,8 +392,8 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60f045b69b5e80b8944b25afe74ae6b974f3044d84b4a7a113da04745b2524cc" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-primitives 1.5.2", + "alloy-sol-types 1.5.2", "http", "serde", "serde_json", @@ -364,12 +412,12 @@ dependencies = [ "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-any", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "async-trait", "auto_impl", "derive_more", @@ -387,7 +435,7 @@ checksum = "5e9762ac5cca67b0f6ab614f7f8314942eead1c8eeef61511ea43a6ff048dbe0" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-serde", "serde", ] @@ -402,7 +450,7 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-op-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "op-alloy-consensus", "op-revm", @@ -418,10 +466,37 @@ checksum = "6472c610150c4c4c15be9e1b964c9b78068f933bda25fb9cdf09b9ac2bb66f36" dependencies = [ "alloy-chains", "alloy-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "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.13.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.8.5", + "ruint", + "rustc-hash 2.1.1", + "serde", + "sha3", + "tiny-keccak", +] + [[package]] name = "alloy-primitives" version = "1.5.2" @@ -447,7 +522,7 @@ dependencies = [ "rand 0.9.2", "rapidhash", "ruint", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "sha3", "tiny-keccak", @@ -465,12 +540,14 @@ dependencies = [ "alloy-json-rpc", "alloy-network", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-engine", "alloy-rpc-types-eth", + "alloy-rpc-types-txpool", "alloy-signer", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "alloy-transport", "alloy-transport-http", "alloy-transport-ipc", @@ -502,7 +579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4082778c908aa801a1f9fdc85d758812842ab4b2aaba58e9dbe7626d708ab7e1" dependencies = [ "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-transport", "auto_impl", "bimap", @@ -512,7 +589,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", "wasmtimer", ] @@ -546,7 +623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26dd083153d2cb73cce1516f5a3f9c3af74764a2761d901581a355777468bd8f" dependencies = [ "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-pubsub", "alloy-transport", "alloy-transport-http", @@ -559,7 +636,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", "url", "wasmtimer", @@ -571,7 +648,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c998214325cfee1fbe61e5abaed3a435f4ca746ac7399b46feb57c364552452" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-serde", @@ -585,7 +662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "730a38742dc0753f25b8ce7330c2fa88d79f165c5fc2f19f3d35291739c42e83" dependencies = [ "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "serde", "serde_json", ] @@ -596,7 +673,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2b03d65fcf579fbf17d3aac32271f99e2b562be04097436cd6e766b3e06613b" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -620,7 +697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b6654644613f33fd2e6f333f4ce8ad0a26f036c0513699d7bc168bba18d412d" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "derive_more", "ethereum_ssz", @@ -639,7 +716,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467025b916f32645f322a085d0017f2996d0200ac89dd82a4fc2bf0f17b9afa3" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "derive_more", "serde", "serde_with", @@ -653,7 +730,7 @@ checksum = "933aaaace9faa6d7efda89472add89a8bfd15270318c47a2be8bb76192c951e2" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", "derive_more", @@ -675,10 +752,10 @@ dependencies = [ "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "arbitrary", "itertools 0.14.0", "serde", @@ -695,7 +772,7 @@ checksum = "1826454c2890af6d642bf052909e0162ad7f261d172e56ef2e936d479960699c" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -708,7 +785,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498375e6a13b6edd04422a13d2b1a6187183e5a3aa14c5907b4c566551248bab" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -722,7 +799,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d9123d321ecd70925646eb2c60b1d9b7a965f860fbd717643e2c20fcf85d48d" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -734,7 +811,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1a0d2d5c64881f3723232eaaf6c2d9f4f88b061c63e87194b2db785ff3aa31f" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "serde", "serde_json", @@ -746,7 +823,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ea4ac9765e5a7582877ca53688e041fe184880fe75f16edf0945b24a319c710" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "async-trait", "auto_impl", "either", @@ -763,7 +840,7 @@ checksum = "3c9d85b9f7105ab5ce7dae7b0da33cd9d977601a48f759e1c82958978dd1a905" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-signer", "async-trait", "coins-bip32", @@ -774,18 +851,50 @@ dependencies = [ "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.114", +] + [[package]] name = "alloy-sol-macro" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09eb18ce0df92b4277291bbaa0ed70545d78b02948df756bbd3d6214bf39a218" dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", + "alloy-sol-macro-expander 1.5.2", + "alloy-sol-macro-input 1.5.2", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[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.13.0", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.114", + "syn-solidity 0.8.26", + "tiny-keccak", ] [[package]] @@ -794,8 +903,8 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95d9fa2daf21f59aa546d549943f10b5cce1ae59986774019fbedae834ffe01b" dependencies = [ - "alloy-json-abi", - "alloy-sol-macro-input", + "alloy-json-abi 1.5.2", + "alloy-sol-macro-input 1.5.2", "const-hex", "heck", "indexmap 2.13.0", @@ -803,17 +912,33 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "syn-solidity", + "syn-solidity 1.5.2", "tiny-keccak", ] +[[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", + "proc-macro2", + "quote", + "syn 2.0.114", + "syn-solidity 0.8.26", +] + [[package]] name = "alloy-sol-macro-input" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9396007fe69c26ee118a19f4dee1f5d1d6be186ea75b3881adf16d87f8444686" dependencies = [ - "alloy-json-abi", + "alloy-json-abi 1.5.2", "const-hex", "dunce", "heck", @@ -822,7 +947,17 @@ dependencies = [ "quote", "serde_json", "syn 2.0.114", - "syn-solidity", + "syn-solidity 1.5.2", +] + +[[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", ] [[package]] @@ -835,15 +970,28 @@ dependencies = [ "winnow", ] +[[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.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09aeea64f09a7483bdcd4193634c7e5cf9fd7775ee767585270cd8ce2d69dc95" dependencies = [ - "alloy-json-abi", - "alloy-primitives", - "alloy-sol-macro", + "alloy-json-abi 1.5.2", + "alloy-primitives 1.5.2", + "alloy-sol-macro 1.5.2", "serde", ] @@ -864,7 +1012,7 @@ dependencies = [ "serde_json", "thiserror 2.0.17", "tokio", - "tower", + "tower 0.5.2", "tracing", "url", "wasmtimer", @@ -878,10 +1026,13 @@ checksum = "400dc298aaabdbd48be05448c4a19eaa38416c446043f3e54561249149269c32" dependencies = [ "alloy-json-rpc", "alloy-transport", + "opentelemetry 0.31.0", + "opentelemetry-http 0.31.0", "reqwest", "serde_json", - "tower", + "tower 0.5.2", "tracing", + "tracing-opentelemetry 0.32.0", "url", ] @@ -928,7 +1079,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428aa0f0e0658ff091f8f667c406e034b431cb10abd39de4f507520968acc499" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "arrayvec", @@ -1362,6 +1513,84 @@ 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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl 0.2.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.17", + "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.114", + "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.114", +] + [[package]] name = "asn1_der" version = "0.7.6" @@ -1390,6 +1619,24 @@ dependencies = [ "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 1.1.3", + "slab", + "windows-sys 0.61.2", +] + [[package]] name = "async-lock" version = "3.4.2" @@ -1455,12 +1702,37 @@ dependencies = [ "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" @@ -1489,48 +1761,181 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "az" -version = "1.2.1" +name = "aws-lc-rs" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] [[package]] -name = "backon" -version = "1.6.0" +name = "aws-lc-sys" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ - "fastrand", - "tokio", + "bindgen 0.69.5", + "cc", + "cmake", + "dunce", + "fs_extra", ] [[package]] -name = "base-access-lists" -version = "0.2.1" +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ - "alloy-consensus", - "alloy-contract", - "alloy-eip7928", - "alloy-primitives", - "alloy-rlp", - "alloy-sol-types", - "base-primitives", - "eyre", - "op-revm", - "reth-evm", - "reth-optimism-chainspec", - "reth-optimism-evm", - "revm", + "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", - "tracing", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", ] [[package]] -name = "base-bundles" -version = "0.2.1" +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +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.2", + "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 = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "tokio", +] + +[[package]] +name = "base-access-lists" +version = "0.2.1" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eip7928", + "alloy-primitives 1.5.2", + "alloy-rlp", + "alloy-sol-types 1.5.2", + "base-primitives", + "eyre", + "op-revm", + "reth-evm", + "reth-optimism-chainspec", + "reth-optimism-evm", + "revm", + "serde", + "tracing", +] + +[[package]] +name = "base-bundles" +version = "0.2.1" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-serde", "alloy-signer-local", @@ -1559,7 +1964,7 @@ version = "0.2.1" dependencies = [ "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-client", "alloy-rpc-types", @@ -1570,7 +1975,7 @@ dependencies = [ "derive_more", "eyre", "futures-util", - "jsonrpsee", + "jsonrpsee 0.26.0", "op-alloy-network", "op-alloy-rpc-types-engine", "reth", @@ -1586,7 +1991,7 @@ dependencies = [ "reth-rpc-layer", "reth-tracing", "tokio", - "tower", + "tower 0.5.2", "tracing", "tracing-subscriber 0.3.22", "url", @@ -1601,14 +2006,14 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-op-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-client", "alloy-rpc-types", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-sol-macro", - "alloy-sol-types", + "alloy-sol-macro 1.5.2", + "alloy-sol-types 1.5.2", "arc-swap", "base-client-node", "base-flashblocks", @@ -1617,8 +2022,8 @@ dependencies = [ "derive_more", "eyre", "futures-util", - "jsonrpsee", - "jsonrpsee-types", + "jsonrpsee 0.26.0", + "jsonrpsee-types 0.26.0", "metrics", "metrics-derive", "once_cell", @@ -1661,7 +2066,7 @@ dependencies = [ name = "base-flashtypes" version = "0.2.1" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-serde", @@ -1680,12 +2085,12 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-client", "base-bundles", "base-client-node", "eyre", - "jsonrpsee", + "jsonrpsee 0.26.0", "op-alloy-consensus", "rand 0.9.2", "reth", @@ -1712,11 +2117,11 @@ dependencies = [ "alloy-contract", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-signer", "alloy-signer-local", - "alloy-sol-macro", - "alloy-sol-types", + "alloy-sol-macro 1.5.2", + "alloy-sol-types 1.5.2", "eyre", "op-alloy-network", "op-alloy-rpc-types", @@ -1750,14 +2155,14 @@ dependencies = [ name = "base-txpool" version = "0.2.1" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "base-client-node", "chrono", "derive_more", "eyre", "futures", "httpmock", - "jsonrpsee", + "jsonrpsee 0.26.0", "lru 0.16.3", "metrics", "reth", @@ -1804,11 +2209,20 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-url" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5b428e9fb429c6fda7316e9b006f993e6b4c33005e4659339fb5214479dddec" +dependencies = [ + "base64 0.22.1", +] + [[package]] name = "base64ct" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bech32" @@ -1831,6 +2245,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.114", + "which", +] + [[package]] name = "bindgen" version = "0.71.1" @@ -1844,7 +2281,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.1", "shlex", "syn 2.0.114", ] @@ -1862,7 +2299,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.1", "shlex", "syn 2.0.114", ] @@ -1898,6 +2335,32 @@ dependencies = [ "hex-conservative", ] +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[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.114", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -1926,6 +2389,15 @@ dependencies = [ "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 = "block-buffer" version = "0.10.4" @@ -1968,7 +2440,7 @@ dependencies = [ "boa_string", "indexmap 2.13.0", "num-bigint", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2009,7 +2481,7 @@ dependencies = [ "portable-atomic", "rand 0.9.2", "regress", - "rustc-hash", + "rustc-hash 2.1.1", "ryu-js", "serde", "serde_json", @@ -2047,7 +2519,7 @@ dependencies = [ "indexmap 2.13.0", "once_cell", "phf", - "rustc-hash", + "rustc-hash 2.1.1", "static_assertions", ] @@ -2062,7 +2534,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -2080,7 +2552,7 @@ dependencies = [ "num-bigint", "num-traits", "regress", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2092,11 +2564,61 @@ dependencies = [ "fast-float2", "itoa", "paste", - "rustc-hash", + "rustc-hash 2.1.1", "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.17", + "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.0" @@ -2295,6 +2817,34 @@ 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.13.0", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.114", + "tempfile", + "toml 0.9.11+spec-1.1.0", +] + +[[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.15" @@ -2333,6 +2883,30 @@ 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", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.42" @@ -2344,7 +2918,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2382,6 +2956,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -2435,6 +3010,34 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "coco-provider" +version = "0.3.0" +source = "git+https://github.com/automata-network/coco-provider-sdk#3a832b8cf5e88ef71649ab56e4efd67067b26b7c" +dependencies = [ + "bincode", + "bitfield 0.19.4", + "cbindgen", + "iocuddle", + "libc", + "log", + "rand 0.8.5", + "serde", + "serde-big-array", + "sysinfo 0.35.2", + "tss-esapi", + "uuid", +] + [[package]] name = "coins-bip32" version = "0.12.0" @@ -2618,16 +3221,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "cordyceps" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" -dependencies = [ - "loom", - "tracing", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -2849,10 +3442,26 @@ dependencies = [ ] [[package]] -name = "ctr" -version = "0.9.2" +name = "ctor" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +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", ] @@ -3042,6 +3651,23 @@ dependencies = [ "syn 2.0.114", ] +[[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", + "time", + "x509-parser 0.15.1", +] + [[package]] name = "debug-helper" version = "0.3.13" @@ -3066,9 +3692,38 @@ 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.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.5" @@ -3166,12 +3821,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "diatomic-waker" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c" - [[package]] name = "diff" version = "0.1.13" @@ -3285,6 +3934,17 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "docker_credential" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "doctest-file" version = "1.0.0" @@ -3300,6 +3960,33 @@ 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" @@ -3405,6 +4092,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -3413,6 +4101,21 @@ dependencies = [ "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" @@ -3465,6 +4168,26 @@ dependencies = [ "syn 2.0.114", ] +[[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.114", +] + [[package]] name = "equator" version = "0.4.2" @@ -3510,6 +4233,17 @@ dependencies = [ "version_check", ] +[[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.7.0" @@ -3527,7 +4261,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "hex", "serde", "serde_derive", @@ -3540,7 +4274,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "ethereum_serde_utils", "itertools 0.13.0", "serde", @@ -3744,6 +4478,12 @@ 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" @@ -3775,16 +4515,13 @@ dependencies = [ ] [[package]] -name = "futures-buffered" -version = "0.2.12" +name = "futures-bounded" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e0e1f38ec07ba4abbde21eed377082f17ccb988be9d988a5adbf4bafc118fd" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" dependencies = [ - "cordyceps", - "diatomic-waker", - "futures-core", - "pin-project-lite", - "spin", + "futures-timer", + "futures-util", ] [[package]] @@ -3799,16 +4536,14 @@ dependencies = [ [[package]] name = "futures-concurrency" -version = "7.6.3" +version = "7.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb68017df91f2e477ed4bea586c59eaecaa47ed885a770d0444e21e62572cd2" +checksum = "69a9561702beff46b705a8ac9c0803ec4c7fc5d01330a99b1feaf86e206e92ba" dependencies = [ "fixedbitset", - "futures-buffered", "futures-core", "futures-lite", "pin-project", - "slab", "smallvec", ] @@ -3827,6 +4562,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -3859,6 +4595,17 @@ dependencies = [ "syn 2.0.114", ] +[[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.31" @@ -3905,21 +4652,6 @@ 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", - "windows-result 0.4.1", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -3933,9 +4665,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -4120,6 +4852,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.1.5", + "serde", ] [[package]] @@ -4224,6 +4957,7 @@ dependencies = [ "rand 0.9.2", "ring", "serde", + "socket2 0.5.10", "thiserror 2.0.17", "tinyvec", "tokio", @@ -4271,6 +5005,21 @@ 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" @@ -4401,6 +5150,21 @@ dependencies = [ "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.7" @@ -4468,9 +5232,27 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "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]] @@ -4613,6 +5395,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "if-addrs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "if-addrs" version = "0.14.0" @@ -4623,6 +5415,50 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "if-watch" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf9d64cfcf380606e64f9a0bcf493616b65331199f984151a6fa11a7b3cde38" +dependencies = [ + "async-io", + "core-foundation 0.9.4", + "fnv", + "futures", + "if-addrs 0.10.2", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration", + "tokio", + "windows 0.53.0", +] + +[[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.2", + "tokio", + "url", + "xmltree", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -4744,6 +5580,15 @@ dependencies = [ "syn 2.0.114", ] +[[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.2.3" @@ -4768,6 +5613,12 @@ 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.2" @@ -4822,6 +5673,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -4888,6 +5748,20 @@ dependencies = [ "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" @@ -4895,11 +5769,11 @@ 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-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", @@ -4917,7 +5791,7 @@ dependencies = [ "futures-util", "gloo-net", "http", - "jsonrpsee-core", + "jsonrpsee-core 0.26.0", "pin-project", "rustls", "rustls-pki-types", @@ -4931,6 +5805,30 @@ dependencies = [ "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.2", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tower 0.5.2", + "tracing", +] + [[package]] name = "jsonrpsee-core" version = "0.26.0" @@ -4944,21 +5842,43 @@ dependencies = [ "http", "http-body", "http-body-util", - "jsonrpsee-types", + "jsonrpsee-types 0.26.0", "parking_lot", "pin-project", "rand 0.9.2", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "thiserror 2.0.17", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "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", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tower 0.5.2", + "url", +] + [[package]] name = "jsonrpsee-http-client" version = "0.26.0" @@ -4970,18 +5890,30 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "rustls", "rustls-platform-verifier", "serde", "serde_json", "thiserror 2.0.17", "tokio", - "tower", + "tower 0.5.2", "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.114", +] + [[package]] name = "jsonrpsee-proc-macros" version = "0.26.0" @@ -4995,6 +5927,32 @@ dependencies = [ "syn 2.0.114", ] +[[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.17", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.5.2", + "tracing", +] + [[package]] name = "jsonrpsee-server" version = "0.26.0" @@ -5007,8 +5965,8 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "pin-project", "route-recognizer", "serde", @@ -5018,10 +5976,21 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.5.2", "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.17", +] + [[package]] name = "jsonrpsee-types" version = "0.26.0" @@ -5041,9 +6010,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7902885de4779f711a95d82c8da2d7e5f9f3a7c7cfa44d51c067fd1c29d72a3c" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", - "tower", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "tower 0.5.2", ] [[package]] @@ -5054,9 +6023,9 @@ checksum = "9b6fceceeb05301cc4c065ab3bd2fa990d41ff4eb44e4ca1b30fa99c057c3e79" dependencies = [ "http", "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", - "tower", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "tower 0.5.2", "url", ] @@ -5135,6 +6104,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.180" @@ -5160,7 +6135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5169,6 +6144,149 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[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.17", +] + +[[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.5", + "rand_core 0.6.4", + "thiserror 2.0.17", + "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.5", + "rw-stream-sink", + "thiserror 2.0.17", + "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", + "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.17", + "tracing", +] + [[package]] name = "libp2p-identity" version = "0.2.13" @@ -5182,12 +6300,241 @@ dependencies = [ "k256", "multihash", "quick-protobuf", + "rand 0.8.5", "sha2", "thiserror 2.0.17", "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", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "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.5", + "snow", + "static_assertions", + "thiserror 2.0.17", + "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.5", + "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.5", + "ring", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.17", + "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.5", + "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.5", + "tracing", +] + +[[package]] +name = "libp2p-swarm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa762e5215919a34e31c35d4b18bf2e18566ecab7f8a3d39535f4a3068f8b62" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "lru 0.12.5", + "multistream-select", + "rand 0.8.5", + "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.114", +] + +[[package]] +name = "libp2p-tcp" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4e030c52c46c8d01559b2b8ca9b7c4185f10576016853129ca1fe5cd1a644" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "socket2 0.5.10", + "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.17", + "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.17", + "tracing", + "yamux 0.12.1", + "yamux 0.13.8", +] + [[package]] name = "libproc" version = "0.14.11" @@ -5278,19 +6625,6 @@ 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.22", -] - [[package]] name = "lru" version = "0.12.5" @@ -5360,6 +6694,16 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "macros" +version = "0.1.0" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "match-lookup" version = "0.1.1" @@ -5380,6 +6724,28 @@ 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.7.6" @@ -5433,11 +6799,39 @@ 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", + "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.13.0", + "ipnet", + "metrics", + "metrics-util 0.20.1", + "quanta", + "thiserror 2.0.17", + "tokio", + "tracing", ] [[package]] @@ -5462,9 +6856,29 @@ 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", +] + +[[package]] +name = "metrics-util" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfb1365fea27e6dd9dc1dbc19f570198bc86914533ad639dae939635f096be4" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.16.1", "metrics", "quanta", "rand 0.9.2", @@ -5558,10 +6972,13 @@ version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" dependencies = [ + "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", + "event-listener", + "futures-util", "parking_lot", "portable-atomic", "smallvec", @@ -5590,7 +7007,7 @@ dependencies = [ "percent-encoding", "serde", "static_assertions", - "unsigned-varint", + "unsigned-varint 0.8.0", "url", ] @@ -5607,32 +7024,145 @@ dependencies = [ ] [[package]] -name = "multihash" -version = "0.19.3" +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "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.5", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe 0.1.6", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror 1.0.69", +] + +[[package]] +name = "netlink-proto" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.17", +] + +[[package]] +name = "netlink-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" +dependencies = [ + "bytes", + "futures", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ - "core2", - "unsigned-varint", + "smallvec", ] [[package]] -name = "native-tls" -version = "0.2.14" +name = "nix" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ + "bitflags 1.3.2", + "cfg-if", "libc", - "log", - "openssl", - "openssl-probe 0.1.6", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", ] +[[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" @@ -5725,6 +7255,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[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.114", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -5822,6 +7363,52 @@ dependencies = [ "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.10.0", +] + +[[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.1", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -5853,7 +7440,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", @@ -5878,7 +7465,7 @@ checksum = "f63f27e65be273ec8fcb0b6af0fd850b550979465ab93423705ceb3dfddbd2ab" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-types-eth", "alloy-signer", @@ -5892,8 +7479,8 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ef9114426b16172254555aad34a8ea96c01895e40da92f5d12ea680a1baeaa7" dependencies = [ - "alloy-primitives", - "jsonrpsee", + "alloy-primitives 1.5.2", + "jsonrpsee 0.26.0", ] [[package]] @@ -5905,7 +7492,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "derive_more", @@ -5923,7 +7510,7 @@ checksum = "d8f24b8cb66e4b33e6c9e508bf46b8ecafc92eadd0b93fedd306c0accb477657" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-engine", "alloy-serde", @@ -5936,6 +7523,141 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "op-rbuilder" +version = "0.2.1" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eips", + "alloy-evm", + "alloy-json-rpc", + "alloy-network", + "alloy-op-evm", + "alloy-primitives 1.5.2", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types-beacon", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer-local", + "alloy-sol-types 1.5.2", + "alloy-transport", + "alloy-transport-http", + "anyhow", + "async-trait", + "chrono", + "clap", + "clap_builder", + "concurrent-queue", + "ctor", + "dashmap 6.1.0", + "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.2", + "reqwest", + "reth", + "reth-basic-payload-builder", + "reth-chain-state", + "reth-chainspec", + "reth-cli", + "reth-cli-commands", + "reth-cli-util", + "reth-db", + "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", + "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", + "rollup-boost", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "serde_yaml", + "sha3", + "shellexpand", + "tar", + "tempfile", + "testcontainers", + "thiserror 2.0.17", + "tikv-jemallocator", + "time", + "tips-core", + "tokio", + "tokio-tungstenite 0.26.2", + "tokio-util", + "tower 0.5.2", + "tracing", + "tracing-subscriber 0.3.22", + "url", + "uuid", + "vergen", + "vergen-git2", +] + [[package]] name = "op-revm" version = "12.0.2" @@ -6003,6 +7725,20 @@ 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.17", + "tracing", +] + [[package]] name = "opentelemetry" version = "0.31.0" @@ -6017,6 +7753,20 @@ dependencies = [ "tracing", ] +[[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", + "tracing", +] + [[package]] name = "opentelemetry-http" version = "0.31.0" @@ -6026,8 +7776,30 @@ dependencies = [ "async-trait", "bytes", "http", - "opentelemetry", + "opentelemetry 0.31.0", + "reqwest", +] + +[[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", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tonic 0.12.3", + "tracing", ] [[package]] @@ -6037,37 +7809,73 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" dependencies = [ "http", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", + "opentelemetry 0.31.0", + "opentelemetry-http 0.31.0", + "opentelemetry-proto 0.31.0", + "opentelemetry_sdk 0.31.0", + "prost 0.14.3", "reqwest", "thiserror 2.0.17", "tokio", - "tonic", + "tonic 0.14.2", "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", - "opentelemetry_sdk", - "prost", - "tonic", + "opentelemetry 0.31.0", + "opentelemetry_sdk 0.31.0", + "prost 0.14.3", + "tonic 0.14.2", "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-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.17", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "opentelemetry_sdk" version = "0.31.0" @@ -6077,7 +7885,7 @@ dependencies = [ "futures-channel", "futures-executor", "futures-util", - "opentelemetry", + "opentelemetry 0.31.0", "percent-encoding", "rand 0.9.2", "thiserror 2.0.17", @@ -6089,6 +7897,15 @@ 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" @@ -6101,6 +7918,25 @@ dependencies = [ "sha2", ] +[[package]] +name = "p2p" +version = "0.2.1" +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" @@ -6167,7 +8003,32 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "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.114", ] [[package]] @@ -6205,6 +8066,15 @@ dependencies = [ "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" @@ -6274,6 +8144,41 @@ dependencies = [ "siphasher", ] +[[package]] +name = "picky-asn1" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295eea0f33c16be21e2a98b908fdd4d73c04dd48c8480991b76dbcf0cb58b212" +dependencies = [ + "oid", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-der" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df7873a9e36d42dadb393bea5e211fe83d793c172afad5fb4ec846ec582793f" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5f20f71a68499ff32310f418a6fad8816eac1a2859ed3f0c5c741389dd6208" +dependencies = [ + "base64 0.21.7", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -6359,6 +8264,31 @@ dependencies = [ "plotters-backend", ] +[[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 1.1.3", + "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", + "opaque-debug", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" @@ -6411,6 +8341,16 @@ dependencies = [ "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.114", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -6517,6 +8457,29 @@ dependencies = [ "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.114", +] + [[package]] name = "proptest" version = "1.9.0" @@ -6570,19 +8533,42 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.1" +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 = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.14.3", ] [[package]] name = "prost-derive" -version = "0.14.1" +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.114", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", @@ -6632,6 +8618,19 @@ 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" @@ -6640,10 +8639,11 @@ checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", + "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.1", "rustls", "socket2 0.6.1", "thiserror 2.0.17", @@ -6663,7 +8663,7 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash", + "rustc-hash 2.1.1", "rustls", "rustls-pki-types", "slab", @@ -6708,6 +8708,16 @@ 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.5" @@ -6757,7 +8767,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -6790,9 +8800,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.2.0" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2988730ee014541157f48ce4dcc603940e00915edc3c7f9a8d78092256bb2493" +checksum = "5d8b5b858a440a0bc02625b62dd95131b9201aa9f69f411195dd4a7cfb1de3d7" dependencies = [ "rand 0.9.2", "rustversion", @@ -6848,12 +8858,34 @@ dependencies = [ "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" @@ -6878,7 +8910,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 1.0.69", ] @@ -6889,7 +8921,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 2.0.17", ] @@ -6967,9 +8999,11 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -6979,6 +9013,7 @@ dependencies = [ "hyper-util", "js-sys", "log", + "mime", "native-tls", "percent-encoding", "pin-project-lite", @@ -6994,7 +9029,7 @@ dependencies = [ "tokio-native-tls", "tokio-rustls", "tokio-util", - "tower", + "tower 0.5.2", "tower-http", "tower-service", "url", @@ -7064,7 +9099,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "futures-core", "futures-util", "metrics", @@ -7088,7 +9123,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-signer", "alloy-signer-local", "derive_more", @@ -7122,7 +9157,7 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-trie", "auto_impl", "derive_more", @@ -7154,7 +9189,7 @@ dependencies = [ "alloy-chains", "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "backon", "clap", @@ -7216,7 +9251,7 @@ dependencies = [ "tar", "tokio", "tokio-stream", - "toml", + "toml 0.8.23", "tracing", "zstd", ] @@ -7237,7 +9272,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "cfg-if", "eyre", "libc", @@ -7257,7 +9292,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-trie", "arbitrary", "bytes", @@ -7290,7 +9325,7 @@ dependencies = [ "reth-prune-types", "reth-stages-types", "serde", - "toml", + "toml 0.8.23", "url", ] @@ -7300,7 +9335,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "reth-execution-types", "reth-primitives-traits", @@ -7327,7 +9362,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-types-engine", "alloy-transport", @@ -7350,7 +9385,7 @@ name = "reth-db" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "derive_more", "eyre", "metrics", @@ -7364,9 +9399,9 @@ dependencies = [ "reth-static-file-types", "reth-storage-errors", "reth-tracing", - "rustc-hash", + "rustc-hash 2.1.1", "strum 0.27.2", - "sysinfo", + "sysinfo 0.33.1", "tempfile", "thiserror 2.0.17", ] @@ -7378,7 +9413,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "bytes", "derive_more", @@ -7406,7 +9441,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "boyer-moore-magiclen", "eyre", "reth-chainspec", @@ -7435,7 +9470,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "bytes", "modular-bitfield", @@ -7449,7 +9484,7 @@ name = "reth-discv4" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "discv5", "enr", @@ -7474,7 +9509,7 @@ name = "reth-discv5" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "derive_more", "discv5", @@ -7498,7 +9533,7 @@ name = "reth-dns-discovery" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "data-encoding", "enr", "hickory-resolver", @@ -7524,7 +9559,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "async-compression", "futures", @@ -7560,7 +9595,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rlp", "alloy-rpc-types-engine", @@ -7570,7 +9605,7 @@ dependencies = [ "derive_more", "eyre", "futures-util", - "jsonrpsee", + "jsonrpsee 0.26.0", "reth-chainspec", "reth-cli-commands", "reth-config", @@ -7616,7 +9651,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "aes", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "block-padding", "byteorder", @@ -7647,7 +9682,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "eyre", "futures-util", @@ -7672,7 +9707,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "auto_impl", "futures", @@ -7720,7 +9755,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-engine", "crossbeam-channel", @@ -7802,7 +9837,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "ethereum_ssz", "ethereum_ssz_derive", @@ -7816,7 +9851,7 @@ name = "reth-era-downloader" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "bytes", "eyre", "futures-util", @@ -7832,7 +9867,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "eyre", "futures-util", "reth-db-api", @@ -7865,7 +9900,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-chains", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "bytes", "derive_more", @@ -7896,7 +9931,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "bytes", "derive_more", @@ -7939,7 +9974,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-chainspec", "reth-consensus", "reth-consensus-common", @@ -7954,7 +9989,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-engine", "reth-engine-primitives", @@ -7973,11 +10008,11 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-eip2124", "alloy-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "auto_impl", "once_cell", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -7987,7 +10022,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-engine", "reth-basic-payload-builder", @@ -8016,7 +10051,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", @@ -8047,7 +10082,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "derive_more", "futures-util", @@ -8070,7 +10105,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "reth-chainspec", "reth-ethereum-forks", @@ -8088,7 +10123,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "nybbles", "reth-storage-errors", @@ -8103,7 +10138,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "derive_more", "reth-ethereum-primitives", "reth-primitives-traits", @@ -8120,7 +10155,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "eyre", "futures", "itertools 0.14.0", @@ -8157,7 +10192,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-chain-state", "reth-execution-types", "reth-primitives-traits", @@ -8181,12 +10216,12 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-debug", "eyre", "futures", - "jsonrpsee", + "jsonrpsee 0.26.0", "pretty_assertions", "reth-engine-primitives", "reth-evm", @@ -8212,14 +10247,14 @@ dependencies = [ "futures", "futures-util", "interprocess", - "jsonrpsee", + "jsonrpsee 0.26.0", "pin-project", "serde_json", "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.5.2", "tracing", ] @@ -8265,7 +10300,7 @@ name = "reth-net-banlist" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", ] [[package]] @@ -8274,7 +10309,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "futures-util", - "if-addrs", + "if-addrs 0.14.0", "reqwest", "serde_with", "thiserror 2.0.17", @@ -8289,7 +10324,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "aquamarine", "auto_impl", @@ -8325,7 +10360,7 @@ dependencies = [ "reth-tasks", "reth-tokio-util", "reth-transaction-pool", - "rustc-hash", + "rustc-hash 2.1.1", "schnellru", "secp256k1 0.30.0", "serde", @@ -8343,7 +10378,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-admin", "alloy-rpc-types-eth", "auto_impl", @@ -8369,7 +10404,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "derive_more", "futures", @@ -8390,7 +10425,7 @@ name = "reth-network-peers" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "enr", "secp256k1 0.30.0", @@ -8462,7 +10497,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-types", "alloy-rpc-types-engine", @@ -8470,7 +10505,7 @@ dependencies = [ "eyre", "fdlimit", "futures", - "jsonrpsee", + "jsonrpsee 0.26.0", "rayon", "reth-basic-payload-builder", "reth-chain-state", @@ -8530,7 +10565,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "clap", "derive_more", @@ -8569,7 +10604,7 @@ dependencies = [ "shellexpand", "strum 0.27.2", "thiserror 2.0.17", - "toml", + "toml 0.8.23", "tracing", "url", "vergen", @@ -8620,7 +10655,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "chrono", "futures-util", "reth-chain-state", @@ -8645,7 +10680,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "derive_more", "futures", @@ -8669,18 +10704,18 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "eyre", "http", - "jsonrpsee-server", + "jsonrpsee-server 0.26.0", "metrics", - "metrics-exporter-prometheus", + "metrics-exporter-prometheus 0.16.2", "metrics-process", - "metrics-util", + "metrics-util 0.19.1", "procfs 0.17.0", "reqwest", "reth-metrics", "reth-tasks", "tikv-jemalloc-ctl", "tokio", - "tower", + "tower 0.5.2", "tracing", ] @@ -8706,7 +10741,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "derive_more", "miniz_oxide", "op-alloy-consensus", @@ -8731,7 +10766,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "clap", "derive_more", @@ -8781,7 +10816,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-trie", "reth-chainspec", "reth-consensus", @@ -8808,7 +10843,7 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-op-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "op-alloy-consensus", "op-alloy-rpc-types-engine", "op-revm", @@ -8834,7 +10869,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-serde", "brotli", @@ -8872,7 +10907,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-op-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "once_cell", "reth-ethereum-forks", ] @@ -8884,7 +10919,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "clap", @@ -8935,7 +10970,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-debug", "alloy-rpc-types-engine", @@ -8974,7 +11009,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "bytes", @@ -8995,7 +11030,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-client", "alloy-rpc-types-debug", "alloy-rpc-types-engine", @@ -9006,9 +11041,9 @@ dependencies = [ "derive_more", "eyre", "futures", - "jsonrpsee", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee 0.26.0", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "metrics", "op-alloy-consensus", "op-alloy-network", @@ -9044,7 +11079,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", ] @@ -9066,7 +11101,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-client", "alloy-rpc-types-eth", "alloy-serde", @@ -9100,7 +11135,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types", "futures-util", "metrics", @@ -9133,7 +11168,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "auto_impl", "either", @@ -9153,7 +11188,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-transaction-pool", ] @@ -9189,7 +11224,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-trie", @@ -9221,7 +11256,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "dashmap 6.1.0", "eyre", @@ -9265,7 +11300,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "itertools 0.14.0", "metrics", "rayon", @@ -9279,7 +11314,7 @@ dependencies = [ "reth-prune-types", "reth-static-file-types", "reth-tokio-util", - "rustc-hash", + "rustc-hash 2.1.1", "thiserror 2.0.17", "tokio", "tracing", @@ -9290,7 +11325,7 @@ name = "reth-prune-types" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "derive_more", "modular-bitfield", @@ -9306,7 +11341,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "futures", "reth-eth-wire", @@ -9325,7 +11360,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "eyre", "futures", "parking_lot", @@ -9351,7 +11386,7 @@ name = "reth-revm" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-primitives-traits", "reth-storage-api", "reth-storage-errors", @@ -9370,7 +11405,7 @@ dependencies = [ "alloy-evm", "alloy-genesis", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-client", "alloy-rpc-types", @@ -9393,8 +11428,8 @@ dependencies = [ "http-body", "hyper", "itertools 0.14.0", - "jsonrpsee", - "jsonrpsee-types", + "jsonrpsee 0.26.0", + "jsonrpsee-types 0.26.0", "jsonwebtoken", "parking_lot", "pin-project", @@ -9433,7 +11468,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", "tracing-futures", ] @@ -9446,7 +11481,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types", "alloy-rpc-types-admin", "alloy-rpc-types-anvil", @@ -9458,7 +11493,7 @@ dependencies = [ "alloy-rpc-types-trace", "alloy-rpc-types-txpool", "alloy-serde", - "jsonrpsee", + "jsonrpsee 0.26.0", "reth-chain-state", "reth-engine-primitives", "reth-network-peers", @@ -9475,7 +11510,7 @@ dependencies = [ "alloy-provider", "dyn-clone", "http", - "jsonrpsee", + "jsonrpsee 0.26.0", "metrics", "pin-project", "reth-chain-state", @@ -9500,7 +11535,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-util", - "tower", + "tower 0.5.2", "tower-http", "tracing", ] @@ -9513,12 +11548,12 @@ dependencies = [ "alloy-consensus", "alloy-json-rpc", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-signer", "auto_impl", "dyn-clone", - "jsonrpsee-types", + "jsonrpsee-types 0.26.0", "op-alloy-consensus", "op-alloy-network", "op-alloy-rpc-types", @@ -9538,11 +11573,11 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "async-trait", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "metrics", "parking_lot", "reth-chainspec", @@ -9573,7 +11608,7 @@ dependencies = [ "alloy-evm", "alloy-json-rpc", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-rpc-types-mev", @@ -9582,8 +11617,8 @@ dependencies = [ "auto_impl", "dyn-clone", "futures", - "jsonrpsee", - "jsonrpsee-types", + "jsonrpsee 0.26.0", + "jsonrpsee-types 0.26.0", "parking_lot", "reth-chain-state", "reth-chainspec", @@ -9615,16 +11650,16 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-client", "alloy-rpc-types-eth", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "alloy-transport", "derive_more", "futures", "itertools 0.14.0", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "metrics", "rand 0.9.2", "reqwest", @@ -9660,9 +11695,9 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-rpc-types-engine", "http", - "jsonrpsee-http-client", + "jsonrpsee-http-client 0.26.0", "pin-project", - "tower", + "tower 0.5.2", "tower-http", "tracing", ] @@ -9673,10 +11708,10 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "reth-errors", "reth-network-api", "serde", @@ -9690,7 +11725,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "bincode", "eyre", "futures-util", @@ -9737,7 +11772,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "aquamarine", "auto_impl", "futures-util", @@ -9763,7 +11798,7 @@ name = "reth-stages-types" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "bytes", "modular-bitfield", @@ -9777,7 +11812,7 @@ name = "reth-static-file" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "parking_lot", "rayon", "reth-codecs", @@ -9797,7 +11832,7 @@ name = "reth-static-file-types" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "clap", "derive_more", "serde", @@ -9811,7 +11846,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "auto_impl", "reth-chainspec", @@ -9833,7 +11868,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "derive_more", "reth-primitives-traits", @@ -9869,7 +11904,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "rand 0.8.5", "rand 0.9.2", "reth-ethereum-primitives", @@ -9911,12 +11946,12 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "clap", "eyre", - "opentelemetry", - "opentelemetry-otlp", + "opentelemetry 0.31.0", + "opentelemetry-otlp 0.31.0", "opentelemetry-semantic-conventions", - "opentelemetry_sdk", + "opentelemetry_sdk 0.31.0", "tracing", - "tracing-opentelemetry", + "tracing-opentelemetry 0.32.0", "tracing-subscriber 0.3.22", "url", ] @@ -9928,7 +11963,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "aquamarine", "auto_impl", @@ -9951,7 +11986,7 @@ dependencies = [ "reth-tasks", "revm-interpreter", "revm-primitives", - "rustc-hash", + "rustc-hash 2.1.1", "schnellru", "serde", "serde_json", @@ -9969,7 +12004,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-trie", "auto_impl", @@ -9993,7 +12028,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", @@ -10019,7 +12054,7 @@ name = "reth-trie-db" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-db-api", "reth-execution-errors", "reth-primitives-traits", @@ -10032,7 +12067,7 @@ name = "reth-trie-parallel" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "crossbeam-channel", "dashmap 6.1.0", @@ -10057,7 +12092,7 @@ name = "reth-trie-sparse" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-trie", "auto_impl", @@ -10076,7 +12111,7 @@ name = "reth-trie-sparse-parallel" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-trie", "metrics", @@ -10231,10 +12266,10 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21caa99f22184a6818946362778cccd3ff02f743c1e085bee87700671570ecb7" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-rpc-types-trace", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "anstyle", "boa_engine", "boa_gc", @@ -10289,7 +12324,7 @@ version = "21.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29e161db429d465c09ba9cbff0df49e31049fe6b549e28eb0b7bd642fcbd4412" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "num_enum", "once_cell", "serde", @@ -10325,7 +12360,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -10403,6 +12438,60 @@ dependencies = [ "chrono", ] +[[package]] +name = "rollup-boost" +version = "0.1.0" +source = "git+https://github.com/flashbots/rollup-boost?tag=v0.7.11#196237bab2a02298de994b439e0455abb1ac512f" +dependencies = [ + "alloy-primitives 1.5.2", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "backoff", + "bytes", + "clap", + "dashmap 6.1.0", + "dotenvy", + "eyre", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "jsonrpsee 0.25.1", + "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", + "reth-optimism-payload-builder", + "rustls", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.17", + "tokio", + "tokio-tungstenite 0.26.2", + "tokio-util", + "tower 0.5.2", + "tower-http", + "tracing", + "tracing-opentelemetry 0.29.0", + "tracing-subscriber 0.3.22", + "url", + "uuid", + "vergen", + "vergen-git2", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -10438,6 +12527,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rtnetlink" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" +dependencies = [ + "futures", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-packet-utils", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "rug" version = "1.28.0" @@ -10485,6 +12592,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -10527,6 +12640,15 @@ dependencies = [ "semver 1.0.27", ] +[[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 = "0.38.44" @@ -10555,10 +12677,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -10580,6 +12703,15 @@ dependencies = [ "security-framework 3.5.1", ] +[[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.13.2" @@ -10619,10 +12751,11 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -10646,6 +12779,17 @@ dependencies = [ "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.22" @@ -10711,12 +12855,6 @@ dependencies = [ "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" @@ -10880,6 +13018,25 @@ dependencies = [ "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" @@ -10914,6 +13071,17 @@ dependencies = [ "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_regex" version = "1.1.0" @@ -10924,6 +13092,17 @@ dependencies = [ "serde", ] +[[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.114", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -10933,6 +13112,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -10976,6 +13164,19 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serdect" version = "0.2.0" @@ -10997,6 +13198,12 @@ dependencies = [ "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" @@ -11175,6 +13382,23 @@ 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" @@ -11211,12 +13435,6 @@ dependencies = [ "sha1", ] -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - [[package]] name = "spki" version = "0.7.3" @@ -11251,6 +13469,29 @@ 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.114", +] + +[[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.114", +] + [[package]] name = "strum" version = "0.26.3" @@ -11322,6 +13563,18 @@ dependencies = [ "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.114", +] + [[package]] name = "syn-solidity" version = "1.5.2" @@ -11343,6 +13596,18 @@ 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" @@ -11367,6 +13632,41 @@ dependencies = [ "windows 0.57.0", ] +[[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 = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "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 = "tabwriter" version = "1.4.1" @@ -11416,6 +13716,55 @@ dependencies = [ "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#0c75c913a8a00728efa17e068e319ea742eba85f" +dependencies = [ + "alloy", + "anyhow", + "base64-url", + "cbindgen", + "chrono", + "clap", + "coco-provider", + "dcap-rs", + "hex", + "rand 0.8.5", + "serde", + "tokio", + "ureq", + "x509-parser 0.15.1", +] + +[[package]] +name = "tdx-quote-provider" +version = "0.1.0" +dependencies = [ + "axum 0.8.8", + "clap", + "dotenvy", + "eyre", + "hex", + "metrics", + "metrics-derive", + "metrics-exporter-prometheus 0.17.2", + "reqwest", + "serde", + "serde_json", + "tdx", + "thiserror 2.0.17", + "tokio", + "tracing", + "tracing-subscriber 0.3.22", +] + [[package]] name = "tempfile" version = "3.24.0" @@ -11429,6 +13778,35 @@ dependencies = [ "windows-sys 0.61.2", ] +[[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.17", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "url", +] + [[package]] name = "thin-vec" version = "0.2.14" @@ -11603,6 +13981,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tips-core" +version = "0.1.0" +source = "git+https://github.com/base/tips?rev=c08eaa4fe10c26de8911609b41ddab4918698325#c08eaa4fe10c26de8911609b41ddab4918698325" +dependencies = [ + "alloy-consensus", + "alloy-primitives 1.5.2", + "alloy-provider", + "alloy-serde", + "op-alloy-consensus", + "op-alloy-flz", + "serde", + "tracing", + "tracing-subscriber 0.3.22", + "uuid", +] + [[package]] name = "tokio" version = "1.49.0" @@ -11663,6 +14058,21 @@ dependencies = [ "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" @@ -11671,10 +14081,12 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", + "native-tls", "rustls", "rustls-native-certs", "rustls-pki-types", "tokio", + "tokio-native-tls", "tokio-rustls", "tungstenite 0.26.2", "webpki-roots 0.26.11", @@ -11716,11 +14128,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -11747,7 +14174,7 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.13.0", "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", "winnow", @@ -11774,12 +14201,48 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[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" +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.2" @@ -11800,7 +14263,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -11813,8 +14276,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" dependencies = [ "bytes", - "prost", - "tonic", + "prost 0.14.3", + "tonic 0.14.2", +] + +[[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", ] [[package]] @@ -11861,7 +14344,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -11969,6 +14452,24 @@ dependencies = [ "tracing-subscriber 0.3.22", ] +[[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.22", + "web-time", +] + [[package]] name = "tracing-opentelemetry" version = "0.32.0" @@ -11976,8 +14477,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6e5658463dd88089aba75c7791e1d3120633b1bfde22478b28f625a9bb1b8e" dependencies = [ "js-sys", - "opentelemetry", - "opentelemetry_sdk", + "opentelemetry 0.31.0", + "opentelemetry_sdk 0.31.0", "rustversion", "smallvec", "thiserror 2.0.17", @@ -12034,7 +14535,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "ethereum_hashing", "ethereum_ssz", "smallvec", @@ -12075,6 +14576,39 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tss-esapi" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ea9ccde878b029392ac97b5be1f470173d06ea41d18ad0bb3c92794c16a0f2" +dependencies = [ + "bitfield 0.14.0", + "enumflags2", + "getrandom 0.2.17", + "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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535cd192581c2ec4d5f82e670b1d3fbba6a23ccce8c85de387642051d7cad5b5" +dependencies = [ + "pkg-config", + "target-lexicon", +] + [[package]] name = "tungstenite" version = "0.26.2" @@ -12086,6 +14620,7 @@ dependencies = [ "http", "httparse", "log", + "native-tls", "rand 0.9.2", "rustls", "rustls-pki-types", @@ -12211,6 +14746,18 @@ dependencies = [ "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" @@ -12223,6 +14770,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[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" @@ -12269,6 +14834,7 @@ dependencies = [ "getrandom 0.3.4", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] @@ -12526,6 +15092,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "widestring" version = "1.2.1" @@ -12563,6 +15141,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" +dependencies = [ + "windows-core 0.53.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.57.0" @@ -12573,16 +15161,38 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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-collections 0.3.2", "windows-core 0.62.2", - "windows-future", - "windows-numerics", + "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]] @@ -12594,6 +15204,16 @@ dependencies = [ "windows-core 0.62.2", ] +[[package]] +name = "windows-core" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.57.0" @@ -12606,6 +15226,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -12614,9 +15247,20 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement 0.60.2", "windows-interface 0.59.3", - "windows-link", + "windows-link 0.2.1", "windows-result 0.4.1", - "windows-strings", + "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]] @@ -12626,8 +15270,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core 0.62.2", - "windows-link", - "windows-threading", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -12674,12 +15318,28 @@ dependencies = [ "syn 2.0.114", ] +[[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" @@ -12687,7 +15347,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core 0.62.2", - "windows-link", + "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]] @@ -12699,13 +15370,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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", + "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]] @@ -12714,7 +15403,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -12768,7 +15457,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]] @@ -12823,7 +15512,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", @@ -12834,13 +15523,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]] @@ -13088,6 +15786,52 @@ 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.1", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", + "rusticata-macros", + "thiserror 2.0.17", + "time", +] + [[package]] name = "xattr" version = "1.6.1" @@ -13098,18 +15842,73 @@ dependencies = [ "rustix 1.1.3", ] +[[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.5", + "static_assertions", +] + +[[package]] +name = "yamux" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deab71f2e20691b4728b349c6cee8fc7223880fa67b6b4f92225ec32225447e5" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.9.2", + "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.1" @@ -13130,7 +15929,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -13171,7 +15970,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -13230,9 +16029,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 0a202757..81155f65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,11 @@ rust-version = "1.88" license = "MIT" homepage = "https://github.com/base/node-reth" repository = "https://github.com/base/node-reth" +exclude = [".github/"] [workspace] resolver = "2" -members = ["bin/*", "crates/client/*", "crates/shared/*"] +members = ["bin/*", "crates/client/*", "crates/shared/*", "crates/builder/*"] default-members = ["bin/node"] [workspace.metadata.cargo-udeps.ignore] @@ -70,6 +71,8 @@ reth-ipc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-exex = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-tracing = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-cli-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-db-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } @@ -79,8 +82,9 @@ reth-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" reth-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", features = ["client"] } reth-optimism-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-testing-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-node = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } @@ -90,13 +94,48 @@ reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9. reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", features = [ - "op", -] } +reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", features = ["op"] } +reth-chain-state = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-trie = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-trie-parallel = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-metrics = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-trie-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-revm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-payload-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-tracing-otlp = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-forks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-txpool = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } # revm -revm = { version = "31.0.2", default-features = false } +revm = { version = "31.0.2", features = ["std", "secp256k1", "optional_balance_check"], default-features = false } revm-bytecode = { version = "7.1.1", default-features = false } +revm-inspectors = { version = "0.32.0", default-features = false } +revm-database = { version = "9.0.5", default-features = false } +revm-state = { version = "8.1.1", default-features = false } +revm-primitives = { version = "21.0.2", default-features = false } +revm-interpreter = { version = "29.0.1", default-features = false } +revm-inspector = { version = "12.0.0", default-features = false } +revm-context = { version = "11.0.2", default-features = false } +revm-context-interface = { version = "12.0.1", default-features = false } +revm-database-interface = { version = "8.0.5", default-features = false } # alloy alloy-rlp = "0.3.10" @@ -108,25 +147,39 @@ alloy-genesis = "1.0.41" alloy-signer = "1.0.41" alloy-signer-local = "1.0.41" alloy-hardforks = "0.4.4" -alloy-provider = "1.0.41" +alloy-provider = { version = "1.0.41", features = ["ipc", "pubsub", "txpool-api", "engine-api"] } alloy-contract = "1.0.41" -alloy-sol-types = "1.4.1" +alloy-sol-types = { version = "1.4.1", features = ["json"] } alloy-sol-macro = "1.4.1" -alloy-primitives = "1.4.1" -alloy-consensus = "1.0.41" +alloy-primitives = { version = "1.4.1", default-features = false, features = ["map-foldhash"] } +alloy-consensus = { version = "1.0.41", features = ["kzg"] } alloy-rpc-types = "1.0.41" alloy-rpc-client = "1.0.41" alloy-rpc-types-eth = "1.0.41" -alloy-rpc-types-engine = "1.0.41" +alloy-rpc-types-engine = { version = "1.0.41", features = ["ssz"] } +alloy-chains = "0.2.5" +alloy-evm = { version = "0.23.0", default-features = false } +alloy-pubsub = "1.0.41" +alloy-json-rpc = "1.0.41" +alloy-transport-http = "1.0.41" +alloy-network = "1.0.41" +alloy-network-primitives = "1.0.41" +alloy-transport = "1.0.41" +alloy-node-bindings = "1.0.41" +alloy-rpc-types-beacon = { version = "1.0.41", features = ["ssz"] } # op-alloy -op-alloy-flz = "0.13.1" -op-alloy-network = "0.22.0" -op-alloy-rpc-types = "0.22.0" -op-alloy-consensus = "0.22.0" -op-alloy-rpc-jsonrpsee = "0.22.0" -op-alloy-rpc-types-engine = "0.22.0" +op-alloy-flz = { version = "0.13.1", default-features = false } +op-alloy-network = { version = "0.22.0", default-features = false } +op-alloy-rpc-types = { version = "0.22.0", default-features = false } +op-alloy-consensus = { version = "0.22.0", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.22.0", default-features = false } +op-alloy-rpc-types-engine = { version = "0.22.0", default-features = false } alloy-op-evm = { version = "0.23.3", default-features = false } +alloy-op-hardforks = "0.4.4" + +# rollup-boost +rollup-boost = { git = "https://github.com/flashbots/rollup-boost", tag = "v0.7.11" } # op-revm op-revm = { version = "12.0.2", default-features = false } @@ -150,9 +203,10 @@ url = "2.5.7" lru = "0.16.2" rand = "0.9.2" uuid = "1.19.0" -time = "0.3.44" +time = { version = "0.3.44", features = ["macros", "formatting", "parsing"] } rayon = "1.11" -clap = "4.5.53" +clap = { version = "4.5.53", features = ["derive", "env", "string"] } +clap_builder = "4.5.19" eyre = "0.6.12" bytes = "1.11.0" brotli = "8.0.2" @@ -171,3 +225,29 @@ serde_json = "1.0.145" metrics-derive = "0.1.0" tracing-subscriber = "0.3.22" thiserror = "2.0" +async-trait = "0.1.83" +parking_lot = "0.12.3" +auto_impl = "1.2.0" +serde_with = "3.8.1" +secp256k1 = "0.30" +either = { version = "1.15.0", default-features = false } +tokio-util = { version = "0.7.4", features = ["codec"] } +warp = "0.3.7" +flate2 = "1.0.37" +prometheus = "0.13.4" +ctor = "0.2" +dashmap = "6.1" +hex = "0.4" +lazy_static = "1.4.0" +tikv-jemallocator = "0.6" +ahash = "0.8.6" +vergen = "9.0.4" +vergen-git2 = "1.0.5" +opentelemetry = { version = "0.31", features = ["trace"] } +jsonrpsee-core = "0.26.0" +ethereum_ssz = "0.9.0" +ethereum_ssz_derive = "0.9.0" + +# base +concurrent-queue = "2.5.0" +tips-core = { git = "https://github.com/base/tips", rev = "c08eaa4fe10c26de8911609b41ddab4918698325", default-features = false } diff --git a/Justfile b/Justfile index b231c87e..4118cd4e 100644 --- a/Justfile +++ b/Justfile @@ -39,7 +39,7 @@ zepter-fix: @command -v zepter >/dev/null 2>&1 || cargo install zepter zepter format features --fix -# Runs tests across workspace with all features enabled +# Runs tests across workspace with all features enabled (excludes builder crates) test: build-contracts @command -v cargo-nextest >/dev/null 2>&1 || cargo install cargo-nextest RUSTFLAGS="-D warnings" cargo nextest run --workspace --all-features @@ -48,16 +48,16 @@ test: build-contracts hack: cargo hack check --feature-powerset --no-dev-deps -# Checks formatting +# Checks formatting (builder crates excluded via rustfmt.toml) check-format: cargo +nightly fmt --all -- --check -# Fixes any formatting issues +# Fixes any formatting issues (builder crates excluded via rustfmt.toml) format-fix: - cargo fix --allow-dirty --allow-staged + cargo fix --allow-dirty --allow-staged --workspace cargo +nightly fmt --all -# Checks clippy +# Checks clippy (excludes builder crates) check-clippy: cargo clippy --all-targets -- -D warnings @@ -89,7 +89,7 @@ build-contracts: clean: cargo clean -# Checks if there are any unused dependencies +# Checks if there are any unused dependencies (excludes builder crates) check-udeps: build-contracts @command -v cargo-udeps >/dev/null 2>&1 || cargo install cargo-udeps cargo +nightly udeps --workspace --all-features --all-targets @@ -113,3 +113,47 @@ benches: # Runs flashblocks pending state benchmarks bench-flashblocks: cargo bench -p base-flashblocks --bench pending_state + +# ============================================ +# Builder Crate Targets +# ============================================ + +# Builds builder crates +build-builder: + cargo build -p op-rbuilder -p p2p -p tdx-quote-provider + +# Builds op-rbuilder binary +build-op-rbuilder: + cargo build -p op-rbuilder --bin op-rbuilder + +# Builds tester binary (requires testing feature) +build-tester: + cargo build -p op-rbuilder --bin tester --features "testing" + +# Builds tdx-quote-provider binary +build-tdx-quote-provider: + cargo build -p tdx-quote-provider --bin tdx-quote-provider + +# Runs tests for builder crates (with OTEL env vars disabled) +test-builder: + OTEL_EXPORTER_OTLP_ENDPOINT="" OTEL_EXPORTER_OTLP_HEADERS="" OTEL_SDK_DISABLED="true" \ + cargo test -p op-rbuilder -p p2p -p tdx-quote-provider --verbose + +# Runs clippy on builder crates (using nightly, matching op-rbuilder's original) +check-clippy-builder: + cargo +nightly clippy -p op-rbuilder -p p2p -p tdx-quote-provider --all-features -- -D warnings + +# Fixes formatting for builder crates +format-builder: + cargo +nightly fmt -p op-rbuilder -p p2p -p tdx-quote-provider + +# Full builder CI check +ci-builder: build-builder test-builder check-clippy-builder + +# Builds builder release binary +build-builder-release: + cargo build --release -p op-rbuilder --bin op-rbuilder + +# Builds builder with maxperf profile +build-builder-maxperf: + cargo build --profile maxperf -p op-rbuilder --bin op-rbuilder --features jemalloc diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml new file mode 100644 index 00000000..517cb67f --- /dev/null +++ b/crates/builder/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.rust] +unreachable_pub = "deny" +dead_code = "allow" + +[lints.clippy] +unused_async = "warn" + +[dependencies] +p2p = { path = "../p2p" } + +tips-core.workspace = true + +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 +concurrent-queue.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" ] + +jemalloc = [ + "dep:tikv-jemallocator", + "reth-cli-util/jemalloc", + "reth-optimism-cli/jemalloc", +] +jemalloc-prof = [ + "jemalloc", + "reth-cli-util/jemalloc-prof", + "reth/jemalloc-prof", + "tikv-jemallocator?/profiling", +] + +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 = [ + "ctor", + "http-body-util", + "hyper", + "hyper-util", + "macros", + "nanoid", + "reth-ipc", + "reth-node-builder/test-utils", + "rlimit", +] + +interop = [] + +telemetry = [ "opentelemetry", "reth-tracing-otlp" ] + +custom-engine-api = [] + +[[bin]] +name = "op-rbuilder" +path = "src/bin/op-rbuilder/main.rs" + +[[bin]] +name = "tester" +required-features = ["testing"] diff --git a/crates/builder/op-rbuilder/build.rs b/crates/builder/op-rbuilder/build.rs new file mode 100644 index 00000000..97acc1fe --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs new file mode 100644 index 00000000..587cce8a --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs new file mode 100644 index 00000000..4282d54d --- /dev/null +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -0,0 +1,262 @@ +//! 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, + /// Whether to enable TIPS Resource Metering + #[arg(long = "builder.enable-resource-metering", default_value = "false")] + pub enable_resource_metering: bool, + + /// Buffer size for tx data store (LRU eviction when full) + #[arg(long = "builder.tx-data-store-buffer-size", default_value = "10000")] + pub tx_data_store_buffer_size: usize, + + /// 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/crates/builder/op-rbuilder/src/args/playground.rs b/crates/builder/op-rbuilder/src/args/playground.rs new file mode 100644 index 00000000..3ef24c1d --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/bin/op-rbuilder/main.rs b/crates/builder/op-rbuilder/src/bin/op-rbuilder/main.rs new file mode 100644 index 00000000..985b93c4 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/bin/tester/main.rs b/crates/builder/op-rbuilder/src/bin/tester/main.rs new file mode 100644 index 00000000..52c4812e --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs new file mode 100644 index 00000000..590ae1b2 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -0,0 +1,496 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; +use alloy_evm::Database; +use alloy_op_evm::OpEvm; +use alloy_primitives::{ + Address, B256, Bytes, TxKind, U256, + map::{HashMap, 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, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, + precompiles::PrecompilesMap, +}; +use reth_node_api::PayloadBuilderError; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::Recovered; +use reth_provider::{ProviderError, StateProvider}; +use reth_revm::{State, database::StateProviderDatabase}; +use reth_rpc_api::eth::{EthTxEnvError, transaction::TryIntoTxEnv}; +use revm::{ + DatabaseCommit, DatabaseRef, + context::{ + ContextTr, + result::{EVMError, ExecutionResult, ResultAndState}, + }, + inspector::NoOpInspector, + state::Account, +}; +use tracing::{trace, warn}; + +use crate::{ + builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer, +}; + +#[derive(Debug, Default)] +pub struct SimulationSuccessResult { + pub gas_used: u64, + pub output: T::Return, + pub state_changes: HashMap, +} + +#[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, + db: &mut State, + top_of_block: bool, + ) -> Result, BuilderTransactionError> { + { + let builder_txs = self.simulate_builder_txs_with_state_copy( + state_provider, + info, + builder_ctx, + db, + top_of_block, + )?; + + let mut evm = builder_ctx + .evm_config + .evm_with_env(&mut *db, builder_ctx.evm_env.clone()); + + 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 buidler 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 ResultAndState { result, state } = match evm.transact(&builder_tx.signed_tx) { + Ok(res) => res, + Err(err) => { + if let Some(err) = err.as_invalid_tx_err() { + if err.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %err, ?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", %err, ?builder_tx.signed_tx, "skipping invalid builder transaction and its descendants"); + invalid.insert(builder_tx.signed_tx.signer()); + } + + continue; + } + // this is an error that we should treat as fatal for this attempt + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + }; + + if !result.is_success() { + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), result = ?result, "builder tx reverted"); + invalid.insert(builder_tx.signed_tx.signer()); + continue; + } + + // Add gas used by the transaction to cumulative gas used, before creating the receipt + let gas_used = result.gas_used(); + info.cumulative_gas_used += gas_used; + info.cumulative_da_bytes_used += builder_tx.da_size; + + let ctx = ReceiptBuilderCtx { + tx: builder_tx.signed_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(builder_ctx.build_receipt(ctx, None)); + + // Commit changes + evm.db_mut().commit(state); + + // 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()); + } + + // Release the db reference by dropping evm + drop(evm); + + 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 tx_env = tx.try_into_tx_env(evm.cfg(), evm.block())?; + let to = tx_env.base.kind.into_to().unwrap_or_default(); + + let ResultAndState { result, state } = match evm.transact(tx_env) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; + + match result { + ExecutionResult::Success { + output, + gas_used, + 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/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs new file mode 100644 index 00000000..dce507b2 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -0,0 +1,773 @@ +use alloy_consensus::{Eip658Value, Transaction, conditional::BlockConditionalAttributes}; +use alloy_eips::{Encodable2718, Typed2718}; +use alloy_evm::Database; +use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; +use alloy_primitives::{BlockHash, Bytes, U256}; +use alloy_rpc_types_eth::Withdrawals; +use core::fmt::Debug; +use op_alloy_consensus::OpDepositReceipt; +use op_revm::OpSpecId; +use reth::payload::PayloadBuilderAttributes; +use reth_basic_payload_builder::PayloadConfig; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_evm::{ + ConfigureEvm, Evm, EvmEnv, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, + op_revm::L1BlockInfo, +}; +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::SealedHeader; +use reth_primitives_traits::{InMemorySize, SignedTransaction}; +use reth_revm::{State, context::Block}; +use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; +use revm::{DatabaseCommit, context::result::ResultAndState, 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_data_store::{TxData, TxDataStore}, + tx_signer::Signer, +}; + +/// 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, + /// Unified transaction data store (backrun bundles + resource metering) + pub tx_data_store: TxDataStore, +} + +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().payload_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().payload_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().payload_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 { + /// Constructs a receipt for the given transaction. + pub fn build_receipt( + &self, + ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, + deposit_nonce: Option, + ) -> OpReceipt { + let receipt_builder = self.evm_config.block_executor_factory().receipt_builder(); + match receipt_builder.build_receipt(ctx) { + Ok(receipt) => receipt, + Err(ctx) => { + let receipt = alloy_consensus::Receipt { + // Success flag was added in `EIP-658: Embedding transaction status code + // in receipts`. + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + }; + + receipt_builder.build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_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 + // this is only set for post-Canyon deposit + // transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + }) + } + } + } + + /// Executes all sequencer transactions that are included in the payload attributes. + pub(super) fn execute_sequencer_transactions( + &self, + db: &mut State, + ) -> Result, PayloadBuilderError> { + let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); + + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + + 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) + })?; + + // Cache the depositor account prior to the state transition 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_nonce = (self.is_regolith_active() && sequencer_tx.is_deposit()) + .then(|| { + evm.db_mut() + .load_cache_account(sequencer_tx.signer()) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + }) + .transpose() + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( + sequencer_tx.signer(), + )) + })?; + + let ResultAndState { result, state } = match evm.transact(&sequencer_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; + } + // 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 + let gas_used = result.gas_used(); + info.cumulative_gas_used += 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(), + ); + } + + let ctx = ReceiptBuilderCtx { + tx: sequencer_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + + info.receipts.push(self.build_receipt(ctx, depositor_nonce)); + + // commit changes + evm.db_mut().commit(state); + + // 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(evm.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, + db: &mut State, + best_txs: &mut impl PayloadTxsBounds, + block_gas_limit: u64, + block_da_limit: Option, + block_da_footprint_limit: Option, + ) -> Result, PayloadBuilderError> { + 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(); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + + 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| { + info!( + 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; + + let TxData { + metering: _resource_usage, + backrun_bundles, + } = self.tx_data_store.get(&tx_hash); + + // 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 ResultAndState { result, state } = match evm.transact(&tx) { + Ok(res) => res, + Err(err) => { + if let Some(err) = err.as_invalid_tx_err() { + if err.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + log_txn(TxnExecutionResult::NonceTooLow); + trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + log_txn(TxnExecutionResult::InternalError(err.clone())); + trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + } + + continue; + } + // 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; + + // Run the per-address gas limiting before checking if the tx has + // reverted or not, as this is a check against maliciously searchers + // sending txs that are expensive to compute but always revert. + let gas_used = result.gas_used(); + if self + .address_gas_limiter + .consume_gas(tx.signer(), gas_used) + .is_err() + { + log_txn(TxnExecutionResult::MaxGasUsageExceeded); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + let is_success = result.is_success(); + if is_success { + 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(), result = ?result, "skipping reverted transaction"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } else { + log_txn(TxnExecutionResult::Reverted); + } + } + + // add gas used by the transaction to cumulative gas used, before creating the + // receipt + if let Some(max_gas_per_txn) = self.max_gas_per_txn + && gas_used > max_gas_per_txn + { + log_txn(TxnExecutionResult::MaxGasUsageExceeded); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + info.cumulative_gas_used += gas_used; + // record tx da size + info.cumulative_da_bytes_used += tx_da_size; + + // Push transaction changeset and calculate header bloom filter for receipt. + let ctx = ReceiptBuilderCtx { + tx: tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, None)); + + // commit changes + evm.db_mut().commit(state); + + // 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()); + + if is_success && !backrun_bundles.is_empty() { + self.metrics.backrun_target_txs_found_total.increment(1); + let backrun_start_time = Instant::now(); + + // Bundles are pre-sorted by total_priority_fee (descending) from the store + 'bundle_loop: for stored_bundle in backrun_bundles { + info!( + target: "payload_builder", + message = "Executing backrun bundle", + tx_hash = ?tx_hash, + bundle_id = ?stored_bundle.bundle_id, + tx_count = stored_bundle.backrun_txs.len(), + ); + + let total_effective_tip: u128 = stored_bundle + .backrun_txs + .iter() + .map(|tx| tx.effective_tip_per_gas(base_fee).unwrap_or(0)) + .sum(); + if total_effective_tip < miner_fee { + self.metrics + .backrun_bundles_rejected_low_fee_total + .increment(1); + info!( + target: "payload_builder", + bundle_id = ?stored_bundle.bundle_id, + target_fee = miner_fee, + total_effective_tip = total_effective_tip, + "Backrun bundle rejected: total effective tip below target tx" + ); + break 'bundle_loop; + } + + let total_backrun_gas: u64 = stored_bundle + .backrun_txs + .iter() + .map(|tx| tx.gas_limit()) + .sum(); + let total_backrun_da_size: u64 = stored_bundle + .backrun_txs + .iter() + .map(|tx| tx.estimated_da_size()) + .sum(); + + if let Err(result) = info.is_tx_over_limits( + total_backrun_da_size, + block_gas_limit, + tx_da_limit, + block_da_limit, + total_backrun_gas, + info.da_footprint_scalar, + block_da_footprint_limit, + ) { + self.metrics + .backrun_bundles_rejected_over_limits_total + .increment(1); + info!( + target: "payload_builder", + bundle_id = ?stored_bundle.bundle_id, + result = ?result, + "Backrun bundle rejected: exceeds block limits" + ); + continue 'bundle_loop; + } + + // All-or-nothing: simulate all txs first, only commit if all succeed + let mut pending_results = Vec::with_capacity(stored_bundle.backrun_txs.len()); + + for backrun_tx in &stored_bundle.backrun_txs { + let consensus_tx = backrun_tx.clone_into_consensus(); + let ResultAndState { result, state } = match evm.transact(&consensus_tx) { + Ok(res) => res, + Err(err) => { + if err.as_invalid_tx_err().is_some() { + self.metrics.backrun_bundles_invalid_tx_total.increment(1); + info!( + target: "payload_builder", + target_tx = ?tx_hash, + failed_tx = ?backrun_tx.hash(), + bundle_id = ?stored_bundle.bundle_id, + error = %err, + "Backrun bundle failed with invalid tx error" + ); + continue 'bundle_loop; + } + self.metrics.backrun_bundles_fatal_error_total.increment(1); + return Err(PayloadBuilderError::evm(err)); + } + }; + + if !result.is_success() { + self.metrics.backrun_bundles_reverted_total.increment(1); + info!( + target: "payload_builder", + target_tx = ?tx_hash, + failed_tx = ?backrun_tx.hash(), + bundle_id = ?stored_bundle.bundle_id, + gas_used = result.gas_used(), + "Backrun bundle reverted (all-or-nothing)" + ); + continue 'bundle_loop; + } + + pending_results.push((backrun_tx.clone(), consensus_tx, result, state)); + } + + for (backrun_tx, consensus_tx, result, state) in pending_results { + let backrun_gas_used = result.gas_used(); + + info.cumulative_gas_used += backrun_gas_used; + info.cumulative_da_bytes_used += backrun_tx.estimated_da_size(); + + let ctx = ReceiptBuilderCtx { + tx: consensus_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, None)); + + evm.db_mut().commit(state); + + let miner_fee = backrun_tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(backrun_gas_used); + + info.executed_senders.push(backrun_tx.sender()); + info.executed_transactions.push(consensus_tx.into_inner()); + } + + self.metrics.backrun_bundles_landed_total.increment(1); + } + + self.metrics + .backrun_bundle_execution_duration + .record(backrun_start_time.elapsed()); + + // Remove the target tx from the backrun bundle store as already executed + self.tx_data_store.remove_backrun_bundles(&tx_hash); + } + } + + 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/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs new file mode 100644 index 00000000..ec2a45d1 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs new file mode 100644 index 00000000..bc1063ac --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs new file mode 100644 index 00000000..cdc6ae91 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs new file mode 100644 index 00000000..5f7f46cb --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -0,0 +1,90 @@ +use crate::{ + builders::{BuilderConfig, OpPayloadBuilderCtx, flashblocks::FlashblocksConfig}, + gas_limiter::{AddressGasLimiter, args::GasLimiterArgs}, + metrics::OpRBuilderMetrics, + traits::ClientBounds, + tx_data_store::TxDataStore, +}; +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, + /// Unified transaction data store (backrun bundles + resource metering) + tx_data_store: TxDataStore, +} + +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, + tx_data_store: builder_config.tx_data_store, + }) + } + + 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()), + tx_data_store: self.tx_data_store.clone(), + } + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs new file mode 100644 index 00000000..87d4494c --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/builders/flashblocks/p2p.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/p2p.rs new file mode 100644 index 00000000..9f947d95 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs new file mode 100644 index 00000000..9562f53d --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -0,0 +1,1205 @@ +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::payload::PayloadBuilderAttributes; +use reth_basic_payload_builder::BuildOutcome; +use reth_chain_state::ExecutedBlock; +use reth_chainspec::EthChainSpec; +use reth_evm::{ConfigureEvm, execute::BlockBuilder}; +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_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; +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 + 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 = OpPayloadBuilderAttributes; + 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 + .payload_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(), + tx_data_store: self.config.tx_data_store.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 { + mut 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.payload_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()))?; + + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let db = StateProviderDatabase::new(&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(cached_reads.as_db_mut(db)) + .with_bundle_update() + .build(); + + let mut info = execute_pre_steps(&mut state, &ctx)?; + 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 state, false) + { + error!( + target: "payload_builder", + "Error adding builder txs to fallback block: {}", + e + ); + }; + + 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 + .send(payload.clone()) + .await + .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, + ) + .await + { + 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)] + async fn build_next_flashblock< + DB: Database + std::fmt::Debug + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + >( + &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> { + 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 builder_txs = + match self + .builder_tx + .add_builder_txs(&state_provider, info, ctx, state, 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, + state, + 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, state, false) + { + error!(target: "payload_builder", "Error simulating builder txs: {}", e); + }; + + 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 + .send(new_payload.clone()) + .await + .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 = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; + + async fn try_build( + &self, + args: BuildArguments, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + self.build_payload(args, best_payload).await + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct FlashblocksMetadata { + receipts: HashMap::Receipt>, + new_account_balances: HashMap, + block_number: u64, +} + +fn execute_pre_steps( + state: &mut State, + ctx: &OpPayloadBuilderCtx, +) -> Result, PayloadBuilderError> +where + DB: Database + std::fmt::Debug, + ExtraCtx: std::fmt::Debug + Default, +{ + // 1. apply pre-execution changes + ctx.evm_config + .builder_for_next_block(state, ctx.parent(), ctx.block_env_attributes.clone()) + .map_err(PayloadBuilderError::other)? + .apply_pre_execution_changes()?; + + // 2. execute sequencer transactions + let info = ctx.execute_sequencer_transactions(state)?; + + Ok(info) +} + +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 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().payload_attributes.timestamp, + mix_hash: ctx.attributes().payload_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().payload_attributes.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_hash, + }; + + // 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 executed = ExecutedBlock { + recovered_block: Arc::new(recovered_block), + execution_output: Arc::new(execution_outcome), + 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() + .payload_attributes + .parent_beacon_block_root + .unwrap(), + parent_hash: ctx.parent().hash(), + fee_recipient: ctx.attributes().suggested_fee_recipient(), + prev_randao: ctx.attributes().payload_attributes.prev_randao, + block_number: ctx.parent().number + 1, + gas_limit: ctx.block_gas_limit(), + timestamp: ctx.attributes().payload_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: info.cumulative_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/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs new file mode 100644 index 00000000..96b6f683 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs @@ -0,0 +1,447 @@ +use crate::{ + builders::flashblocks::{ + ctx::OpPayloadSyncerCtx, p2p::Message, payload::FlashblocksExecutionInfo, + }, + primitives::reth::ExecutionInfo, + traits::ClientBounds, +}; +use alloy_evm::eth::receipt_builder::ReceiptBuilderCtx; +use alloy_primitives::B64; +use eyre::{WrapErr as _, bail}; +use op_alloy_consensus::OpTxEnvelope; +use reth::revm::{State, database::StateProviderDatabase}; +use reth_basic_payload_builder::PayloadConfig; +use reth_evm::FromRecoveredTx; +use reth_node_builder::Events; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_node::{OpEngineTypes, OpPayloadBuilderAttributes}; +use reth_optimism_payload_builder::OpBuiltPayload; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +use reth_payload_builder::EthPayloadBuilderAttributes; +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::primitives::SealedHeader; + use reth_evm::{ConfigureEvm as _, execute::BlockBuilder as _}; + + 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 chain_spec = client.chain_spec(); + 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")?; + + ctx.evm_config() + .builder_for_next_block( + &mut state, + &Arc::new(SealedHeader::new(parent_header.clone(), parent_hash)), + block_env_attributes.clone(), + ) + .wrap_err("failed to create evm builder for next block")? + .apply_pre_execution_changes() + .wrap_err("failed to apply pre execution changes")?; + + 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 { + eip_1559_params: Some(eip_1559_parameters), + payload_attributes: EthPayloadBuilderAttributes { + id: payload.id(), // unused + parent: parent_hash, // unused + suggested_fee_recipient: payload.block().sealed_header().beneficiary, + withdrawals: payload + .block() + .body() + .withdrawals + .clone() + .unwrap_or_default(), + parent_beacon_block_root: payload.block().sealed_header().parent_beacon_block_root, + timestamp, + prev_randao: payload.block().sealed_header().mix_hash, + }, + ..Default::default() + }, + ); + + execute_transactions( + &mut info, + &mut state, + payload.block().body().transactions.clone(), + payload.block().header().gas_used, + ctx.evm_config(), + evm_env.clone(), + ctx.max_gas_per_txn(), + is_canyon_active(&chain_spec, timestamp), + is_regolith_active(&chain_spec, timestamp), + ) + .wrap_err("failed to execute best transactions")?; + + let builder_ctx = ctx.into_op_payload_builder_ctx( + payload_config, + evm_env.clone(), + block_env_attributes, + cancel, + ); + + 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)) +} + +#[allow(clippy::too_many_arguments)] +fn execute_transactions( + info: &mut ExecutionInfo, + state: &mut State, + txs: Vec, + gas_limit: u64, + evm_config: &reth_optimism_evm::OpEvmConfig, + evm_env: alloy_evm::EvmEnv, + max_gas_per_txn: Option, + is_canyon_active: bool, + is_regolith_active: bool, +) -> eyre::Result<()> { + use alloy_evm::{Evm as _, EvmError as _}; + use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; + use reth_evm::ConfigureEvm as _; + use reth_primitives_traits::SignerRecoverable as _; + use revm::{ + DatabaseCommit as _, + context::{TxEnv, result::ResultAndState}, + }; + + let mut evm = evm_config.evm_with_env(&mut *state, evm_env); + + for tx in txs { + let sender = tx + .recover_signer() + .wrap_err("failed to recover tx signer")?; + let tx_env = TxEnv::from_recovered_tx(&tx, sender); + let executable_tx = match tx { + OpTxEnvelope::Deposit(ref tx) => { + let deposit = DepositTransactionParts { + mint: Some(tx.mint), + source_hash: tx.source_hash, + is_system_transaction: tx.is_system_transaction, + }; + OpTransaction { + base: tx_env, + enveloped_tx: None, + deposit, + } + } + OpTxEnvelope::Legacy(_) => { + let mut tx = OpTransaction::new(tx_env); + tx.enveloped_tx = Some(vec![0x00].into()); + tx + } + OpTxEnvelope::Eip2930(_) => { + let mut tx = OpTransaction::new(tx_env); + tx.enveloped_tx = Some(vec![0x00].into()); + tx + } + OpTxEnvelope::Eip1559(_) => { + let mut tx = OpTransaction::new(tx_env); + tx.enveloped_tx = Some(vec![0x00].into()); + tx + } + OpTxEnvelope::Eip7702(_) => { + let mut tx = OpTransaction::new(tx_env); + tx.enveloped_tx = Some(vec![0x00].into()); + tx + } + }; + + let ResultAndState { result, state } = match evm.transact_raw(executable_tx) { + Ok(res) => res, + Err(err) => { + if let Some(err) = err.as_invalid_tx_err() { + // TODO: what invalid txs are allowed in the block? + // reverting txs should be allowed (?) but not straight up invalid ones + tracing::error!(error = %err, "skipping invalid transaction in flashblock"); + continue; + } + return Err(err).wrap_err("failed to execute flashblock transaction"); + } + }; + + if let Some(max_gas_per_txn) = max_gas_per_txn + && result.gas_used() > max_gas_per_txn + { + return Err(eyre::eyre!( + "transaction exceeded max gas per txn limit in flashblock" + )); + } + + let tx_gas_used = result.gas_used(); + info.cumulative_gas_used = info + .cumulative_gas_used + .checked_add(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"); + } + + let depositor_nonce = (is_regolith_active && tx.is_deposit()) + .then(|| { + evm.db_mut() + .load_cache_account(sender) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + }) + .transpose() + .wrap_err("failed to get depositor nonce")?; + + let ctx = ReceiptBuilderCtx { + tx: &tx, + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + + info.receipts.push(build_receipt( + evm_config, + ctx, + depositor_nonce, + is_canyon_active, + )); + + evm.db_mut().commit(state); + + // append sender and transaction to the respective lists + info.executed_senders.push(sender); + info.executed_transactions.push(tx.clone()); + } + + Ok(()) +} + +fn build_receipt( + evm_config: &OpEvmConfig, + ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, + deposit_nonce: Option, + is_canyon_active: bool, +) -> OpReceipt { + use alloy_consensus::Eip658Value; + use alloy_op_evm::block::receipt_builder::OpReceiptBuilder as _; + use op_alloy_consensus::OpDepositReceipt; + use reth_evm::ConfigureEvm as _; + + let receipt_builder = evm_config.block_executor_factory().receipt_builder(); + match receipt_builder.build_receipt(ctx) { + Ok(receipt) => receipt, + Err(ctx) => { + let receipt = alloy_consensus::Receipt { + // Success flag was added in `EIP-658: Embedding transaction status code + // in receipts`. + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + }; + + receipt_builder.build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_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 + // this is only set for post-Canyon deposit + // transactions. + deposit_receipt_version: is_canyon_active.then_some(1), + }) + } + } +} + +fn is_canyon_active(chain_spec: &OpChainSpec, timestamp: u64) -> bool { + use reth_optimism_chainspec::OpHardforks as _; + chain_spec.is_canyon_active_at_timestamp(timestamp) +} + +fn is_regolith_active(chain_spec: &OpChainSpec, timestamp: u64) -> bool { + use reth_optimism_chainspec::OpHardforks as _; + chain_spec.is_regolith_active_at_timestamp(timestamp) +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs new file mode 100644 index 00000000..2c1e684b --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -0,0 +1,220 @@ +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(',') + .map(|s| s.to_string()) + .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(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 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( + OpEvmConfig::optimism(ctx.chain_spec()), + 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, + OpEvmConfig::optimism(ctx.chain_spec()), + 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("custom payload builder service", Box::pin(payload_service)); + ctx.task_executor().spawn_critical( + "flashblocks payload handler", + Box::pin(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/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs new file mode 100644 index 00000000..e2f003dc --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs new file mode 100644 index 00000000..4f645cca --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -0,0 +1,717 @@ +use alloy_primitives::B256; +use futures_util::{Future, FutureExt}; +use reth::{ + providers::{BlockReaderIdExt, StateProviderFactory}, + tasks::TaskSpawner, +}; +use reth_basic_payload_builder::{ + BasicPayloadJobGeneratorConfig, HeaderForPayload, PayloadConfig, PrecachedState, +}; +use reth_node_api::{NodePrimitives, PayloadBuilderAttributes, PayloadKind}; +use reth_payload_builder::{ + KeepPayloadJobAlive, PayloadBuilderError, PayloadJob, PayloadJobGenerator, +}; +use reth_payload_primitives::BuiltPayload; +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; + +/// 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: PayloadBuilderAttributes; + /// 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, + attributes: ::Attributes, + ) -> 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 attributes.parent().is_zero() { + // use latest block if parent is zero: genesis block + self.client + .latest_header()? + .ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))? + } else { + self.client + .sealed_header_by_hash(attributes.parent())? + .ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))? + }; + + 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(attributes.timestamp()) + self.extra_block_deadline; + + let deadline = Box::pin(tokio::time::sleep(deadline)); + let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); + + 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 + '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 + '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_blocking(Box::pin(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 + '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::TokioTaskExecutor; + use reth_chain_state::ExecutedBlock; + use reth_node_api::NodePrimitives; + use reth_optimism_payload_builder::{OpPayloadPrimitives, payload::OpPayloadBuilderAttributes}; + use reth_optimism_primitives::OpPrimitives; + use reth_primitives::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 + std::thread::sleep(Duration::from_millis(10)); + } + } + } + + #[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 = TokioTaskExecutor::default(); + 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.payload_attributes.parent = client.latest_header()?.unwrap().hash(); + + { + let job = generator.new_payload_job(attr.clone())?; + 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 mut job = generator.new_payload_job(attr.clone())?; + 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/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs new file mode 100644 index 00000000..7950060a --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -0,0 +1,213 @@ +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; + +use crate::tx_data_store::TxDataStore; +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, + + /// Unified transaction data store (backrun bundles + resource metering) + pub tx_data_store: TxDataStore, +} + +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) + .field("tx_data_store", &self.tx_data_store) + .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(), + tx_data_store: TxDataStore::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(), + tx_data_store: TxDataStore::new( + args.enable_resource_metering, + args.tx_data_store_buffer_size, + ), + 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/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs new file mode 100644 index 00000000..ececc887 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/builders/standard/mod.rs b/crates/builder/op-rbuilder/src/builders/standard/mod.rs new file mode 100644 index 00000000..15b006a5 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/standard/mod.rs @@ -0,0 +1,33 @@ +use super::BuilderConfig; +use crate::{ + builders::standard::service::StandardServiceBuilder, + traits::{NodeBounds, PoolBounds}, +}; + +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/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs new file mode 100644 index 00000000..ed81640e --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -0,0 +1,626 @@ +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::payload::PayloadBuilderAttributes; +use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; +use reth_chain_state::ExecutedBlock; +use reth_evm::{ConfigureEvm, execute::BlockBuilder}; +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_primitives::OpTransactionSigned; +use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; +use reth_primitives::RecoveredBlock; +use reth_primitives_traits::InMemorySize; +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 = OpPayloadBuilderAttributes; + 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, + cancel: _, // TODO + best_payload: _, + } = args; + + let args = BuildArguments { + cached_reads, + config, + 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 args = BuildArguments { + config, + 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 + .payload_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(), + tx_data_store: self.config.tx_data_store.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 + ctx.evm_config + .builder_for_next_block(db, ctx.parent(), ctx.block_env_attributes.clone()) + .map_err(PayloadBuilderError::other)? + .apply_pre_execution_changes()?; + + let sequencer_tx_start_time = Instant::now(); + + // 3. execute sequencer transactions + let mut info = ctx.execute_sequencer_transactions(db)?; + + 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, db, 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, + db, + &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, db, false) { + error!(target: "payload_builder", "Error adding builder txs to fallback block: {}", e); + }; + + 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().payload_attributes.timestamp, + mix_hash: ctx.attributes().payload_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().payload_attributes.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_hash, + }; + + // 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"); + + // create the executed block data + let executed = ExecutedBlock { + recovered_block: Arc::new( + RecoveredBlock::>::new_sealed( + sealed_block.as_ref().clone(), + info.executed_senders, + ), + ), + execution_output: Arc::new(execution_outcome), + 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/crates/builder/op-rbuilder/src/builders/standard/service.rs b/crates/builder/op-rbuilder/src/builders/standard/service.rs new file mode 100644 index 00000000..39484e86 --- /dev/null +++ b/crates/builder/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("payload builder service", Box::pin(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/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs new file mode 100644 index 00000000..7856bb01 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs new file mode 100644 index 00000000..df712f98 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs new file mode 100644 index 00000000..7651b846 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/flashtestations/mod.rs b/crates/builder/op-rbuilder/src/flashtestations/mod.rs new file mode 100644 index 00000000..f80a2e0c --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs new file mode 100644 index 00000000..73896119 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs new file mode 100644 index 00000000..d6412bf6 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/gas_limiter/args.rs b/crates/builder/op-rbuilder/src/gas_limiter/args.rs new file mode 100644 index 00000000..ec7f8008 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/gas_limiter/error.rs b/crates/builder/op-rbuilder/src/gas_limiter/error.rs new file mode 100644 index 00000000..a85b7f77 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs b/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs new file mode 100644 index 00000000..f7898657 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/gas_limiter/mod.rs b/crates/builder/op-rbuilder/src/gas_limiter/mod.rs new file mode 100644 index 00000000..71daae7c --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs new file mode 100644 index 00000000..45add569 --- /dev/null +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -0,0 +1,191 @@ +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, + tx_data_store::{BaseApiExtServer, TxDataStoreExt}, +}; +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, sync::Arc}; + +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, OpChainSpec>>, + 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 tx_data_store = builder_config.tx_data_store.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())?; + } + + let tx_data_store_ext = TxDataStoreExt::new(tx_data_store); + ctx.modules + .add_or_replace_configured(tx_data_store_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("txlogging", task); + } + Ok(()) + }) + .launch() + .await?; + + handle.node_exit_future.await?; + Ok(()) + } +} diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs new file mode 100644 index 00000000..20d2a647 --- /dev/null +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -0,0 +1,18 @@ +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_data_store; +pub mod tx_signer; + +#[cfg(test)] +pub mod mock_tx; +#[cfg(any(test, feature = "testing"))] +pub mod tests; diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs new file mode 100644 index 00000000..5d72d9ee --- /dev/null +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -0,0 +1,310 @@ +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, + /// Count of the number of times transactions had metering information + pub metering_known_transaction: Counter, + /// Count of the number of times transactions did not have any metering information + pub metering_unknown_transaction: Counter, + /// Count of the number of times we were unable to resolve metering information due to locking + pub metering_locked_transaction: Counter, + /// Current number of backrun bundles in store + pub backrun_bundles_in_store: Gauge, + /// Number of target transactions found with backrun bundles + pub backrun_target_txs_found_total: Counter, + /// Number of backrun bundles received via RPC + pub backrun_bundles_received_total: Counter, + /// Number of backrun bundles that reverted during execution (all-or-nothing) + pub backrun_bundles_reverted_total: Counter, + /// Number of backrun bundles skipped due to invalid tx errors (nonce too low, etc.) + pub backrun_bundles_invalid_tx_total: Counter, + /// Number of backrun bundles that caused fatal EVM errors + pub backrun_bundles_fatal_error_total: Counter, + /// Number of backrun bundles rejected due to priority fee below target tx + pub backrun_bundles_rejected_low_fee_total: Counter, + /// Number of backrun bundles rejected due to exceeding block limits + pub backrun_bundles_rejected_over_limits_total: Counter, + /// Number of backrun bundles successfully landed in a block + pub backrun_bundles_landed_total: Counter, + /// Latency of inserting a backrun bundle into the store + pub backrun_bundle_insert_duration: Histogram, + /// Duration of executing all backrun bundles for a target transaction + pub backrun_bundle_execution_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/crates/builder/op-rbuilder/src/mock_tx.rs b/crates/builder/op-rbuilder/src/mock_tx.rs new file mode 100644 index 00000000..3802fa9f --- /dev/null +++ b/crates/builder/op-rbuilder/src/mock_tx.rs @@ -0,0 +1,453 @@ +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 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/crates/builder/op-rbuilder/src/monitor_tx_pool.rs b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs new file mode 100644 index 00000000..00b341f2 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs new file mode 100644 index 00000000..6e4c471b --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/primitives/mod.rs b/crates/builder/op-rbuilder/src/primitives/mod.rs new file mode 100644 index 00000000..da146d30 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs b/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs new file mode 100644 index 00000000..aaa733ca --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs @@ -0,0 +1,427 @@ +//! 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, + OpPayloadAttributes, ProtocolVersion, SuperchainSignal, +}; +use reth::builder::NodeTypes; +use reth_node_api::{EngineApiValidator, EngineTypes}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_rpc::OpEngineApiServer; +use reth_rpc_api::IntoEngineApiRpcModule; +use reth_storage_api::{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(), + Box::new(ctx.node.task_executor().clone()), + client, + EngineCapabilities::new(OP_ENGINE_CAPABILITIES.iter().copied()), + engine_validator, + ctx.config.engine.accept_execution_requests_hash, + ); + + Ok(OpEngineApiExt::new(OpEngineApi::new(inner))) + } +} + +pub struct OpEngineApiExt { + inner: OpEngineApi, +} + +impl OpEngineApiExt +where + Provider: HeaderProvider + BlockReader + StateProviderFactory + '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 + '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 signal_superchain_v1(&self, signal: SuperchainSignal) -> RpcResult { + self.inner.signal_superchain_v1(signal).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; + + /// Signals superchain information to the Engine. + /// Returns the latest supported OP-Stack protocol version of the execution engine. + /// See also + #[method(name = "engine_signalSuperchainV1")] + async fn signal_superchain_v1(&self, _signal: SuperchainSignal) -> 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/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs new file mode 100644 index 00000000..7865a1c8 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/primitives/reth/mod.rs b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs new file mode 100644 index 00000000..ed2b38b9 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/primitives/telemetry.rs b/crates/builder/op-rbuilder/src/primitives/telemetry.rs new file mode 100644 index 00000000..dfedbcc5 --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/telemetry.rs @@ -0,0 +1,37 @@ +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; + + if args.otlp_endpoint.is_none() { + return Err(eyre::eyre!("OTLP endpoint is not set")); + } + + // Otlp uses evn vars inside + + if let Some(headers) = &args.otlp_headers { + unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", headers) }; + } + + // Create OTLP layer with custom configuration + let otlp_layer = reth_tracing_otlp::span_layer( + "op-rbuilder", + &Url::parse(args.otlp_endpoint.as_ref().unwrap()).expect("Invalid OTLP endpoint"), + reth_tracing_otlp::OtlpProtocol::Http, + )?; + + // 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/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs new file mode 100644 index 00000000..f3fa8924 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs new file mode 100644 index 00000000..2883fec4 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -0,0 +1,653 @@ +use crate::tests::{ChainDriverExt, LocalInstance, framework::ONE_ETH}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{TxHash, U256}; +use alloy_provider::Provider; +use macros::rb_test; +use tips_core::{AcceptedBundle, MeterBundleResponse}; +use uuid::Uuid; + +/// Tests that backrun bundles are all-or-nothing: +/// - If any backrun tx in a bundle reverts, the entire bundle is excluded +/// - Even successful txs in the bundle are not included +#[rb_test(flashblocks)] +async fn backrun_bundle_all_or_nothing_revert(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(3, ONE_ETH).await?; + + // 1. Build target tx first (we need Recovered for bundle) + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + // Send to mempool manually (send() doesn't return the Recovered tx) + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // 2. Create backrun transactions: + // - backrun_ok: valid tx with HIGH priority (executes first, succeeds) + // - backrun_revert: tx that will REVERT (executes second, fails) + // Both must have priority fee >= target's (20) to pass fee validation + let backrun_ok = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) // High priority - executes first + .build() + .await; + let backrun_ok_hash = backrun_ok.tx_hash().clone(); + + let backrun_revert = driver + .create_transaction() + .with_signer(accounts[2]) + .with_max_priority_fee_per_gas(25) // >= target's 20, but executes second (lower than 50) + .with_revert() // This tx will revert + .build() + .await; + let backrun_revert_hash = backrun_revert.tx_hash().clone(); + + // 3. Insert backrun bundle into store + // Bundle format: [target_tx, backrun_txs...] + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun_ok, backrun_revert], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .tx_data_store() + .insert_backrun_bundle(bundle) + .expect("Failed to insert backrun bundle"); + + // 4. Build the block + driver.build_new_block().await?; + + // 5. Verify block contents + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + // Target tx SHOULD be in block (it was in mempool independently) + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + // backrun_ok should NOT be in block (all-or-nothing: bundle failed) + assert!( + !tx_hashes.contains(&backrun_ok_hash), + "backrun_ok should NOT be in block (all-or-nothing revert)" + ); + + // backrun_revert should NOT be in block (it caused the revert) + assert!( + !tx_hashes.contains(&backrun_revert_hash), + "backrun_revert should NOT be in block" + ); + + Ok(()) +} + +/// Tests that multiple backrun bundles for the same target tx are sorted by total priority fee +/// - Bundles with higher total priority fee are processed first +/// - Both bundles can land if they don't conflict +#[rb_test(flashblocks)] +async fn backrun_bundles_sorted_by_total_fee(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(5, ONE_ETH).await?; + + // 1. Build target tx with priority fee 20 + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + // Send to mempool manually + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // 2. Create Bundle A with HIGH total priority fee + // Two txs: 60 + 50 = 110 total + let bundle_a_tx1 = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(60) + .build() + .await; + let bundle_a_tx1_hash = bundle_a_tx1.tx_hash().clone(); + + let bundle_a_tx2 = driver + .create_transaction() + .with_signer(accounts[2]) + .with_max_priority_fee_per_gas(50) + .build() + .await; + let bundle_a_tx2_hash = bundle_a_tx2.tx_hash().clone(); + + // 3. Create Bundle B with LOW total priority fee + // Two txs: 30 + 25 = 55 total + let bundle_b_tx1 = driver + .create_transaction() + .with_signer(accounts[3]) + .with_max_priority_fee_per_gas(30) + .build() + .await; + let bundle_b_tx1_hash = bundle_b_tx1.tx_hash().clone(); + + let bundle_b_tx2 = driver + .create_transaction() + .with_signer(accounts[4]) + .with_max_priority_fee_per_gas(25) + .build() + .await; + let bundle_b_tx2_hash = bundle_b_tx2.tx_hash().clone(); + + // 4. Insert Bundle B FIRST (lower total fee), then Bundle A (higher total fee) + // This verifies that sorting reorders them correctly + let bundle_b = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx.clone(), bundle_b_tx1, bundle_b_tx2], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + let bundle_a = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, bundle_a_tx1, bundle_a_tx2], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + // Insert in "wrong" order - B first, then A + rbuilder + .tx_data_store() + .insert_backrun_bundle(bundle_b) + .expect("Failed to insert bundle B"); + rbuilder + .tx_data_store() + .insert_backrun_bundle(bundle_a) + .expect("Failed to insert bundle A"); + + // 5. Build the block + driver.build_new_block().await?; + + // 6. Verify block contents + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + // All txs should be in block + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx not included in block" + ); + assert!( + tx_hashes.contains(&bundle_a_tx1_hash), + "Bundle A tx1 not included in block" + ); + assert!( + tx_hashes.contains(&bundle_a_tx2_hash), + "Bundle A tx2 not included in block" + ); + assert!( + tx_hashes.contains(&bundle_b_tx1_hash), + "Bundle B tx1 not included in block" + ); + assert!( + tx_hashes.contains(&bundle_b_tx2_hash), + "Bundle B tx2 not included in block" + ); + + // 7. Verify ordering: Bundle A txs come BEFORE Bundle B txs + // (higher total fee bundle processed first) + let a_tx1_pos = tx_hashes + .iter() + .position(|h| *h == bundle_a_tx1_hash) + .expect("Bundle A tx1 position not found"); + let a_tx2_pos = tx_hashes + .iter() + .position(|h| *h == bundle_a_tx2_hash) + .expect("Bundle A tx2 position not found"); + let b_tx1_pos = tx_hashes + .iter() + .position(|h| *h == bundle_b_tx1_hash) + .expect("Bundle B tx1 position not found"); + let b_tx2_pos = tx_hashes + .iter() + .position(|h| *h == bundle_b_tx2_hash) + .expect("Bundle B tx2 position not found"); + + // Bundle A (higher total fee) should come before Bundle B + let bundle_a_last_pos = a_tx1_pos.max(a_tx2_pos); + let bundle_b_first_pos = b_tx1_pos.min(b_tx2_pos); + + assert!( + bundle_a_last_pos < bundle_b_first_pos, + "Bundle A (total fee 110) should be processed before Bundle B (total fee 55). \ + Bundle A last tx at pos {}, Bundle B first tx at pos {}", + bundle_a_last_pos, + bundle_b_first_pos + ); + + Ok(()) +} + +/// Tests that backrun bundles are rejected if total bundle priority fee < target tx priority fee +#[rb_test(flashblocks)] +async fn backrun_bundle_rejected_low_total_fee(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(3, ONE_ETH).await?; + + // 1. Build target tx with HIGH priority fee (100) + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(100) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + // Send to mempool manually + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // 2. Create backrun transactions with LOW total fee: + // - backrun_1: priority fee 30 + // - backrun_2: priority fee 20 + // - Total: 30 + 20 = 50 < target's 100 → bundle rejected + let backrun_1 = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(30) + .build() + .await; + let backrun_1_hash = backrun_1.tx_hash().clone(); + + let backrun_2 = driver + .create_transaction() + .with_signer(accounts[2]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let backrun_2_hash = backrun_2.tx_hash().clone(); + + // 3. Insert backrun bundle into store + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun_1, backrun_2], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .tx_data_store() + .insert_backrun_bundle(bundle) + .expect("Failed to insert backrun bundle"); + + // 4. Build the block + driver.build_new_block().await?; + + // 5. Verify block contents + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + // Target tx SHOULD be in block (it was in mempool independently) + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + // backrun_1 should NOT be in block (bundle rejected: total fee 50 < target fee 100) + assert!( + !tx_hashes.contains(&backrun_1_hash), + "backrun_1 should NOT be in block (bundle rejected: total fee below target)" + ); + + // backrun_2 should NOT be in block (bundle rejected) + assert!( + !tx_hashes.contains(&backrun_2_hash), + "backrun_2 should NOT be in block" + ); + + Ok(()) +} + +#[rb_test(flashblocks)] +async fn backrun_bundle_rejected_exceeds_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(2, ONE_ETH).await?; + + // Set gas limit high enough for builder tx + target tx, but not backrun + // Flashblocks has additional overhead, so use higher limits + // Set limit to 500k, backrun requests 1M -> rejected + driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (500_000,)) + .await?; + + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + let backrun = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) + .with_gas_limit(1_000_000) + .build() + .await; + let backrun_hash = backrun.tx_hash().clone(); + + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .tx_data_store() + .insert_backrun_bundle(bundle) + .expect("Failed to insert backrun bundle"); + + driver.build_new_block().await?; + + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + assert!( + !tx_hashes.contains(&backrun_hash), + "Backrun should NOT be in block (exceeds gas limit)" + ); + + Ok(()) +} + +#[rb_test(flashblocks)] +async fn backrun_bundle_rejected_exceeds_da_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(2, ONE_ETH).await?; + + // Set DA limit high enough for builder tx + target tx, but not backrun + // Flashblocks has additional overhead, so use higher limits + // Set block limit to 500 bytes, then create a backrun with large calldata + driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 500)) + .await?; + + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // Create backrun with large calldata to exceed DA limit + let backrun = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) + .with_input(vec![0u8; 1000].into()) + .build() + .await; + let backrun_hash = backrun.tx_hash().clone(); + + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .tx_data_store() + .insert_backrun_bundle(bundle) + .expect("Failed to insert backrun bundle"); + + driver.build_new_block().await?; + + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + assert!( + !tx_hashes.contains(&backrun_hash), + "Backrun should NOT be in block (exceeds DA limit)" + ); + + Ok(()) +} + +/// Tests that backrun bundles with invalid tx errors (e.g. nonce too low) are skipped gracefully +#[rb_test(flashblocks)] +async fn backrun_bundle_invalid_tx_skipped(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(3, ONE_ETH).await?; + + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + let backrun_tx = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) + .with_nonce(0) + .build() + .await; + let backrun_tx_hash = backrun_tx.tx_hash().clone(); + + // Send a conflicting tx with same nonce but higher fee - it will be included first + let conflicting_tx = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(100) + .with_nonce(0) + .send() + .await?; + let conflicting_tx_hash = conflicting_tx.tx_hash().clone(); + + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun_tx], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .tx_data_store() + .insert_backrun_bundle(bundle) + .expect("Failed to insert backrun bundle"); + + driver.build_new_block().await?; + + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + assert!( + tx_hashes.contains(&conflicting_tx_hash), + "Conflicting tx should be included in block" + ); + + assert!( + !tx_hashes.contains(&backrun_tx_hash), + "Backrun tx should NOT be in block (nonce-too-low EVM error)" + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/data_availability.rs b/crates/builder/op-rbuilder/src/tests/data_availability.rs new file mode 100644 index 00000000..9621f398 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs new file mode 100644 index 00000000..c60e9d5a --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -0,0 +1,662 @@ +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?; + // We could only produce block with deposits + builder tx because of short time frame + assert_eq!(block.transactions.len(), 2); + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(1, 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_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/crates/builder/op-rbuilder/src/tests/flashtestations.rs b/crates/builder/op-rbuilder/src/tests/flashtestations.rs new file mode 100644 index 00000000..002ce5cf --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/forks.rs b/crates/builder/op-rbuilder/src/tests/forks.rs new file mode 100644 index 00000000..d136c375 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs new file mode 100644 index 00000000..5638293b --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json new file mode 100644 index 00000000..b661a1b2 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json new file mode 100644 index 00000000..1a3b1e6c --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json new file mode 100644 index 00000000..d823e0c3 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json new file mode 100644 index 00000000..1ccf04a5 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl b/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl new file mode 100644 index 00000000..824b494e --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/artifacts/quote-output.bin b/crates/builder/op-rbuilder/src/tests/framework/artifacts/quote-output.bin new file mode 100644 index 00000000..70a42bbf Binary files /dev/null and b/crates/builder/op-rbuilder/src/tests/framework/artifacts/quote-output.bin differ diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-jwt-secret.txt b/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-jwt-secret.txt new file mode 100644 index 00000000..6e72091c --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-jwt-secret.txt @@ -0,0 +1 @@ +688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-quote.bin b/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-quote.bin new file mode 100644 index 00000000..aea0077d Binary files /dev/null and b/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-quote.bin differ diff --git a/crates/builder/op-rbuilder/src/tests/framework/contracts.rs b/crates/builder/op-rbuilder/src/tests/framework/contracts.rs new file mode 100644 index 00000000..5f9b2449 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/driver.rs b/crates/builder/op-rbuilder/src/tests/framework/driver.rs new file mode 100644 index 00000000..5246bd5e --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/driver.rs @@ -0,0 +1,362 @@ +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 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(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/crates/builder/op-rbuilder/src/tests/framework/external.rs b/crates/builder/op-rbuilder/src/tests/framework/external.rs new file mode 100644 index 00000000..8bb99eda --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs new file mode 100644 index 00000000..9458aea7 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -0,0 +1,618 @@ +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_data_store::TxDataStore, + 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::TaskManager, +}; +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; + +/// Clears OTEL-related environment variables that can interfere with CLI argument parsing. +/// This is necessary because clap reads env vars for args with `env = "..."` attributes, +/// and external OTEL env vars (e.g., `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`) may contain +/// values that are incompatible with the CLI's expected values. +fn clear_otel_env_vars() { + for key in [ + "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_HEADERS", + "OTEL_EXPORTER_OTLP_PROTOCOL", + "OTEL_LOGS_EXPORTER", + "OTEL_METRICS_EXPORTER", + "OTEL_TRACES_EXPORTER", + "OTEL_SDK_DISABLED", + ] { + // SAFETY: We're in a test environment where env var mutation is acceptable + unsafe { std::env::remove_var(key) }; + } +} + +/// 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, + tx_data_store: TxDataStore, +} + +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 tx_data_store = builder_config.tx_data_store.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.executor()) + .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, + tx_data_store, + }) + } + + /// 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 { + clear_otel_env_vars(); + 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 { + clear_otel_env_vars(); + 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 fn tx_data_store(&self) -> &TxDataStore { + &self.tx_data_store + } + + 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, + }; + + 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() -> TaskManager { + TaskManager::new(tokio::runtime::Handle::current()) +} + +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/crates/builder/op-rbuilder/src/tests/framework/macros/Cargo.toml b/crates/builder/op-rbuilder/src/tests/framework/macros/Cargo.toml new file mode 100644 index 00000000..c131b63b --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/macros/src/lib.rs b/crates/builder/op-rbuilder/src/tests/framework/macros/src/lib.rs new file mode 100644 index 00000000..daa88c7e --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs new file mode 100644 index 00000000..b259215b --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs new file mode 100644 index 00000000..35872764 --- /dev/null +++ b/crates/builder/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::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/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs new file mode 100644 index 00000000..99772de1 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/gas_limiter.rs b/crates/builder/op-rbuilder/src/tests/gas_limiter.rs new file mode 100644 index 00000000..00dfb035 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs b/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs new file mode 100644 index 00000000..619509db --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs new file mode 100644 index 00000000..87384e5c --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -0,0 +1,49 @@ +// 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 backrun; + +#[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/crates/builder/op-rbuilder/src/tests/ordering.rs b/crates/builder/op-rbuilder/src/tests/ordering.rs new file mode 100644 index 00000000..bba31b0e --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tests/revert.rs b/crates/builder/op-rbuilder/src/tests/revert.rs new file mode 100644 index 00000000..76e1c5b9 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/revert.rs @@ -0,0 +1,437 @@ +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. + // transactions from [0, i] should be dropped, transactions from [i+1, 10] should be queued + for j in 0..=i { + assert!(rbuilder.pool().is_dropped(*pending_txn[j].tx_hash())); + } + for j in i + 1..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 progresses beyond the bundle range. The transaction is dropped from the pool + driver.build_new_block().await?; // Block 4 + assert!(rbuilder.pool().is_dropped(*reverted_bundle.tx_hash())); + + driver.build_new_block().await?; // Block 5 + 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?; + let receipt = provider.get_transaction_receipt(*tx_hash).await?; + assert!(receipt.is_none()); + + let _ = driver.build_new_block().await?; + let receipt = provider.get_transaction_receipt(*tx_hash).await?; + assert!(receipt.is_none()); + + // Dropped + let _ = driver.build_new_block().await?; + let receipt = provider.get_transaction_receipt(*tx_hash).await; + + assert!(receipt.is_err()); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/smoke.rs b/crates/builder/op-rbuilder/src/tests/smoke.rs new file mode 100644 index 00000000..63cfa77b --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/smoke.rs @@ -0,0 +1,298 @@ +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. +#[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(()) +} + +#[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(()) +} + +#[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/crates/builder/op-rbuilder/src/tests/txpool.rs b/crates/builder/op-rbuilder/src/tests/txpool.rs new file mode 100644 index 00000000..235ccd26 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/txpool.rs @@ -0,0 +1,69 @@ +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, + ..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/crates/builder/op-rbuilder/src/traits.rs b/crates/builder/op-rbuilder/src/traits.rs new file mode 100644 index 00000000..ef9dde45 --- /dev/null +++ b/crates/builder/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/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs new file mode 100644 index 00000000..64f7e43f --- /dev/null +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -0,0 +1,300 @@ +use std::{borrow::Cow, sync::Arc}; + +use alloy_consensus::{BlobTransactionValidationError, conditional::BlockConditionalAttributes}; +use alloy_eips::{Typed2718, 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::{Recovered, kzg::KzgSettings}; +use reth_primitives_traits::InMemorySize; +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 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/crates/builder/op-rbuilder/src/tx_data_store.rs b/crates/builder/op-rbuilder/src/tx_data_store.rs new file mode 100644 index 00000000..f6c6b6c0 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tx_data_store.rs @@ -0,0 +1,548 @@ +use crate::{metrics::OpRBuilderMetrics, tx::FBPooledTransaction}; +use alloy_consensus::Transaction; +use alloy_primitives::{Address, TxHash}; +use concurrent_queue::ConcurrentQueue; +use jsonrpsee::{ + core::{RpcResult, async_trait}, + proc_macros::rpc, +}; +use reth_optimism_txpool::OpPooledTransaction; +use reth_transaction_pool::PoolTransaction; +use std::{ + fmt::Debug, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::Instant, +}; +use tips_core::{AcceptedBundle, MeterBundleResponse}; +use tracing::{debug, info, warn}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct StoredBackrunBundle { + pub bundle_id: Uuid, + pub sender: Address, + pub backrun_txs: Vec, + pub total_priority_fee: u128, +} + +#[derive(Clone, Default)] +pub struct TxData { + pub metering: Option, + pub backrun_bundles: Vec, +} + +struct StoreData { + by_tx_hash: dashmap::DashMap, + lru: ConcurrentQueue, + metering_enabled: AtomicBool, +} + +#[derive(Clone)] +pub struct TxDataStore { + data: Arc, + metrics: OpRBuilderMetrics, +} + +impl Debug for TxDataStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TxDataStore") + .field("entries", &self.data.by_tx_hash.len()) + .field( + "metering_enabled", + &self.data.metering_enabled.load(Ordering::Relaxed), + ) + .finish() + } +} + +impl TxDataStore { + pub fn new(enable_resource_metering: bool, buffer_size: usize) -> Self { + Self { + data: Arc::new(StoreData { + by_tx_hash: dashmap::DashMap::new(), + lru: ConcurrentQueue::bounded(buffer_size), + metering_enabled: AtomicBool::new(enable_resource_metering), + }), + metrics: OpRBuilderMetrics::default(), + } + } + + fn evict_if_needed(&self) { + if self.data.lru.is_full() + && let Ok(evicted_hash) = self.data.lru.pop() + { + self.data.by_tx_hash.remove(&evicted_hash); + debug!( + target: "tx_data_store", + evicted_tx = ?evicted_hash, + "Evicted old transaction data" + ); + } + } + + pub fn get(&self, tx_hash: &TxHash) -> TxData { + let metering_enabled = self.data.metering_enabled.load(Ordering::Relaxed); + + let Some(entry) = self.data.by_tx_hash.get(tx_hash) else { + if metering_enabled { + self.metrics.metering_unknown_transaction.increment(1); + } + return TxData { + metering: None, + backrun_bundles: vec![], + }; + }; + + let data = entry.clone(); + + if metering_enabled { + if data.metering.is_some() { + self.metrics.metering_known_transaction.increment(1); + } else { + self.metrics.metering_unknown_transaction.increment(1); + } + } + + data + } + + pub fn insert_backrun_bundle(&self, bundle: AcceptedBundle) -> Result<(), String> { + if bundle.txs.len() < 2 { + return Err("Bundle must have at least 2 transactions (target + backrun)".to_string()); + } + + let target_tx_hash = bundle.txs[0].tx_hash(); + + // Convert OpTxEnvelope transactions to FBPooledTransaction + let backrun_txs: Vec = bundle.txs[1..] + .iter() + .filter_map(|tx| { + let (envelope, signer) = tx.clone().into_parts(); + let pooled_envelope: op_alloy_consensus::OpPooledTransaction = + envelope.try_into().ok()?; + let recovered_pooled = + alloy_consensus::transaction::Recovered::new_unchecked(pooled_envelope, signer); + let pooled = OpPooledTransaction::from_pooled(recovered_pooled); + Some(FBPooledTransaction::from(pooled)) + }) + .collect(); + + if backrun_txs.is_empty() { + return Err("No valid poolable transactions in backrun bundle".to_string()); + } + + let backrun_sender = backrun_txs[0].sender(); + + self.evict_if_needed(); + let _ = self.data.lru.push(target_tx_hash); + + let total_priority_fee: u128 = backrun_txs + .iter() + .map(|tx| tx.max_priority_fee_per_gas().unwrap_or(0)) + .sum(); + + let stored_bundle = StoredBackrunBundle { + bundle_id: *bundle.uuid(), + sender: backrun_sender, + backrun_txs, + total_priority_fee, + }; + + let replaced = { + let mut entry = self.data.by_tx_hash.entry(target_tx_hash).or_default(); + let replaced = if let Some(pos) = entry + .backrun_bundles + .iter() + .position(|b| b.sender == backrun_sender) + { + entry.backrun_bundles[pos] = stored_bundle; + true + } else { + entry.backrun_bundles.push(stored_bundle); + false + }; + entry + .backrun_bundles + .sort_by(|a, b| b.total_priority_fee.cmp(&a.total_priority_fee)); + replaced + }; + + if replaced { + info!( + target: "tx_data_store", + target_tx = ?target_tx_hash, + sender = ?backrun_sender, + bundle_id = ?bundle.uuid(), + "Replaced existing backrun bundle from same sender" + ); + } + + info!( + target: "tx_data_store", + target_tx = ?target_tx_hash, + sender = ?backrun_sender, + bundle_id = ?bundle.uuid(), + "Stored backrun bundle" + ); + + self.metrics + .backrun_bundles_in_store + .set(self.data.by_tx_hash.len() as f64); + + Ok(()) + } + + pub fn remove_backrun_bundles(&self, target_tx_hash: &TxHash) { + if let Some(mut entry) = self.data.by_tx_hash.get_mut(target_tx_hash) { + let bundle_count = entry.backrun_bundles.len(); + entry.backrun_bundles.clear(); + + if bundle_count > 0 { + info!( + target: "tx_data_store", + target_tx = ?target_tx_hash, + bundle_count, + "Removed backrun bundles" + ); + } + + if entry.metering.is_none() && entry.backrun_bundles.is_empty() { + drop(entry); + self.data.by_tx_hash.remove(target_tx_hash); + } + } + + self.metrics + .backrun_bundles_in_store + .set(self.data.by_tx_hash.len() as f64); + } + + pub fn insert_metering(&self, tx_hash: TxHash, metering_info: MeterBundleResponse) { + self.evict_if_needed(); + let _ = self.data.lru.push(tx_hash); + + let mut entry = self.data.by_tx_hash.entry(tx_hash).or_default(); + entry.metering = Some(metering_info); + } + + pub fn clear_metering(&self) { + for mut entry in self.data.by_tx_hash.iter_mut() { + entry.metering = None; + } + self.data + .by_tx_hash + .retain(|_, v| v.metering.is_some() || !v.backrun_bundles.is_empty()); + } + + pub fn set_metering_enabled(&self, enabled: bool) { + self.data.metering_enabled.store(enabled, Ordering::Relaxed); + } + + pub fn len(&self) -> usize { + self.data.by_tx_hash.len() + } + + pub fn is_empty(&self) -> bool { + self.data.by_tx_hash.is_empty() + } +} + +impl Default for TxDataStore { + fn default() -> Self { + Self::new(false, 10_000) + } +} + +#[cfg_attr(not(test), rpc(server, namespace = "base"))] +#[cfg_attr(test, rpc(server, client, namespace = "base"))] +pub trait BaseApiExt { + #[method(name = "sendBackrunBundle")] + async fn send_backrun_bundle(&self, bundle: AcceptedBundle) -> RpcResult<()>; + + #[method(name = "setMeteringInformation")] + async fn set_metering_information( + &self, + tx_hash: TxHash, + meter: MeterBundleResponse, + ) -> RpcResult<()>; + + #[method(name = "setMeteringEnabled")] + async fn set_metering_enabled(&self, enabled: bool) -> RpcResult<()>; + + #[method(name = "clearMeteringInformation")] + async fn clear_metering_information(&self) -> RpcResult<()>; +} + +pub struct TxDataStoreExt { + store: TxDataStore, + metrics: OpRBuilderMetrics, +} + +impl TxDataStoreExt { + pub fn new(store: TxDataStore) -> Self { + Self { + store, + metrics: OpRBuilderMetrics::default(), + } + } +} + +#[async_trait] +impl BaseApiExtServer for TxDataStoreExt { + async fn send_backrun_bundle(&self, bundle: AcceptedBundle) -> RpcResult<()> { + self.metrics.backrun_bundles_received_total.increment(1); + + let start = Instant::now(); + self.store.insert_backrun_bundle(bundle).map_err(|e| { + warn!(target: "tx_data_store", error = %e, "Failed to store bundle"); + jsonrpsee::types::ErrorObject::owned( + jsonrpsee::types::error::INTERNAL_ERROR_CODE, + format!("Failed to store bundle: {e}"), + None::<()>, + ) + })?; + self.metrics + .backrun_bundle_insert_duration + .record(start.elapsed().as_secs_f64()); + + Ok(()) + } + + async fn set_metering_information( + &self, + tx_hash: TxHash, + metering: MeterBundleResponse, + ) -> RpcResult<()> { + self.store.insert_metering(tx_hash, metering); + Ok(()) + } + + async fn set_metering_enabled(&self, enabled: bool) -> RpcResult<()> { + self.store.set_metering_enabled(enabled); + Ok(()) + } + + async fn clear_metering_information(&self) -> RpcResult<()> { + self.store.clear_metering(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::{SignableTransaction, transaction::Recovered}; + use alloy_primitives::{Address, B256, TxHash, U256}; + use alloy_provider::network::TxSignerSync; + use alloy_signer_local::PrivateKeySigner; + use op_alloy_consensus::OpTxEnvelope; + use op_alloy_rpc_types::OpTransactionRequest; + + fn create_recovered_tx( + from: &PrivateKeySigner, + nonce: u64, + to: Address, + ) -> Recovered { + let mut txn = OpTransactionRequest::default() + .value(U256::from(10_000)) + .gas_limit(21_000) + .max_fee_per_gas(200) + .max_priority_fee_per_gas(100) + .from(from.address()) + .to(to) + .nonce(nonce) + .build_typed_tx() + .unwrap(); + + let sig = from.sign_transaction_sync(&mut txn).unwrap(); + let envelope = + OpTxEnvelope::Eip1559(txn.eip1559().cloned().unwrap().into_signed(sig).clone()); + Recovered::new_unchecked(envelope, from.address()) + } + + fn create_test_accepted_bundle(txs: Vec>) -> AcceptedBundle { + AcceptedBundle { + uuid: Uuid::new_v4(), + txs, + block_number: 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + } + } + + fn create_test_metering(gas_used: u64) -> MeterBundleResponse { + MeterBundleResponse { + bundle_hash: B256::random(), + bundle_gas_price: U256::from(123), + coinbase_diff: U256::from(123), + eth_sent_to_coinbase: U256::from(123), + gas_fees: U256::from(123), + results: vec![], + state_block_number: 4, + state_flashblock_index: None, + total_gas_used: gas_used, + total_execution_time_us: 533, + } + } + + #[test] + fn test_unified_get() { + let store = TxDataStore::new(true, 100); + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + + let target_tx = create_recovered_tx(&alice, 0, bob.address()); + let backrun_tx = create_recovered_tx(&alice, 1, bob.address()); + let target_tx_hash = target_tx.tx_hash(); + + store.insert_metering(target_tx_hash, create_test_metering(21000)); + + let bundle = create_test_accepted_bundle(vec![target_tx, backrun_tx]); + store.insert_backrun_bundle(bundle).unwrap(); + + let result = store.get(&target_tx_hash); + assert!(result.metering.is_some()); + assert_eq!(result.metering.as_ref().unwrap().total_gas_used, 21000); + assert_eq!(result.backrun_bundles.len(), 1); + } + + #[test] + fn test_backrun_bundle_store() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + + let target_tx = create_recovered_tx(&alice, 0, bob.address()); + let backrun_tx1 = create_recovered_tx(&alice, 1, bob.address()); + let backrun_tx2 = create_recovered_tx(&alice, 2, bob.address()); + + let target_tx_hash = target_tx.tx_hash(); + + let store = TxDataStore::new(false, 100); + + let single_tx_bundle = create_test_accepted_bundle(vec![target_tx.clone()]); + assert!(store.insert_backrun_bundle(single_tx_bundle).is_err()); + assert_eq!(store.len(), 0); + + let valid_bundle = + create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx1.clone()]); + assert!(store.insert_backrun_bundle(valid_bundle).is_ok()); + assert_eq!(store.len(), 1); + + let data = store.get(&target_tx_hash); + assert_eq!(data.backrun_bundles.len(), 1); + assert_eq!(data.backrun_bundles[0].backrun_txs.len(), 1); + assert_eq!( + *data.backrun_bundles[0].backrun_txs[0].hash(), + backrun_tx1.tx_hash() + ); + + let replacement_bundle = + create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx2.clone()]); + assert!(store.insert_backrun_bundle(replacement_bundle).is_ok()); + assert_eq!(store.len(), 1); + + let data = store.get(&target_tx_hash); + assert_eq!(data.backrun_bundles.len(), 1); + assert_eq!( + *data.backrun_bundles[0].backrun_txs[0].hash(), + backrun_tx2.tx_hash() + ); + + store.remove_backrun_bundles(&target_tx_hash); + assert!(store.get(&target_tx_hash).backrun_bundles.is_empty()); + } + + #[test] + fn test_backrun_bundle_multiple_senders() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + let charlie = PrivateKeySigner::random(); + + let target_tx = create_recovered_tx(&alice, 0, bob.address()); + let alice_backrun = create_recovered_tx(&alice, 1, bob.address()); + let charlie_backrun = create_recovered_tx(&charlie, 0, bob.address()); + + let target_tx_hash = target_tx.tx_hash(); + let store = TxDataStore::new(false, 100); + + let alice_bundle = create_test_accepted_bundle(vec![target_tx.clone(), alice_backrun]); + store.insert_backrun_bundle(alice_bundle).unwrap(); + + let charlie_bundle = create_test_accepted_bundle(vec![target_tx.clone(), charlie_backrun]); + store.insert_backrun_bundle(charlie_bundle).unwrap(); + + let data = store.get(&target_tx_hash); + assert_eq!(data.backrun_bundles.len(), 2); + } + + #[test] + fn test_lru_eviction() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + + let store = TxDataStore::new(false, 2); + + for nonce in 0..3u64 { + let target = create_recovered_tx(&alice, nonce * 2, bob.address()); + let backrun = create_recovered_tx(&alice, nonce * 2 + 1, bob.address()); + let bundle = create_test_accepted_bundle(vec![target, backrun]); + let _ = store.insert_backrun_bundle(bundle); + } + + assert_eq!(store.len(), 2); + } + + #[test] + fn test_metering_insert_and_get() { + let store = TxDataStore::new(true, 100); + let tx_hash = TxHash::random(); + let meter_data = create_test_metering(21000); + + store.insert_metering(tx_hash, meter_data); + let data = store.get(&tx_hash); + assert_eq!(data.metering.as_ref().unwrap().total_gas_used, 21000); + + store.insert_metering(tx_hash, create_test_metering(50000)); + let data = store.get(&tx_hash); + assert_eq!(data.metering.as_ref().unwrap().total_gas_used, 50000); + } + + #[test] + fn test_clear_metering() { + let store = TxDataStore::new(true, 100); + + let tx1 = TxHash::random(); + let tx2 = TxHash::random(); + + store.insert_metering(tx1, create_test_metering(1000)); + store.insert_metering(tx2, create_test_metering(2000)); + + assert!(store.get(&tx1).metering.is_some()); + assert!(store.get(&tx2).metering.is_some()); + + store.clear_metering(); + + assert!(store.get(&tx1).metering.is_none()); + assert!(store.get(&tx2).metering.is_none()); + } +} diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs new file mode 100644 index 00000000..6b8c86d9 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -0,0 +1,203 @@ +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::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, + }; + 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/crates/builder/p2p/Cargo.toml b/crates/builder/p2p/Cargo.toml new file mode 100644 index 00000000..33d57c31 --- /dev/null +++ b/crates/builder/p2p/Cargo.toml @@ -0,0 +1,32 @@ +[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.rust] +unreachable_pub = "deny" +dead_code = "allow" + +[lints.clippy] +unused_async = "warn" diff --git a/crates/builder/p2p/src/behaviour.rs b/crates/builder/p2p/src/behaviour.rs new file mode 100644 index 00000000..bedfb78e --- /dev/null +++ b/crates/builder/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/crates/builder/p2p/src/lib.rs b/crates/builder/p2p/src/lib.rs new file mode 100644 index 00000000..757be3b2 --- /dev/null +++ b/crates/builder/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/crates/builder/p2p/src/outgoing.rs b/crates/builder/p2p/src/outgoing.rs new file mode 100644 index 00000000..2440e0f7 --- /dev/null +++ b/crates/builder/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/crates/builder/tdx-quote-provider/.dockerignore b/crates/builder/tdx-quote-provider/.dockerignore new file mode 100644 index 00000000..de51f7f6 --- /dev/null +++ b/crates/builder/tdx-quote-provider/.dockerignore @@ -0,0 +1,6 @@ +target/ +.git/ +.github/ +.gitignore +tests/ +README.md \ No newline at end of file diff --git a/crates/builder/tdx-quote-provider/Cargo.toml b/crates/builder/tdx-quote-provider/Cargo.toml new file mode 100644 index 00000000..f672de56 --- /dev/null +++ b/crates/builder/tdx-quote-provider/Cargo.toml @@ -0,0 +1,47 @@ +[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"} + +[lints.rust] +dead_code = "allow" + +[lints.clippy] +unused_async = "warn" + +[dev-dependencies] +reqwest.workspace = true + +[[bin]] +name = "tdx-quote-provider" +path = "src/main.rs" + +[lib] +name = "tdx_quote_provider" +path = "src/lib.rs" diff --git a/crates/builder/tdx-quote-provider/Dockerfile b/crates/builder/tdx-quote-provider/Dockerfile new file mode 100644 index 00000000..a9c2b715 --- /dev/null +++ b/crates/builder/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/crates/builder/tdx-quote-provider/README.md b/crates/builder/tdx-quote-provider/README.md new file mode 100644 index 00000000..99b24cd0 --- /dev/null +++ b/crates/builder/tdx-quote-provider/README.md @@ -0,0 +1,100 @@ +# 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 . +``` + +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 +``` diff --git a/crates/builder/tdx-quote-provider/src/lib.rs b/crates/builder/tdx-quote-provider/src/lib.rs new file mode 100644 index 00000000..3aad8c3a --- /dev/null +++ b/crates/builder/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/crates/builder/tdx-quote-provider/src/main.rs b/crates/builder/tdx-quote-provider/src/main.rs new file mode 100644 index 00000000..a820ce97 --- /dev/null +++ b/crates/builder/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/crates/builder/tdx-quote-provider/src/metrics.rs b/crates/builder/tdx-quote-provider/src/metrics.rs new file mode 100644 index 00000000..695fad58 --- /dev/null +++ b/crates/builder/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/crates/builder/tdx-quote-provider/src/provider.rs b/crates/builder/tdx-quote-provider/src/provider.rs new file mode 100644 index 00000000..0479edf8 --- /dev/null +++ b/crates/builder/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/crates/builder/tdx-quote-provider/src/server.rs b/crates/builder/tdx-quote-provider/src/server.rs new file mode 100644 index 00000000..b8d5e295 --- /dev/null +++ b/crates/builder/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/crates/builder/tdx-quote-provider/tests/mod.rs b/crates/builder/tdx-quote-provider/tests/mod.rs new file mode 100644 index 00000000..f4a70f9f --- /dev/null +++ b/crates/builder/tdx-quote-provider/tests/mod.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +mod simple; diff --git a/crates/builder/tdx-quote-provider/tests/simple.rs b/crates/builder/tdx-quote-provider/tests/simple.rs new file mode 100644 index 00000000..afa06028 --- /dev/null +++ b/crates/builder/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/crates/builder/tdx-quote-provider/tests/test_data/quote.bin b/crates/builder/tdx-quote-provider/tests/test_data/quote.bin new file mode 100644 index 00000000..ba753696 Binary files /dev/null and b/crates/builder/tdx-quote-provider/tests/test_data/quote.bin differ diff --git a/rustfmt.toml b/rustfmt.toml index 6e800413..b7910780 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -4,3 +4,8 @@ group_imports = "StdExternalCrate" use_small_heuristics = "Max" trailing_comma = "Vertical" use_field_init_shorthand = true + +# Ignore builder crates - they have their own formatting style +ignore = [ + "crates/builder/**", +] diff --git a/scripts/check-crate-deps.sh b/scripts/check-crate-deps.sh index f8b478aa..6ae94711 100755 --- a/scripts/check-crate-deps.sh +++ b/scripts/check-crate-deps.sh @@ -24,7 +24,7 @@ VIOLATIONS=$(cargo metadata --format-version 1 --no-deps | jq -r ' ') if [ -n "$VIOLATIONS" ]; then - echo "ERROR: Found dependency boundary violations:" + echo "ERROR: Found shared -> client dependency violations:" echo "$VIOLATIONS" | while read -r violation; do echo " - $violation" done @@ -33,4 +33,29 @@ if [ -n "$VIOLATIONS" ]; then exit 1 fi -echo "All shared crates have valid dependencies (no client crate dependencies)" +echo "Checking that shared crates don't depend on builder crates..." + +# Check for shared -> builder violations +BUILDER_VIOLATIONS=$(cargo metadata --format-version 1 --no-deps | jq -r ' + [.packages[] + | select(.manifest_path | contains("/crates/shared/")) + | . as $pkg + | .dependencies[] + | select(.path) + | select(.path | contains("/crates/builder/")) + | "\($pkg.name) -> \(.name)" + ] + | .[] +') + +if [ -n "$BUILDER_VIOLATIONS" ]; then + echo "ERROR: Found shared -> builder dependency violations:" + echo "$BUILDER_VIOLATIONS" | while read -r violation; do + echo " - $violation" + done + echo "" + echo "Shared crates (crates/shared/) must not depend on builder crates (crates/builder/)" + exit 1 +fi + +echo "All crate dependencies are valid"