diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c1cf89..d4c83ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -178,14 +178,12 @@ jobs: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: dtolnay/rust-toolchain@0c3131df9e5407c0c36352032d04af846dbe0fb7 # nightly - with: - components: rustfmt + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: cache-on-failure: true add-rust-environment-hash-key: "false" - key: nightly-${{ hashFiles('Cargo.lock') }} + key: stable-${{ hashFiles('Cargo.lock') }} - name: Install just uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 - - run: just check-format + - run: just check-deny diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml new file mode 100644 index 0000000..b883b12 --- /dev/null +++ b/.github/workflows/ui.yml @@ -0,0 +1,109 @@ +name: UI +permissions: + contents: read + +on: + push: + branches: [ master ] + paths: ['ui/**'] + pull_request: + paths: ['ui/**'] + +jobs: + changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + projects: ${{ steps.filter.outputs.projects }} + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: paths + with: + filters: | + tips: + - 'ui/tips/**' + - id: filter + run: | + projects='[]' + if [ "${{ steps.paths.outputs.tips }}" == "true" ]; then + projects=$(echo "$projects" | jq -c '. + ["tips"]') + fi + echo "projects=$projects" >> $GITHUB_OUTPUT + + lint: + name: Lint (${{ matrix.project }}) + needs: changes + if: ${{ needs.changes.outputs.projects != '[]' }} + runs-on: ubuntu-latest + strategy: + matrix: + project: ${{ fromJson(needs.changes.outputs.projects) }} + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '20' + cache: 'yarn' + cache-dependency-path: ui/${{ matrix.project }}/yarn.lock + - run: cp .env.example ui/${{ matrix.project }}/.env + - run: cd ui/${{ matrix.project }} && yarn install + - run: cd ui/${{ matrix.project }} && yarn lint + + type-check: + name: Type Check (${{ matrix.project }}) + needs: changes + if: ${{ needs.changes.outputs.projects != '[]' }} + runs-on: ubuntu-latest + strategy: + matrix: + project: ${{ fromJson(needs.changes.outputs.projects) }} + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '20' + cache: 'yarn' + cache-dependency-path: ui/${{ matrix.project }}/yarn.lock + - run: cp .env.example ui/${{ matrix.project }}/.env + - run: cd ui/${{ matrix.project }} && yarn install + - run: cd ui/${{ matrix.project }} && npx tsc --noEmit + + build: + name: Build (${{ matrix.project }}) + needs: changes + if: ${{ needs.changes.outputs.projects != '[]' }} + runs-on: ubuntu-latest + strategy: + matrix: + project: ${{ fromJson(needs.changes.outputs.projects) }} + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 + with: + egress-policy: audit + + - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '20' + cache: 'yarn' + cache-dependency-path: ui/${{ matrix.project }}/yarn.lock + - run: cp .env.example ui/${{ matrix.project }}/.env + - run: cd ui/${{ matrix.project }} && yarn install + - run: cd ui/${{ matrix.project }} && yarn build diff --git a/Cargo.lock b/Cargo.lock index 4caa85d..ad3ee02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,25 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -24,7 +43,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39" dependencies = [ "alloy-primitives", + "alloy-rlp", "num_enum", + "serde", "strum", ] @@ -48,11 +69,11 @@ dependencies = [ "k256", "once_cell", "rand 0.8.5", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -79,7 +100,7 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -103,8 +124,9 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "borsh", + "k256", "serde", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -137,10 +159,62 @@ dependencies = [ "c-kzg", "derive_more", "either", + "ethereum_ssz", + "ethereum_ssz_derive", "serde", "serde_with", "sha2", - "thiserror", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-evm" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96827207397445a919a8adc49289b53cc74e48e460411740bba31cec2fc307d" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-hardforks", + "alloy-op-hardforks", + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-sol-types", + "auto_impl", + "derive_more", + "op-alloy", + "op-revm", + "revm", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-genesis" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05864eef929c4d28895ae4b4d8ac9c6753c4df66e873b9c8fafc8089b59c1502" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-hardforks" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", ] [[package]] @@ -163,10 +237,10 @@ checksum = "d2dd146b3de349a6ffaa4e4e319ab3a90371fb159fb0bddeb1c7bbe8b1792eff" dependencies = [ "alloy-primitives", "alloy-sol-types", - "http", + "http 1.4.0", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tracing", ] @@ -193,7 +267,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -209,6 +283,36 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-op-evm" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54dc5c46a92fc7267055a174d30efb34e2599a0047102a4d38a025ae521435ba" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-op-hardforks", + "alloy-primitives", + "auto_impl", + "op-alloy", + "op-revm", + "revm", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-op-hardforks" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6472c610150c4c4c15be9e1b964c9b78068f933bda25fb9cdf09b9ac2bb66f36" +dependencies = [ + "alloy-chains", + "alloy-hardforks", + "alloy-primitives", + "auto_impl", +] + [[package]] name = "alloy-primitives" version = "1.5.4" @@ -220,7 +324,8 @@ dependencies = [ "cfg-if", "const-hex", "derive_more", - "foldhash", + "foldhash 0.2.0", + "getrandom 0.3.4", "hashbrown 0.16.1", "indexmap 2.13.0", "itoa", @@ -263,13 +368,13 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "lru", + "lru 0.16.3", "parking_lot", "pin-project", "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -328,11 +433,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0622d8bcac2f16727590aa33f4c3f05ea98130e7e4b4924bce8be85da5ad0dae" dependencies = [ "alloy-primitives", + "alloy-rpc-types-eth", "alloy-rpc-types-txpool", "alloy-serde", "serde", ] +[[package]] +name = "alloy-rpc-types-admin" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38c5ac70457ecc74e87fe1a5a19f936419224ded0eb0636241452412ca92733" +dependencies = [ + "alloy-genesis", + "alloy-primitives", + "serde", + "serde_json", +] + [[package]] name = "alloy-rpc-types-any" version = "1.5.2" @@ -344,6 +462,25 @@ dependencies = [ "alloy-serde", ] +[[package]] +name = "alloy-rpc-types-engine" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336ef381c7409f23c69f6e79bddc1917b6e832cff23e7a5cf84b9381d53582e6" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more", + "ethereum_ssz", + "ethereum_ssz_derive", + "rand 0.8.5", + "serde", + "strum", +] + [[package]] name = "alloy-rpc-types-eth" version = "1.5.2" @@ -362,7 +499,21 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-rpc-types-trace" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1aec4e1c66505d067933ea1a949a4fb60a19c4cfc2f109aa65873ea99e62ea8" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", + "thiserror 2.0.18", ] [[package]] @@ -400,7 +551,23 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-signer-local" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1259dac1f534a4c66c1d65237c89915d0010a2a91d6c3b0bada24dc5ee0fb917" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror 2.0.18", ] [[package]] @@ -481,14 +648,14 @@ checksum = "78f169b85eb9334871db986e7eaf59c58a03d86a30cc68b846573d47ed0656bb" dependencies = [ "alloy-json-rpc", "auto_impl", - "base64", + "base64 0.22.1", "derive_more", "futures", "futures-utils-wasm", "parking_lot", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "tower", "tracing", @@ -536,7 +703,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45ceac797eb8a56bdf5ab1fab353072c17d472eab87645ca847afe720db3246d" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", "syn 2.0.114", @@ -607,6 +774,65 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-r1cs-std", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -733,6 +959,50 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-relations", + "ark-std 0.5.0", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff 0.5.0", + "ark-std 0.5.0", + "tracing", + "tracing-subscriber 0.2.25", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -760,12 +1030,24 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ + "ark-serialize-derive", "ark-std 0.5.0", "arrayvec", "digest 0.10.7", "num-bigint", ] +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "ark-std" version = "0.3.0" @@ -796,6 +1078,12 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -805,6 +1093,27 @@ dependencies = [ "serde", ] +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -844,6 +1153,16 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + [[package]] name = "auto_impl" version = "1.3.0" @@ -862,1174 +1181,1425 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "base16ct" -version = "0.2.0" +name = "aws-config" +version = "1.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "96571e6996817bf3d58f6b569e4b9fd2e9d2fcf9f7424eed07b2ce9bb87535e5" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http 0.62.6", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 1.4.0", + "time", + "tokio", + "tracing", + "url", +] [[package]] -name = "base64" -version = "0.22.1" +name = "aws-credential-types" +version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] [[package]] -name = "base64ct" -version = "1.8.3" +name = "aws-lc-rs" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] [[package]] -name = "bit-set" -version = "0.8.0" +name = "aws-lc-sys" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" dependencies = [ - "bit-vec", + "cc", + "cmake", + "dunce", + "fs_extra", ] [[package]] -name = "bit-vec" -version = "0.8.0" +name = "aws-runtime" +version = "1.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "959dab27ce613e6c9658eb3621064d0e2027e5f2acb65bc526a43577facea557" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http 0.62.6", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] [[package]] -name = "bitcoin-io" -version = "0.1.4" +name = "aws-sdk-s3" +version = "1.119.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" +checksum = "1d65fddc3844f902dfe1864acb8494db5f9342015ee3ab7890270d36fbd2e01c" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http 0.62.6", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "lru 0.12.5", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] [[package]] -name = "bitcoin_hashes" -version = "0.14.1" +name = "aws-sdk-sts" +version = "1.96.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +checksum = "357e9a029c7524db6a0099cd77fbd5da165540339e7296cca603531bc783b56c" dependencies = [ - "bitcoin-io", - "hex-conservative", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.62.6", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", ] [[package]] -name = "bitflags" -version = "2.10.0" +name = "aws-sigv4" +version = "1.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "69e523e1c4e8e7e8ff219d732988e22bfeae8a1cafdbe6d9eca1546fa080be7c" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http 0.62.6", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "percent-encoding", + "sha2", + "time", + "tracing", +] [[package]] -name = "bitvec" -version = "1.0.1" +name = "aws-smithy-async" +version = "1.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "52eec3db979d18cb807fc1070961cc51d87d069abe9ab57917769687368a8c6c" dependencies = [ - "funty", - "radium", - "tap", - "wyz", + "futures-util", + "pin-project-lite", + "tokio", ] [[package]] -name = "block-buffer" -version = "0.10.4" +name = "aws-smithy-checksums" +version = "0.63.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "87294a084b43d649d967efe58aa1f9e0adc260e13a6938eb904c0ae9b45824ae" dependencies = [ - "generic-array", + "aws-smithy-http 0.62.6", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", ] [[package]] -name = "blst" -version = "0.3.16" +name = "aws-smithy-eventstream" +version = "0.60.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +checksum = "35b9c7354a3b13c66f60fe4616d6d1969c9fd36b1b5333a5dfb3ee716b33c588" dependencies = [ - "cc", - "glob", - "threadpool", - "zeroize", + "aws-smithy-types", + "bytes", + "crc32fast", ] [[package]] -name = "borsh" -version = "1.6.0" +name = "aws-smithy-http" +version = "0.62.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" dependencies = [ - "borsh-derive", - "cfg_aliases", + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", ] [[package]] -name = "borsh-derive" -version = "1.6.0" +name = "aws-smithy-http" +version = "0.63.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "630e67f2a31094ffa51b210ae030855cb8f3b7ee1329bdd8d085aaf61e8b97fc" dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.114", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", ] [[package]] -name = "bumpalo" -version = "3.19.1" +name = "aws-smithy-http-client" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "12fb0abf49ff0cab20fd31ac1215ed7ce0ea92286ba09e2854b42ba5cabe7525" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.13", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.8.1", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tracing", +] [[package]] -name = "byte-slice-cast" -version = "1.2.3" +name = "aws-smithy-json" +version = "0.61.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" +checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" +dependencies = [ + "aws-smithy-types", +] [[package]] -name = "byteorder" -version = "1.5.0" +name = "aws-smithy-observability" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "c0a46543fbc94621080b3cf553eb4cbbdc41dd9780a30c4756400f0139440a1d" +dependencies = [ + "aws-smithy-runtime-api", +] [[package]] -name = "bytes" -version = "1.11.0" +name = "aws-smithy-query" +version = "0.60.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "0cebbddb6f3a5bd81553643e9c7daf3cc3dc5b0b5f398ac668630e8a84e6fff0" dependencies = [ - "serde", + "aws-smithy-types", + "urlencoding", ] [[package]] -name = "c-kzg" -version = "2.1.5" +name = "aws-smithy-runtime" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" +checksum = "f3df87c14f0127a0d77eb261c3bc45d5b4833e2a1f63583ebfb728e4852134ee" dependencies = [ - "blst", - "cc", - "glob", - "hex", - "libc", - "once_cell", - "serde", + "aws-smithy-async", + "aws-smithy-http 0.63.3", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", ] [[package]] -name = "cadence" -version = "1.6.0" +name = "aws-smithy-runtime-api" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3075f133bee430b7644c54fb629b9b4420346ffa275a45c81a6babe8b09b4f51" +checksum = "49952c52f7eebb72ce2a754d3866cc0f87b97d2a46146b79f80f3a93fb2b3716" dependencies = [ - "crossbeam-channel", + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", ] [[package]] -name = "cc" -version = "1.2.54" +name = "aws-smithy-types" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "3b3a26048eeab0ddeba4b4f9d51654c79af8c3b32357dc5f336cee85ab331c33" dependencies = [ - "find-msvc-tools", - "shlex", + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", ] [[package]] -name = "cfg-if" -version = "1.0.4" +name = "aws-smithy-xml" +version = "0.60.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" +dependencies = [ + "xmlparser", +] [[package]] -name = "cfg_aliases" -version = "0.2.1" +name = "aws-types" +version = "1.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", +] [[package]] -name = "chrono" -version = "0.4.43" +name = "axum" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link", + "axum-core", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", ] [[package]] -name = "clap" -version = "4.5.56" +name = "axum-core" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "clap_builder", - "clap_derive", + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", ] [[package]] -name = "clap_builder" -version = "4.5.56" +name = "backon" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", + "fastrand", + "tokio", ] [[package]] -name = "clap_derive" -version = "4.5.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +name = "base-bundles" +version = "0.0.0" +source = "git+https://github.com/base/base?branch=main#943245444136009827653ae015b1ab87d2ac4fb1" dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.114", + "alloy-consensus", + "alloy-primitives", + "alloy-provider", + "alloy-serde", + "op-alloy-consensus 0.23.1", + "op-alloy-flz", + "serde", + "uuid", ] [[package]] -name = "clap_lex" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +name = "base-reth-rpc-types" +version = "0.0.0" +source = "git+https://github.com/base/base?branch=main#943245444136009827653ae015b1ab87d2ac4fb1" +dependencies = [ + "reth-optimism-evm", + "reth-rpc-eth-types", +] [[package]] -name = "colorchoice" -version = "1.0.4" +name = "base16ct" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] -name = "const-hex" -version = "1.17.0" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" -dependencies = [ - "cfg-if", - "cpufeatures", - "proptest", - "serde_core", -] +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "const-oid" -version = "0.9.6" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "const_format" -version = "0.2.35" +name = "base64-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" dependencies = [ - "const_format_proc_macros", + "outref", + "vsimd", ] [[package]] -name = "const_format_proc_macros" -version = "0.2.34" +name = "base64ct" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] -name = "convert_case" -version = "0.10.0" +name = "bindgen" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "unicode-segmentation", + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.114", ] [[package]] -name = "core-foundation" -version = "0.9.4" +name = "bit-set" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "core-foundation-sys", - "libc", + "bit-vec", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" +name = "bit-vec" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] -name = "cpufeatures" -version = "0.2.17" +name = "bitcoin-io" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] -name = "crc" -version = "3.4.0" +name = "bitcoin_hashes" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ - "crc-catalog", + "bitcoin-io", + "hex-conservative", ] [[package]] -name = "crc-catalog" -version = "2.4.0" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "bitflags" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "crossbeam-utils", + "serde_core", ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.4" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "serde", + "tap", + "wyz", +] [[package]] -name = "crypto-bigint" -version = "0.5.5" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "blst" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" dependencies = [ - "generic-array", - "typenum", + "cc", + "glob", + "threadpool", + "zeroize", ] [[package]] -name = "darling" -version = "0.21.3" +name = "bollard" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" dependencies = [ - "darling_core", - "darling_macro", + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http 1.4.0", + "http-body-util", + "hyper 1.8.1", + "hyper-named-pipe", + "hyper-rustls 0.27.7", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", ] [[package]] -name = "darling_core" -version = "0.21.3" +name = "bollard-stubs" +version = "1.47.1-rc.27.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", "serde", - "strsim", - "syn 2.0.114", + "serde_repr", + "serde_with", ] [[package]] -name = "darling_macro" -version = "0.21.3" +name = "borsh" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ - "darling_core", - "quote", - "syn 2.0.114", + "borsh-derive", + "cfg_aliases", ] [[package]] -name = "dashmap" -version = "6.1.0" +name = "borsh-derive" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", "once_cell", - "parking_lot_core", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "der" -version = "0.7.10" +name = "bumpalo" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] -name = "deranged" -version = "0.5.5" +name = "byte-slice-cast" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", - "serde_core", -] +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] -name = "derivative" -version = "2.2.0" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "derive_more" -version = "2.1.1" +name = "bytes" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ - "derive_more-impl", + "serde", ] [[package]] -name = "derive_more-impl" -version = "2.1.1" +name = "bytes-utils" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.1", - "syn 2.0.114", - "unicode-xid", + "bytes", + "either", ] [[package]] -name = "digest" -version = "0.9.0" +name = "c-kzg" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" dependencies = [ - "generic-array", + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", ] [[package]] -name = "digest" -version = "0.10.7" +name = "cadence" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "3075f133bee430b7644c54fb629b9b4420346ffa275a45c81a6babe8b09b4f51" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "crossbeam-channel", ] [[package]] -name = "displaydoc" -version = "0.2.5" +name = "cc" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", + "find-msvc-tools", + "jobserver", + "libc", + "shlex", ] [[package]] -name = "dotenvy" -version = "0.15.7" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] -name = "dunce" -version = "1.0.5" +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] -name = "dyn-clone" -version = "1.0.20" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "ecdsa" -version = "0.16.9" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "serdect", - "signature", - "spki", -] +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "educe" -version = "0.6.0" +name = "chrono" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 2.0.114", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", ] [[package]] -name = "either" -version = "1.15.0" +name = "clang-sys" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ - "serde", + "glob", + "libc", + "libloading", ] [[package]] -name = "elliptic-curve" -version = "0.13.8" +name = "clap" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" dependencies = [ - "base16ct", - "crypto-bigint", - "digest 0.10.7", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "serdect", - "subtle", - "zeroize", + "clap_builder", + "clap_derive", ] [[package]] -name = "enum-ordinalize" -version = "4.3.2" +name = "clap_builder" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" dependencies = [ - "enum-ordinalize-derive", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "enum-ordinalize-derive" -version = "4.3.2" +name = "clap_derive" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ + "heck", "proc-macro2", "quote", "syn 2.0.114", ] [[package]] -name = "equivalent" -version = "1.0.2" +name = "clap_lex" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] -name = "errno" -version = "0.3.14" +name = "cmake" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ - "libc", - "windows-sys 0.61.2", + "cc", ] [[package]] -name = "fastrand" -version = "2.3.0" +name = "colorchoice" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] -name = "fastrlp" -version = "0.3.1" +name = "combine" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "arrayvec", - "auto_impl", "bytes", + "memchr", ] [[package]] -name = "fastrlp" -version = "0.4.0" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "arrayvec", - "auto_impl", - "bytes", + "crossbeam-utils", ] [[package]] -name = "ff" -version = "0.13.1" +name = "console" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ - "rand_core 0.6.4", - "subtle", + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", ] [[package]] -name = "find-msvc-tools" -version = "0.1.8" +name = "const-hex" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] [[package]] -name = "fixed-hash" -version = "0.8.0" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", -] +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "fnv" -version = "1.0.7" +name = "const_format" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] [[package]] -name = "foldhash" -version = "0.2.0" +name = "const_format_proc_macros" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "convert_case" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ - "foreign-types-shared", + "unicode-segmentation", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] -name = "form_urlencoded" -version = "1.2.2" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "percent-encoding", + "core-foundation-sys", + "libc", ] [[package]] -name = "funty" -version = "2.0.0" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "futures" -version = "0.3.31" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "libc", ] [[package]] -name = "futures-channel" -version = "0.3.31" +name = "crc" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ - "futures-core", - "futures-sink", + "crc-catalog", ] [[package]] -name = "futures-core" -version = "0.3.31" +name = "crc-catalog" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] -name = "futures-executor" -version = "0.3.31" +name = "crc-fast" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "crc", + "digest 0.10.7", + "rand 0.9.2", + "regex", + "rustversion", ] [[package]] -name = "futures-io" -version = "0.3.31" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] [[package]] -name = "futures-macro" -version = "0.3.31" +name = "critical-section" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] -name = "futures-sink" -version = "0.3.31" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "futures-task" -version = "0.3.31" +name = "crossbeam-deque" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] [[package]] -name = "futures-util" -version = "0.3.31" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "crossbeam-utils", ] [[package]] -name = "futures-utils-wasm" -version = "0.1.0" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] -name = "generic-array" -version = "0.14.9" +name = "crunchy" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "typenum", - "version_check", + "generic-array", + "rand_core 0.6.4", + "subtle", "zeroize", ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "cfg-if", - "libc", - "wasi", + "generic-array", + "typenum", ] [[package]] -name = "getrandom" -version = "0.3.4" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] -name = "glob" -version = "0.3.3" +name = "darling" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] [[package]] -name = "group" -version = "0.13.0" +name = "darling_core" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "darling_core" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim", + "syn 2.0.114", +] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "darling_macro" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.114", +] [[package]] -name = "hashbrown" -version = "0.16.1" +name = "darling_macro" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", - "serde", - "serde_core", + "darling_core 0.21.3", + "quote", + "syn 2.0.114", ] [[package]] -name = "heck" -version = "0.5.0" +name = "dashmap" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] [[package]] -name = "hermit-abi" -version = "0.5.2" +name = "deadpool" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +dependencies = [ + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", +] [[package]] -name = "hex" -version = "0.4.3" +name = "deadpool-runtime" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" [[package]] -name = "hex-conservative" -version = "0.2.2" +name = "der" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "arrayvec", + "const-oid", + "zeroize", ] [[package]] -name = "hmac" -version = "0.12.1" +name = "deranged" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ - "digest 0.10.7", + "powerfmt", + "serde_core", ] [[package]] -name = "http" -version = "1.4.0" +name = "derivative" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "bytes", - "itoa", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "derive-where" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ - "bytes", - "http", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "http-body-util" -version = "0.1.3" +name = "derive_more" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", + "derive_more-impl", ] [[package]] -name = "httparse" -version = "1.10.1" +name = "derive_more-impl" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.114", + "unicode-xid", +] [[package]] -name = "hyper" -version = "1.8.1" +name = "digest" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", + "generic-array", ] [[package]] -name = "hyper-tls" -version = "0.6.0" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "block-buffer", + "const-oid", + "crypto-common", + "subtle", ] [[package]] -name = "hyper-util" -version = "0.1.19" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "iana-time-zone" -version = "0.1.65" +name = "docker_credential" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", + "base64 0.21.7", + "serde", + "serde_json", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "dotenvy" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "icu_collections" -version = "2.1.1" +name = "downcast" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] -name = "icu_locale_core" -version = "2.1.1" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "icu_normalizer" -version = "2.1.1" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] -name = "icu_normalizer_data" -version = "2.1.1" +name = "ecdsa" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] [[package]] -name = "icu_properties" -version = "2.1.2" +name = "educe" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "icu_properties_data" -version = "2.1.2" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] -name = "icu_provider" -version = "2.1.1" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "encode_unicode" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] -name = "idna" -version = "1.1.0" +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", + "cfg-if", ] [[package]] -name = "idna_adapter" -version = "1.2.1" +name = "enr" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "851bd664a3d3a3c175cff92b2f0df02df3c541b4895d0ae307611827aae46152" dependencies = [ - "icu_normalizer", - "icu_properties", + "alloy-rlp", + "base64 0.22.1", + "bytes", + "hex", + "log", + "rand 0.8.5", + "secp256k1 0.30.0", + "sha3", + "zeroize", ] [[package]] -name = "impl-codec" -version = "0.6.0" +name = "enum-ordinalize" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" dependencies = [ - "parity-scale-codec", + "enum-ordinalize-derive", ] [[package]] -name = "impl-trait-for-tuples" -version = "0.2.3" +name = "enum-ordinalize-derive" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", @@ -2037,185 +2607,175 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "indexmap" -version = "2.13.0" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", + "libc", + "windows-sys 0.59.0", ] [[package]] -name = "ipnet" -version = "2.11.0" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] [[package]] -name = "iri-string" -version = "0.7.10" +name = "ethereum_serde_utils" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" dependencies = [ - "memchr", + "alloy-primitives", + "hex", "serde", + "serde_derive", + "serde_json", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "ethereum_ssz" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] [[package]] -name = "itertools" -version = "0.10.5" +name = "ethereum_ssz_derive" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" dependencies = [ - "either", + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "itertools" -version = "0.13.0" +name = "event-listener" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ - "either", + "concurrent-queue", + "parking", + "pin-project-lite", ] [[package]] -name = "itertools" -version = "0.14.0" +name = "event-listener-strategy" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "either", + "event-listener", + "pin-project-lite", ] [[package]] -name = "itoa" -version = "1.0.17" +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "js-sys" -version = "0.3.85" +name = "fastrlp" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" dependencies = [ - "once_cell", - "wasm-bindgen", + "arrayvec", + "auto_impl", + "bytes", ] [[package]] -name = "k256" -version = "0.13.4" +name = "fastrlp" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "serdect", - "sha2", + "arrayvec", + "auto_impl", + "bytes", ] [[package]] -name = "keccak" -version = "0.1.5" +name = "ff" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "cpufeatures", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "keccak-asm" -version = "0.1.5" +name = "filetime" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ - "digest 0.10.7", - "sha3-asm", + "cfg-if", + "libc", + "libredox", ] [[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.1" +name = "find-msvc-tools" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] -name = "lock_api" -version = "0.4.14" +name = "fixed-hash" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ - "scopeguard", + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", ] [[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru" -version = "0.16.3" +name = "fixed-map" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "86ed19add84e8cb9e8cc5f7074de0324247149ffef0b851e215fb0edc50c229b" dependencies = [ - "hashbrown 0.16.1", + "fixed-map-derive", + "serde", ] [[package]] -name = "macro-string" -version = "0.1.4" +name = "fixed-map-derive" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +checksum = "6dc7a9cb3326bafb80642c5ce99b39a2c0702d4bfa8ee8a3e773791a6cbe2407" dependencies = [ "proc-macro2", "quote", @@ -2223,137 +2783,118 @@ dependencies = [ ] [[package]] -name = "matchers" -version = "0.2.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "memchr" -version = "2.7.6" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] -name = "mempool-rebroadcaster" -version = "0.1.0" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-types", - "alloy-rpc-types-eth", - "alloy-trie", - "clap", - "dotenvy", - "serde", - "serde_json", - "tokio", - "tracing", - "tracing-subscriber", -] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] -name = "mio" -version = "1.1.1" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", + "foreign-types-shared", ] [[package]] -name = "native-tls" -version = "0.2.14" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "nu-ansi-term" -version = "0.50.3" +name = "form_urlencoded" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "windows-sys 0.61.2", + "percent-encoding", ] [[package]] -name = "num-bigint" -version = "0.4.6" +name = "fragile" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] +checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" [[package]] -name = "num-conv" -version = "0.2.0" +name = "fs_extra" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] -name = "num-integer" -version = "0.1.46" +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] -name = "num-traits" -version = "0.2.19" +name = "futures" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ - "autocfg", - "libm", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "num_cpus" -version = "1.17.0" +name = "futures-channel" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ - "hermit-abi", - "libc", + "futures-core", + "futures-sink", ] [[package]] -name = "num_enum" -version = "0.7.5" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ - "num_enum_derive", - "rustversion", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "num_enum_derive" -version = "0.7.5" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -2361,503 +2902,3345 @@ dependencies = [ ] [[package]] -name = "nybbles" -version = "0.4.7" +name = "futures-sink" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5676b5c379cf5b03da1df2b3061c4a4e2aa691086a56ac923e08c143f53f59" -dependencies = [ - "alloy-rlp", - "cfg-if", - "proptest", - "ruint", - "serde", - "smallvec", +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "futures-utils-wasm" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "generic-array" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] [[package]] -name = "openssl" -version = "0.10.75" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "bitflags", "cfg-if", - "foreign-types", + "js-sys", "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "wasi", + "wasm-bindgen", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", ] [[package]] -name = "openssl-probe" -version = "0.1.6" +name = "glob" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] -name = "openssl-sys" -version = "0.9.111" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "ff", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "opentelemetry" -version = "0.31.0" +name = "h2" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ + "bytes", + "fnv", "futures-core", "futures-sink", - "js-sys", - "pin-project-lite", - "thiserror", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", "tracing", ] [[package]] -name = "opentelemetry-http" -version = "0.31.0" +name = "h2" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ - "async-trait", + "atomic-waker", "bytes", - "http", - "opentelemetry", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "parity-scale-codec" -version = "3.7.5" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "const_format", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "rustversion", - "serde", -] +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "parity-scale-codec-derive" -version = "3.7.5" +name = "hashbrown" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.114", -] +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] -name = "parking_lot" -version = "0.12.5" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "lock_api", - "parking_lot_core", + "allocator-api2", + "equivalent", + "foldhash 0.1.5", ] [[package]] -name = "parking_lot_core" -version = "0.9.12" +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", + "allocator-api2", + "equivalent", + "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] -name = "paste" -version = "1.0.15" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "hermit-abi" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] -name = "pest" -version = "2.8.5" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" -dependencies = [ - "memchr", - "ucd-trie", -] +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "pin-project" -version = "1.1.10" +name = "hex-conservative" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ - "pin-project-internal", + "arrayvec", ] [[package]] -name = "pin-project-internal" -version = "1.1.10" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", + "digest 0.10.7", ] [[package]] -name = "pin-project-lite" -version = "0.2.16" +name = "home" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "http" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ - "der", - "spki", + "bytes", + "itoa", ] [[package]] -name = "pkg-config" -version = "0.3.32" +name = "http-body" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] [[package]] -name = "potential_utf" -version = "0.1.4" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "zerovec", + "bytes", + "http 1.4.0", ] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ - "zerocopy", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] -name = "primitive-types" -version = "0.12.2" +name = "hyper" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ - "fixed-hash", - "impl-codec", - "uint", + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", ] [[package]] -name = "proc-macro-crate" -version = "3.4.0" +name = "hyper-named-pipe" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ - "toml_edit", + "hex", + "hyper 1.8.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", ] [[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" +name = "hyper-rustls" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "proc-macro2", - "quote", + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", ] [[package]] -name = "proc-macro-error2" -version = "2.0.1" +name = "hyper-rustls" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.114", + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "log", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", ] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "unicode-ident", + "bytes", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] -name = "proptest" -version = "1.9.0" +name = "hyper-util" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ - "bit-set", - "bit-vec", - "bitflags", - "num-traits", - "rand 0.9.2", - "rand_chacha 0.9.0", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.5.10", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "hyperlocal" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] [[package]] -name = "quote" -version = "1.0.44" +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "proc-macro2", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] [[package]] -name = "radium" -version = "0.7.0" +name = "icu_collections" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "rand" -version = "0.8.5" +name = "icu_locale_core" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "serde", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "rand" -version = "0.9.2" +name = "icu_normalizer" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", - "serde", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "icu_normalizer_data" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonrpsee" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a" +dependencies = [ + "jsonrpsee-core", + "jsonrpsee-http-client", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", + "tokio", + "tracing", +] + +[[package]] +name = "jsonrpsee-core" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "316c96719901f05d1137f19ba598b5fe9c9bc39f4335f67f6be8613921946480" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "jsonrpsee-types", + "parking_lot", + "pin-project", + "rand 0.9.2", + "rustc-hash", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-http-client" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790bedefcec85321e007ff3af84b4e417540d5c87b3c9779b9e247d1bcc3dab8" +dependencies = [ + "base64 0.22.1", + "http-body 1.0.1", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "jsonrpsee-core", + "jsonrpsee-types", + "rustls 0.23.36", + "rustls-platform-verifier", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower", + "url", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da3f8ab5ce1bb124b6d082e62dffe997578ceaf0aeb9f3174a214589dc00f07" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "jsonrpsee-server" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f" +dependencies = [ + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "jsonrpsee-core", + "jsonrpsee-types", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" +dependencies = [ + "http 1.4.0", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.7.0", +] + +[[package]] +name = "libz-sys" +version = "1.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mempool-rebroadcaster" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-rpc-types-eth", + "alloy-trie", + "clap", + "dotenvy", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber 0.3.22", +] + +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a87f4b19620e4c561f7b48f5e6ca085b1780def671696a6a3d9d0c137360ec" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[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 1.8.1", + "hyper-util", + "indexmap 2.13.0", + "ipnet", + "metrics", + "metrics-util", + "quanta", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[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", + "rand_xoshiro", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mockall" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "moka" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "event-listener", + "futures-util", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[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 = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "nybbles" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5676b5c379cf5b03da1df2b3061c4a4e2aa691086a56ac923e08c143f53f59" +dependencies = [ + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "op-alloy" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b8fee21003dd4f076563de9b9d26f8c97840157ef78593cd7f262c5ca99848" +dependencies = [ + "op-alloy-consensus 0.23.1", + "op-alloy-network 0.23.1", + "op-alloy-provider", + "op-alloy-rpc-types 0.23.1", + "op-alloy-rpc-types-engine", +] + +[[package]] +name = "op-alloy-consensus" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726da827358a547be9f1e37c2a756b9e3729cb0350f43408164794b370cad8ae" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "op-alloy-consensus" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736381a95471d23e267263cfcee9e1d96d30b9754a94a2819148f83379de8a86" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "op-alloy-flz" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" + +[[package]] +name = "op-alloy-network" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63f27e65be273ec8fcb0b6af0fd850b550979465ab93423705ceb3dfddbd2ab" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-signer", + "op-alloy-consensus 0.22.4", + "op-alloy-rpc-types 0.22.4", +] + +[[package]] +name = "op-alloy-network" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4034183dca6bff6632e7c24c92e75ff5f0eabb58144edb4d8241814851334d47" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-signer", + "op-alloy-consensus 0.23.1", + "op-alloy-rpc-types 0.23.1", +] + +[[package]] +name = "op-alloy-provider" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6753d90efbaa8ea8bcb89c1737408ca85fa60d7adb875049d3f382c063666f86" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-engine", + "alloy-transport", + "async-trait", + "op-alloy-rpc-types-engine", +] + +[[package]] +name = "op-alloy-rpc-types" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562dd4462562c41f9fdc4d860858c40e14a25df7f983ae82047f15f08fce4d19" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "op-alloy-consensus 0.22.4", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "op-alloy-rpc-types" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd87c6b9e5b6eee8d6b76f41b04368dca0e9f38d83338e5b00e730c282098a4" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "op-alloy-consensus 0.23.1", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "op-alloy-rpc-types-engine" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727699310a18cdeed32da3928c709e2704043b6584ed416397d5da65694efc" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "alloy-serde", + "derive_more", + "ethereum_ssz", + "ethereum_ssz_derive", + "op-alloy-consensus 0.23.1", + "serde", + "sha2", + "snap", + "thiserror 2.0.18", +] + +[[package]] +name = "op-revm" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c92b75162c2ed1661849fa51683b11254a5b661798360a2c24be918edafd40" +dependencies = [ + "auto_impl", + "revm", + "serde", +] + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http 1.4.0", + "opentelemetry", +] + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[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]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.10.0", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.36", + "socket2 0.5.10", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls 0.23.36", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8b5b858a440a0bc02625b62dd95131b9201aa9f69f411195dd4a7cfb1de3d7" +dependencies = [ + "rand 0.9.2", + "rustversion", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rdkafka" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b52c81ac3cac39c9639b95c20452076e74b8d9a71bc6fc4d83407af2ea6fff" +dependencies = [ + "futures-channel", + "futures-util", + "libc", + "log", + "rdkafka-sys", + "serde", + "serde_derive", + "serde_json", + "slab", + "tokio", +] + +[[package]] +name = "rdkafka-sys" +version = "4.10.0+2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e234cf318915c1059d4921ef7f75616b5219b10b46e9f3a511a15eb4b56a3f77" +dependencies = [ + "libc", + "libz-sys", + "num_enum", + "openssl-sys", + "pkg-config", + "zstd-sys", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "reth-chain-state" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "derive_more", + "metrics", + "parking_lot", + "pin-project", + "reth-chainspec", + "reth-errors", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-metrics", + "reth-primitives-traits", + "reth-storage-api", + "reth-trie", + "revm-database", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-chainspec" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "auto_impl", + "derive_more", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "serde_json", +] + +[[package]] +name = "reth-codecs" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "bytes", + "modular-bitfield", + "op-alloy-consensus 0.23.1", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", +] + +[[package]] +name = "reth-codecs-derive" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "reth-consensus" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "auto_impl", + "reth-execution-types", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-consensus-common" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "reth-chainspec", + "reth-consensus", + "reth-primitives-traits", +] + +[[package]] +name = "reth-db-models" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "bytes", + "reth-primitives-traits", + "serde", +] + +[[package]] +name = "reth-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "reth-consensus", + "reth-execution-errors", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-eth-wire-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-hardforks", + "alloy-primitives", + "alloy-rlp", + "bytes", + "derive_more", + "reth-chainspec", + "reth-codecs-derive", + "reth-ethereum-primitives", + "reth-primitives-traits", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-ethereum-forks" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eip2124", + "alloy-hardforks", + "alloy-primitives", + "auto_impl", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "reth-ethereum-primitives" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "reth-primitives-traits", + "reth-zstd-compressors", + "serde", + "serde_with", +] + +[[package]] +name = "reth-evm" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "auto_impl", + "derive_more", + "futures-util", + "rayon", + "reth-execution-errors", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-execution-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "nybbles", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-execution-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "derive_more", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-trie-common", + "revm", + "serde", + "serde_with", +] + +[[package]] +name = "reth-fs-util" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-metrics" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "metrics", + "metrics-derive", +] + +[[package]] +name = "reth-net-banlist" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "ipnet", +] + +[[package]] +name = "reth-network-api" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rpc-types-admin", + "alloy-rpc-types-eth", + "auto_impl", + "derive_more", + "enr", + "futures", + "reth-eth-wire-types", + "reth-ethereum-forks", + "reth-network-p2p", + "reth-network-peers", + "reth-network-types", + "reth-tokio-util", + "thiserror 2.0.18", + "tokio", + "tokio-stream", +] + +[[package]] +name = "reth-network-p2p" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "auto_impl", + "derive_more", + "futures", + "reth-consensus", + "reth-eth-wire-types", + "reth-ethereum-primitives", + "reth-network-peers", + "reth-network-types", + "reth-primitives-traits", + "reth-storage-errors", + "tokio", + "tracing", +] + +[[package]] +name = "reth-network-peers" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "secp256k1 0.30.0", + "serde_with", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "reth-network-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eip2124", + "reth-net-banlist", + "reth-network-peers", + "serde_json", + "tracing", +] + +[[package]] +name = "reth-optimism-chainspec" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-hardforks", + "alloy-primitives", + "derive_more", + "miniz_oxide", + "op-alloy-consensus 0.23.1", + "op-alloy-rpc-types 0.23.1", + "reth-chainspec", + "reth-ethereum-forks", + "reth-network-peers", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-primitives-traits", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-optimism-consensus" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-trie", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-execution-types", + "reth-optimism-chainspec", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "reth-optimism-evm" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-op-evm", + "alloy-primitives", + "op-alloy-consensus 0.23.1", + "op-alloy-rpc-types-engine", + "op-revm", + "reth-chainspec", + "reth-evm", + "reth-execution-errors", + "reth-execution-types", + "reth-optimism-chainspec", + "reth-optimism-consensus", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-primitives-traits", + "reth-storage-errors", + "revm", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-optimism-forks" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-op-hardforks", + "alloy-primitives", + "once_cell", + "reth-ethereum-forks", +] + +[[package]] +name = "reth-optimism-primitives" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "op-alloy-consensus 0.23.1", + "reth-primitives-traits", + "serde", + "serde_with", +] + +[[package]] +name = "reth-primitives-traits" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-trie", + "auto_impl", + "bytes", + "derive_more", + "once_cell", + "op-alloy-consensus 0.23.1", + "reth-codecs", + "revm-bytecode", + "revm-primitives", + "revm-state", + "secp256k1 0.30.0", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-prune-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "derive_more", + "serde", + "strum", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-revm" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-rpc-convert" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-evm", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-signer", + "auto_impl", + "dyn-clone", + "jsonrpsee-types", + "reth-ethereum-primitives", + "reth-evm", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-rpc-eth-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-network", + "alloy-primitives", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "derive_more", + "futures", + "itertools 0.14.0", + "jsonrpsee-core", + "jsonrpsee-types", + "metrics", + "rand 0.9.2", + "reqwest", + "reth-chain-state", + "reth-chainspec", + "reth-errors", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-metrics", + "reth-primitives-traits", + "reth-revm", + "reth-rpc-convert", + "reth-rpc-server-types", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "reth-trie", + "revm", + "revm-inspectors", + "schnellru", + "serde", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "reth-rpc-server-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "jsonrpsee-core", + "jsonrpsee-types", + "reth-errors", + "reth-network-api", + "serde", + "strum", +] + +[[package]] +name = "reth-stages-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "bytes", + "reth-trie-common", + "serde", +] + +[[package]] +name = "reth-static-file-types" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "derive_more", + "fixed-map", + "serde", + "strum", +] + +[[package]] +name = "reth-storage-api" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "auto_impl", + "reth-chainspec", + "reth-db-models", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "revm-database", + "serde_json", +] + +[[package]] +name = "reth-storage-errors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "derive_more", + "reth-primitives-traits", + "reth-prune-types", + "reth-static-file-types", + "revm-database-interface", + "revm-state", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-tasks" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "auto_impl", + "dyn-clone", + "futures-util", + "metrics", + "reth-metrics", + "thiserror 2.0.18", + "tokio", + "tracing", + "tracing-futures", +] + +[[package]] +name = "reth-tokio-util" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-transaction-pool" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "aquamarine", + "auto_impl", + "bitflags 2.10.0", + "futures-util", + "metrics", + "parking_lot", + "pin-project", + "reth-chain-state", + "reth-chainspec", + "reth-eth-wire-types", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-fs-util", + "reth-metrics", + "reth-primitives-traits", + "reth-storage-api", + "reth-tasks", + "revm-interpreter", + "revm-primitives", + "rustc-hash", + "schnellru", + "serde", + "serde_json", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-trie" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "auto_impl", + "itertools 0.14.0", + "reth-execution-errors", + "reth-primitives-traits", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "reth-trie-sparse", + "revm-database", + "tracing", +] + +[[package]] +name = "reth-trie-common" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-trie", + "arrayvec", + "bytes", + "derive_more", + "itertools 0.14.0", + "nybbles", + "rayon", + "reth-primitives-traits", + "revm-database", + "serde", + "serde_with", +] + +[[package]] +name = "reth-trie-sparse" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "auto_impl", + "reth-execution-errors", + "reth-primitives-traits", + "reth-trie-common", + "smallvec", + "tracing", +] + +[[package]] +name = "reth-zstd-compressors" +version = "1.10.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.10.2#8e3b5e6a99439561b73c5dd31bd3eced2e994d60" +dependencies = [ + "zstd", +] + +[[package]] +name = "revm" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" +dependencies = [ + "bitvec", + "phf", + "revm-primitives", + "serde", ] [[package]] -name = "rand_chacha" -version = "0.9.0" +name = "revm-context" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "892ff3e6a566cf8d72ffb627fdced3becebbd9ba64089c25975b9b028af326a5" dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", + "bitvec", + "cfg-if", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "revm-context-interface" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "57f61cc6d23678c4840af895b19f8acfbbd546142ec8028b6526c53cc1c16c98" dependencies = [ - "getrandom 0.2.17", + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", ] [[package]] -name = "rand_core" -version = "0.9.5" +name = "revm-database" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" dependencies = [ - "getrandom 0.3.4", + "alloy-eips", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", "serde", ] [[package]] -name = "rand_xorshift" -version = "0.4.0" +name = "revm-database-interface" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +checksum = "b7bf93ac5b91347c057610c0d96e923db8c62807e03f036762d03e981feddc1d" dependencies = [ - "rand_core 0.9.5", + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "serde", + "thiserror 2.0.18", ] [[package]] -name = "rapidhash" -version = "4.2.1" +name = "revm-handler" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8b5b858a440a0bc02625b62dd95131b9201aa9f69f411195dd4a7cfb1de3d7" +checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" dependencies = [ - "rustversion", + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", + "serde", ] [[package]] -name = "redox_syscall" -version = "0.5.18" +name = "revm-inspector" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" dependencies = [ - "bitflags", + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", + "serde", + "serde_json", ] [[package]] -name = "ref-cast" -version = "1.0.25" +name = "revm-inspectors" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "6e435414e9de50a1b930da602067c76365fea2fea11e80ceb50783c94ddd127f" dependencies = [ - "ref-cast-impl", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-sol-types", + "anstyle", + "colorchoice", + "revm", + "serde", + "serde_json", + "thiserror 2.0.18", ] [[package]] -name = "ref-cast-impl" -version = "1.0.25" +name = "revm-interpreter" +version = "32.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "revm-state", + "serde", ] [[package]] -name = "regex-automata" -version = "0.4.13" +name = "revm-precompile" +version = "32.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "50c1285c848d240678bf69cb0f6179ff5a4aee6fc8e921d89708087197a0aff3" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "c-kzg", + "cfg-if", + "k256", + "p256", + "revm-primitives", + "ripemd", + "secp256k1 0.31.1", + "sha2", ] [[package]] -name = "regex-syntax" -version = "0.8.8" +name = "revm-primitives" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "ba580c56a8ec824a64f8a1683577876c2e1dbe5247044199e9b881421ad5dcf9" +dependencies = [ + "alloy-primitives", + "num_enum", + "once_cell", + "serde", +] [[package]] -name = "reqwest" -version = "0.12.28" +name = "revm-state" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" dependencies = [ - "base64", - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", + "alloy-eip7928", + "bitflags 2.10.0", + "revm-bytecode", + "revm-primitives", "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", ] [[package]] @@ -2870,6 +6253,29 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "rlp" version = "0.5.2" @@ -2880,6 +6286,12 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + [[package]] name = "ruint" version = "1.17.2" @@ -2919,6 +6331,9 @@ name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +dependencies = [ + "rand 0.8.5", +] [[package]] name = "rustc-hex" @@ -2950,11 +6365,60 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.9", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "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]] @@ -2963,9 +6427,59 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.9", + "security-framework 3.5.1", + "security-framework-sys", + "webpki-root-certs 0.26.11", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2990,6 +6504,24 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.28" @@ -3023,12 +6555,39 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schnellru" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" +dependencies = [ + "ahash", + "cfg-if", + "hashbrown 0.13.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + [[package]] name = "sec1" version = "0.7.3" @@ -3052,10 +6611,21 @@ checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.2", + "secp256k1-sys 0.11.0", +] + [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -3065,14 +6635,36 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", - "core-foundation", + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3148,6 +6740,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ + "indexmap 2.13.0", "itoa", "memchr", "serde", @@ -3155,6 +6748,28 @@ 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_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_urlencoded" version = "0.7.1" @@ -3173,7 +6788,7 @@ version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", @@ -3192,7 +6807,7 @@ version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", "syn 2.0.114", @@ -3208,6 +6823,49 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0b343e184fc3b7bb44dff0705fffcf4b3756ba6aff420dddd8b24ca145e555" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f50427f258fb77356e4cd4aa0e87e2bd2c66dbcee41dc405282cae2bfc26c83" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "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" @@ -3269,7 +6927,7 @@ dependencies = [ "clap", "tokio", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.22", ] [[package]] @@ -3292,6 +6950,18 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "sketches-ddsketch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" + [[package]] name = "slab" version = "0.4.11" @@ -3307,6 +6977,22 @@ dependencies = [ "serde", ] +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.2" @@ -3317,6 +7003,22 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "soketto" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures", + "http 1.4.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", +] + [[package]] name = "spki" version = "0.7.3" @@ -3345,6 +7047,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.27.2" @@ -3426,6 +7151,33 @@ dependencies = [ "syn 2.0.114", ] +[[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 = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -3442,7 +7194,60 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "testcontainers" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a4f01f39bb10fc2a5ab23eb0d888b1e2bb168c157f61a1b98e6c501c639c74" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes", + "docker_credential", + "either", + "etcetera", + "futures", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "url", +] + +[[package]] +name = "testcontainers-modules" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d43ed4e8f58424c3a2c6c56dbea6643c3c23e8666a34df13c54f0a184e6c707" +dependencies = [ + "testcontainers", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", ] [[package]] @@ -3451,7 +7256,18 @@ version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -3524,6 +7340,168 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tips-audit" +version = "0.1.0" +dependencies = [ + "anyhow", + "aws-config", + "aws-credential-types", + "aws-sdk-s3", + "clap", + "dotenvy", + "rdkafka", + "tips-audit-lib", + "tips-core", + "tokio", + "tracing", +] + +[[package]] +name = "tips-audit-lib" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "anyhow", + "async-trait", + "aws-config", + "aws-sdk-s3", + "bytes", + "futures", + "metrics", + "metrics-derive", + "rdkafka", + "serde", + "serde_json", + "testcontainers", + "testcontainers-modules", + "tips-core", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "tips-core" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-signer-local", + "base-bundles", + "metrics-exporter-prometheus", + "op-alloy-consensus 0.22.4", + "op-alloy-rpc-types 0.22.4", + "serde_json", + "tracing", + "tracing-subscriber 0.3.22", +] + +[[package]] +name = "tips-ingress-rpc" +version = "0.1.0" +dependencies = [ + "alloy-provider", + "anyhow", + "clap", + "dotenvy", + "jsonrpsee", + "op-alloy-network 0.22.4", + "rdkafka", + "tips-audit-lib", + "tips-core", + "tips-ingress-rpc-lib", + "tokio", + "tracing", +] + +[[package]] +name = "tips-ingress-rpc-lib" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-provider", + "alloy-signer-local", + "anyhow", + "async-trait", + "axum", + "backon", + "base-reth-rpc-types", + "clap", + "jsonrpsee", + "metrics", + "metrics-derive", + "mockall", + "moka", + "op-alloy-consensus 0.22.4", + "op-alloy-network 0.22.4", + "op-revm", + "rdkafka", + "serde_json", + "tips-audit-lib", + "tips-core", + "tokio", + "tracing", + "url", + "uuid", + "wiremock", +] + +[[package]] +name = "tips-system-tests" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-signer-local", + "anyhow", + "async-trait", + "aws-credential-types", + "aws-sdk-s3", + "bytes", + "clap", + "dashmap", + "hex", + "indicatif", + "op-alloy-consensus 0.22.4", + "op-alloy-network 0.22.4", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rdkafka", + "reqwest", + "serde", + "serde_json", + "serial_test", + "tips-audit-lib", + "tips-core", + "tokio", + "tracing", + "tracing-subscriber 0.3.22", + "url", + "uuid", +] + [[package]] name = "tokio" version = "1.49.0" @@ -3536,7 +7514,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3562,6 +7540,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.36", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.18" @@ -3571,7 +7569,22 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util", + "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]] @@ -3582,6 +7595,7 @@ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -3638,11 +7652,11 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "bitflags 2.10.0", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower", @@ -3694,6 +7708,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -3717,7 +7741,7 @@ dependencies = [ "tracing", "tracing-core", "tracing-log", - "tracing-subscriber", + "tracing-subscriber 0.3.22", "web-time", ] @@ -3731,6 +7755,15 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.22" @@ -3798,12 +7831,24 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -3817,6 +7862,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -3829,6 +7880,19 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -3847,6 +7911,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -3856,6 +7926,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3973,6 +8053,55 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.5", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -4014,6 +8143,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -4032,13 +8172,49 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] @@ -4050,6 +8226,52 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows-targets" version = "0.53.5" @@ -4057,58 +8279,190 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.1" @@ -4124,6 +8478,29 @@ dependencies = [ "memchr", ] +[[package]] +name = "wiremock" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" +dependencies = [ + "assert-json-diff", + "base64 0.22.1", + "deadpool", + "futures", + "http 1.4.0", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", + "url", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -4145,6 +8522,22 @@ dependencies = [ "tap", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yoke" version = "0.8.1" @@ -4267,3 +8660,32 @@ name = "zmij" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "bindgen", + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 4e07355..8ba4eb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,35 +1,124 @@ [workspace] resolver = "2" -members = ["crates/mempool-rebroadcaster", "crates/sidecrush"] +members = [ + # Existing services + "crates/mempool-rebroadcaster", + "crates/sidecrush", + # Tips crates + "crates/tips/core", + "crates/tips/audit", + "crates/tips/ingress-rpc", + "crates/tips/system-tests", + # Tips binaries + "bin/tips/audit", + "bin/tips/ingress-rpc", +] + +[workspace.package] +version = "0.1.0" +edition = "2024" +rust-version = "1.88" +license = "MIT" +homepage = "https://github.com/base/infra" +repository = "https://github.com/base/infra" + +[workspace.lints.rust] +missing-debug-implementations = "warn" +missing-docs = "warn" +unreachable-pub = "warn" +unused-must-use = "deny" +rust-2018-idioms = "deny" +unnameable-types = "warn" + +[workspace.lints.rustdoc] +all = "warn" + +[workspace.lints.clippy] +all = { level = "warn", priority = -1 } +missing-const-for-fn = "warn" +use-self = "warn" +option-if-let-else = "warn" +redundant-clone = "warn" [workspace.dependencies] -clap = { version = "4.0", features = ["derive", "env"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt", "ansi", "json"] } -tokio = { version = "1.0", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -metrics = "0.24.1" -metrics-derive = "0.1" -cadence = "1.4" +# Internal crates +tips-core = { path = "crates/tips/core" } +tips-audit-lib = { path = "crates/tips/audit" } +tips-ingress-rpc-lib = { path = "crates/tips/ingress-rpc" } +tips-system-tests = { path = "crates/tips/system-tests" } + +# base-reth +base-reth-rpc-types = { git = "https://github.com/base/base", branch = "main" } +base-bundles = { git = "https://github.com/base/base", branch = "main" } + +# revm +op-revm = { version = "15.0.0", default-features = false } +revm-context-interface = { version = "15.0.0", default-features = false } # alloy -alloy-primitives = { version = "1.5.2", default-features = false, features = [ - "map-foldhash", -] } -alloy-genesis = { version = "1.5.2", default-features = false } -alloy-eips = { version = "1.5.2", default-features = false } -alloy-rpc-types = { version = "1.5.2", default-features = false } -alloy-rpc-types-engine = { version = "1.5.2", default-features = false } -alloy-rpc-types-eth = { version = "1.5.2" } -alloy-consensus = { version = "1.5.2" } +alloy-serde = { version = "1.0.41", default-features = false } +alloy-signer = { version = "1.0.41", default-features = false } +alloy-network = { version = "1.0.41", default-features = false } +alloy-provider = { version = "1.0.41", default-features = false } +alloy-consensus = { version = "1.0.41", default-features = false } +alloy-sol-types = { version = "1.4.1", default-features = false } +alloy-rpc-types = { version = "1.1.2", default-features = false } +alloy-primitives = { version = "1.4.1", default-features = false } +alloy-signer-local = { version = "1.0.41", default-features = false } +# alloy deps needed by existing services +alloy-genesis = { version = "1.0.41", default-features = false } +alloy-eips = { version = "1.0.41", default-features = false } +alloy-rpc-types-engine = { version = "1.0.41", default-features = false } +alloy-rpc-types-eth = { version = "1.0.41" } alloy-trie = { version = "0.9.1", default-features = false } -alloy-provider = { version = "1.5.2" } alloy-hardforks = { version = "0.5" } -alloy-rpc-client = { version = "1.5.2" } -alloy-transport-http = { version = "1.5.2" } +alloy-rpc-client = { version = "1.0.41" } +alloy-transport-http = { version = "1.0.41" } # op-alloy +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-rpc-types-engine = { version = "0.22.0", default-features = false } op-alloy-consensus = { version = "0.22.0", default-features = false } +op-alloy-rpc-types-engine = { version = "0.22.0", default-features = false } + +# tokio +tokio = { version = "1.47.1", default-features = false } + +# async +async-trait = "0.1.89" + +# rpc +jsonrpsee = { version = "0.26.0", default-features = false } + +# kafka and s3 +bytes = { version = "1.8.0", default-features = false } +rdkafka = { version = "0.37.0", default-features = false } +aws-config = { version = "1.1.7", default-features = false } +aws-sdk-s3 = { version = "1.106.0", default-features = false } +aws-credential-types = { version = "1.1.7", default-features = false } + +# misc +url = { version = "2.5.7", default-features = false } +axum = { version = "0.8.3", default-features = false } +serde = { version = "1.0.219", default-features = false } +uuid = { version = "1.18.1", default-features = false } +clap = { version = "4.5.47", default-features = false } +backon = { version = "1.5.2", default-features = false } +chrono = { version = "0.4.42", default-features = false } +anyhow = { version = "1.0.99", default-features = false } +tracing = { version = "0.1.41", default-features = false } +wiremock = { version = "0.6.2", default-features = false } +dotenvy = { version = "0.15.7", default-features = false } +metrics = { version = "0.24.1", default-features = false } +metrics-derive = { version = "0.1", default-features = false } +serde_json = { version = "1.0.143", default-features = false } +testcontainers = { version = "0.23.1", default-features = false } +tracing-subscriber = { version = "0.3.20", default-features = false } +testcontainers-modules = { version = "0.11.2", default-features = false } +metrics-exporter-prometheus = { version = "0.17.0", default-features = false } +futures = { version = "0.3.31", default-features = false } +moka = { version = "0.12.12", default-features = false } + +# existing service dependencies +cadence = "1.4" diff --git a/bin/tips/audit/Cargo.toml b/bin/tips/audit/Cargo.toml new file mode 100644 index 0000000..7d43244 --- /dev/null +++ b/bin/tips/audit/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "tips-audit" +version.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[dependencies] +tips-core.workspace = true +tips-audit-lib.workspace = true +clap.workspace = true +tokio.workspace = true +anyhow.workspace = true +tracing.workspace = true +dotenvy.workspace = true +rdkafka.workspace = true +aws-config.workspace = true +aws-sdk-s3.workspace = true +aws-credential-types.workspace = true diff --git a/bin/tips/audit/src/main.rs b/bin/tips/audit/src/main.rs new file mode 100644 index 0000000..4f031a4 --- /dev/null +++ b/bin/tips/audit/src/main.rs @@ -0,0 +1,137 @@ +use std::net::SocketAddr; + +use anyhow::Result; +use aws_config::{BehaviorVersion, Region}; +use aws_credential_types::Credentials; +use aws_sdk_s3::{Client as S3Client, config::Builder as S3ConfigBuilder}; +use clap::{Parser, ValueEnum}; +use rdkafka::consumer::Consumer; +use tips_audit_lib::{ + KafkaAuditArchiver, KafkaAuditLogReader, S3EventReaderWriter, create_kafka_consumer, +}; +use tips_core::{logger::init_logger_with_format, metrics::init_prometheus_exporter}; +use tracing::info; + +#[derive(Debug, Clone, ValueEnum)] +enum S3ConfigType { + Aws, + Manual, +} + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[arg(long, env = "TIPS_AUDIT_KAFKA_PROPERTIES_FILE")] + kafka_properties_file: String, + + #[arg(long, env = "TIPS_AUDIT_KAFKA_TOPIC")] + kafka_topic: String, + + #[arg(long, env = "TIPS_AUDIT_S3_BUCKET")] + s3_bucket: String, + + #[arg(long, env = "TIPS_AUDIT_LOG_LEVEL", default_value = "info")] + log_level: String, + + #[arg(long, env = "TIPS_AUDIT_LOG_FORMAT", default_value = "pretty")] + log_format: tips_core::logger::LogFormat, + + #[arg(long, env = "TIPS_AUDIT_S3_CONFIG_TYPE", default_value = "aws")] + s3_config_type: S3ConfigType, + + #[arg(long, env = "TIPS_AUDIT_S3_ENDPOINT")] + s3_endpoint: Option, + + #[arg(long, env = "TIPS_AUDIT_S3_REGION", default_value = "us-east-1")] + s3_region: String, + + #[arg(long, env = "TIPS_AUDIT_S3_ACCESS_KEY_ID")] + s3_access_key_id: Option, + + #[arg(long, env = "TIPS_AUDIT_S3_SECRET_ACCESS_KEY")] + s3_secret_access_key: Option, + + #[arg(long, env = "TIPS_AUDIT_METRICS_ADDR", default_value = "0.0.0.0:9002")] + metrics_addr: SocketAddr, + + #[arg(long, env = "TIPS_AUDIT_WORKER_POOL_SIZE", default_value = "80")] + worker_pool_size: usize, + + #[arg(long, env = "TIPS_AUDIT_CHANNEL_BUFFER_SIZE", default_value = "500")] + channel_buffer_size: usize, + + #[arg(long, env = "TIPS_AUDIT_NOOP_ARCHIVE", default_value = "false")] + noop_archive: bool, +} + +#[tokio::main] +async fn main() -> Result<()> { + dotenvy::dotenv().ok(); + + let args = Args::parse(); + + init_logger_with_format(&args.log_level, args.log_format); + + init_prometheus_exporter(args.metrics_addr).expect("Failed to install Prometheus exporter"); + + info!( + kafka_properties_file = %args.kafka_properties_file, + kafka_topic = %args.kafka_topic, + s3_bucket = %args.s3_bucket, + metrics_addr = %args.metrics_addr, + "Starting audit archiver" + ); + + let consumer = create_kafka_consumer(&args.kafka_properties_file)?; + consumer.subscribe(&[&args.kafka_topic])?; + + let reader = KafkaAuditLogReader::new(consumer, args.kafka_topic.clone())?; + + let s3_client = create_s3_client(&args).await?; + let s3_bucket = args.s3_bucket.clone(); + let writer = S3EventReaderWriter::new(s3_client, s3_bucket); + + let mut archiver = KafkaAuditArchiver::new( + reader, + writer, + args.worker_pool_size, + args.channel_buffer_size, + args.noop_archive, + ); + + info!("Audit archiver initialized, starting main loop"); + + archiver.run().await +} + +async fn create_s3_client(args: &Args) -> Result { + match args.s3_config_type { + S3ConfigType::Manual => { + let region = args.s3_region.clone(); + let mut config_builder = + aws_config::defaults(BehaviorVersion::latest()).region(Region::new(region)); + + if let Some(endpoint) = &args.s3_endpoint { + config_builder = config_builder.endpoint_url(endpoint); + } + + if let (Some(access_key), Some(secret_key)) = + (&args.s3_access_key_id, &args.s3_secret_access_key) + { + let credentials = Credentials::new(access_key, secret_key, None, None, "manual"); + config_builder = config_builder.credentials_provider(credentials); + } + + let config = config_builder.load().await; + let s3_config_builder = S3ConfigBuilder::from(&config).force_path_style(true); + + info!(message = "manually configuring s3 client"); + Ok(S3Client::from_conf(s3_config_builder.build())) + } + S3ConfigType::Aws => { + info!(message = "using aws s3 client"); + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + Ok(S3Client::new(&config)) + } + } +} diff --git a/bin/tips/ingress-rpc/Cargo.toml b/bin/tips/ingress-rpc/Cargo.toml new file mode 100644 index 0000000..dd8d9d2 --- /dev/null +++ b/bin/tips/ingress-rpc/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tips-ingress-rpc" +version.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[dependencies] +tips-core.workspace = true +tips-audit-lib.workspace = true +tips-ingress-rpc-lib.workspace = true +clap.workspace = true +tokio.workspace = true +anyhow.workspace = true +tracing.workspace = true +dotenvy.workspace = true +rdkafka.workspace = true +jsonrpsee.workspace = true +op-alloy-network.workspace = true +alloy-provider.workspace = true diff --git a/bin/tips/ingress-rpc/src/main.rs b/bin/tips/ingress-rpc/src/main.rs new file mode 100644 index 0000000..0853ca1 --- /dev/null +++ b/bin/tips/ingress-rpc/src/main.rs @@ -0,0 +1,108 @@ +use alloy_provider::ProviderBuilder; +use clap::Parser; +use jsonrpsee::server::Server; +use op_alloy_network::Optimism; +use rdkafka::{ClientConfig, producer::FutureProducer}; +use tips_audit_lib::{BundleEvent, KafkaBundleEventPublisher, connect_audit_to_publisher}; +use tips_core::{ + AcceptedBundle, MeterBundleResponse, kafka::load_kafka_config_from_file, + logger::init_logger_with_format, metrics::init_prometheus_exporter, +}; +use tips_ingress_rpc_lib::{ + Config, connect_ingress_to_builder, + health::bind_health_server, + queue::KafkaMessageQueue, + service::{IngressApiServer, IngressService, Providers}, +}; +use tokio::sync::{broadcast, mpsc}; +use tracing::info; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + dotenvy::dotenv().ok(); + + let config = Config::parse(); + let cfg = config.clone(); + + init_logger_with_format(&config.log_level, config.log_format); + + init_prometheus_exporter(config.metrics_addr).expect("Failed to install Prometheus exporter"); + + info!( + message = "Starting ingress service", + address = %config.address, + port = config.port, + mempool_url = %config.mempool_url, + simulation_rpc = %config.simulation_rpc, + metrics_address = %config.metrics_addr, + health_check_address = %config.health_check_addr, + ); + + let providers = Providers { + mempool: ProviderBuilder::new() + .disable_recommended_fillers() + .network::() + .connect_http(config.mempool_url), + simulation: ProviderBuilder::new() + .disable_recommended_fillers() + .network::() + .connect_http(config.simulation_rpc), + raw_tx_forward: config.raw_tx_forward_rpc.clone().map(|url| { + ProviderBuilder::new() + .disable_recommended_fillers() + .network::() + .connect_http(url) + }), + }; + + let ingress_client_config = + ClientConfig::from_iter(load_kafka_config_from_file(&config.ingress_kafka_properties)?); + + let queue_producer: FutureProducer = ingress_client_config.create()?; + + let queue = KafkaMessageQueue::new(queue_producer); + + let audit_client_config = + ClientConfig::from_iter(load_kafka_config_from_file(&config.audit_kafka_properties)?); + + let audit_producer: FutureProducer = audit_client_config.create()?; + + let audit_publisher = KafkaBundleEventPublisher::new(audit_producer, config.audit_topic); + let (audit_tx, audit_rx) = mpsc::unbounded_channel::(); + connect_audit_to_publisher(audit_rx, audit_publisher); + + let (builder_tx, _) = + broadcast::channel::(config.max_buffered_meter_bundle_responses); + let (builder_backrun_tx, _) = + broadcast::channel::(config.max_buffered_backrun_bundles); + config.builder_rpcs.iter().for_each(|builder_rpc| { + let metering_rx = builder_tx.subscribe(); + let backrun_rx = builder_backrun_tx.subscribe(); + connect_ingress_to_builder(metering_rx, backrun_rx, builder_rpc.clone()); + }); + + let health_check_addr = config.health_check_addr; + let (bound_health_addr, health_handle) = bind_health_server(health_check_addr).await?; + info!( + message = "Health check server started", + address = %bound_health_addr + ); + + let service = + IngressService::new(providers, queue, audit_tx, builder_tx, builder_backrun_tx, cfg); + let bind_addr = format!("{}:{}", config.address, config.port); + + let server = Server::builder().build(&bind_addr).await?; + let addr = server.local_addr()?; + let handle = server.start(service.into_rpc()); + + info!( + message = "Ingress RPC server started", + address = %addr + ); + + handle.stopped().await; + health_handle.abort(); + + Ok(()) +} diff --git a/crates/tips/audit/Cargo.toml b/crates/tips/audit/Cargo.toml new file mode 100644 index 0000000..d74b28b --- /dev/null +++ b/crates/tips/audit/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "tips-audit-lib" +version.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[lints] +workspace = true + +[dependencies] +bytes.workspace = true +metrics.workspace = true +async-trait.workspace = true +metrics-derive.workspace = true +tips-core = { workspace = true, features = ["test-utils"] } +serde = { workspace = true, features = ["std", "derive"] } +tokio = { workspace = true, features = ["full"] } +uuid = { workspace = true, features = ["v4", "serde"] } +tracing = { workspace = true, features = ["std"] } +anyhow = { workspace = true, features = ["std"] } +serde_json = { workspace = true, features = ["std"] } +rdkafka = { workspace = true, features = ["tokio", "libz", "zstd", "ssl-vendored"] } +alloy-consensus = { workspace = true, features = ["std"] } +alloy-primitives = { workspace = true, features = ["map-foldhash", "serde"] } +aws-sdk-s3 = { workspace = true, features = ["rustls", "default-https-client", "rt-tokio"] } +futures = { workspace = true } + +[dev-dependencies] +testcontainers = { workspace = true, features = ["blocking"] } +testcontainers-modules = { workspace = true, features = ["postgres", "kafka", "minio"] } +aws-config = { workspace = true, features = ["default-https-client", "rt-tokio"] } diff --git a/crates/tips/audit/src/archiver.rs b/crates/tips/audit/src/archiver.rs new file mode 100644 index 0000000..15bea24 --- /dev/null +++ b/crates/tips/audit/src/archiver.rs @@ -0,0 +1,156 @@ +use std::{ + fmt, + marker::PhantomData, + sync::Arc, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, +}; + +use anyhow::Result; +use tokio::{ + sync::{Mutex, mpsc}, + time::sleep, +}; +use tracing::{error, info}; + +use crate::{ + metrics::Metrics, + reader::{Event, EventReader}, + storage::EventWriter, +}; + +/// Archives audit events from Kafka to S3 storage. +pub struct KafkaAuditArchiver +where + R: EventReader, + W: EventWriter + Clone + Send + 'static, +{ + reader: R, + event_tx: mpsc::Sender, + metrics: Metrics, + _phantom: PhantomData, +} + +impl fmt::Debug for KafkaAuditArchiver +where + R: EventReader, + W: EventWriter + Clone + Send + 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("KafkaAuditArchiver").finish_non_exhaustive() + } +} + +impl KafkaAuditArchiver +where + R: EventReader, + W: EventWriter + Clone + Send + 'static, +{ + /// Creates a new archiver with the given reader and writer. + pub fn new( + reader: R, + writer: W, + worker_pool_size: usize, + channel_buffer_size: usize, + noop_archive: bool, + ) -> Self { + let (event_tx, event_rx) = mpsc::channel(channel_buffer_size); + let metrics = Metrics::default(); + + Self::spawn_workers(writer, event_rx, metrics.clone(), worker_pool_size, noop_archive); + + Self { reader, event_tx, metrics, _phantom: PhantomData } + } + + fn spawn_workers( + writer: W, + event_rx: mpsc::Receiver, + metrics: Metrics, + worker_pool_size: usize, + noop_archive: bool, + ) { + let event_rx = Arc::new(Mutex::new(event_rx)); + + for worker_id in 0..worker_pool_size { + let writer = writer.clone(); + let metrics = metrics.clone(); + let event_rx = event_rx.clone(); + + tokio::spawn(async move { + loop { + let event = { + let mut rx = event_rx.lock().await; + rx.recv().await + }; + + match event { + Some(event) => { + let archive_start = Instant::now(); + // tmp: only use this to clear kafka consumer offset + // TODO: use debug! later + if noop_archive { + info!( + worker_id, + bundle_id = %event.event.bundle_id(), + tx_ids = ?event.event.transaction_ids(), + timestamp = event.timestamp, + "Noop archive - skipping event" + ); + metrics.events_processed.increment(1); + metrics.in_flight_archive_tasks.decrement(1.0); + continue; + } + if let Err(e) = writer.archive_event(event).await { + error!(worker_id, error = %e, "Failed to write event"); + } else { + metrics + .archive_event_duration + .record(archive_start.elapsed().as_secs_f64()); + metrics.events_processed.increment(1); + } + metrics.in_flight_archive_tasks.decrement(1.0); + } + None => { + info!(worker_id, "Worker stopped - channel closed"); + break; + } + } + } + }); + } + } + + /// Runs the archiver loop, reading events and writing them to storage. + pub async fn run(&mut self) -> Result<()> { + loop { + let read_start = Instant::now(); + match self.reader.read_event().await { + Ok(event) => { + self.metrics.kafka_read_duration.record(read_start.elapsed().as_secs_f64()); + + let now_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as i64; + let event_age_ms = now_ms.saturating_sub(event.timestamp); + self.metrics.event_age.record(event_age_ms as f64); + + self.metrics.in_flight_archive_tasks.increment(1.0); + if let Err(e) = self.event_tx.send(event).await { + error!(error = %e, "Failed to send event to worker pool"); + self.metrics.in_flight_archive_tasks.decrement(1.0); + } + + let commit_start = Instant::now(); + if let Err(e) = self.reader.commit().await { + error!(error = %e, "Failed to commit message"); + } + self.metrics.kafka_commit_duration.record(commit_start.elapsed().as_secs_f64()); + } + Err(e) => { + error!(error = %e, "Error reading events"); + sleep(Duration::from_secs(1)).await; + } + } + } + } +} diff --git a/crates/tips/audit/src/lib.rs b/crates/tips/audit/src/lib.rs new file mode 100644 index 0000000..fe64f9f --- /dev/null +++ b/crates/tips/audit/src/lib.rs @@ -0,0 +1,73 @@ +//! Audit library for tracking and archiving bundle and user operation events. +//! +//! This crate provides functionality for publishing events to Kafka, +//! archiving them to S3, and reading event history. + +#![doc(issue_tracker_base_url = "https://github.com/base/tips/issues/")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +mod archiver; +pub use archiver::KafkaAuditArchiver; + +mod metrics; +pub use metrics::Metrics; + +mod publisher; +pub use publisher::{ + BundleEventPublisher, KafkaBundleEventPublisher, KafkaUserOpEventPublisher, + LoggingBundleEventPublisher, LoggingUserOpEventPublisher, UserOpEventPublisher, +}; + +mod reader; +pub use reader::{ + Event, EventReader, KafkaAuditLogReader, KafkaUserOpAuditLogReader, UserOpEventReader, + UserOpEventWrapper, assign_topic_partition, create_kafka_consumer, +}; + +mod storage; +pub use storage::{ + BundleEventS3Reader, BundleHistory, BundleHistoryEvent, EventWriter, S3EventReaderWriter, + S3Key, TransactionMetadata, UserOpEventS3Reader, UserOpEventWriter, UserOpHistory, + UserOpHistoryEvent, +}; + +mod types; +use tokio::sync::mpsc; +use tracing::error; +pub use types::{ + BundleEvent, BundleId, DropReason, Transaction, TransactionId, UserOpDropReason, UserOpEvent, + UserOpHash, +}; + +/// Connects a bundle event receiver to a publisher, spawning a task to forward events. +pub fn connect_audit_to_publisher

(event_rx: mpsc::UnboundedReceiver, publisher: P) +where + P: BundleEventPublisher + 'static, +{ + tokio::spawn(async move { + let mut event_rx = event_rx; + while let Some(event) = event_rx.recv().await { + if let Err(e) = publisher.publish(event).await { + error!(error = %e, "failed to publish bundle event"); + } + } + }); +} + +/// Connects a user operation event receiver to a publisher, spawning a task to forward events. +pub fn connect_userop_audit_to_publisher

( + event_rx: mpsc::UnboundedReceiver, + publisher: P, +) where + P: UserOpEventPublisher + 'static, +{ + tokio::spawn(async move { + let mut event_rx = event_rx; + while let Some(event) = event_rx.recv().await { + if let Err(e) = publisher.publish(event).await { + error!(error = %e, "Failed to publish user op event"); + } + } + }); +} diff --git a/crates/tips/audit/src/metrics.rs b/crates/tips/audit/src/metrics.rs new file mode 100644 index 0000000..906de8e --- /dev/null +++ b/crates/tips/audit/src/metrics.rs @@ -0,0 +1,55 @@ +use metrics::{Counter, Gauge, Histogram}; +use metrics_derive::Metrics; + +/// Metrics for audit operations including Kafka reads, S3 writes, and event processing. +#[derive(Metrics, Clone)] +#[metrics(scope = "tips_audit")] +pub struct Metrics { + /// Duration of archive_event operations. + #[metric(describe = "Duration of archive_event")] + pub archive_event_duration: Histogram, + + /// Age of event when processed (now - event timestamp). + #[metric(describe = "Age of event when processed (now - event timestamp)")] + pub event_age: Histogram, + + /// Duration of Kafka read_event operations. + #[metric(describe = "Duration of Kafka read_event")] + pub kafka_read_duration: Histogram, + + /// Duration of Kafka commit operations. + #[metric(describe = "Duration of Kafka commit")] + pub kafka_commit_duration: Histogram, + + /// Duration of update_bundle_history operations. + #[metric(describe = "Duration of update_bundle_history")] + pub update_bundle_history_duration: Histogram, + + /// Duration of updating all transaction indexes. + #[metric(describe = "Duration of update all transaction indexes")] + pub update_tx_indexes_duration: Histogram, + + /// Duration of S3 get_object operations. + #[metric(describe = "Duration of S3 get_object")] + pub s3_get_duration: Histogram, + + /// Duration of S3 put_object operations. + #[metric(describe = "Duration of S3 put_object")] + pub s3_put_duration: Histogram, + + /// Total events processed. + #[metric(describe = "Total events processed")] + pub events_processed: Counter, + + /// Total S3 writes skipped due to deduplication. + #[metric(describe = "Total S3 writes skipped due to dedup")] + pub s3_writes_skipped: Counter, + + /// Number of in-flight archive tasks. + #[metric(describe = "Number of in-flight archive tasks")] + pub in_flight_archive_tasks: Gauge, + + /// Number of failed archive tasks. + #[metric(describe = "Number of failed archive tasks")] + pub failed_archive_tasks: Counter, +} diff --git a/crates/tips/audit/src/publisher.rs b/crates/tips/audit/src/publisher.rs new file mode 100644 index 0000000..b845f14 --- /dev/null +++ b/crates/tips/audit/src/publisher.rs @@ -0,0 +1,228 @@ +use anyhow::Result; +use async_trait::async_trait; +use rdkafka::producer::{FutureProducer, FutureRecord}; +use tracing::{debug, error, info}; + +use crate::types::{BundleEvent, UserOpEvent}; + +/// Trait for publishing bundle events. +#[async_trait] +pub trait BundleEventPublisher: Send + Sync { + /// Publishes a single bundle event. + async fn publish(&self, event: BundleEvent) -> Result<()>; + + /// Publishes multiple bundle events. + async fn publish_all(&self, events: Vec) -> Result<()>; +} + +/// Publishes bundle events to Kafka. +#[derive(Clone)] +pub struct KafkaBundleEventPublisher { + producer: FutureProducer, + topic: String, +} + +impl std::fmt::Debug for KafkaBundleEventPublisher { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KafkaBundleEventPublisher") + .field("topic", &self.topic) + .finish_non_exhaustive() + } +} + +impl KafkaBundleEventPublisher { + /// Creates a new Kafka bundle event publisher. + pub const fn new(producer: FutureProducer, topic: String) -> Self { + Self { producer, topic } + } + + async fn send_event(&self, event: &BundleEvent) -> Result<()> { + let bundle_id = event.bundle_id(); + let key = event.generate_event_key(); + let payload = serde_json::to_vec(event)?; + + let record = FutureRecord::to(&self.topic).key(&key).payload(&payload); + + match self.producer.send(record, tokio::time::Duration::from_secs(5)).await { + Ok(_) => { + debug!( + bundle_id = %bundle_id, + topic = %self.topic, + payload_size = payload.len(), + "successfully published event" + ); + Ok(()) + } + Err((err, _)) => { + error!( + bundle_id = %bundle_id, + topic = %self.topic, + error = %err, + "failed to publish event" + ); + Err(anyhow::anyhow!("Failed to publish event: {err}")) + } + } + } +} + +#[async_trait] +impl BundleEventPublisher for KafkaBundleEventPublisher { + async fn publish(&self, event: BundleEvent) -> Result<()> { + self.send_event(&event).await + } + + async fn publish_all(&self, events: Vec) -> Result<()> { + for event in events { + self.send_event(&event).await?; + } + Ok(()) + } +} + +/// Publishes bundle events to logs (for testing/debugging). +#[derive(Clone, Debug)] +pub struct LoggingBundleEventPublisher; + +impl LoggingBundleEventPublisher { + /// Creates a new logging bundle event publisher. + pub const fn new() -> Self { + Self + } +} + +impl Default for LoggingBundleEventPublisher { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl BundleEventPublisher for LoggingBundleEventPublisher { + async fn publish(&self, event: BundleEvent) -> Result<()> { + info!( + bundle_id = %event.bundle_id(), + event = ?event, + "Received bundle event" + ); + Ok(()) + } + + async fn publish_all(&self, events: Vec) -> Result<()> { + for event in events { + self.publish(event).await?; + } + Ok(()) + } +} + +/// Trait for publishing user operation events. +#[async_trait] +pub trait UserOpEventPublisher: Send + Sync { + /// Publishes a single user operation event. + async fn publish(&self, event: UserOpEvent) -> Result<()>; + + /// Publishes multiple user operation events. + async fn publish_all(&self, events: Vec) -> Result<()>; +} + +/// Publishes user operation events to Kafka. +#[derive(Clone)] +pub struct KafkaUserOpEventPublisher { + producer: FutureProducer, + topic: String, +} + +impl std::fmt::Debug for KafkaUserOpEventPublisher { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KafkaUserOpEventPublisher") + .field("topic", &self.topic) + .finish_non_exhaustive() + } +} + +impl KafkaUserOpEventPublisher { + /// Creates a new Kafka user operation event publisher. + pub const fn new(producer: FutureProducer, topic: String) -> Self { + Self { producer, topic } + } + + async fn send_event(&self, event: &UserOpEvent) -> Result<()> { + let user_op_hash = event.user_op_hash(); + let key = event.generate_event_key(); + let payload = serde_json::to_vec(event)?; + + let record = FutureRecord::to(&self.topic).key(&key).payload(&payload); + + match self.producer.send(record, tokio::time::Duration::from_secs(5)).await { + Ok(_) => { + debug!( + user_op_hash = %user_op_hash, + topic = %self.topic, + payload_size = payload.len(), + "Successfully published user op event" + ); + Ok(()) + } + Err((err, _)) => { + error!( + user_op_hash = %user_op_hash, + topic = %self.topic, + error = %err, + "Failed to publish user op event" + ); + Err(anyhow::anyhow!("Failed to publish user op event: {err}")) + } + } + } +} + +#[async_trait] +impl UserOpEventPublisher for KafkaUserOpEventPublisher { + async fn publish(&self, event: UserOpEvent) -> Result<()> { + self.send_event(&event).await + } + + async fn publish_all(&self, events: Vec) -> Result<()> { + for event in events { + self.send_event(&event).await?; + } + Ok(()) + } +} + +/// Publishes user operation events to logs (for testing/debugging). +#[derive(Clone, Debug)] +pub struct LoggingUserOpEventPublisher; + +impl LoggingUserOpEventPublisher { + /// Creates a new logging user operation event publisher. + pub const fn new() -> Self { + Self + } +} + +impl Default for LoggingUserOpEventPublisher { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl UserOpEventPublisher for LoggingUserOpEventPublisher { + async fn publish(&self, event: UserOpEvent) -> Result<()> { + info!( + user_op_hash = %event.user_op_hash(), + event = ?event, + "Received user op event" + ); + Ok(()) + } + + async fn publish_all(&self, events: Vec) -> Result<()> { + for event in events { + self.publish(event).await?; + } + Ok(()) + } +} diff --git a/crates/tips/audit/src/reader.rs b/crates/tips/audit/src/reader.rs new file mode 100644 index 0000000..4a97f95 --- /dev/null +++ b/crates/tips/audit/src/reader.rs @@ -0,0 +1,248 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use anyhow::Result; +use async_trait::async_trait; +use rdkafka::{ + Timestamp, TopicPartitionList, + config::ClientConfig, + consumer::{Consumer, StreamConsumer}, + message::Message, +}; +use tips_core::kafka::load_kafka_config_from_file; +use tokio::time::sleep; +use tracing::{debug, error, info}; + +use crate::types::{BundleEvent, UserOpEvent}; + +/// Creates a Kafka consumer from a properties file. +pub fn create_kafka_consumer(kafka_properties_file: &str) -> Result { + let client_config: ClientConfig = + ClientConfig::from_iter(load_kafka_config_from_file(kafka_properties_file)?); + let consumer: StreamConsumer = client_config.create()?; + Ok(consumer) +} + +/// Assigns a topic partition to a consumer. +pub fn assign_topic_partition(consumer: &StreamConsumer, topic: &str) -> Result<()> { + let mut tpl = TopicPartitionList::new(); + tpl.add_partition(topic, 0); + consumer.assign(&tpl)?; + Ok(()) +} + +/// A bundle event with metadata from Kafka. +#[derive(Debug, Clone)] +pub struct Event { + /// The event key. + pub key: String, + /// The bundle event. + pub event: BundleEvent, + /// The event timestamp in milliseconds. + pub timestamp: i64, +} + +/// Trait for reading bundle events. +#[async_trait] +pub trait EventReader { + /// Reads the next event. + async fn read_event(&mut self) -> Result; + /// Commits the last read message. + async fn commit(&mut self) -> Result<()>; +} + +/// Reads bundle audit events from Kafka. +pub struct KafkaAuditLogReader { + consumer: StreamConsumer, + topic: String, + last_message_offset: Option, + last_message_partition: Option, +} + +impl std::fmt::Debug for KafkaAuditLogReader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KafkaAuditLogReader") + .field("topic", &self.topic) + .field("last_message_offset", &self.last_message_offset) + .field("last_message_partition", &self.last_message_partition) + .finish_non_exhaustive() + } +} + +impl KafkaAuditLogReader { + /// Creates a new Kafka audit log reader. + pub fn new(consumer: StreamConsumer, topic: String) -> Result { + consumer.subscribe(&[&topic])?; + Ok(Self { consumer, topic, last_message_offset: None, last_message_partition: None }) + } +} + +#[async_trait] +impl EventReader for KafkaAuditLogReader { + async fn read_event(&mut self) -> Result { + match self.consumer.recv().await { + Ok(message) => { + let payload = + message.payload().ok_or_else(|| anyhow::anyhow!("Message has no payload"))?; + + // Extract Kafka timestamp, use current time as fallback + let timestamp = match message.timestamp() { + Timestamp::CreateTime(millis) => millis, + Timestamp::LogAppendTime(millis) => millis, + Timestamp::NotAvailable => { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() + as i64 + } + }; + + let event: BundleEvent = serde_json::from_slice(payload)?; + + info!( + bundle_id = %event.bundle_id(), + tx_ids = ?event.transaction_ids(), + timestamp = timestamp, + offset = message.offset(), + partition = message.partition(), + "Received event with timestamp" + ); + + self.last_message_offset = Some(message.offset()); + self.last_message_partition = Some(message.partition()); + + let key = message + .key() + .map(|k| String::from_utf8_lossy(k).to_string()) + .ok_or_else(|| anyhow::anyhow!("Message missing required key"))?; + + let event_result = Event { key, event, timestamp }; + + Ok(event_result) + } + Err(e) => { + error!(error = %e, "Error receiving message from Kafka"); + sleep(Duration::from_secs(1)).await; + Err(e.into()) + } + } + } + + async fn commit(&mut self) -> Result<()> { + if let (Some(offset), Some(partition)) = + (self.last_message_offset, self.last_message_partition) + { + let mut tpl = TopicPartitionList::new(); + tpl.add_partition_offset(&self.topic, partition, rdkafka::Offset::Offset(offset + 1))?; + self.consumer.commit(&tpl, rdkafka::consumer::CommitMode::Async)?; + } + Ok(()) + } +} + +impl KafkaAuditLogReader { + /// Returns the topic this reader is subscribed to. + pub fn topic(&self) -> &str { + &self.topic + } +} + +/// A user operation event with metadata from Kafka. +#[derive(Debug, Clone)] +pub struct UserOpEventWrapper { + /// The event key. + pub key: String, + /// The user operation event. + pub event: UserOpEvent, + /// The event timestamp in milliseconds. + pub timestamp: i64, +} + +/// Trait for reading user operation events. +#[async_trait] +pub trait UserOpEventReader { + /// Reads the next user operation event. + async fn read_event(&mut self) -> Result; + /// Commits the last read message. + async fn commit(&mut self) -> Result<()>; +} + +/// Reads user operation audit events from Kafka. +pub struct KafkaUserOpAuditLogReader { + consumer: StreamConsumer, + topic: String, + last_message_offset: Option, + last_message_partition: Option, +} + +impl std::fmt::Debug for KafkaUserOpAuditLogReader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KafkaUserOpAuditLogReader") + .field("topic", &self.topic) + .field("last_message_offset", &self.last_message_offset) + .field("last_message_partition", &self.last_message_partition) + .finish_non_exhaustive() + } +} + +impl KafkaUserOpAuditLogReader { + /// Creates a new Kafka user operation audit log reader. + pub fn new(consumer: StreamConsumer, topic: String) -> Result { + consumer.subscribe(&[&topic])?; + Ok(Self { consumer, topic, last_message_offset: None, last_message_partition: None }) + } +} + +#[async_trait] +impl UserOpEventReader for KafkaUserOpAuditLogReader { + async fn read_event(&mut self) -> Result { + match self.consumer.recv().await { + Ok(message) => { + let payload = + message.payload().ok_or_else(|| anyhow::anyhow!("Message has no payload"))?; + + let timestamp = match message.timestamp() { + Timestamp::CreateTime(millis) => millis, + Timestamp::LogAppendTime(millis) => millis, + Timestamp::NotAvailable => { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() + as i64 + } + }; + + let event: UserOpEvent = serde_json::from_slice(payload)?; + + debug!( + user_op_hash = %event.user_op_hash(), + timestamp = timestamp, + offset = message.offset(), + partition = message.partition(), + "Received UserOp event" + ); + + self.last_message_offset = Some(message.offset()); + self.last_message_partition = Some(message.partition()); + + let key = message + .key() + .map(|k| String::from_utf8_lossy(k).to_string()) + .ok_or_else(|| anyhow::anyhow!("Message missing required key"))?; + + Ok(UserOpEventWrapper { key, event, timestamp }) + } + Err(e) => { + error!(error = %e, "Error receiving UserOp message from Kafka"); + sleep(Duration::from_secs(1)).await; + Err(e.into()) + } + } + } + + async fn commit(&mut self) -> Result<()> { + if let (Some(offset), Some(partition)) = + (self.last_message_offset, self.last_message_partition) + { + let mut tpl = TopicPartitionList::new(); + tpl.add_partition_offset(&self.topic, partition, rdkafka::Offset::Offset(offset + 1))?; + self.consumer.commit(&tpl, rdkafka::consumer::CommitMode::Async)?; + } + Ok(()) + } +} diff --git a/crates/tips/audit/src/storage.rs b/crates/tips/audit/src/storage.rs new file mode 100644 index 0000000..f931521 --- /dev/null +++ b/crates/tips/audit/src/storage.rs @@ -0,0 +1,924 @@ +use std::{fmt, fmt::Debug, time::Instant}; + +use alloy_primitives::{Address, TxHash, U256}; +use anyhow::Result; +use async_trait::async_trait; +use aws_sdk_s3::{ + Client as S3Client, error::SdkError, operation::get_object::GetObjectError, + primitives::ByteStream, +}; +use futures::future; +use serde::{Deserialize, Serialize}; +use tips_core::AcceptedBundle; +use tracing::info; + +use crate::{ + metrics::Metrics, + reader::Event, + types::{ + BundleEvent, BundleId, DropReason, TransactionId, UserOpDropReason, UserOpEvent, UserOpHash, + }, +}; + +/// S3 key types for storing different event types. +#[derive(Debug)] +pub enum S3Key { + /// Key for bundle events. + Bundle(BundleId), + /// Key for transaction lookups by hash. + TransactionByHash(TxHash), + /// Key for user operation events. + UserOp(UserOpHash), +} + +impl fmt::Display for S3Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Bundle(bundle_id) => write!(f, "bundles/{bundle_id}"), + Self::TransactionByHash(hash) => write!(f, "transactions/by_hash/{hash}"), + Self::UserOp(user_op_hash) => write!(f, "userops/{user_op_hash}"), + } + } +} + +/// Metadata for a transaction, tracking which bundles it belongs to. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct TransactionMetadata { + /// Bundle IDs that contain this transaction. + pub bundle_ids: Vec, +} + +/// History event for a bundle. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "event", content = "data")] +pub enum BundleHistoryEvent { + /// Bundle was received. + Received { + /// Event key. + key: String, + /// Event timestamp. + timestamp: i64, + /// The accepted bundle. + bundle: Box, + }, + /// Bundle was cancelled. + Cancelled { + /// Event key. + key: String, + /// Event timestamp. + timestamp: i64, + }, + /// Bundle was included by a builder. + BuilderIncluded { + /// Event key. + key: String, + /// Event timestamp. + timestamp: i64, + /// Builder identifier. + builder: String, + /// Block number. + block_number: u64, + /// Flashblock index. + flashblock_index: u64, + }, + /// Bundle was included in a block. + BlockIncluded { + /// Event key. + key: String, + /// Event timestamp. + timestamp: i64, + /// Block number. + block_number: u64, + /// Block hash. + block_hash: TxHash, + }, + /// Bundle was dropped. + Dropped { + /// Event key. + key: String, + /// Event timestamp. + timestamp: i64, + /// Drop reason. + reason: DropReason, + }, +} + +impl BundleHistoryEvent { + /// Returns the event key. + pub fn key(&self) -> &str { + match self { + Self::Received { key, .. } => key, + Self::Cancelled { key, .. } => key, + Self::BuilderIncluded { key, .. } => key, + Self::BlockIncluded { key, .. } => key, + Self::Dropped { key, .. } => key, + } + } +} + +/// History of events for a bundle. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct BundleHistory { + /// List of history events. + pub history: Vec, +} + +/// History event for a user operation. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "event", content = "data")] +pub enum UserOpHistoryEvent { + /// User operation was added to the mempool. + AddedToMempool { + /// Event key. + key: String, + /// Event timestamp. + timestamp: i64, + /// Sender address. + sender: Address, + /// Entry point address. + entry_point: Address, + /// Nonce. + nonce: U256, + }, + /// User operation was dropped. + Dropped { + /// Event key. + key: String, + /// Event timestamp. + timestamp: i64, + /// Drop reason. + reason: UserOpDropReason, + }, + /// User operation was included in a block. + Included { + /// Event key. + key: String, + /// Event timestamp. + timestamp: i64, + /// Block number. + block_number: u64, + /// Transaction hash. + tx_hash: TxHash, + }, +} + +impl UserOpHistoryEvent { + /// Returns the event key. + pub fn key(&self) -> &str { + match self { + Self::AddedToMempool { key, .. } => key, + Self::Dropped { key, .. } => key, + Self::Included { key, .. } => key, + } + } +} + +/// History of events for a user operation. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct UserOpHistory { + /// List of history events. + pub history: Vec, +} + +pub(crate) use crate::reader::UserOpEventWrapper; + +fn update_bundle_history_transform( + bundle_history: BundleHistory, + event: &Event, +) -> Option { + let mut history = bundle_history.history; + let bundle_id = event.event.bundle_id(); + + // Check for deduplication - if event with same key already exists, skip + if history.iter().any(|h| h.key() == event.key) { + info!( + bundle_id = %bundle_id, + event_key = %event.key, + "Event already exists, skipping due to deduplication" + ); + return None; + } + + let history_event = match &event.event { + BundleEvent::Received { bundle, .. } => BundleHistoryEvent::Received { + key: event.key.clone(), + timestamp: event.timestamp, + bundle: bundle.clone(), + }, + BundleEvent::Cancelled { .. } => { + BundleHistoryEvent::Cancelled { key: event.key.clone(), timestamp: event.timestamp } + } + BundleEvent::BuilderIncluded { builder, block_number, flashblock_index, .. } => { + BundleHistoryEvent::BuilderIncluded { + key: event.key.clone(), + timestamp: event.timestamp, + builder: builder.clone(), + block_number: *block_number, + flashblock_index: *flashblock_index, + } + } + BundleEvent::BlockIncluded { block_number, block_hash, .. } => { + BundleHistoryEvent::BlockIncluded { + key: event.key.clone(), + timestamp: event.timestamp, + block_number: *block_number, + block_hash: *block_hash, + } + } + BundleEvent::Dropped { reason, .. } => BundleHistoryEvent::Dropped { + key: event.key.clone(), + timestamp: event.timestamp, + reason: reason.clone(), + }, + }; + + history.push(history_event); + let bundle_history = BundleHistory { history }; + + info!( + bundle_id = %bundle_id, + event_count = bundle_history.history.len(), + "Updated bundle history" + ); + + Some(bundle_history) +} + +fn update_transaction_metadata_transform( + transaction_metadata: TransactionMetadata, + bundle_id: BundleId, +) -> Option { + let mut bundle_ids = transaction_metadata.bundle_ids; + + if bundle_ids.contains(&bundle_id) { + return None; + } + + bundle_ids.push(bundle_id); + Some(TransactionMetadata { bundle_ids }) +} + +fn update_userop_history_transform( + userop_history: UserOpHistory, + event: &UserOpEventWrapper, +) -> Option { + let mut history = userop_history.history; + let user_op_hash = event.event.user_op_hash(); + + if history.iter().any(|h| h.key() == event.key) { + info!( + user_op_hash = %user_op_hash, + event_key = %event.key, + "UserOp event already exists, skipping due to deduplication" + ); + return None; + } + + let history_event = match &event.event { + UserOpEvent::AddedToMempool { sender, entry_point, nonce, .. } => { + UserOpHistoryEvent::AddedToMempool { + key: event.key.clone(), + timestamp: event.timestamp, + sender: *sender, + entry_point: *entry_point, + nonce: *nonce, + } + } + UserOpEvent::Dropped { reason, .. } => UserOpHistoryEvent::Dropped { + key: event.key.clone(), + timestamp: event.timestamp, + reason: reason.clone(), + }, + UserOpEvent::Included { block_number, tx_hash, .. } => UserOpHistoryEvent::Included { + key: event.key.clone(), + timestamp: event.timestamp, + block_number: *block_number, + tx_hash: *tx_hash, + }, + }; + + history.push(history_event); + let userop_history = UserOpHistory { history }; + + info!( + user_op_hash = %user_op_hash, + event_count = userop_history.history.len(), + "Updated user op history" + ); + + Some(userop_history) +} + +/// Trait for writing bundle events to storage. +#[async_trait] +pub trait EventWriter { + /// Archives a bundle event. + async fn archive_event(&self, event: Event) -> Result<()>; +} + +/// Trait for writing user operation events to storage. +#[async_trait] +pub trait UserOpEventWriter { + /// Archives a user operation event. + async fn archive_userop_event(&self, event: UserOpEventWrapper) -> Result<()>; +} + +/// Trait for reading bundle events from S3. +#[async_trait] +pub trait BundleEventS3Reader { + /// Gets the bundle history for a given bundle ID. + async fn get_bundle_history(&self, bundle_id: BundleId) -> Result>; + /// Gets transaction metadata for a given transaction hash. + async fn get_transaction_metadata( + &self, + tx_hash: TxHash, + ) -> Result>; +} + +/// Trait for reading user operation events from S3. +#[async_trait] +pub trait UserOpEventS3Reader { + /// Gets the user operation history for a given hash. + async fn get_userop_history(&self, user_op_hash: UserOpHash) -> Result>; +} + +/// S3-backed event reader and writer. +#[derive(Clone, Debug)] +pub struct S3EventReaderWriter { + s3_client: S3Client, + bucket: String, + metrics: Metrics, +} + +impl S3EventReaderWriter { + /// Creates a new S3 event reader/writer. + pub fn new(s3_client: S3Client, bucket: String) -> Self { + Self { s3_client, bucket, metrics: Metrics::default() } + } + + async fn update_bundle_history(&self, event: Event) -> Result<()> { + let s3_key = S3Key::Bundle(event.event.bundle_id()).to_string(); + + self.idempotent_write::(&s3_key, |current_history| { + update_bundle_history_transform(current_history, &event) + }) + .await + } + + async fn update_transaction_by_hash_index( + &self, + tx_id: &TransactionId, + bundle_id: BundleId, + ) -> Result<()> { + let s3_key = S3Key::TransactionByHash(tx_id.hash); + let key = s3_key.to_string(); + + self.idempotent_write::(&key, |current_metadata| { + update_transaction_metadata_transform(current_metadata, bundle_id) + }) + .await + } + + async fn update_userop_history(&self, event: UserOpEventWrapper) -> Result<()> { + let s3_key = S3Key::UserOp(event.event.user_op_hash()).to_string(); + + self.idempotent_write::(&s3_key, |current_history| { + update_userop_history_transform(current_history, &event) + }) + .await + } + + async fn idempotent_write(&self, key: &str, mut transform_fn: F) -> Result<()> + where + T: for<'de> Deserialize<'de> + Serialize + Clone + Default + Debug, + F: FnMut(T) -> Option, + { + const MAX_RETRIES: usize = 5; + const BASE_DELAY_MS: u64 = 100; + + for attempt in 0..MAX_RETRIES { + let get_start = Instant::now(); + let (current_value, etag) = self.get_object_with_etag::(key).await?; + self.metrics.s3_get_duration.record(get_start.elapsed().as_secs_f64()); + + let value = current_value.unwrap_or_default(); + + match transform_fn(value.clone()) { + Some(new_value) => { + let content = serde_json::to_string(&new_value)?; + + let mut put_request = self + .s3_client + .put_object() + .bucket(&self.bucket) + .key(key) + .body(ByteStream::from(content.into_bytes())); + + if let Some(etag) = etag { + put_request = put_request.if_match(etag); + } else { + put_request = put_request.if_none_match("*"); + } + + let put_start = Instant::now(); + match put_request.send().await { + Ok(_) => { + self.metrics.s3_put_duration.record(put_start.elapsed().as_secs_f64()); + info!( + s3_key = %key, + attempt = attempt + 1, + "Successfully wrote object with idempotent write" + ); + return Ok(()); + } + Err(e) => { + self.metrics.s3_put_duration.record(put_start.elapsed().as_secs_f64()); + + if attempt < MAX_RETRIES - 1 { + let delay = BASE_DELAY_MS * 2_u64.pow(attempt as u32); + info!( + s3_key = %key, + attempt = attempt + 1, + delay_ms = delay, + error = %e, + "Conflict detected, retrying with backoff" + ); + tokio::time::sleep(tokio::time::Duration::from_millis(delay)).await; + } else { + return Err(anyhow::anyhow!( + "Failed to write after {MAX_RETRIES} attempts: {e}" + )); + } + } + } + } + None => { + self.metrics.s3_writes_skipped.increment(1); + info!( + s3_key = %key, + "Transform function returned None, no write required" + ); + return Ok(()); + } + } + } + + Err(anyhow::anyhow!("Exceeded maximum retry attempts")) + } + + async fn get_object_with_etag(&self, key: &str) -> Result<(Option, Option)> + where + T: for<'de> Deserialize<'de>, + { + match self.s3_client.get_object().bucket(&self.bucket).key(key).send().await { + Ok(response) => { + let etag = response.e_tag().map(|s| s.to_string()); + let body = response.body.collect().await?; + let content = String::from_utf8(body.into_bytes().to_vec())?; + let value: T = serde_json::from_str(&content)?; + Ok((Some(value), etag)) + } + Err(e) => match &e { + SdkError::ServiceError(service_err) => match service_err.err() { + GetObjectError::NoSuchKey(_) => Ok((None, None)), + _ => Err(anyhow::anyhow!("Failed to get object: {e}")), + }, + _ => { + let error_string = e.to_string(); + if error_string.contains("NoSuchKey") + || error_string.contains("NotFound") + || error_string.contains("404") + { + Ok((None, None)) + } else { + Err(anyhow::anyhow!("Failed to get object: {e}")) + } + } + }, + } + } +} + +#[async_trait] +impl EventWriter for S3EventReaderWriter { + async fn archive_event(&self, event: Event) -> Result<()> { + let bundle_id = event.event.bundle_id(); + let transaction_ids = event.event.transaction_ids(); + + let bundle_start = Instant::now(); + let bundle_future = self.update_bundle_history(event); + + let tx_start = Instant::now(); + let tx_futures: Vec<_> = + transaction_ids + .into_iter() + .map(|tx_id| async move { + self.update_transaction_by_hash_index(&tx_id, bundle_id).await + }) + .collect(); + + // Run the bundle and transaction futures concurrently and wait for them to complete + tokio::try_join!(bundle_future, future::try_join_all(tx_futures))?; + + self.metrics.update_bundle_history_duration.record(bundle_start.elapsed().as_secs_f64()); + self.metrics.update_tx_indexes_duration.record(tx_start.elapsed().as_secs_f64()); + + Ok(()) + } +} + +#[async_trait] +impl BundleEventS3Reader for S3EventReaderWriter { + async fn get_bundle_history(&self, bundle_id: BundleId) -> Result> { + let s3_key = S3Key::Bundle(bundle_id).to_string(); + let (bundle_history, _) = self.get_object_with_etag::(&s3_key).await?; + Ok(bundle_history) + } + + async fn get_transaction_metadata( + &self, + tx_hash: TxHash, + ) -> Result> { + let s3_key = S3Key::TransactionByHash(tx_hash).to_string(); + let (transaction_metadata, _) = + self.get_object_with_etag::(&s3_key).await?; + Ok(transaction_metadata) + } +} + +#[async_trait] +impl UserOpEventWriter for S3EventReaderWriter { + async fn archive_userop_event(&self, event: UserOpEventWrapper) -> Result<()> { + self.update_userop_history(event).await + } +} + +#[async_trait] +impl UserOpEventS3Reader for S3EventReaderWriter { + async fn get_userop_history(&self, user_op_hash: UserOpHash) -> Result> { + let s3_key = S3Key::UserOp(user_op_hash).to_string(); + let (userop_history, _) = self.get_object_with_etag::(&s3_key).await?; + Ok(userop_history) + } +} + +#[cfg(test)] +mod tests { + use alloy_primitives::{Address, B256, TxHash, U256}; + use tips_core::{BundleExtensions, test_utils::create_bundle_from_txn_data}; + use uuid::Uuid; + + use super::*; + use crate::{ + reader::Event, + types::{BundleEvent, DropReason, UserOpDropReason, UserOpEvent}, + }; + + fn create_test_event(key: &str, timestamp: i64, bundle_event: BundleEvent) -> Event { + Event { key: key.to_string(), timestamp, event: bundle_event } + } + + #[test] + fn test_update_bundle_history_transform_adds_new_event() { + let bundle_history = BundleHistory { history: vec![] }; + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + let bundle_event = BundleEvent::Received { bundle_id, bundle: Box::new(bundle.clone()) }; + let event = create_test_event("test-key", 1234567890, bundle_event); + + let result = update_bundle_history_transform(bundle_history, &event); + + assert!(result.is_some()); + let bundle_history = result.unwrap(); + assert_eq!(bundle_history.history.len(), 1); + + match &bundle_history.history[0] { + BundleHistoryEvent::Received { key, timestamp: ts, bundle: b } => { + assert_eq!(key, "test-key"); + assert_eq!(*ts, 1234567890); + assert_eq!(b.block_number, bundle.block_number); + } + _ => panic!("Expected Created event"), + } + } + + #[test] + fn test_update_bundle_history_transform_skips_duplicate_key() { + let existing_event = BundleHistoryEvent::Received { + key: "duplicate-key".to_string(), + timestamp: 1111111111, + bundle: Box::new(create_bundle_from_txn_data()), + }; + let bundle_history = BundleHistory { history: vec![existing_event] }; + + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + let bundle_event = BundleEvent::Received { bundle_id, bundle: Box::new(bundle) }; + let event = create_test_event("duplicate-key", 1234567890, bundle_event); + + let result = update_bundle_history_transform(bundle_history, &event); + + assert!(result.is_none()); + } + + #[test] + fn test_update_bundle_history_transform_handles_all_event_types() { + let bundle_history = BundleHistory { history: vec![] }; + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + + let bundle_event = BundleEvent::Received { bundle_id, bundle: Box::new(bundle) }; + let event = create_test_event("test-key", 1234567890, bundle_event); + let result = update_bundle_history_transform(bundle_history.clone(), &event); + assert!(result.is_some()); + + let bundle_event = BundleEvent::Cancelled { bundle_id }; + let event = create_test_event("test-key-2", 1234567890, bundle_event); + let result = update_bundle_history_transform(bundle_history.clone(), &event); + assert!(result.is_some()); + + let bundle_event = BundleEvent::BuilderIncluded { + bundle_id, + builder: "test-builder".to_string(), + block_number: 12345, + flashblock_index: 1, + }; + let event = create_test_event("test-key-3", 1234567890, bundle_event); + let result = update_bundle_history_transform(bundle_history.clone(), &event); + assert!(result.is_some()); + + let bundle_event = BundleEvent::BlockIncluded { + bundle_id, + block_number: 12345, + block_hash: TxHash::from([1u8; 32]), + }; + let event = create_test_event("test-key-4", 1234567890, bundle_event); + let result = update_bundle_history_transform(bundle_history.clone(), &event); + assert!(result.is_some()); + + let bundle_event = BundleEvent::Dropped { bundle_id, reason: DropReason::TimedOut }; + let event = create_test_event("test-key-5", 1234567890, bundle_event); + let result = update_bundle_history_transform(bundle_history, &event); + assert!(result.is_some()); + } + + #[test] + fn test_update_transaction_metadata_transform_adds_new_bundle() { + let metadata = TransactionMetadata { bundle_ids: vec![] }; + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + + let result = update_transaction_metadata_transform(metadata, bundle_id); + + assert!(result.is_some()); + let metadata = result.unwrap(); + assert_eq!(metadata.bundle_ids.len(), 1); + assert_eq!(metadata.bundle_ids[0], bundle_id); + } + + #[test] + fn test_update_transaction_metadata_transform_skips_existing_bundle() { + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + let metadata = TransactionMetadata { bundle_ids: vec![bundle_id] }; + + let result = update_transaction_metadata_transform(metadata, bundle_id); + + assert!(result.is_none()); + } + + #[test] + fn test_update_transaction_metadata_transform_adds_to_existing_bundles() { + // Some different, dummy bundle IDs since create_bundle_from_txn_data() returns the same bundle ID + // Even if the same txn is contained across multiple bundles, the bundle ID will be different since the + // UUID is based on the bundle hash. + let existing_bundle_id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); + let new_bundle_id = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap(); + + let metadata = TransactionMetadata { bundle_ids: vec![existing_bundle_id] }; + + let result = update_transaction_metadata_transform(metadata, new_bundle_id); + + assert!(result.is_some()); + let metadata = result.unwrap(); + assert_eq!(metadata.bundle_ids.len(), 2); + assert!(metadata.bundle_ids.contains(&existing_bundle_id)); + assert!(metadata.bundle_ids.contains(&new_bundle_id)); + } + + fn create_test_userop_event( + key: &str, + timestamp: i64, + userop_event: UserOpEvent, + ) -> UserOpEventWrapper { + UserOpEventWrapper { key: key.to_string(), timestamp, event: userop_event } + } + + #[test] + fn test_s3_key_userop_display() { + let hash = B256::from([1u8; 32]); + let key = S3Key::UserOp(hash); + let key_str = key.to_string(); + assert!(key_str.starts_with("userops/")); + assert!(key_str.contains(&format!("{hash}"))); + } + + #[test] + fn test_update_userop_history_transform_adds_new_event() { + let userop_history = UserOpHistory { history: vec![] }; + let user_op_hash = B256::from([1u8; 32]); + let sender = Address::from([2u8; 20]); + let entry_point = Address::from([3u8; 20]); + let nonce = U256::from(1); + + let userop_event = UserOpEvent::AddedToMempool { user_op_hash, sender, entry_point, nonce }; + let event = create_test_userop_event("test-key", 1234567890, userop_event); + + let result = update_userop_history_transform(userop_history, &event); + + assert!(result.is_some()); + let history = result.unwrap(); + assert_eq!(history.history.len(), 1); + + match &history.history[0] { + UserOpHistoryEvent::AddedToMempool { + key, + timestamp: ts, + sender: s, + entry_point: ep, + nonce: n, + } => { + assert_eq!(key, "test-key"); + assert_eq!(*ts, 1234567890); + assert_eq!(*s, sender); + assert_eq!(*ep, entry_point); + assert_eq!(*n, nonce); + } + _ => panic!("Expected AddedToMempool event"), + } + } + + #[test] + fn test_update_userop_history_transform_skips_duplicate_key() { + let user_op_hash = B256::from([1u8; 32]); + let sender = Address::from([2u8; 20]); + let entry_point = Address::from([3u8; 20]); + let nonce = U256::from(1); + + let existing_event = UserOpHistoryEvent::AddedToMempool { + key: "duplicate-key".to_string(), + timestamp: 1111111111, + sender, + entry_point, + nonce, + }; + let userop_history = UserOpHistory { history: vec![existing_event] }; + + let userop_event = UserOpEvent::AddedToMempool { user_op_hash, sender, entry_point, nonce }; + let event = create_test_userop_event("duplicate-key", 1234567890, userop_event); + + let result = update_userop_history_transform(userop_history, &event); + + assert!(result.is_none()); + } + + #[test] + fn test_update_userop_history_transform_handles_dropped_event() { + let userop_history = UserOpHistory { history: vec![] }; + let user_op_hash = B256::from([1u8; 32]); + let reason = UserOpDropReason::Expired; + + let userop_event = UserOpEvent::Dropped { user_op_hash, reason }; + let event = create_test_userop_event("dropped-key", 1234567890, userop_event); + + let result = update_userop_history_transform(userop_history, &event); + + assert!(result.is_some()); + let history = result.unwrap(); + assert_eq!(history.history.len(), 1); + + match &history.history[0] { + UserOpHistoryEvent::Dropped { key, timestamp, reason: r } => { + assert_eq!(key, "dropped-key"); + assert_eq!(*timestamp, 1234567890); + match r { + UserOpDropReason::Expired => {} + _ => panic!("Expected Expired reason"), + } + } + _ => panic!("Expected Dropped event"), + } + } + + #[test] + fn test_update_userop_history_transform_handles_included_event() { + let userop_history = UserOpHistory { history: vec![] }; + let user_op_hash = B256::from([1u8; 32]); + let tx_hash = TxHash::from([4u8; 32]); + let block_number = 12345u64; + + let userop_event = UserOpEvent::Included { user_op_hash, block_number, tx_hash }; + let event = create_test_userop_event("included-key", 1234567890, userop_event); + + let result = update_userop_history_transform(userop_history, &event); + + assert!(result.is_some()); + let history = result.unwrap(); + assert_eq!(history.history.len(), 1); + + match &history.history[0] { + UserOpHistoryEvent::Included { key, timestamp, block_number: bn, tx_hash: th } => { + assert_eq!(key, "included-key"); + assert_eq!(*timestamp, 1234567890); + assert_eq!(*bn, 12345); + assert_eq!(*th, tx_hash); + } + _ => panic!("Expected Included event"), + } + } + + #[test] + fn test_update_userop_history_transform_handles_all_event_types() { + let userop_history = UserOpHistory { history: vec![] }; + let user_op_hash = B256::from([1u8; 32]); + let sender = Address::from([2u8; 20]); + let entry_point = Address::from([3u8; 20]); + let nonce = U256::from(1); + + let userop_event = UserOpEvent::AddedToMempool { user_op_hash, sender, entry_point, nonce }; + let event = create_test_userop_event("key-1", 1234567890, userop_event); + let result = update_userop_history_transform(userop_history.clone(), &event); + assert!(result.is_some()); + + let userop_event = UserOpEvent::Dropped { + user_op_hash, + reason: UserOpDropReason::Invalid("test error".to_string()), + }; + let event = create_test_userop_event("key-2", 1234567891, userop_event); + let result = update_userop_history_transform(userop_history.clone(), &event); + assert!(result.is_some()); + + let userop_event = UserOpEvent::Included { + user_op_hash, + block_number: 12345, + tx_hash: TxHash::from([4u8; 32]), + }; + let event = create_test_userop_event("key-3", 1234567892, userop_event); + let result = update_userop_history_transform(userop_history, &event); + assert!(result.is_some()); + } + + #[test] + fn test_userop_history_event_key_accessor() { + let sender = Address::from([2u8; 20]); + let entry_point = Address::from([3u8; 20]); + let nonce = U256::from(1); + + let event1 = UserOpHistoryEvent::AddedToMempool { + key: "key-1".to_string(), + timestamp: 1234567890, + sender, + entry_point, + nonce, + }; + assert_eq!(event1.key(), "key-1"); + + let event2 = UserOpHistoryEvent::Dropped { + key: "key-2".to_string(), + timestamp: 1234567890, + reason: UserOpDropReason::Expired, + }; + assert_eq!(event2.key(), "key-2"); + + let event3 = UserOpHistoryEvent::Included { + key: "key-3".to_string(), + timestamp: 1234567890, + block_number: 12345, + tx_hash: TxHash::from([4u8; 32]), + }; + assert_eq!(event3.key(), "key-3"); + } + + #[test] + fn test_userop_history_serialization() { + let sender = Address::from([2u8; 20]); + let entry_point = Address::from([3u8; 20]); + let nonce = U256::from(1); + + let history = UserOpHistory { + history: vec![UserOpHistoryEvent::AddedToMempool { + key: "test-key".to_string(), + timestamp: 1234567890, + sender, + entry_point, + nonce, + }], + }; + + let json = serde_json::to_string(&history).unwrap(); + let deserialized: UserOpHistory = serde_json::from_str(&json).unwrap(); + + assert_eq!(deserialized.history.len(), 1); + assert_eq!(deserialized.history[0].key(), "test-key"); + } +} diff --git a/crates/tips/audit/src/types.rs b/crates/tips/audit/src/types.rs new file mode 100644 index 0000000..34acecb --- /dev/null +++ b/crates/tips/audit/src/types.rs @@ -0,0 +1,318 @@ +use alloy_consensus::transaction::{SignerRecoverable, Transaction as ConsensusTransaction}; +use alloy_primitives::{Address, B256, TxHash, U256}; +use bytes::Bytes; +use serde::{Deserialize, Serialize}; +use tips_core::AcceptedBundle; +use uuid::Uuid; + +/// Unique identifier for a transaction. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct TransactionId { + /// The sender address. + pub sender: Address, + /// The transaction nonce. + pub nonce: U256, + /// The transaction hash. + pub hash: TxHash, +} + +/// Unique identifier for a bundle. +pub type BundleId = Uuid; + +/// Reason a bundle was dropped. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum DropReason { + /// Bundle timed out. + TimedOut, + /// Bundle transaction reverted. + Reverted, +} + +/// A transaction with its data. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Transaction { + /// Transaction identifier. + pub id: TransactionId, + /// Raw transaction data. + pub data: Bytes, +} + +/// Hash of a user operation. +pub type UserOpHash = B256; + +/// Reason a user operation was dropped. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum UserOpDropReason { + /// User operation was invalid. + Invalid(String), + /// User operation expired. + Expired, + /// Replaced by a higher fee user operation. + ReplacedByHigherFee, +} + +/// Bundle lifecycle event. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "event", content = "data")] +pub enum BundleEvent { + /// Bundle was received. + Received { + /// Bundle identifier. + bundle_id: BundleId, + /// The accepted bundle. + bundle: Box, + }, + /// Bundle was cancelled. + Cancelled { + /// Bundle identifier. + bundle_id: BundleId, + }, + /// Bundle was included by a builder. + BuilderIncluded { + /// Bundle identifier. + bundle_id: BundleId, + /// Builder identifier. + builder: String, + /// Block number. + block_number: u64, + /// Flashblock index. + flashblock_index: u64, + }, + /// Bundle was included in a block. + BlockIncluded { + /// Bundle identifier. + bundle_id: BundleId, + /// Block number. + block_number: u64, + /// Block hash. + block_hash: TxHash, + }, + /// Bundle was dropped. + Dropped { + /// Bundle identifier. + bundle_id: BundleId, + /// Drop reason. + reason: DropReason, + }, +} + +impl BundleEvent { + /// Returns the bundle ID for this event. + pub const fn bundle_id(&self) -> BundleId { + match self { + Self::Received { bundle_id, .. } => *bundle_id, + Self::Cancelled { bundle_id, .. } => *bundle_id, + Self::BuilderIncluded { bundle_id, .. } => *bundle_id, + Self::BlockIncluded { bundle_id, .. } => *bundle_id, + Self::Dropped { bundle_id, .. } => *bundle_id, + } + } + + /// Returns transaction IDs from this event (only for Received events). + pub fn transaction_ids(&self) -> Vec { + match self { + Self::Received { bundle, .. } => bundle + .txs + .iter() + .filter_map(|envelope| { + envelope.recover_signer().ok().map(|sender| TransactionId { + sender, + nonce: U256::from(envelope.nonce()), + hash: *envelope.hash(), + }) + }) + .collect(), + Self::Cancelled { .. } => vec![], + Self::BuilderIncluded { .. } => vec![], + Self::BlockIncluded { .. } => vec![], + Self::Dropped { .. } => vec![], + } + } + + /// Generates a unique event key for this event. + pub fn generate_event_key(&self) -> String { + match self { + Self::BlockIncluded { bundle_id, block_hash, .. } => { + format!("{bundle_id}-{block_hash}") + } + _ => { + format!( + "{}-{}", + self.bundle_id(), + Uuid::new_v5(&Uuid::NAMESPACE_OID, self.bundle_id().as_bytes()) + ) + } + } + } +} + +/// User operation lifecycle event. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "event", content = "data")] +pub enum UserOpEvent { + /// User operation was added to the mempool. + AddedToMempool { + /// Hash of the user operation. + user_op_hash: UserOpHash, + /// Sender address. + sender: Address, + /// Entry point address. + entry_point: Address, + /// Nonce. + nonce: U256, + }, + /// User operation was dropped. + Dropped { + /// Hash of the user operation. + user_op_hash: UserOpHash, + /// Reason for dropping. + reason: UserOpDropReason, + }, + /// User operation was included in a block. + Included { + /// Hash of the user operation. + user_op_hash: UserOpHash, + /// Block number. + block_number: u64, + /// Transaction hash. + tx_hash: TxHash, + }, +} + +impl UserOpEvent { + /// Returns the user operation hash for this event. + pub const fn user_op_hash(&self) -> UserOpHash { + match self { + Self::AddedToMempool { user_op_hash, .. } => *user_op_hash, + Self::Dropped { user_op_hash, .. } => *user_op_hash, + Self::Included { user_op_hash, .. } => *user_op_hash, + } + } + + /// Generates a unique event key for this event. + pub fn generate_event_key(&self) -> String { + match self { + Self::Included { user_op_hash, tx_hash, .. } => { + format!("{user_op_hash}-{tx_hash}") + } + _ => { + format!( + "{}-{}", + self.user_op_hash(), + Uuid::new_v5(&Uuid::NAMESPACE_OID, self.user_op_hash().as_slice()) + ) + } + } + } +} + +#[cfg(test)] +mod user_op_event_tests { + use alloy_primitives::{address, b256}; + + use super::*; + + fn create_test_user_op_hash() -> UserOpHash { + b256!("1111111111111111111111111111111111111111111111111111111111111111") + } + + #[test] + fn test_user_op_event_added_to_mempool_serialization() { + let event = UserOpEvent::AddedToMempool { + user_op_hash: create_test_user_op_hash(), + sender: address!("2222222222222222222222222222222222222222"), + entry_point: address!("0000000071727De22E5E9d8BAf0edAc6f37da032"), + nonce: U256::from(1), + }; + + let json = serde_json::to_string(&event).unwrap(); + assert!(json.contains("\"event\":\"AddedToMempool\"")); + + let deserialized: UserOpEvent = serde_json::from_str(&json).unwrap(); + assert_eq!(event.user_op_hash(), deserialized.user_op_hash()); + } + + #[test] + fn test_user_op_event_dropped_serialization() { + let event = UserOpEvent::Dropped { + user_op_hash: create_test_user_op_hash(), + reason: UserOpDropReason::Invalid("gas too low".to_string()), + }; + + let json = serde_json::to_string(&event).unwrap(); + assert!(json.contains("\"event\":\"Dropped\"")); + assert!(json.contains("gas too low")); + + let deserialized: UserOpEvent = serde_json::from_str(&json).unwrap(); + assert_eq!(event.user_op_hash(), deserialized.user_op_hash()); + } + + #[test] + fn test_user_op_event_included_serialization() { + let event = UserOpEvent::Included { + user_op_hash: create_test_user_op_hash(), + block_number: 12345, + tx_hash: b256!("3333333333333333333333333333333333333333333333333333333333333333"), + }; + + let json = serde_json::to_string(&event).unwrap(); + assert!(json.contains("\"event\":\"Included\"")); + assert!(json.contains("\"block_number\":12345")); + + let deserialized: UserOpEvent = serde_json::from_str(&json).unwrap(); + assert_eq!(event.user_op_hash(), deserialized.user_op_hash()); + } + + #[test] + fn test_user_op_hash_accessor() { + let hash = create_test_user_op_hash(); + + let added = UserOpEvent::AddedToMempool { + user_op_hash: hash, + sender: address!("2222222222222222222222222222222222222222"), + entry_point: address!("0000000071727De22E5E9d8BAf0edAc6f37da032"), + nonce: U256::from(1), + }; + assert_eq!(added.user_op_hash(), hash); + + let dropped = + UserOpEvent::Dropped { user_op_hash: hash, reason: UserOpDropReason::Expired }; + assert_eq!(dropped.user_op_hash(), hash); + + let included = UserOpEvent::Included { + user_op_hash: hash, + block_number: 100, + tx_hash: b256!("4444444444444444444444444444444444444444444444444444444444444444"), + }; + assert_eq!(included.user_op_hash(), hash); + } + + #[test] + fn test_generate_event_key_included() { + let user_op_hash = + b256!("1111111111111111111111111111111111111111111111111111111111111111"); + let tx_hash = b256!("2222222222222222222222222222222222222222222222222222222222222222"); + + let event = UserOpEvent::Included { user_op_hash, block_number: 100, tx_hash }; + + let key = event.generate_event_key(); + assert!(key.contains(&format!("{user_op_hash}"))); + assert!(key.contains(&format!("{tx_hash}"))); + } + + #[test] + fn test_user_op_drop_reason_variants() { + let invalid = UserOpDropReason::Invalid("test reason".to_string()); + let json = serde_json::to_string(&invalid).unwrap(); + assert!(json.contains("Invalid")); + assert!(json.contains("test reason")); + + let expired = UserOpDropReason::Expired; + let json = serde_json::to_string(&expired).unwrap(); + assert!(json.contains("Expired")); + + let replaced = UserOpDropReason::ReplacedByHigherFee; + let json = serde_json::to_string(&replaced).unwrap(); + assert!(json.contains("ReplacedByHigherFee")); + } +} diff --git a/crates/tips/audit/tests/common/mod.rs b/crates/tips/audit/tests/common/mod.rs new file mode 100644 index 0000000..e9c0572 --- /dev/null +++ b/crates/tips/audit/tests/common/mod.rs @@ -0,0 +1,69 @@ +use rdkafka::{ClientConfig, consumer::StreamConsumer, producer::FutureProducer}; +use testcontainers::runners::AsyncRunner; +use testcontainers_modules::{kafka, kafka::Kafka, minio::MinIO}; +use uuid::Uuid; + +pub(crate) struct TestHarness { + pub s3_client: aws_sdk_s3::Client, + pub bucket_name: String, + #[allow(dead_code)] // TODO is read + pub kafka_producer: FutureProducer, + #[allow(dead_code)] // TODO is read + pub kafka_consumer: StreamConsumer, + _minio_container: testcontainers::ContainerAsync, + _kafka_container: testcontainers::ContainerAsync, +} + +impl TestHarness { + pub(crate) async fn new() -> Result> { + let minio_container = MinIO::default().start().await?; + let s3_port = minio_container.get_host_port_ipv4(9000).await?; + let s3_endpoint = format!("http://127.0.0.1:{s3_port}"); + + let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region("us-east-1") + .endpoint_url(&s3_endpoint) + .credentials_provider(aws_sdk_s3::config::Credentials::new( + "minioadmin", + "minioadmin", + None, + None, + "test", + )) + .load() + .await; + + let s3_client = aws_sdk_s3::Client::new(&config); + let bucket_name = format!("test-bucket-{}", Uuid::new_v4()); + + s3_client.create_bucket().bucket(&bucket_name).send().await?; + + let kafka_container = Kafka::default().start().await?; + let bootstrap_servers = + format!("127.0.0.1:{}", kafka_container.get_host_port_ipv4(kafka::KAFKA_PORT).await?); + + let kafka_producer = ClientConfig::new() + .set("bootstrap.servers", &bootstrap_servers) + .set("message.timeout.ms", "5000") + .create::() + .expect("Failed to create Kafka FutureProducer"); + + let kafka_consumer = ClientConfig::new() + .set("group.id", "testcontainer-rs") + .set("bootstrap.servers", &bootstrap_servers) + .set("session.timeout.ms", "6000") + .set("enable.auto.commit", "false") + .set("auto.offset.reset", "earliest") + .create::() + .expect("Failed to create Kafka StreamConsumer"); + + Ok(Self { + s3_client, + bucket_name, + kafka_producer, + kafka_consumer, + _minio_container: minio_container, + _kafka_container: kafka_container, + }) + } +} diff --git a/crates/tips/audit/tests/integration_tests.rs b/crates/tips/audit/tests/integration_tests.rs new file mode 100644 index 0000000..9d49147 --- /dev/null +++ b/crates/tips/audit/tests/integration_tests.rs @@ -0,0 +1,117 @@ +#![allow(missing_docs)] + +use std::time::Duration; + +use alloy_primitives::{Address, B256, U256}; +use tips_audit_lib::{ + BundleEvent, BundleEventPublisher, BundleEventS3Reader, DropReason, KafkaAuditArchiver, + KafkaAuditLogReader, KafkaBundleEventPublisher, KafkaUserOpAuditLogReader, + KafkaUserOpEventPublisher, S3EventReaderWriter, UserOpEvent, UserOpEventPublisher, + UserOpEventReader, +}; +use tips_core::{BundleExtensions, test_utils::create_bundle_from_txn_data}; +use uuid::Uuid; +mod common; +use common::TestHarness; + +#[tokio::test] +#[ignore = "TODO doesn't appear to work with minio, should test against a real S3 bucket"] +async fn test_kafka_publisher_s3_archiver_integration() +-> Result<(), Box> { + let harness = TestHarness::new().await?; + let topic = "test-mempool-events"; + + let s3_writer = + S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let bundle = create_bundle_from_txn_data(); + let test_bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + let test_events = [ + BundleEvent::Received { bundle_id: test_bundle_id, bundle: Box::new(bundle.clone()) }, + BundleEvent::Dropped { bundle_id: test_bundle_id, reason: DropReason::TimedOut }, + ]; + + let publisher = KafkaBundleEventPublisher::new(harness.kafka_producer, topic.to_string()); + + for event in test_events.iter() { + publisher.publish(event.clone()).await?; + } + + let mut consumer = KafkaAuditArchiver::new( + KafkaAuditLogReader::new(harness.kafka_consumer, topic.to_string())?, + s3_writer.clone(), + 1, + 100, + false, + ); + + tokio::spawn(async move { + consumer.run().await.expect("error running consumer"); + }); + + // Wait for the messages to be received + let mut counter = 0; + loop { + counter += 1; + if counter > 10 { + panic!("unable to complete archiving within the deadline"); + } + + tokio::time::sleep(Duration::from_secs(1)).await; + let bundle_history = s3_writer.get_bundle_history(test_bundle_id).await?; + + if let Some(history) = bundle_history { + if history.history.len() != test_events.len() { + continue; + } else { + break; + } + } else { + continue; + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_userop_kafka_publisher_reader_integration() +-> Result<(), Box> { + let harness = TestHarness::new().await?; + let topic = "test-userop-events"; + + let test_user_op_hash = B256::from_slice(&[1u8; 32]); + let test_sender = Address::from_slice(&[2u8; 20]); + let test_entry_point = Address::from_slice(&[3u8; 20]); + let test_nonce = U256::from(42); + + let test_event = UserOpEvent::AddedToMempool { + user_op_hash: test_user_op_hash, + sender: test_sender, + entry_point: test_entry_point, + nonce: test_nonce, + }; + + let publisher = KafkaUserOpEventPublisher::new(harness.kafka_producer, topic.to_string()); + publisher.publish(test_event.clone()).await?; + + let mut reader = KafkaUserOpAuditLogReader::new(harness.kafka_consumer, topic.to_string())?; + + let received = tokio::time::timeout(Duration::from_secs(10), reader.read_event()).await??; + + assert_eq!(received.event.user_op_hash(), test_user_op_hash); + + match received.event { + UserOpEvent::AddedToMempool { user_op_hash, sender, entry_point, nonce } => { + assert_eq!(user_op_hash, test_user_op_hash); + assert_eq!(sender, test_sender); + assert_eq!(entry_point, test_entry_point); + assert_eq!(nonce, test_nonce); + } + _ => panic!("Expected AddedToMempool event"), + } + + reader.commit().await?; + + Ok(()) +} diff --git a/crates/tips/audit/tests/s3_test.rs b/crates/tips/audit/tests/s3_test.rs new file mode 100644 index 0000000..f0463e1 --- /dev/null +++ b/crates/tips/audit/tests/s3_test.rs @@ -0,0 +1,397 @@ +#![allow(missing_docs)] + +use std::sync::Arc; + +use alloy_primitives::{Address, B256, TxHash, U256}; +use tips_audit_lib::{ + BundleEvent, BundleEventS3Reader, Event, EventWriter, S3EventReaderWriter, UserOpDropReason, + UserOpEvent, UserOpEventS3Reader, UserOpEventWrapper, UserOpEventWriter, +}; +use tokio::task::JoinSet; +use uuid::Uuid; + +mod common; +use common::TestHarness; +use tips_core::{ + BundleExtensions, + test_utils::{TXN_HASH, create_bundle_from_txn_data}, +}; + +fn create_test_event(key: &str, timestamp: i64, bundle_event: BundleEvent) -> Event { + Event { key: key.to_string(), timestamp, event: bundle_event } +} + +#[tokio::test] +async fn test_event_write_and_read() -> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + let event = create_test_event( + "test-key-1", + 1234567890, + BundleEvent::Received { bundle_id, bundle: Box::new(bundle.clone()) }, + ); + + writer.archive_event(event).await?; + + let bundle_history = writer.get_bundle_history(bundle_id).await?; + assert!(bundle_history.is_some()); + + let history = bundle_history.unwrap(); + assert_eq!(history.history.len(), 1); + assert_eq!(history.history[0].key(), "test-key-1"); + + let metadata = writer.get_transaction_metadata(TXN_HASH).await?; + assert!(metadata.is_some()); + + if let Some(metadata) = metadata { + assert!(metadata.bundle_ids.contains(&bundle_id)); + } + + let bundle_id_two = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + let bundle = create_bundle_from_txn_data(); + let event = create_test_event( + "test-key-2", + 1234567890, + BundleEvent::Received { bundle_id: bundle_id_two, bundle: Box::new(bundle.clone()) }, + ); + + writer.archive_event(event).await?; + + let metadata = writer.get_transaction_metadata(TXN_HASH).await?; + assert!(metadata.is_some()); + + if let Some(metadata) = metadata { + assert!(metadata.bundle_ids.contains(&bundle_id)); + assert!(metadata.bundle_ids.contains(&bundle_id_two)); + } + + Ok(()) +} + +#[tokio::test] +async fn test_events_appended() -> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + + let events = [ + create_test_event( + "test-key-1", + 1234567890, + BundleEvent::Received { bundle_id, bundle: Box::new(bundle.clone()) }, + ), + create_test_event("test-key-2", 1234567891, BundleEvent::Cancelled { bundle_id }), + ]; + + for (idx, event) in events.iter().enumerate() { + writer.archive_event(event.clone()).await?; + + let bundle_history = writer.get_bundle_history(bundle_id).await?; + assert!(bundle_history.is_some()); + + let history = bundle_history.unwrap(); + assert_eq!(history.history.len(), idx + 1); + + let keys: Vec = history.history.iter().map(|e| e.key().to_string()).collect(); + assert_eq!( + keys, + events.iter().map(|e| e.key.clone()).take(idx + 1).collect::>() + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_event_deduplication() -> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + let bundle = create_bundle_from_txn_data(); + let event = create_test_event( + "duplicate-key", + 1234567890, + BundleEvent::Received { bundle_id, bundle: Box::new(bundle.clone()) }, + ); + + writer.archive_event(event.clone()).await?; + writer.archive_event(event).await?; + + let bundle_history = writer.get_bundle_history(bundle_id).await?; + assert!(bundle_history.is_some()); + + let history = bundle_history.unwrap(); + assert_eq!(history.history.len(), 1); + assert_eq!(history.history[0].key(), "duplicate-key"); + + Ok(()) +} + +#[tokio::test] +async fn test_nonexistent_data() -> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let bundle = create_bundle_from_txn_data(); + let nonexistent_bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + let bundle_history = writer.get_bundle_history(nonexistent_bundle_id).await?; + assert!(bundle_history.is_none()); + + let nonexistent_tx_hash = TxHash::from([255u8; 32]); + let metadata = writer.get_transaction_metadata(nonexistent_tx_hash).await?; + assert!(metadata.is_none()); + + Ok(()) +} + +#[tokio::test] +#[ignore = "TODO doesn't appear to work with minio, should test against a real S3 bucket"] +async fn test_concurrent_writes_for_bundle() -> Result<(), Box> +{ + let harness = TestHarness::new().await?; + let writer = + Arc::new(S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone())); + + let bundle = create_bundle_from_txn_data(); + let bundle_id = Uuid::new_v5(&Uuid::NAMESPACE_OID, bundle.bundle_hash().as_slice()); + + let event = create_test_event( + "hello-dan", + 1234567889i64, + BundleEvent::Received { bundle_id, bundle: Box::new(bundle.clone()) }, + ); + + writer.archive_event(event.clone()).await?; + + let mut join_set: JoinSet>> = + JoinSet::new(); + + for i in 0..4 { + let writer_clone = writer.clone(); + let key = if i % 4 == 0 { "shared-key".to_string() } else { format!("unique-key-{i}") }; + + let event = create_test_event( + &key, + 1234567890 + i as i64, + BundleEvent::Received { bundle_id, bundle: Box::new(bundle.clone()) }, + ); + + join_set.spawn(async move { + writer_clone.archive_event(event.clone()).await.map_err(|e| e.into()) + }); + } + + let tasks = join_set.join_all().await; + assert_eq!(tasks.len(), 4); + for t in tasks.iter() { + assert!(t.is_ok()); + } + + let bundle_history = writer.get_bundle_history(bundle_id).await?; + assert!(bundle_history.is_some()); + + let history = bundle_history.unwrap(); + + let shared_count = history.history.iter().filter(|e| e.key() == "shared-key").count(); + assert_eq!(shared_count, 1); + + let unique_count = + history.history.iter().filter(|e| e.key().starts_with("unique-key-")).count(); + assert_eq!(unique_count, 3); + + assert_eq!(history.history.len(), 4); + + Ok(()) +} + +fn create_test_userop_event( + key: &str, + timestamp: i64, + userop_event: UserOpEvent, +) -> UserOpEventWrapper { + UserOpEventWrapper { key: key.to_string(), timestamp, event: userop_event } +} + +#[tokio::test] +async fn test_userop_event_write_and_read() -> Result<(), Box> +{ + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let user_op_hash = B256::from([1u8; 32]); + let sender = Address::from([2u8; 20]); + let entry_point = Address::from([3u8; 20]); + let nonce = U256::from(1); + + let event = create_test_userop_event( + "test-userop-key-1", + 1234567890, + UserOpEvent::AddedToMempool { user_op_hash, sender, entry_point, nonce }, + ); + + writer.archive_userop_event(event).await?; + + let userop_history = writer.get_userop_history(user_op_hash).await?; + assert!(userop_history.is_some()); + + let history = userop_history.unwrap(); + assert_eq!(history.history.len(), 1); + assert_eq!(history.history[0].key(), "test-userop-key-1"); + + Ok(()) +} + +#[tokio::test] +async fn test_userop_events_appended() -> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let user_op_hash = B256::from([10u8; 32]); + let sender = Address::from([11u8; 20]); + let entry_point = Address::from([12u8; 20]); + let nonce = U256::from(1); + let tx_hash = TxHash::from([13u8; 32]); + + let events = [ + create_test_userop_event( + "userop-key-1", + 1234567890, + UserOpEvent::AddedToMempool { user_op_hash, sender, entry_point, nonce }, + ), + create_test_userop_event( + "userop-key-2", + 1234567891, + UserOpEvent::Included { user_op_hash, block_number: 12345, tx_hash }, + ), + ]; + + for (idx, event) in events.iter().enumerate() { + writer.archive_userop_event(event.clone()).await?; + + let userop_history = writer.get_userop_history(user_op_hash).await?; + assert!(userop_history.is_some()); + + let history = userop_history.unwrap(); + assert_eq!(history.history.len(), idx + 1); + + let keys: Vec = history.history.iter().map(|e| e.key().to_string()).collect(); + assert_eq!( + keys, + events.iter().map(|e| e.key.clone()).take(idx + 1).collect::>() + ); + } + + Ok(()) +} + +#[tokio::test] +async fn test_userop_event_deduplication() -> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let user_op_hash = B256::from([20u8; 32]); + let sender = Address::from([21u8; 20]); + let entry_point = Address::from([22u8; 20]); + let nonce = U256::from(1); + + let event = create_test_userop_event( + "duplicate-userop-key", + 1234567890, + UserOpEvent::AddedToMempool { user_op_hash, sender, entry_point, nonce }, + ); + + writer.archive_userop_event(event.clone()).await?; + writer.archive_userop_event(event).await?; + + let userop_history = writer.get_userop_history(user_op_hash).await?; + assert!(userop_history.is_some()); + + let history = userop_history.unwrap(); + assert_eq!(history.history.len(), 1); + assert_eq!(history.history[0].key(), "duplicate-userop-key"); + + Ok(()) +} + +#[tokio::test] +async fn test_userop_nonexistent_returns_none() +-> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let nonexistent_hash = B256::from([255u8; 32]); + let userop_history = writer.get_userop_history(nonexistent_hash).await?; + assert!(userop_history.is_none()); + + Ok(()) +} + +#[tokio::test] +async fn test_userop_all_event_types() -> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let user_op_hash = B256::from([30u8; 32]); + let sender = Address::from([31u8; 20]); + let entry_point = Address::from([32u8; 20]); + let nonce = U256::from(1); + let tx_hash = TxHash::from([33u8; 32]); + + let event1 = create_test_userop_event( + "event-added", + 1234567890, + UserOpEvent::AddedToMempool { user_op_hash, sender, entry_point, nonce }, + ); + writer.archive_userop_event(event1).await?; + + let event2 = create_test_userop_event( + "event-included", + 1234567891, + UserOpEvent::Included { user_op_hash, block_number: 12345, tx_hash }, + ); + writer.archive_userop_event(event2).await?; + + let userop_history = writer.get_userop_history(user_op_hash).await?; + assert!(userop_history.is_some()); + + let history = userop_history.unwrap(); + assert_eq!(history.history.len(), 2); + assert_eq!(history.history[0].key(), "event-added"); + assert_eq!(history.history[1].key(), "event-included"); + + Ok(()) +} + +#[tokio::test] +async fn test_userop_dropped_event() -> Result<(), Box> { + let harness = TestHarness::new().await?; + let writer = S3EventReaderWriter::new(harness.s3_client.clone(), harness.bucket_name.clone()); + + let user_op_hash = B256::from([40u8; 32]); + + let event = create_test_userop_event( + "event-dropped", + 1234567890, + UserOpEvent::Dropped { + user_op_hash, + reason: UserOpDropReason::Invalid("AA21 didn't pay prefund".to_string()), + }, + ); + writer.archive_userop_event(event).await?; + + let userop_history = writer.get_userop_history(user_op_hash).await?; + assert!(userop_history.is_some()); + + let history = userop_history.unwrap(); + assert_eq!(history.history.len(), 1); + assert_eq!(history.history[0].key(), "event-dropped"); + + Ok(()) +} diff --git a/crates/tips/core/Cargo.toml b/crates/tips/core/Cargo.toml new file mode 100644 index 0000000..5ab4237 --- /dev/null +++ b/crates/tips/core/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "tips-core" +description = "Core primitives and utilities for TIPS" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +test-utils = [ "dep:alloy-signer-local", "dep:op-alloy-rpc-types" ] + +[dependencies] +tracing = { workspace = true, features = ["std"] } +alloy-consensus = { workspace = true, features = ["std"] } +alloy-rpc-types = { workspace = true, features = ["eth"] } +alloy-provider = { workspace = true, features = ["reqwest"] } +alloy-signer-local = { workspace = true, optional = true } +op-alloy-consensus = { workspace = true, features = ["std", "k256", "serde"] } +alloy-primitives = { workspace = true, features = ["map-foldhash", "serde"] } +op-alloy-rpc-types = { workspace = true, features = ["std"], optional = true } +metrics-exporter-prometheus = { workspace = true, features = ["http-listener"] } +tracing-subscriber = { workspace = true, features = ["std", "fmt", "ansi", "env-filter", "json"] } + +base-bundles = { workspace = true } + +[dev-dependencies] +alloy-signer-local.workspace = true +serde_json = { workspace = true, features = ["std"] } +op-alloy-rpc-types = { workspace = true, features = ["std"] } diff --git a/crates/tips/core/README.md b/crates/tips/core/README.md new file mode 100644 index 0000000..6dd1d3d --- /dev/null +++ b/crates/tips/core/README.md @@ -0,0 +1,3 @@ +# `tips-core` + +Core primitives and utilities for TIPS. diff --git a/crates/tips/core/src/kafka.rs b/crates/tips/core/src/kafka.rs new file mode 100644 index 0000000..ac07051 --- /dev/null +++ b/crates/tips/core/src/kafka.rs @@ -0,0 +1,21 @@ +use std::{collections::HashMap, fs}; + +pub fn load_kafka_config_from_file( + properties_file_path: &str, +) -> Result, std::io::Error> { + let kafka_properties = fs::read_to_string(properties_file_path)?; + + let mut config = HashMap::new(); + + for line in kafka_properties.lines() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some((key, value)) = line.split_once('=') { + config.insert(key.trim().to_string(), value.trim().to_string()); + } + } + + Ok(config) +} diff --git a/crates/tips/core/src/lib.rs b/crates/tips/core/src/lib.rs new file mode 100644 index 0000000..9baa41e --- /dev/null +++ b/crates/tips/core/src/lib.rs @@ -0,0 +1,18 @@ +#![doc = include_str!("../README.md")] +#![doc(issue_tracker_base_url = "https://github.com/base/tips/issues/")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![allow(missing_docs)] + +use alloy_rpc_types as _; + +pub mod kafka; +pub mod logger; +pub mod metrics; +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; + +pub use base_bundles::{ + AcceptedBundle, Bundle, BundleExtensions, BundleHash, BundleTxs, CancelBundle, + MeterBundleResponse, ParsedBundle, +}; diff --git a/crates/tips/core/src/logger.rs b/crates/tips/core/src/logger.rs new file mode 100644 index 0000000..ccdb8d2 --- /dev/null +++ b/crates/tips/core/src/logger.rs @@ -0,0 +1,63 @@ +use std::str::FromStr; + +use tracing::warn; +use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LogFormat { + Pretty, + Json, + Compact, +} + +impl FromStr for LogFormat { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "json" => Ok(Self::Json), + "compact" => Ok(Self::Compact), + "pretty" => Ok(Self::Pretty), + _ => { + warn!("Invalid log format '{}', defaulting to 'pretty'", s); + Ok(Self::Pretty) + } + } + } +} + +pub fn init_logger(log_level: &str) { + init_logger_with_format(log_level, LogFormat::Pretty); +} + +pub fn init_logger_with_format(log_level: &str, format: LogFormat) { + let level = match log_level.to_lowercase().as_str() { + "trace" => tracing::Level::TRACE, + "debug" => tracing::Level::DEBUG, + "info" => tracing::Level::INFO, + "warn" => tracing::Level::WARN, + "error" => tracing::Level::ERROR, + _ => { + warn!("Invalid log level '{}', defaulting to 'info'", log_level); + tracing::Level::INFO + } + }; + + let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(level.to_string())); + + match format { + LogFormat::Json => { + tracing_subscriber::registry() + .with(env_filter) + .with(fmt::layer().json().flatten_event(true).with_current_span(true)) + .init(); + } + LogFormat::Compact => { + tracing_subscriber::registry().with(env_filter).with(fmt::layer().compact()).init(); + } + LogFormat::Pretty => { + tracing_subscriber::registry().with(env_filter).with(fmt::layer().pretty()).init(); + } + } +} diff --git a/crates/tips/core/src/metrics.rs b/crates/tips/core/src/metrics.rs new file mode 100644 index 0000000..bb5f67f --- /dev/null +++ b/crates/tips/core/src/metrics.rs @@ -0,0 +1,10 @@ +use std::net::SocketAddr; + +use metrics_exporter_prometheus::PrometheusBuilder; + +pub fn init_prometheus_exporter(addr: SocketAddr) -> Result<(), Box> { + PrometheusBuilder::new() + .with_http_listener(addr) + .install() + .map_err(|e| Box::new(e) as Box) +} diff --git a/crates/tips/core/src/test_utils.rs b/crates/tips/core/src/test_utils.rs new file mode 100644 index 0000000..6285ce4 --- /dev/null +++ b/crates/tips/core/src/test_utils.rs @@ -0,0 +1,75 @@ +use alloy_consensus::SignableTransaction; +use alloy_primitives::{Address, B256, Bytes, TxHash, U256, b256, bytes}; +use alloy_provider::network::{TxSignerSync, eip2718::Encodable2718}; +use alloy_signer_local::PrivateKeySigner; +use op_alloy_consensus::OpTxEnvelope; +use op_alloy_rpc_types::OpTransactionRequest; + +use crate::{AcceptedBundle, Bundle, MeterBundleResponse}; + +// https://basescan.org/tx/0x4f7ddfc911f5cf85dd15a413f4cbb2a0abe4f1ff275ed13581958c0bcf043c5e +pub const TXN_DATA: Bytes = bytes!( + "0x02f88f8221058304b6b3018315fb3883124f80948ff2f0a8d017c79454aa28509a19ab9753c2dd1480a476d58e1a0182426068c9ea5b00000000000000000002f84f00000000083e4fda54950000c080a086fbc7bbee41f441fb0f32f7aa274d2188c460fe6ac95095fa6331fa08ec4ce7a01aee3bcc3c28f7ba4e0c24da9ae85e9e0166c73cabb42c25ff7b5ecd424f3105" +); + +pub const TXN_HASH: TxHash = + b256!("0x4f7ddfc911f5cf85dd15a413f4cbb2a0abe4f1ff275ed13581958c0bcf043c5e"); + +pub fn create_bundle_from_txn_data() -> AcceptedBundle { + AcceptedBundle::new( + Bundle { txs: vec![TXN_DATA], ..Default::default() }.try_into().unwrap(), + create_test_meter_bundle_response(), + ) +} + +pub fn create_transaction(from: PrivateKeySigner, nonce: u64, to: Address) -> OpTxEnvelope { + 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(); + OpTxEnvelope::Eip1559(txn.eip1559().cloned().unwrap().into_signed(sig)) +} + +pub fn create_test_bundle( + txns: Vec, + block_number: Option, + min_timestamp: Option, + max_timestamp: Option, +) -> AcceptedBundle { + let txs = txns.iter().map(|t| t.encoded_2718().into()).collect(); + + let bundle = Bundle { + txs, + block_number: block_number.unwrap_or(0), + min_timestamp, + max_timestamp, + ..Default::default() + }; + let meter_bundle_response = create_test_meter_bundle_response(); + + AcceptedBundle::new(bundle.try_into().unwrap(), meter_bundle_response) +} + +pub fn create_test_meter_bundle_response() -> MeterBundleResponse { + MeterBundleResponse { + bundle_gas_price: U256::from(0), + bundle_hash: B256::default(), + coinbase_diff: U256::from(0), + eth_sent_to_coinbase: U256::from(0), + gas_fees: U256::from(0), + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + state_root_time_us: 0, + } +} diff --git a/crates/tips/ingress-rpc/Cargo.toml b/crates/tips/ingress-rpc/Cargo.toml new file mode 100644 index 0000000..f136782 --- /dev/null +++ b/crates/tips/ingress-rpc/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "tips-ingress-rpc-lib" +version.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[dependencies] +url.workspace = true +tips-core.workspace = true +op-revm.workspace = true +metrics.workspace = true +tips-audit-lib.workspace = true +async-trait.workspace = true +metrics-derive.workspace = true +op-alloy-network.workspace = true +alloy-signer-local.workspace = true +base-reth-rpc-types.workspace = true +alloy-primitives = { workspace = true, features = ["map-foldhash", "serde"] } +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true, features = ["std"] } +anyhow = { workspace = true, features = ["std"] } +serde_json = { workspace = true, features = ["std"] } +rdkafka = { workspace = true, features = ["tokio", "libz", "zstd", "ssl-vendored"] } +alloy-consensus = { workspace = true, features = ["std"] } +alloy-provider = { workspace = true, features = ["reqwest"] } +jsonrpsee = { workspace = true, features = ["server", "macros"] } +backon = { workspace = true, features = ["std", "tokio-sleep"] } +axum = { workspace = true, features = ["tokio", "http1", "json"] } +clap = { version = "4.5.47", features = ["std", "derive", "env"] } +op-alloy-consensus = { workspace = true, features = ["std", "k256", "serde"] } +moka = { workspace = true, features = ["future"] } +uuid = { workspace = true, features = ["v5"] } + +[dev-dependencies] +mockall = "0.13" +wiremock.workspace = true +jsonrpsee = { workspace = true, features = ["server", "http-client", "macros"] } diff --git a/crates/tips/ingress-rpc/src/health.rs b/crates/tips/ingress-rpc/src/health.rs new file mode 100644 index 0000000..718104e --- /dev/null +++ b/crates/tips/ingress-rpc/src/health.rs @@ -0,0 +1,32 @@ +use std::net::SocketAddr; + +use axum::{Router, http::StatusCode, response::IntoResponse, routing::get}; +use tracing::info; + +/// Health check handler that always returns 200 OK +async fn health() -> impl IntoResponse { + StatusCode::OK +} + +/// Bind and start the health check server on the specified address. +/// Returns a handle that can be awaited to run the server. +pub async fn bind_health_server( + addr: SocketAddr, +) -> anyhow::Result<(SocketAddr, tokio::task::JoinHandle>)> { + let app = Router::new().route("/health", get(health)); + + let listener = tokio::net::TcpListener::bind(addr).await?; + let bound_addr = listener.local_addr()?; + + info!( + message = "Health check server bound successfully", + address = %bound_addr + ); + + let handle = tokio::spawn(async move { + axum::serve(listener, app).await?; + Ok(()) + }); + + Ok((bound_addr, handle)) +} diff --git a/crates/tips/ingress-rpc/src/lib.rs b/crates/tips/ingress-rpc/src/lib.rs new file mode 100644 index 0000000..00c1aa3 --- /dev/null +++ b/crates/tips/ingress-rpc/src/lib.rs @@ -0,0 +1,199 @@ +pub mod health; +pub mod metrics; +pub mod queue; +pub mod service; +pub mod validation; +use std::{ + net::{IpAddr, SocketAddr}, + str::FromStr, +}; + +use alloy_primitives::TxHash; +use alloy_provider::{Provider, ProviderBuilder, RootProvider}; +use clap::Parser; +use op_alloy_network::Optimism; +use tips_core::{AcceptedBundle, MeterBundleResponse}; +use tokio::sync::broadcast; +use tracing::{error, warn}; +use url::Url; + +#[derive(Debug, Clone, Copy)] +pub enum TxSubmissionMethod { + Mempool, + Kafka, + MempoolAndKafka, + None, +} + +impl FromStr for TxSubmissionMethod { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "mempool" => Ok(TxSubmissionMethod::Mempool), + "kafka" => Ok(TxSubmissionMethod::Kafka), + "mempool,kafka" | "kafka,mempool" => Ok(TxSubmissionMethod::MempoolAndKafka), + "none" => Ok(TxSubmissionMethod::None), + _ => Err(format!( + "Invalid submission method: '{s}'. Valid options: mempool, kafka, mempool,kafka, kafka,mempool, none" + )), + } + } +} + +#[derive(Parser, Debug, Clone)] +#[command(author, version, about, long_about = None)] +pub struct Config { + /// Address to bind the RPC server to + #[arg(long, env = "TIPS_INGRESS_ADDRESS", default_value = "0.0.0.0")] + pub address: IpAddr, + + /// Port to bind the RPC server to + #[arg(long, env = "TIPS_INGRESS_PORT", default_value = "8080")] + pub port: u16, + + /// URL of the mempool service to proxy transactions to + #[arg(long, env = "TIPS_INGRESS_RPC_MEMPOOL")] + pub mempool_url: Url, + + /// Method to submit transactions to the mempool + #[arg(long, env = "TIPS_INGRESS_TX_SUBMISSION_METHOD", default_value = "mempool")] + pub tx_submission_method: TxSubmissionMethod, + + /// Kafka brokers for publishing mempool events + #[arg(long, env = "TIPS_INGRESS_KAFKA_INGRESS_PROPERTIES_FILE")] + pub ingress_kafka_properties: String, + + /// Kafka topic for queuing transactions before the DB Writer + #[arg(long, env = "TIPS_INGRESS_KAFKA_INGRESS_TOPIC", default_value = "tips-ingress")] + pub ingress_topic: String, + + /// Kafka properties file for audit events + #[arg(long, env = "TIPS_INGRESS_KAFKA_AUDIT_PROPERTIES_FILE")] + pub audit_kafka_properties: String, + + /// Kafka topic for audit events + #[arg(long, env = "TIPS_INGRESS_KAFKA_AUDIT_TOPIC", default_value = "tips-audit")] + pub audit_topic: String, + + #[arg(long, env = "TIPS_INGRESS_LOG_LEVEL", default_value = "info")] + pub log_level: String, + + #[arg(long, env = "TIPS_INGRESS_LOG_FORMAT", default_value = "pretty")] + pub log_format: tips_core::logger::LogFormat, + + /// Default lifetime for sent transactions in seconds (default: 3 hours) + #[arg( + long, + env = "TIPS_INGRESS_SEND_TRANSACTION_DEFAULT_LIFETIME_SECONDS", + default_value = "10800" + )] + pub send_transaction_default_lifetime_seconds: u64, + + /// URL of the simulation RPC service for bundle metering + #[arg(long, env = "TIPS_INGRESS_RPC_SIMULATION")] + pub simulation_rpc: Url, + + /// Port to bind the Prometheus metrics server to + #[arg(long, env = "TIPS_INGRESS_METRICS_ADDR", default_value = "0.0.0.0:9002")] + pub metrics_addr: SocketAddr, + + /// Configurable block time in milliseconds (default: 2000 milliseconds) + #[arg(long, env = "TIPS_INGRESS_BLOCK_TIME_MILLISECONDS", default_value = "2000")] + pub block_time_milliseconds: u64, + + /// Timeout for bundle metering in milliseconds (default: 2000 milliseconds) + #[arg(long, env = "TIPS_INGRESS_METER_BUNDLE_TIMEOUT_MS", default_value = "2000")] + pub meter_bundle_timeout_ms: u64, + + /// URLs of the builder RPC service for setting metering information + #[arg(long, env = "TIPS_INGRESS_BUILDER_RPCS", value_delimiter = ',')] + pub builder_rpcs: Vec, + + /// Maximum number of `MeterBundleResponse`s to buffer in memory + #[arg(long, env = "TIPS_INGRESS_MAX_BUFFERED_METER_BUNDLE_RESPONSES", default_value = "100")] + pub max_buffered_meter_bundle_responses: usize, + + /// Maximum number of backrun bundles to buffer in memory + #[arg(long, env = "TIPS_INGRESS_MAX_BUFFERED_BACKRUN_BUNDLES", default_value = "100")] + pub max_buffered_backrun_bundles: usize, + + /// Address to bind the health check server to + #[arg(long, env = "TIPS_INGRESS_HEALTH_CHECK_ADDR", default_value = "0.0.0.0:8081")] + pub health_check_addr: SocketAddr, + + /// chain id + #[arg(long, env = "TIPS_INGRESS_CHAIN_ID", default_value = "11")] + pub chain_id: u64, + + /// Enable backrun bundle submission to op-rbuilder + #[arg(long, env = "TIPS_INGRESS_BACKRUN_ENABLED", default_value = "false")] + pub backrun_enabled: bool, + + /// Maximum number of transactions allowed in a backrun bundle (including target tx) + #[arg(long, env = "MAX_BACKRUN_TXS", default_value = "5")] + pub max_backrun_txs: usize, + + /// Maximum total gas limit for all transactions in a backrun bundle + #[arg(long, env = "MAX_BACKRUN_GAS_LIMIT", default_value = "5000000")] + pub max_backrun_gas_limit: u64, + + /// URL of third-party RPC endpoint to forward raw transactions to (enables forwarding if set) + #[arg(long, env = "TIPS_INGRESS_RAW_TX_FORWARD_RPC")] + pub raw_tx_forward_rpc: Option, + + /// TTL for bundle cache in seconds + #[arg(long, env = "TIPS_INGRESS_BUNDLE_CACHE_TTL", default_value = "20")] + pub bundle_cache_ttl: u64, + + /// Enable sending to builder + #[arg(long, env = "TIPS_INGRESS_SEND_TO_BUILDER", default_value = "false")] + pub send_to_builder: bool, +} + +pub fn connect_ingress_to_builder( + metering_rx: broadcast::Receiver, + backrun_rx: broadcast::Receiver, + builder_rpc: Url, +) { + let builder: RootProvider = ProviderBuilder::new() + .disable_recommended_fillers() + .network::() + .connect_http(builder_rpc); + + let metering_builder = builder.clone(); + tokio::spawn(async move { + let mut event_rx = metering_rx; + while let Ok(event) = event_rx.recv().await { + if event.results.is_empty() { + warn!(message = "received metering information with no transactions", hash=%event.bundle_hash); + continue; + } + + let tx_hash = event.results[0].tx_hash; + if let Err(e) = metering_builder + .client() + .request::<(TxHash, MeterBundleResponse), ()>( + "base_setMeteringInformation", + (tx_hash, event), + ) + .await + { + error!(error = %e, "Failed to set metering information for tx hash: {tx_hash}"); + } + } + }); + + tokio::spawn(async move { + let mut event_rx = backrun_rx; + while let Ok(accepted_bundle) = event_rx.recv().await { + if let Err(e) = builder + .client() + .request::<(AcceptedBundle,), ()>("base_sendBackrunBundle", (accepted_bundle,)) + .await + { + error!(error = ?e, "Failed to send backrun bundle to builder"); + } + } + }); +} diff --git a/crates/tips/ingress-rpc/src/metrics.rs b/crates/tips/ingress-rpc/src/metrics.rs new file mode 100644 index 0000000..a62b3b8 --- /dev/null +++ b/crates/tips/ingress-rpc/src/metrics.rs @@ -0,0 +1,54 @@ +use metrics::{Counter, Histogram}; +use metrics_derive::Metrics; +use tokio::time::Duration; + +pub fn record_histogram(rpc_latency: Duration, rpc: String) { + metrics::histogram!("tips_ingress_rpc_rpc_latency", "rpc" => rpc) + .record(rpc_latency.as_secs_f64()); +} + +#[derive(Metrics, Clone)] +#[metrics(scope = "tips_ingress_rpc")] +pub struct Metrics { + #[metric(describe = "Number of valid transactions received")] + pub transactions_received: Counter, + + #[metric(describe = "Number of valid bundles parsed")] + pub bundles_parsed: Counter, + + #[metric(describe = "Number of bundles simulated")] + pub successful_simulations: Counter, + + #[metric(describe = "Number of bundles simulated")] + pub failed_simulations: Counter, + + #[metric(describe = "Number of bundles sent to kafka")] + pub sent_to_kafka: Counter, + + #[metric(describe = "Number of transactions sent to mempool")] + pub sent_to_mempool: Counter, + + #[metric(describe = "Duration of validate_tx")] + pub validate_tx_duration: Histogram, + + #[metric(describe = "Duration of validate_bundle")] + pub validate_bundle_duration: Histogram, + + #[metric(describe = "Duration of meter_bundle")] + pub meter_bundle_duration: Histogram, + + #[metric(describe = "Duration of send_raw_transaction")] + pub send_raw_transaction_duration: Histogram, + + #[metric(describe = "Total backrun bundles received")] + pub backrun_bundles_received_total: Counter, + + #[metric(describe = "Duration to send backrun bundle to op-rbuilder")] + pub backrun_bundles_sent_duration: Histogram, + + #[metric(describe = "Total raw transactions forwarded to additional endpoint")] + pub raw_tx_forwards_total: Counter, + + #[metric(describe = "Number of bundles that exceeded the metering time")] + pub bundles_exceeded_metering_time: Counter, +} diff --git a/crates/tips/ingress-rpc/src/queue.rs b/crates/tips/ingress-rpc/src/queue.rs new file mode 100644 index 0000000..1e1372f --- /dev/null +++ b/crates/tips/ingress-rpc/src/queue.rs @@ -0,0 +1,130 @@ +use std::sync::Arc; + +use alloy_primitives::B256; +use anyhow::Result; +use async_trait::async_trait; +use backon::{ExponentialBuilder, Retryable}; +use rdkafka::producer::{FutureProducer, FutureRecord}; +use tips_core::AcceptedBundle; +use tokio::time::Duration; +use tracing::{error, info}; + +#[async_trait] +pub trait MessageQueue: Send + Sync { + async fn publish(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()>; +} + +pub struct KafkaMessageQueue { + producer: FutureProducer, +} + +impl KafkaMessageQueue { + pub fn new(producer: FutureProducer) -> Self { + Self { producer } + } +} + +#[async_trait] +impl MessageQueue for KafkaMessageQueue { + async fn publish(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()> { + let enqueue = || async { + let record = FutureRecord::to(topic).key(key).payload(payload); + + match self.producer.send(record, Duration::from_secs(5)).await { + Ok((partition, offset)) => { + info!( + key = %key, + partition = partition, + offset = offset, + topic = %topic, + "Successfully enqueued message" + ); + Ok(()) + } + Err((err, _)) => { + error!( + key = key, + error = %err, + topic = topic, + "Failed to enqueue message" + ); + Err(anyhow::anyhow!("Failed to enqueue bundle: {err}")) + } + } + }; + + enqueue + .retry( + &ExponentialBuilder::default() + .with_min_delay(Duration::from_millis(100)) + .with_max_delay(Duration::from_secs(5)) + .with_max_times(3), + ) + .notify(|err: &anyhow::Error, dur: Duration| { + info!("retrying to enqueue message {:?} after {:?}", err, dur); + }) + .await + } +} + +pub struct BundleQueuePublisher { + queue: Arc, + topic: String, +} + +impl BundleQueuePublisher { + pub fn new(queue: Arc, topic: String) -> Self { + Self { queue, topic } + } + + pub async fn publish(&self, bundle: &AcceptedBundle, hash: &B256) -> Result<()> { + let key = hash.to_string(); + let payload = serde_json::to_vec(bundle)?; + self.queue.publish(&self.topic, &key, &payload).await + } +} + +#[cfg(test)] +mod tests { + use rdkafka::config::ClientConfig; + use tips_core::{ + AcceptedBundle, Bundle, BundleExtensions, test_utils::create_test_meter_bundle_response, + }; + use tokio::time::{Duration, Instant}; + + use super::*; + + fn create_test_bundle() -> Bundle { + Bundle::default() + } + + #[tokio::test] + async fn test_backoff_retry_logic() { + // use an invalid broker address to trigger the backoff logic + let producer = ClientConfig::new() + .set("bootstrap.servers", "localhost:9999") + .set("message.timeout.ms", "100") + .create() + .expect("Producer creation failed"); + + let publisher = KafkaMessageQueue::new(producer); + let bundle = create_test_bundle(); + let accepted_bundle = + AcceptedBundle::new(bundle.try_into().unwrap(), create_test_meter_bundle_response()); + let bundle_hash = &accepted_bundle.bundle_hash(); + + let start = Instant::now(); + let result = publisher + .publish( + "tips-ingress-rpc", + bundle_hash.to_string().as_str(), + &serde_json::to_vec(&accepted_bundle).unwrap(), + ) + .await; + let elapsed = start.elapsed(); + + // the backoff tries at minimum 100ms, so verify we tried at least once + assert!(result.is_err()); + assert!(elapsed >= Duration::from_millis(100)); + } +} diff --git a/crates/tips/ingress-rpc/src/service.rs b/crates/tips/ingress-rpc/src/service.rs new file mode 100644 index 0000000..cf576f0 --- /dev/null +++ b/crates/tips/ingress-rpc/src/service.rs @@ -0,0 +1,709 @@ +use std::{ + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; + +use alloy_consensus::{ + Transaction, + transaction::{Recovered, SignerRecoverable}, +}; +use alloy_primitives::{B256, Bytes}; +use alloy_provider::{Provider, RootProvider, network::eip2718::Decodable2718}; +use base_reth_rpc_types::EthApiError; +use jsonrpsee::{ + core::{RpcResult, async_trait}, + proc_macros::rpc, +}; +use moka::future::Cache; +use op_alloy_consensus::OpTxEnvelope; +use op_alloy_network::Optimism; +use tips_audit_lib::BundleEvent; +use tips_core::{ + AcceptedBundle, Bundle, BundleExtensions, BundleHash, CancelBundle, MeterBundleResponse, + ParsedBundle, +}; +use tokio::{ + sync::{broadcast, mpsc}, + time::{Duration, Instant, timeout}, +}; +use tracing::{debug, info, warn}; + +use crate::{ + Config, TxSubmissionMethod, + metrics::{Metrics, record_histogram}, + queue::{BundleQueuePublisher, MessageQueue}, + validation::validate_bundle, +}; + +/// RPC providers for different endpoints +pub struct Providers { + pub mempool: RootProvider, + pub simulation: RootProvider, + pub raw_tx_forward: Option>, +} + +#[rpc(server, namespace = "eth")] +pub trait IngressApi { + /// `eth_sendBundle` can be used to send your bundles to the builder. + #[method(name = "sendBundle")] + async fn send_bundle(&self, bundle: Bundle) -> RpcResult; + + #[method(name = "sendBackrunBundle")] + async fn send_backrun_bundle(&self, bundle: Bundle) -> RpcResult; + + /// `eth_cancelBundle` is used to prevent a submitted bundle from being included on-chain. + #[method(name = "cancelBundle")] + async fn cancel_bundle(&self, request: CancelBundle) -> RpcResult<()>; + + /// Handler for: `eth_sendRawTransaction` + #[method(name = "sendRawTransaction")] + async fn send_raw_transaction(&self, tx: Bytes) -> RpcResult; +} + +pub struct IngressService { + mempool_provider: Arc>, + simulation_provider: Arc>, + raw_tx_forward_provider: Option>>, + tx_submission_method: TxSubmissionMethod, + bundle_queue_publisher: BundleQueuePublisher, + audit_channel: mpsc::UnboundedSender, + send_transaction_default_lifetime_seconds: u64, + metrics: Metrics, + block_time_milliseconds: u64, + meter_bundle_timeout_ms: u64, + builder_tx: broadcast::Sender, + backrun_enabled: bool, + builder_backrun_tx: broadcast::Sender, + max_backrun_txs: usize, + max_backrun_gas_limit: u64, + bundle_cache: Cache, + send_to_builder: bool, +} + +impl IngressService { + pub fn new( + providers: Providers, + queue: Q, + audit_channel: mpsc::UnboundedSender, + builder_tx: broadcast::Sender, + builder_backrun_tx: broadcast::Sender, + config: Config, + ) -> Self { + let mempool_provider = Arc::new(providers.mempool); + let simulation_provider = Arc::new(providers.simulation); + let raw_tx_forward_provider = providers.raw_tx_forward.map(Arc::new); + let queue_connection = Arc::new(queue); + + // A TTL cache to deduplicate bundles with the same Bundle ID + let bundle_cache = + Cache::builder().time_to_live(Duration::from_secs(config.bundle_cache_ttl)).build(); + Self { + mempool_provider, + simulation_provider, + raw_tx_forward_provider, + tx_submission_method: config.tx_submission_method, + bundle_queue_publisher: BundleQueuePublisher::new( + queue_connection.clone(), + config.ingress_topic, + ), + audit_channel, + send_transaction_default_lifetime_seconds: config + .send_transaction_default_lifetime_seconds, + metrics: Metrics::default(), + block_time_milliseconds: config.block_time_milliseconds, + meter_bundle_timeout_ms: config.meter_bundle_timeout_ms, + builder_tx, + backrun_enabled: config.backrun_enabled, + builder_backrun_tx, + max_backrun_txs: config.max_backrun_txs, + max_backrun_gas_limit: config.max_backrun_gas_limit, + bundle_cache, + send_to_builder: config.send_to_builder, + } + } +} + +fn validate_backrun_bundle_limits( + txs_count: usize, + total_gas_limit: u64, + max_backrun_txs: usize, + max_backrun_gas_limit: u64, +) -> Result<(), String> { + if txs_count < 2 { + return Err( + "Backrun bundle must have at least 2 transactions (target + backrun)".to_string() + ); + } + if txs_count > max_backrun_txs { + return Err(format!( + "Backrun bundle exceeds max transaction count: {txs_count} > {max_backrun_txs}", + )); + } + if total_gas_limit > max_backrun_gas_limit { + return Err(format!( + "Backrun bundle exceeds max gas limit: {total_gas_limit} > {max_backrun_gas_limit}", + )); + } + Ok(()) +} + +#[async_trait] +impl IngressApiServer for IngressService { + async fn send_backrun_bundle(&self, bundle: Bundle) -> RpcResult { + if !self.backrun_enabled { + return Err(EthApiError::InvalidParams("Backrun bundle submission is disabled".into()) + .into_rpc_err()); + } + + let start = Instant::now(); + let (accepted_bundle, bundle_hash) = + self.validate_parse_and_meter_bundle(&bundle, false).await?; + + let total_gas_limit: u64 = accepted_bundle.txs.iter().map(|tx| tx.gas_limit()).sum(); + validate_backrun_bundle_limits( + accepted_bundle.txs.len(), + total_gas_limit, + self.max_backrun_txs, + self.max_backrun_gas_limit, + ) + .map_err(|e| EthApiError::InvalidParams(e).into_rpc_err())?; + + self.metrics.backrun_bundles_received_total.increment(1); + + self.builder_backrun_tx.send(accepted_bundle.clone()).map_err(|e| { + EthApiError::InvalidParams(format!("Failed to send backrun bundle: {e}")).into_rpc_err() + })?; + + self.send_audit_event(&accepted_bundle, bundle_hash); + + self.metrics.backrun_bundles_sent_duration.record(start.elapsed().as_secs_f64()); + + Ok(BundleHash { bundle_hash }) + } + + async fn send_bundle(&self, bundle: Bundle) -> RpcResult { + let (accepted_bundle, bundle_hash) = + self.validate_parse_and_meter_bundle(&bundle, true).await?; + + // Get meter_bundle_response for builder broadcast + let meter_bundle_response = accepted_bundle.meter_bundle_response.clone(); + + // asynchronously send the meter bundle response to the builder + self.builder_tx + .send(meter_bundle_response) + .map_err(|e| EthApiError::InvalidParams(e.to_string()).into_rpc_err())?; + + // publish the bundle to the queue + if let Err(e) = self.bundle_queue_publisher.publish(&accepted_bundle, &bundle_hash).await { + warn!(message = "Failed to publish bundle to queue", bundle_hash = %bundle_hash, error = %e); + return Err(EthApiError::InvalidParams("Failed to queue bundle".into()).into_rpc_err()); + } + + info!( + message = "queued bundle", + bundle_hash = %bundle_hash, + ); + + // asynchronously send the audit event to the audit channel + self.send_audit_event(&accepted_bundle, bundle_hash); + + Ok(BundleHash { bundle_hash }) + } + + async fn cancel_bundle(&self, _request: CancelBundle) -> RpcResult<()> { + warn!(message = "TODO: implement cancel_bundle", method = "cancel_bundle"); + todo!("implement cancel_bundle") + } + + async fn send_raw_transaction(&self, data: Bytes) -> RpcResult { + let start = Instant::now(); + let transaction = self.get_tx(&data).await?; + + self.metrics.transactions_received.increment(1); + + let send_to_kafka = matches!( + self.tx_submission_method, + TxSubmissionMethod::Kafka | TxSubmissionMethod::MempoolAndKafka + ); + let send_to_mempool = matches!( + self.tx_submission_method, + TxSubmissionMethod::Mempool | TxSubmissionMethod::MempoolAndKafka + ); + + // Forward before metering + if let Some(forward_provider) = self.raw_tx_forward_provider.clone() { + self.metrics.raw_tx_forwards_total.increment(1); + let tx_data = data.clone(); + let tx_hash = transaction.tx_hash(); + tokio::spawn(async move { + match forward_provider.send_raw_transaction(tx_data.iter().as_slice()).await { + Ok(_) => { + debug!(message = "Forwarded raw tx", hash = %tx_hash); + } + Err(e) => { + warn!(message = "Failed to forward raw tx", hash = %tx_hash, error = %e); + } + } + }); + } + + let expiry_timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + + self.send_transaction_default_lifetime_seconds; + + let bundle = Bundle { + txs: vec![data.clone()], + max_timestamp: Some(expiry_timestamp), + reverting_tx_hashes: vec![transaction.tx_hash()], + ..Default::default() + }; + + let parsed_bundle: ParsedBundle = bundle + .clone() + .try_into() + .map_err(|e: String| EthApiError::InvalidParams(e).into_rpc_err())?; + + let bundle_hash = &parsed_bundle.bundle_hash(); + + if self.bundle_cache.get(bundle_hash).await.is_some() { + debug!( + message = "Duplicate bundle detected, skipping Kafka publish", + bundle_hash = %bundle_hash, + transaction_hash = %transaction.tx_hash(), + ); + } else { + self.bundle_cache.insert(*bundle_hash, ()).await; + self.metrics.bundles_parsed.increment(1); + + let meter_bundle_response = match self.meter_bundle(&bundle, bundle_hash).await { + Ok(response) => { + info!(message = "Metering succeeded for raw transaction", bundle_hash = %bundle_hash, response = ?response); + Some(response) + } + Err(e) => { + warn!( + bundle_hash = %bundle_hash, + error = %e, + "Metering failed for raw transaction" + ); + None + } + }; + + if let Some(meter_info) = meter_bundle_response.as_ref() { + self.metrics.successful_simulations.increment(1); + if self.send_to_builder { + _ = self.builder_tx.send(meter_info.clone()); + } + } else { + self.metrics.failed_simulations.increment(1); + } + + let accepted_bundle = + AcceptedBundle::new(parsed_bundle, meter_bundle_response.unwrap_or_default()); + + if send_to_kafka { + if let Err(e) = + self.bundle_queue_publisher.publish(&accepted_bundle, bundle_hash).await + { + warn!(message = "Failed to publish Queue::enqueue_bundle", bundle_hash = %bundle_hash, error = %e); + } + + self.metrics.sent_to_kafka.increment(1); + info!(message="queued singleton bundle", txn_hash=%transaction.tx_hash()); + } + + if send_to_mempool { + let response = + self.mempool_provider.send_raw_transaction(data.iter().as_slice()).await; + match response { + Ok(_) => { + self.metrics.sent_to_mempool.increment(1); + debug!(message = "sent transaction to the mempool", hash=%transaction.tx_hash()); + } + Err(e) => { + warn!(message = "Failed to send raw transaction to mempool", error = %e); + } + } + } + + info!( + message = "processed transaction", + bundle_hash = %bundle_hash, + transaction_hash = %transaction.tx_hash(), + ); + + self.send_audit_event(&accepted_bundle, accepted_bundle.bundle_hash()); + } + + self.metrics.send_raw_transaction_duration.record(start.elapsed().as_secs_f64()); + + Ok(transaction.tx_hash()) + } +} + +impl IngressService { + async fn get_tx(&self, data: &Bytes) -> RpcResult> { + if data.is_empty() { + return Err(EthApiError::EmptyRawTransactionData.into_rpc_err()); + } + + let envelope = OpTxEnvelope::decode_2718_exact(data.iter().as_slice()) + .map_err(|_| EthApiError::FailedToDecodeSignedTransaction.into_rpc_err())?; + + let transaction = envelope + .clone() + .try_into_recovered() + .map_err(|_| EthApiError::FailedToDecodeSignedTransaction.into_rpc_err())?; + Ok(transaction) + } + + async fn validate_bundle(&self, bundle: &Bundle) -> RpcResult<()> { + let start = Instant::now(); + if bundle.txs.is_empty() { + return Err(EthApiError::InvalidParams("Bundle cannot have empty transactions".into()) + .into_rpc_err()); + } + + let mut total_gas = 0u64; + let mut tx_hashes = Vec::new(); + for tx_data in &bundle.txs { + let transaction = self.get_tx(tx_data).await?; + total_gas = total_gas.saturating_add(transaction.gas_limit()); + tx_hashes.push(transaction.tx_hash()); + } + validate_bundle(bundle, total_gas, tx_hashes)?; + + self.metrics.validate_bundle_duration.record(start.elapsed().as_secs_f64()); + Ok(()) + } + + /// `meter_bundle` is used to determine how long a bundle will take to execute. A bundle that + /// is within `block_time_milliseconds` will return the `MeterBundleResponse` that can be passed along + /// to the builder. + async fn meter_bundle( + &self, + bundle: &Bundle, + bundle_hash: &B256, + ) -> RpcResult { + let start = Instant::now(); + let timeout_duration = Duration::from_millis(self.meter_bundle_timeout_ms); + + // The future we await has the nested type: + // Result< + // RpcResult, // 1. The inner operation's result + // tokio::time::error::Elapsed // 2. The outer timeout's result + // > + let res: MeterBundleResponse = timeout( + timeout_duration, + self.simulation_provider.client().request("base_meterBundle", (bundle,)), + ) + .await + .map_err(|_| { + warn!(message = "Timed out on requesting metering", bundle_hash = %bundle_hash); + EthApiError::InvalidParams("Timeout on requesting metering".into()).into_rpc_err() + })? + .map_err(|e| EthApiError::InvalidParams(e.to_string()).into_rpc_err())?; + + record_histogram(start.elapsed(), "base_meterBundle".to_string()); + + // we can save some builder payload building computation by not including bundles + // that we know will take longer than the block time to execute + let total_execution_time = (res.total_execution_time_us / 1_000) as u64; + if total_execution_time > self.block_time_milliseconds { + self.metrics.bundles_exceeded_metering_time.increment(1); + return Err( + EthApiError::InvalidParams("Bundle simulation took too long".into()).into_rpc_err() + ); + } + Ok(res) + } + + /// Helper method to validate, parse, and meter a bundle + async fn validate_parse_and_meter_bundle( + &self, + bundle: &Bundle, + to_meter: bool, + ) -> RpcResult<(AcceptedBundle, B256)> { + self.validate_bundle(bundle).await?; + let parsed_bundle: ParsedBundle = bundle + .clone() + .try_into() + .map_err(|e: String| EthApiError::InvalidParams(e).into_rpc_err())?; + let bundle_hash = parsed_bundle.bundle_hash(); + let meter_bundle_response = if to_meter { + self.meter_bundle(bundle, &bundle_hash).await? + } else { + MeterBundleResponse::default() + }; + let accepted_bundle = AcceptedBundle::new(parsed_bundle, meter_bundle_response.clone()); + Ok((accepted_bundle, bundle_hash)) + } + + /// Helper method to send audit event for a bundle + fn send_audit_event(&self, accepted_bundle: &AcceptedBundle, bundle_hash: B256) { + let audit_event = BundleEvent::Received { + bundle_id: *accepted_bundle.uuid(), + bundle: Box::new(accepted_bundle.clone()), + }; + if let Err(e) = self.audit_channel.send(audit_event) { + warn!( + message = "failed to send audit event", + bundle_hash = %bundle_hash, + error = %e + ); + } + } +} + +#[cfg(test)] +mod tests { + use std::{ + net::{IpAddr, SocketAddr}, + str::FromStr, + }; + + use alloy_provider::RootProvider; + use anyhow::Result; + use async_trait::async_trait; + use jsonrpsee::{ + http_client::{HttpClient, HttpClientBuilder}, + server::{ServerBuilder, ServerHandle}, + }; + use mockall::mock; + use tips_core::test_utils::create_test_meter_bundle_response; + use tokio::sync::{broadcast, mpsc}; + use url::Url; + use wiremock::{Mock, MockServer, ResponseTemplate, matchers::method}; + + use super::*; + use crate::{Config, TxSubmissionMethod, queue::MessageQueue}; + struct MockQueue; + + #[async_trait] + impl MessageQueue for MockQueue { + async fn publish(&self, _topic: &str, _key: &str, _payload: &[u8]) -> Result<()> { + Ok(()) + } + } + + fn create_test_config(mock_server: &MockServer) -> Config { + Config { + address: IpAddr::from([127, 0, 0, 1]), + port: 8080, + mempool_url: Url::parse("http://localhost:3000").unwrap(), + tx_submission_method: TxSubmissionMethod::Mempool, + ingress_kafka_properties: String::new(), + ingress_topic: String::new(), + audit_kafka_properties: String::new(), + audit_topic: String::new(), + log_level: String::from("info"), + log_format: tips_core::logger::LogFormat::Pretty, + send_transaction_default_lifetime_seconds: 300, + simulation_rpc: mock_server.uri().parse().unwrap(), + metrics_addr: SocketAddr::from(([127, 0, 0, 1], 9002)), + block_time_milliseconds: 1000, + meter_bundle_timeout_ms: 5000, + builder_rpcs: vec![], + max_buffered_meter_bundle_responses: 100, + max_buffered_backrun_bundles: 100, + health_check_addr: SocketAddr::from(([127, 0, 0, 1], 8081)), + backrun_enabled: false, + raw_tx_forward_rpc: None, + chain_id: 11, + max_backrun_txs: 5, + max_backrun_gas_limit: 5000000, + bundle_cache_ttl: 20, + send_to_builder: false, + } + } + + #[allow(dead_code)] + async fn setup_rpc_server(mock: MockIngressApi) -> (HttpClient, ServerHandle) { + let server = ServerBuilder::default().build("127.0.0.1:0").await.unwrap(); + + let addr = server.local_addr().unwrap(); + let handle = server.start(mock.into_rpc()); + + let client = HttpClientBuilder::default().build(format!("http://{}", addr)).unwrap(); + + (client, handle) + } + + #[tokio::test] + async fn test_timeout_logic() { + let timeout_duration = Duration::from_millis(100); + + // Test a future that takes longer than the timeout + let slow_future = async { + tokio::time::sleep(Duration::from_millis(200)).await; + Ok::(create_test_meter_bundle_response()) + }; + + let result = timeout(timeout_duration, slow_future) + .await + .map_err(|_| { + EthApiError::InvalidParams("Timeout on requesting metering".into()).into_rpc_err() + }) + .map_err(|e| e.to_string()); + + assert!(result.is_err()); + let error_string = format!("{:?}", result.unwrap_err()); + assert!(error_string.contains("Timeout on requesting metering")); + } + + #[tokio::test] + async fn test_timeout_logic_success() { + let timeout_duration = Duration::from_millis(200); + + // Test a future that completes within the timeout + let fast_future = async { + tokio::time::sleep(Duration::from_millis(50)).await; + Ok::(create_test_meter_bundle_response()) + }; + + let result = timeout(timeout_duration, fast_future) + .await + .map_err(|_| { + EthApiError::InvalidParams("Timeout on requesting metering".into()).into_rpc_err() + }) + .map_err(|e| e.to_string()); + + assert!(result.is_ok()); + // we're assumging that `base_meterBundle` will not error hence the second unwrap + let res = result.unwrap().unwrap(); + assert_eq!(res, create_test_meter_bundle_response()); + } + + // Replicate a failed `meter_bundle` request and instead of returning an error, we return a default `MeterBundleResponse` + #[tokio::test] + async fn test_meter_bundle_success() { + let mock_server = MockServer::start().await; + + // Mock error response from base_meterBundle + Mock::given(method("POST")) + .respond_with(ResponseTemplate::new(500).set_body_json(serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -32000, + "message": "Simulation failed" + } + }))) + .mount(&mock_server) + .await; + + let config = create_test_config(&mock_server); + + let provider: RootProvider = + RootProvider::new_http(mock_server.uri().parse().unwrap()); + + let providers = Providers { + mempool: provider.clone(), + simulation: provider.clone(), + raw_tx_forward: None, + }; + + let (audit_tx, _audit_rx) = mpsc::unbounded_channel(); + let (builder_tx, _builder_rx) = broadcast::channel(1); + let (backrun_tx, _backrun_rx) = broadcast::channel(1); + + let service = + IngressService::new(providers, MockQueue, audit_tx, builder_tx, backrun_tx, config); + + let bundle = Bundle::default(); + let bundle_hash = B256::default(); + + let result = service.meter_bundle(&bundle, &bundle_hash).await; + + // Test that meter_bundle returns an error, but we handle it gracefully + assert!(result.is_err()); + let response = result.unwrap_or_else(|_| MeterBundleResponse::default()); + assert_eq!(response, MeterBundleResponse::default()); + } + + #[tokio::test] + async fn test_raw_tx_forward() { + let simulation_server = MockServer::start().await; + let forward_server = MockServer::start().await; + + // Mock error response from base_meterBundle + Mock::given(method("POST")) + .respond_with(ResponseTemplate::new(500).set_body_json(serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -32000, + "message": "Simulation failed" + } + }))) + .mount(&simulation_server) + .await; + + // Mock forward endpoint - expect exactly 1 call + Mock::given(method("POST")) + .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ + "jsonrpc": "2.0", + "id": 1, + "result": "0x0000000000000000000000000000000000000000000000000000000000000000" + }))) + .expect(1) + .mount(&forward_server) + .await; + + let mut config = create_test_config(&simulation_server); + config.tx_submission_method = TxSubmissionMethod::Kafka; // Skip mempool send + + let providers = Providers { + mempool: RootProvider::new_http(simulation_server.uri().parse().unwrap()), + simulation: RootProvider::new_http(simulation_server.uri().parse().unwrap()), + raw_tx_forward: Some(RootProvider::new_http(forward_server.uri().parse().unwrap())), + }; + + let (audit_tx, _audit_rx) = mpsc::unbounded_channel(); + let (builder_tx, _builder_rx) = broadcast::channel(1); + let (backrun_tx, _backrun_rx) = broadcast::channel(1); + + let service = + IngressService::new(providers, MockQueue, audit_tx, builder_tx, backrun_tx, config); + + // Valid signed transaction bytes + let tx_bytes = Bytes::from_str("0x02f86c0d010183072335825208940000000000000000000000000000000000000000872386f26fc1000080c001a0cdb9e4f2f1ba53f9429077e7055e078cf599786e29059cd80c5e0e923bb2c114a01c90e29201e031baf1da66296c3a5c15c200bcb5e6c34da2f05f7d1778f8be07").unwrap(); + + let result = service.send_raw_transaction(tx_bytes).await; + assert!(result.is_ok()); + + // Wait for spawned forward task to complete + tokio::time::sleep(Duration::from_millis(100)).await; + + // wiremock automatically verifies expect(1) when forward_server is dropped + } + mock! { + pub IngressApi {} + + #[async_trait] + impl IngressApiServer for IngressApi { + async fn send_bundle(&self, bundle: Bundle) -> RpcResult; + async fn send_backrun_bundle(&self, bundle: Bundle) -> RpcResult; + async fn cancel_bundle(&self, request: CancelBundle) -> RpcResult<()>; + async fn send_raw_transaction(&self, tx: Bytes) -> RpcResult; + } + } + + #[test] + fn test_validate_backrun_bundle_rejects_invalid() { + // Too few transactions (need at least 2: target + backrun) + let result = validate_backrun_bundle_limits(1, 21000, 5, 5000000); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("at least 2 transactions")); + + // Exceeds max tx count + let result = validate_backrun_bundle_limits(6, 21000, 5, 5000000); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("exceeds max transaction count")); + + // Exceeds max gas limit + let result = validate_backrun_bundle_limits(2, 6000000, 5, 5000000); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("exceeds max gas limit")); + } +} diff --git a/crates/tips/ingress-rpc/src/validation.rs b/crates/tips/ingress-rpc/src/validation.rs new file mode 100644 index 0000000..b2c6d64 --- /dev/null +++ b/crates/tips/ingress-rpc/src/validation.rs @@ -0,0 +1,359 @@ +use std::{ + collections::HashSet, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use alloy_consensus::private::alloy_eips::{BlockId, BlockNumberOrTag}; +use alloy_primitives::{Address, B256, U256}; +use alloy_provider::{Provider, RootProvider}; +use async_trait::async_trait; +use base_reth_rpc_types::{EthApiError, SignError, extract_l1_info_from_tx}; +use jsonrpsee::core::RpcResult; +use op_alloy_network::Optimism; +use op_revm::l1block::L1BlockInfo; +use tips_core::Bundle; +use tokio::time::Instant; +use tracing::warn; + +use crate::metrics::record_histogram; + +const MAX_BUNDLE_GAS: u64 = 25_000_000; + +/// Account info for a given address +pub struct AccountInfo { + pub balance: U256, + pub nonce: u64, + pub code_hash: B256, +} + +/// Interface for fetching account info for a given address +#[async_trait] +pub trait AccountInfoLookup: Send + Sync { + async fn fetch_account_info(&self, address: Address) -> RpcResult; +} + +/// Implementation of the `AccountInfoLookup` trait for the `RootProvider` +#[async_trait] +impl AccountInfoLookup for RootProvider { + async fn fetch_account_info(&self, address: Address) -> RpcResult { + let start = Instant::now(); + let account = self + .get_account(address) + .await + .map_err(|_| EthApiError::Signing(SignError::NoAccount))?; + record_histogram(start.elapsed(), "eth_getAccount".to_string()); + + Ok(AccountInfo { + balance: account.balance, + nonce: account.nonce, + code_hash: account.code_hash, + }) + } +} + +/// Interface for fetching L1 block info for a given block number +#[async_trait] +pub trait L1BlockInfoLookup: Send + Sync { + async fn fetch_l1_block_info(&self) -> RpcResult; +} + +/// Implementation of the `L1BlockInfoLookup` trait for the `RootProvider` +#[async_trait] +impl L1BlockInfoLookup for RootProvider { + async fn fetch_l1_block_info(&self) -> RpcResult { + let start = Instant::now(); + let block = self + .get_block(BlockId::Number(BlockNumberOrTag::Latest)) + .full() + .await + .map_err(|e| { + warn!(message = "failed to fetch latest block", err = %e); + EthApiError::InternalEthError.into_rpc_err() + })? + .ok_or_else(|| { + warn!(message = "empty latest block returned"); + EthApiError::InternalEthError.into_rpc_err() + })?; + record_histogram(start.elapsed(), "eth_getBlockByNumber".to_string()); + + let txs = block.transactions.clone(); + let first_tx = txs.first_transaction().ok_or_else(|| { + warn!(message = "block contains no transactions"); + EthApiError::InternalEthError.into_rpc_err() + })?; + + Ok(extract_l1_info_from_tx(&first_tx.clone()).map_err(|e| { + warn!(message = "failed to extract l1_info from tx", err = %e); + EthApiError::InternalEthError.into_rpc_err() + })?) + } +} + +/// Helper function to validate propeties of a bundle. A bundle is valid if it satisfies the following criteria: +/// - The bundle's max_timestamp is not more than 1 hour in the future +/// - The bundle's gas limit is not greater than the maximum allowed gas limit +/// - The bundle can only contain 3 transactions at once +/// - Partial transaction dropping is not supported, `dropping_tx_hashes` must be empty +/// - revert protection is not supported, all transaction hashes must be in `reverting_tx_hashes` +pub fn validate_bundle(bundle: &Bundle, bundle_gas: u64, tx_hashes: Vec) -> RpcResult<()> { + // Don't allow bundles to be submitted over 1 hour into the future + // TODO: make the window configurable + let valid_timestamp_window = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + + Duration::from_secs(3600).as_secs(); + if let Some(max_timestamp) = bundle.max_timestamp + && max_timestamp > valid_timestamp_window + { + return Err(EthApiError::InvalidParams( + "Bundle cannot be more than 1 hour in the future".into(), + ) + .into_rpc_err()); + } + + // Check max gas limit for the entire bundle + if bundle_gas > MAX_BUNDLE_GAS { + return Err(EthApiError::InvalidParams("Bundle gas limit exceeds maximum allowed".into()) + .into_rpc_err()); + } + + // Can only provide 3 transactions at once + if bundle.txs.len() > 3 { + return Err(EthApiError::InvalidParams("Bundle can only contain 3 transactions".into()) + .into_rpc_err()); + } + + // Partial transaction dropping is not supported, `dropping_tx_hashes` must be empty + if !bundle.dropping_tx_hashes.is_empty() { + return Err(EthApiError::InvalidParams( + "Partial transaction dropping is not supported".into(), + ) + .into_rpc_err()); + } + + // revert protection: all transaction hashes must be in `reverting_tx_hashes` + let reverting_tx_hashes_set: HashSet<_> = bundle.reverting_tx_hashes.iter().collect(); + let tx_hashes_set: HashSet<_> = tx_hashes.iter().collect(); + if reverting_tx_hashes_set != tx_hashes_set { + return Err(EthApiError::InvalidParams( + "Revert protection is not supported. reverting_tx_hashes must include all hashes" + .into(), + ) + .into_rpc_err()); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::time::{SystemTime, UNIX_EPOCH}; + + use alloy_consensus::{SignableTransaction, TxEip1559, transaction::SignerRecoverable}; + use alloy_primitives::{Bytes, bytes}; + use alloy_signer_local::PrivateKeySigner; + use op_alloy_consensus::OpTxEnvelope; + use op_alloy_network::{TxSignerSync, eip2718::Encodable2718}; + + use super::*; + + #[tokio::test] + async fn test_err_bundle_max_timestamp_too_far_in_the_future() { + let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + let too_far_in_the_future = current_time + 3601; + let bundle = Bundle { + txs: vec![], + max_timestamp: Some(too_far_in_the_future), + ..Default::default() + }; + assert_eq!( + validate_bundle(&bundle, 0, vec![]), + Err(EthApiError::InvalidParams( + "Bundle cannot be more than 1 hour in the future".into() + ) + .into_rpc_err()) + ); + } + + #[tokio::test] + async fn test_err_bundle_max_gas_limit_too_high() { + let signer = PrivateKeySigner::random(); + let mut encoded_txs = vec![]; + let mut tx_hashes = vec![]; + + // Create transactions that collectively exceed MAX_BUNDLE_GAS (25M) + // Each transaction uses 4M gas, so 8 transactions = 32M gas > 25M limit + let gas = 4_000_000; + let mut total_gas = 0u64; + for _ in 0..8 { + let mut tx = TxEip1559 { + chain_id: 1, + nonce: 0, + gas_limit: gas, + max_fee_per_gas: 200000u128, + max_priority_fee_per_gas: 100000u128, + to: Address::random().into(), + value: U256::from(1000000u128), + access_list: Default::default(), + input: bytes!("").clone(), + }; + total_gas = total_gas.saturating_add(gas); + + let signature = signer.sign_transaction_sync(&mut tx).unwrap(); + let envelope = OpTxEnvelope::Eip1559(tx.into_signed(signature)); + let tx_hash = envelope.clone().try_into_recovered().unwrap().tx_hash(); + tx_hashes.push(tx_hash); + + // Encode the transaction + let mut encoded = vec![]; + envelope.encode_2718(&mut encoded); + encoded_txs.push(Bytes::from(encoded)); + } + + let bundle = Bundle { + txs: encoded_txs, + block_number: 0, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + ..Default::default() + }; + + // Test should fail due to exceeding gas limit + let result = validate_bundle(&bundle, total_gas, tx_hashes); + assert!(result.is_err()); + if let Err(e) = result { + let error_message = format!("{e:?}"); + assert!(error_message.contains("Bundle gas limit exceeds maximum allowed")); + } + } + + #[tokio::test] + async fn test_err_bundle_too_many_transactions() { + let signer = PrivateKeySigner::random(); + let mut encoded_txs = vec![]; + let mut tx_hashes = vec![]; + + let gas = 4_000_000; + let mut total_gas = 0u64; + for _ in 0..4 { + let mut tx = TxEip1559 { + chain_id: 1, + nonce: 0, + gas_limit: gas, + max_fee_per_gas: 200000u128, + max_priority_fee_per_gas: 100000u128, + to: Address::random().into(), + value: U256::from(1000000u128), + access_list: Default::default(), + input: bytes!("").clone(), + }; + total_gas = total_gas.saturating_add(gas); + + let signature = signer.sign_transaction_sync(&mut tx).unwrap(); + let envelope = OpTxEnvelope::Eip1559(tx.into_signed(signature)); + let tx_hash = envelope.clone().try_into_recovered().unwrap().tx_hash(); + tx_hashes.push(tx_hash); + + // Encode the transaction + let mut encoded = vec![]; + envelope.encode_2718(&mut encoded); + encoded_txs.push(Bytes::from(encoded)); + } + + let bundle = Bundle { + txs: encoded_txs, + block_number: 0, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + ..Default::default() + }; + + // Test should fail due to exceeding gas limit + let result = validate_bundle(&bundle, total_gas, tx_hashes); + assert!(result.is_err()); + if let Err(e) = result { + let error_message = format!("{e:?}"); + assert!(error_message.contains("Bundle can only contain 3 transactions")); + } + } + + #[tokio::test] + async fn test_err_bundle_partial_transaction_dropping_not_supported() { + let bundle = + Bundle { txs: vec![], dropping_tx_hashes: vec![B256::random()], ..Default::default() }; + assert_eq!( + validate_bundle(&bundle, 0, vec![]), + Err(EthApiError::InvalidParams("Partial transaction dropping is not supported".into()) + .into_rpc_err()) + ); + } + + #[tokio::test] + async fn test_err_bundle_not_all_tx_hashes_in_reverting_tx_hashes() { + let signer = PrivateKeySigner::random(); + let mut encoded_txs = vec![]; + let mut tx_hashes = vec![]; + + let gas = 4_000_000; + let mut total_gas = 0u64; + for _ in 0..4 { + let mut tx = TxEip1559 { + chain_id: 1, + nonce: 0, + gas_limit: gas, + max_fee_per_gas: 200000u128, + max_priority_fee_per_gas: 100000u128, + to: Address::random().into(), + value: U256::from(1000000u128), + access_list: Default::default(), + input: bytes!("").clone(), + }; + total_gas = total_gas.saturating_add(gas); + + let signature = signer.sign_transaction_sync(&mut tx).unwrap(); + let envelope = OpTxEnvelope::Eip1559(tx.into_signed(signature)); + let tx_hash = envelope.clone().try_into_recovered().unwrap().tx_hash(); + tx_hashes.push(tx_hash); + + // Encode the transaction + let mut encoded = vec![]; + envelope.encode_2718(&mut encoded); + encoded_txs.push(Bytes::from(encoded)); + } + + let bundle = Bundle { + txs: encoded_txs, + block_number: 0, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: tx_hashes[..2].to_vec(), + ..Default::default() + }; + + // Test should fail due to exceeding gas limit + let result = validate_bundle(&bundle, total_gas, tx_hashes); + assert!(result.is_err()); + if let Err(e) = result { + let error_message = format!("{e:?}"); + assert!(error_message.contains("Bundle can only contain 3 transactions")); + } + } + + #[tokio::test] + async fn test_decode_tx_rejects_empty_bytes() { + // Test that empty bytes fail to decode + use op_alloy_network::eip2718::Decodable2718; + let empty_bytes = Bytes::new(); + let result = OpTxEnvelope::decode_2718(&mut empty_bytes.as_ref()); + assert!(result.is_err(), "Empty bytes should fail decoding"); + } + + #[tokio::test] + async fn test_decode_tx_rejects_invalid_bytes() { + // Test that malformed bytes fail to decode + use op_alloy_network::eip2718::Decodable2718; + let invalid_bytes = Bytes::from(vec![0x01, 0x02, 0x03]); + let result = OpTxEnvelope::decode_2718(&mut invalid_bytes.as_ref()); + assert!(result.is_err(), "Invalid bytes should fail decoding"); + } +} diff --git a/crates/tips/system-tests/Cargo.toml b/crates/tips/system-tests/Cargo.toml new file mode 100644 index 0000000..4d8248f --- /dev/null +++ b/crates/tips/system-tests/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "tips-system-tests" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[lib] +path = "src/lib.rs" + +[dependencies] +hex = "0.4.3" +rand = "0.8" +dashmap = "6.0" +indicatif = "0.17" +rand_chacha = "0.3" +url = { workspace = true } +tips-core = { workspace = true } +bytes = { workspace = true } +async-trait = { workspace = true } +tips-audit-lib = { workspace = true } +alloy-network = { workspace = true } +op-alloy-network = { workspace = true } +alloy-signer-local = { workspace = true } +aws-credential-types = { workspace = true } +serde = { workspace = true, features = ["std", "derive"] } +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true, features = ["std"] } +anyhow = { workspace = true, features = ["std"] } +uuid = { workspace = true, features = ["v5", "serde"] } +serde_json = { workspace = true, features = ["std"] } +reqwest = { version = "0.12.12", features = ["json"] } +rdkafka = { workspace = true, features = ["tokio", "libz", "zstd", "ssl-vendored"] } +alloy-consensus = { workspace = true, features = ["std"] } +alloy-provider = { workspace = true, features = ["reqwest"] } +clap = { version = "4.5", features = ["std", "derive", "env"] } +op-alloy-consensus = { workspace = true, features = ["std", "k256", "serde"] } +alloy-primitives = { workspace = true, features = ["map-foldhash", "serde"] } +tracing-subscriber = { workspace = true, features = ["std", "fmt", "env-filter", "json"] } +aws-sdk-s3 = { workspace = true, features = ["rustls", "default-https-client", "rt-tokio"] } + +[dev-dependencies] +serial_test = "3" +tokio = { workspace = true, features = ["full", "test-util"] } diff --git a/crates/tips/system-tests/METRICS.md b/crates/tips/system-tests/METRICS.md new file mode 100644 index 0000000..49f9a0a --- /dev/null +++ b/crates/tips/system-tests/METRICS.md @@ -0,0 +1,133 @@ +# TIPS Load Testing + +Multi-wallet concurrent load testing tool for measuring TIPS performance. + +## Quick Start + +```bash +# 1. Build +cargo build --release --bin load-test + +# 2. Setup wallets +./target/release/load-test setup \ + --master-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \ + --output wallets.json + +# 3. Run load test +./target/release/load-test load --wallets wallets.json +``` + +--- + +## Configuration Options + +### Setup Command + +Create and fund test wallets from a master wallet. Test wallets are saved to allow test reproducibility and avoid the need to create new wallets for every test run. + +**Usage:** +```bash +./target/release/load-test setup --master-key --output [OPTIONS] +``` + +**Options:** + +| Flag | Description | Default | Example | +|------|-------------|---------|---------| +| `--master-key` | Private key of funded wallet (required) | - | `0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d` | +| `--output` | Save wallets to JSON file (required) | - | `wallets.json` | +| `--sequencer` | L2 sequencer RPC URL | `http://localhost:8547` | `http://localhost:8547` | +| `--num-wallets` | Number of wallets to create | `10` | `100` | +| `--fund-amount` | ETH to fund each wallet | `0.1` | `0.5` | + +**Environment Variables:** +- `MASTER_KEY` - Alternative to `--master-key` flag +- `SEQUENCER_URL` - Alternative to `--sequencer` flag + +### Load Command + +Run load test with funded wallets. Use the `--seed` flag to set the RNG seed for test reproducibility. + +**Usage:** +```bash +./target/release/load-test load --wallets [OPTIONS] +``` + +**Options:** + +| Flag | Description | Default | Example | +|------|-------------|---------|---------| +| `--wallets` | Path to wallets JSON file (required) | - | `wallets.json` | +| `--target` | TIPS ingress RPC URL | `http://localhost:8080` | `http://localhost:8080` | +| `--sequencer` | L2 sequencer RPC URL | `http://localhost:8547` | `http://localhost:8547` | +| `--rate` | Target transaction rate (tx/s) | `100` | `500` | +| `--duration` | Test duration in seconds | `60` | `100` | +| `--tx-timeout` | Timeout for tx inclusion (seconds) | `60` | `120` | +| `--seed` | Random seed for reproducibility | (none) | `42` | +| `--output` | Save metrics to JSON file | (none) | `metrics.json` | + +**Environment Variables:** +- `INGRESS_URL` - Alternative to `--target` flag +- `SEQUENCER_URL` - Alternative to `--sequencer` flag + +--- +--- + +## Metrics Explained + +### Output Example + +``` +Load Test Results +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +Configuration: + Target: http://localhost:8080 + Sequencer: http://localhost:8547 + Wallets: 100 + Target Rate: 100 tx/s + Duration: 60s + TX Timeout: 60s + +Throughput: + Sent: 100.0 tx/s (6000 total) + Included: 98.5 tx/s (5910 total) + Success Rate: 98.5% + +Transaction Results: + Included: 5910 (98.5%) + Reverted: 10 (0.2%) + Timed Out: 70 (1.2%) + Send Errors: 10 (0.1%) +``` + +### Metrics Definitions + +**Throughput:** +- `Sent Rate` - Transactions sent to TIPS per second +- `Included Rate` - Transactions included in blocks per second +- `Success Rate` - Percentage of sent transactions that were included + +**Transaction Results:** +- `Included` - Successfully included in a block with status == true +- `Reverted` - Included in a block but transaction reverted (status == false) +- `Timed Out` - Not included within timeout period +- `Send Errors` - Failed to send to TIPS RPC + +--- + +## Architecture + +``` +Sender Tasks (1 per wallet) Receipt Poller + │ │ + ▼ ▼ + Send to TIPS ──► Tracker ◄── Poll sequencer every 2s + (retry 3x) (pending) │ + │ │ ├─ status=true → included + │ │ ├─ status=false → reverted + │ │ └─ timeout → timed_out + ▼ ▼ + rate/N tx/s Calculate Results → Print Summary +``` + +--- diff --git a/crates/tips/system-tests/README.md b/crates/tips/system-tests/README.md new file mode 100644 index 0000000..d7f410a --- /dev/null +++ b/crates/tips/system-tests/README.md @@ -0,0 +1,30 @@ +# System Tests (Integration Suite) + +Integration coverage for TIPS ingress RPC. Tests talk to the services started by `just start-all`. + +## What we test +- `test_client_can_connect_to_tips` – RPC connectivity. +- `test_send_raw_transaction_accepted` – `eth_sendRawTransaction` lands on-chain with success receipt. +- `test_send_bundle_accepted` – single‑tx bundle via `eth_sendBackrunBundle` returns the correct bundle hash, audit event, and on-chain inclusion. +- `test_send_bundle_with_two_transactions` – multi-tx bundle (2 txs) flows through audit and lands on-chain. + +Each test confirms: +1. The response hash equals `keccak256` of the tx hashes. +2. The bundle audit event is emitted to Kafka. +3. All transactions are included on-chain with successful receipts. + +## How to run +```bash +# Start infrastructure (see ../../SETUP.md for full instructions) +# - just sync && just start-all +# - builder-playground + op-rbuilder are running + +# Run the tests +INTEGRATION_TESTS=1 cargo test --package tips-system-tests --test integration_tests +``` + +**Note:** Tests that share the funded wallet use `#[serial]` to avoid nonce conflicts. + +Defaults: +- Kafka configs: `docker/host-*.properties` (override with the standard `TIPS_INGRESS_KAFKA_*` env vars if needed). +- URLs: `http://localhost:8080` ingress, `http://localhost:8547` sequencer (override via `INGRESS_URL` / `SEQUENCER_URL`). diff --git a/crates/tips/system-tests/src/bin/load-test.rs b/crates/tips/system-tests/src/bin/load-test.rs new file mode 100644 index 0000000..50a36d2 --- /dev/null +++ b/crates/tips/system-tests/src/bin/load-test.rs @@ -0,0 +1,13 @@ +use anyhow::Result; +use clap::Parser; +use tips_system_tests::load_test::{config, load, setup}; + +#[tokio::main] +async fn main() -> Result<()> { + let cli = config::Cli::parse(); + + match cli.command { + config::Commands::Setup(args) => setup::run(args).await, + config::Commands::Load(args) => load::run(args).await, + } +} diff --git a/crates/tips/system-tests/src/client/mod.rs b/crates/tips/system-tests/src/client/mod.rs new file mode 100644 index 0000000..58dabd2 --- /dev/null +++ b/crates/tips/system-tests/src/client/mod.rs @@ -0,0 +1,3 @@ +pub mod tips_rpc; + +pub use tips_rpc::TipsRpcClient; diff --git a/crates/tips/system-tests/src/client/tips_rpc.rs b/crates/tips/system-tests/src/client/tips_rpc.rs new file mode 100644 index 0000000..b03c6e9 --- /dev/null +++ b/crates/tips/system-tests/src/client/tips_rpc.rs @@ -0,0 +1,47 @@ +use alloy_network::Network; +use alloy_primitives::{Bytes, TxHash}; +use alloy_provider::{Provider, RootProvider}; +use anyhow::Result; +use tips_core::{Bundle, BundleHash, CancelBundle}; + +/// Client for TIPS-specific RPC methods (eth_sendBundle, eth_cancelBundle) +/// +/// Wraps a RootProvider to add TIPS functionality while preserving access +/// to standard Ethereum JSON-RPC methods via provider(). +#[derive(Clone)] +pub struct TipsRpcClient { + provider: RootProvider, +} + +impl TipsRpcClient { + pub fn new(provider: RootProvider) -> Self { + Self { provider } + } + + pub async fn send_raw_transaction(&self, signed_tx: Bytes) -> Result { + let tx_hex = format!("0x{}", hex::encode(&signed_tx)); + self.provider + .raw_request("eth_sendRawTransaction".into(), [tx_hex]) + .await + .map_err(Into::into) + } + + pub async fn send_bundle(&self, bundle: Bundle) -> Result { + self.provider.raw_request("eth_sendBundle".into(), [bundle]).await.map_err(Into::into) + } + + pub async fn send_backrun_bundle(&self, bundle: Bundle) -> Result { + self.provider + .raw_request("eth_sendBackrunBundle".into(), [bundle]) + .await + .map_err(Into::into) + } + + pub async fn cancel_bundle(&self, request: CancelBundle) -> Result { + self.provider.raw_request("eth_cancelBundle".into(), [request]).await.map_err(Into::into) + } + + pub fn provider(&self) -> &RootProvider { + &self.provider + } +} diff --git a/crates/tips/system-tests/src/fixtures/mod.rs b/crates/tips/system-tests/src/fixtures/mod.rs new file mode 100644 index 0000000..3094ef9 --- /dev/null +++ b/crates/tips/system-tests/src/fixtures/mod.rs @@ -0,0 +1,3 @@ +pub mod transactions; + +pub use transactions::*; diff --git a/crates/tips/system-tests/src/fixtures/transactions.rs b/crates/tips/system-tests/src/fixtures/transactions.rs new file mode 100644 index 0000000..385d964 --- /dev/null +++ b/crates/tips/system-tests/src/fixtures/transactions.rs @@ -0,0 +1,72 @@ +use alloy_consensus::{SignableTransaction, TxEip1559}; +use alloy_primitives::{Address, Bytes, U256}; +use alloy_provider::{ProviderBuilder, RootProvider}; +use alloy_signer_local::PrivateKeySigner; +use anyhow::Result; +use op_alloy_network::{Optimism, TxSignerSync, eip2718::Encodable2718}; + +/// Create an Optimism RPC provider from a URL string +/// +/// This is a convenience function to avoid repeating the provider setup +/// pattern across tests and runner code. +pub fn create_optimism_provider(url: &str) -> Result> { + Ok(ProviderBuilder::new() + .disable_recommended_fillers() + .network::() + .connect_http(url.parse()?)) +} + +pub fn create_test_signer() -> PrivateKeySigner { + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + .parse() + .expect("Valid test private key") +} + +pub fn create_funded_signer() -> PrivateKeySigner { + // This is the same account used in justfile that has funds in builder-playground + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" + .parse() + .expect("Valid funded private key") +} + +pub fn create_signed_transaction( + signer: &PrivateKeySigner, + to: Address, + value: U256, + nonce: u64, + gas_limit: u64, + max_fee_per_gas: u128, +) -> Result { + let mut tx = TxEip1559 { + chain_id: 13, // Local builder-playground chain ID + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas: max_fee_per_gas / 10, // 10% of max fee as priority fee + to: to.into(), + value, + access_list: Default::default(), + input: Default::default(), + }; + + let signature = signer.sign_transaction_sync(&mut tx)?; + + let envelope = op_alloy_consensus::OpTxEnvelope::Eip1559(tx.into_signed(signature)); + + let mut buf = Vec::new(); + envelope.encode_2718(&mut buf); + + Ok(Bytes::from(buf)) +} + +/// Create a simple load test transaction with standard defaults: +/// - value: 1000 wei (small test amount) +/// - gas_limit: 21000 (standard transfer) +/// - max_fee_per_gas: 1 gwei +pub fn create_load_test_transaction( + signer: &PrivateKeySigner, + to: Address, + nonce: u64, +) -> Result { + create_signed_transaction(signer, to, U256::from(1000), nonce, 21000, 1_000_000_000) +} diff --git a/crates/tips/system-tests/src/lib.rs b/crates/tips/system-tests/src/lib.rs new file mode 100644 index 0000000..c05f0a3 --- /dev/null +++ b/crates/tips/system-tests/src/lib.rs @@ -0,0 +1,3 @@ +pub mod client; +pub mod fixtures; +pub mod load_test; diff --git a/crates/tips/system-tests/src/load_test/config.rs b/crates/tips/system-tests/src/load_test/config.rs new file mode 100644 index 0000000..2aa2de5 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/config.rs @@ -0,0 +1,77 @@ +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(name = "load-test")] +#[command(about = "Load testing tool for TIPS ingress service", long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Setup: Fund N wallets from a master wallet + Setup(SetupArgs), + /// Load: Run load test with funded wallets + Load(LoadArgs), +} + +#[derive(Parser)] +pub struct SetupArgs { + /// Master wallet private key (must have funds) + #[arg(long, env = "MASTER_KEY")] + pub master_key: String, + + /// Sequencer RPC URL + #[arg(long, env = "SEQUENCER_URL", default_value = "http://localhost:8547")] + pub sequencer: String, + + /// Number of wallets to create and fund + #[arg(long, default_value = "10")] + pub num_wallets: usize, + + /// Amount of ETH to fund each wallet + #[arg(long, default_value = "0.1")] + pub fund_amount: f64, + + /// Output file for wallet data (required) + #[arg(long)] + pub output: PathBuf, +} + +#[derive(Parser)] +pub struct LoadArgs { + /// TIPS ingress RPC URL + #[arg(long, env = "INGRESS_URL", default_value = "http://localhost:8080")] + pub target: String, + + /// Sequencer RPC URL (for nonce fetching and receipt polling) + #[arg(long, env = "SEQUENCER_URL", default_value = "http://localhost:8547")] + pub sequencer: String, + + /// Path to wallets JSON file (required) + #[arg(long)] + pub wallets: PathBuf, + + /// Target transaction rate (transactions per second) + #[arg(long, default_value = "100")] + pub rate: u64, + + /// Test duration in seconds + #[arg(long, default_value = "60")] + pub duration: u64, + + /// Timeout for transaction inclusion (seconds) + #[arg(long, default_value = "60")] + pub tx_timeout: u64, + + /// Random seed for reproducibility + #[arg(long)] + pub seed: Option, + + /// Output file for metrics (JSON) + #[arg(long)] + pub output: Option, +} diff --git a/crates/tips/system-tests/src/load_test/load.rs b/crates/tips/system-tests/src/load_test/load.rs new file mode 100644 index 0000000..3b0e097 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/load.rs @@ -0,0 +1,135 @@ +use std::{sync::Arc, time::Duration}; + +use anyhow::{Context, Result}; +use indicatif::{ProgressBar, ProgressStyle}; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; + +use super::{ + config::LoadArgs, + metrics::{TestConfig, calculate_results}, + output::{print_results, save_results}, + poller::ReceiptPoller, + sender::SenderTask, + tracker::TransactionTracker, + wallet::load_wallets, +}; +use crate::{client::TipsRpcClient, fixtures::create_optimism_provider}; + +pub async fn run(args: LoadArgs) -> Result<()> { + let wallets = load_wallets(&args.wallets).context("Failed to load wallets")?; + + if wallets.is_empty() { + anyhow::bail!("No wallets found in file. Run 'setup' command first."); + } + + let num_wallets = wallets.len(); + + let sequencer = create_optimism_provider(&args.sequencer)?; + + let tips_provider = create_optimism_provider(&args.target)?; + let tips_client = TipsRpcClient::new(tips_provider); + + let tracker = TransactionTracker::new(Duration::from_secs(args.duration)); + + let rate_per_wallet = args.rate as f64 / num_wallets as f64; + + let pb = ProgressBar::new(args.duration); + pb.set_style( + ProgressStyle::default_bar() + .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos}/{len}s | Sent: {msg}") + .unwrap() + .progress_chars("##-"), + ); + + let poller = ReceiptPoller::new( + sequencer.clone(), + Arc::clone(&tracker), + Duration::from_secs(args.tx_timeout), + ); + let poller_handle = tokio::spawn(async move { poller.run().await }); + + let mut sender_handles = Vec::new(); + + for (i, wallet) in wallets.into_iter().enumerate() { + let rng = match args.seed { + Some(seed) => ChaCha8Rng::seed_from_u64(seed + i as u64), + None => ChaCha8Rng::from_entropy(), + }; + + let sender = SenderTask::new( + wallet, + tips_client.clone(), + sequencer.clone(), + rate_per_wallet, + Duration::from_secs(args.duration), + Arc::clone(&tracker), + rng, + ); + + let handle = tokio::spawn(async move { sender.run().await }); + + sender_handles.push(handle); + } + + let pb_tracker = Arc::clone(&tracker); + let pb_handle = tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(1)); + loop { + interval.tick().await; + let elapsed = pb_tracker.elapsed().as_secs(); + let sent = pb_tracker.total_sent(); + pb.set_position(elapsed); + pb.set_message(format!("{sent}")); + + if pb_tracker.is_test_completed() { + break; + } + } + pb.finish_with_message("Complete"); + }); + + for handle in sender_handles { + handle.await??; + } + + tracker.mark_test_completed(); + + pb_handle.await?; + + let grace_period = Duration::from_secs(args.tx_timeout + 10); + match tokio::time::timeout(grace_period, poller_handle).await { + Ok(Ok(Ok(()))) => { + println!("✅ All transactions resolved"); + } + Ok(Ok(Err(e))) => { + println!("⚠️ Poller error: {e}"); + } + Ok(Err(e)) => { + println!("⚠️ Poller panicked: {e}"); + } + Err(_) => { + println!("⏱️ Grace period expired, some transactions may still be pending"); + } + } + + let config = TestConfig { + target: args.target.clone(), + sequencer: args.sequencer.clone(), + wallets: num_wallets, + target_rate: args.rate, + duration_secs: args.duration, + tx_timeout_secs: args.tx_timeout, + seed: args.seed, + }; + + let results = calculate_results(&tracker, config); + print_results(&results); + + // Save results if output file specified + if let Some(output_path) = args.output.as_ref() { + save_results(&results, output_path)?; + } + + Ok(()) +} diff --git a/crates/tips/system-tests/src/load_test/metrics.rs b/crates/tips/system-tests/src/load_test/metrics.rs new file mode 100644 index 0000000..a2797f4 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/metrics.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; + +use super::tracker::TransactionTracker; + +#[derive(Debug, Serialize, Deserialize)] +pub struct TestResults { + pub config: TestConfig, + pub results: ThroughputResults, + pub errors: ErrorResults, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TestConfig { + pub target: String, + pub sequencer: String, + pub wallets: usize, + pub target_rate: u64, + pub duration_secs: u64, + pub tx_timeout_secs: u64, + pub seed: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ThroughputResults { + pub sent_rate: f64, + pub included_rate: f64, + pub total_sent: u64, + pub total_included: u64, + pub total_reverted: u64, + pub total_pending: u64, + pub total_timed_out: u64, + pub success_rate: f64, + pub actual_duration_secs: f64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ErrorResults { + pub send_errors: u64, + pub reverted: u64, + pub timed_out: u64, +} + +pub fn calculate_results(tracker: &Arc, config: TestConfig) -> TestResults { + let actual_duration = tracker.elapsed(); + let total_sent = tracker.total_sent(); + let total_included = tracker.total_included(); + let total_reverted = tracker.total_reverted(); + let total_timed_out = tracker.total_timed_out(); + let send_errors = tracker.total_send_errors(); + + let sent_rate = total_sent as f64 / actual_duration.as_secs_f64(); + let included_rate = total_included as f64 / actual_duration.as_secs_f64(); + let success_rate = if total_sent > 0 { total_included as f64 / total_sent as f64 } else { 0.0 }; + + TestResults { + config, + results: ThroughputResults { + sent_rate, + included_rate, + total_sent, + total_included, + total_reverted, + total_pending: tracker.total_pending(), + total_timed_out, + success_rate, + actual_duration_secs: actual_duration.as_secs_f64(), + }, + errors: ErrorResults { send_errors, reverted: total_reverted, timed_out: total_timed_out }, + } +} diff --git a/crates/tips/system-tests/src/load_test/mod.rs b/crates/tips/system-tests/src/load_test/mod.rs new file mode 100644 index 0000000..896b415 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/mod.rs @@ -0,0 +1,9 @@ +pub mod config; +pub mod load; +pub mod metrics; +pub mod output; +pub mod poller; +pub mod sender; +pub mod setup; +pub mod tracker; +pub mod wallet; diff --git a/crates/tips/system-tests/src/load_test/output.rs b/crates/tips/system-tests/src/load_test/output.rs new file mode 100644 index 0000000..e3da139 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/output.rs @@ -0,0 +1,65 @@ +use std::{fs, path::Path}; + +use anyhow::{Context, Result}; + +use super::metrics::TestResults; + +pub fn print_results(results: &TestResults) { + println!("\n"); + println!("Load Test Results"); + println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + + println!("Configuration:"); + println!(" Target: {}", results.config.target); + println!(" Sequencer: {}", results.config.sequencer); + println!(" Wallets: {}", results.config.wallets); + println!(" Target Rate: {} tx/s", results.config.target_rate); + println!(" Duration: {}s", results.config.duration_secs); + println!(" TX Timeout: {}s", results.config.tx_timeout_secs); + if let Some(seed) = results.config.seed { + println!(" Seed: {seed}"); + } + + println!("\nThroughput:"); + println!( + " Sent: {:.1} tx/s ({} total)", + results.results.sent_rate, results.results.total_sent + ); + println!( + " Included: {:.1} tx/s ({} total)", + results.results.included_rate, results.results.total_included + ); + println!(" Success Rate: {:.1}%", results.results.success_rate * 100.0); + + println!("\nTransaction Results:"); + println!( + " Included: {} ({:.1}%)", + results.results.total_included, + (results.results.total_included as f64 / results.results.total_sent as f64) * 100.0 + ); + if results.results.total_reverted > 0 { + println!( + " Reverted: {} ({:.1}%)", + results.results.total_reverted, + (results.results.total_reverted as f64 / results.results.total_sent as f64) * 100.0 + ); + } + println!( + " Timed Out: {} ({:.1}%)", + results.results.total_timed_out, + (results.results.total_timed_out as f64 / results.results.total_sent as f64) * 100.0 + ); + println!(" Send Errors: {}", results.errors.send_errors); + if results.results.total_pending > 0 { + println!(" Still Pending: {}", results.results.total_pending); + } + + println!("\n"); +} + +pub fn save_results(results: &TestResults, path: &Path) -> Result<()> { + let json = serde_json::to_string_pretty(results).context("Failed to serialize results")?; + fs::write(path, json).context("Failed to write results file")?; + println!("💾 Metrics saved to: {}", path.display()); + Ok(()) +} diff --git a/crates/tips/system-tests/src/load_test/poller.rs b/crates/tips/system-tests/src/load_test/poller.rs new file mode 100644 index 0000000..662fa35 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/poller.rs @@ -0,0 +1,74 @@ +use std::{sync::Arc, time::Duration}; + +use alloy_network::ReceiptResponse; +use alloy_provider::{Provider, RootProvider}; +use anyhow::Result; +use op_alloy_network::Optimism; +use tracing::debug; + +use super::tracker::TransactionTracker; + +pub struct ReceiptPoller { + sequencer: RootProvider, + tracker: Arc, + timeout: Duration, +} + +impl ReceiptPoller { + pub fn new( + sequencer: RootProvider, + tracker: Arc, + timeout: Duration, + ) -> Self { + Self { sequencer, tracker, timeout } + } + + pub async fn run(self) -> Result<()> { + let mut interval = tokio::time::interval(Duration::from_secs(2)); // Block time + + loop { + interval.tick().await; + + let pending_txs = self.tracker.get_pending(); + + for (tx_hash, send_time) in pending_txs { + let elapsed = send_time.elapsed(); + + if elapsed > self.timeout { + self.tracker.record_timeout(tx_hash); + debug!("Transaction timed out: {:?}", tx_hash); + continue; + } + + match self.sequencer.get_transaction_receipt(tx_hash).await { + Ok(Some(receipt)) => { + // Verify transaction succeeded (status == true) and is in a block + if receipt.status() && receipt.block_number().is_some() { + self.tracker.record_included(tx_hash); + debug!("Transaction included and succeeded: {:?}", tx_hash); + } else if receipt.block_number().is_some() { + // Transaction was included but reverted + self.tracker.record_reverted(tx_hash); + debug!("Transaction included but reverted: {:?}", tx_hash); + } + // If no block_number yet, keep polling + } + Ok(None) => { + // Transaction not yet included, continue polling + } + Err(e) => { + debug!("Error fetching receipt for {:?}: {}", tx_hash, e); + // Don't mark as timeout, might be temporary RPC error + } + } + } + + // Exit when all transactions resolved and test completed + if self.tracker.all_resolved() && self.tracker.is_test_completed() { + break; + } + } + + Ok(()) + } +} diff --git a/crates/tips/system-tests/src/load_test/sender.rs b/crates/tips/system-tests/src/load_test/sender.rs new file mode 100644 index 0000000..46273b8 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/sender.rs @@ -0,0 +1,107 @@ +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use alloy_network::Network; +use alloy_primitives::{Address, Bytes, keccak256}; +use alloy_provider::{Provider, RootProvider}; +use anyhow::{Context, Result}; +use op_alloy_network::Optimism; +use rand::Rng; +use rand_chacha::ChaCha8Rng; +use tokio::time::sleep; + +use super::{tracker::TransactionTracker, wallet::Wallet}; +use crate::{client::TipsRpcClient, fixtures::create_load_test_transaction}; + +const MAX_RETRIES: u32 = 3; +const INITIAL_BACKOFF_MS: u64 = 100; + +pub struct SenderTask { + wallet: Wallet, + client: TipsRpcClient, + sequencer: RootProvider, + rate_per_wallet: f64, + duration: Duration, + tracker: Arc, + rng: ChaCha8Rng, +} + +impl SenderTask { + pub fn new( + wallet: Wallet, + client: TipsRpcClient, + sequencer: RootProvider, + rate_per_wallet: f64, + duration: Duration, + tracker: Arc, + rng: ChaCha8Rng, + ) -> Self { + Self { wallet, client, sequencer, rate_per_wallet, duration, tracker, rng } + } + + pub async fn run(mut self) -> Result<()> { + let mut nonce = self + .sequencer + .get_transaction_count(self.wallet.address) + .await + .context("Failed to get initial nonce")?; + + let interval_duration = Duration::from_secs_f64(1.0 / self.rate_per_wallet); + let mut ticker = tokio::time::interval(interval_duration); + ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + + let deadline = Instant::now() + self.duration; + + while Instant::now() < deadline { + ticker.tick().await; + + let recipient = self.random_address(); + let tx_bytes = self.create_transaction(recipient, nonce)?; + let tx_hash = keccak256(&tx_bytes); + + // Retry loop with exponential backoff + let mut retries = 0; + let mut backoff_ms = INITIAL_BACKOFF_MS; + + loop { + let send_time = Instant::now(); + + match self.client.send_raw_transaction(tx_bytes.clone()).await { + Ok(_) => { + self.tracker.record_sent(tx_hash, send_time); + nonce += 1; + break; + } + Err(e) => { + retries += 1; + if retries > MAX_RETRIES { + println!( + "Error sending raw transaction after {MAX_RETRIES} retries: {e}" + ); + self.tracker.record_send_error(); + nonce += 1; // Move on to next nonce after max retries + break; + } + // Exponential backoff before retry + sleep(Duration::from_millis(backoff_ms)).await; + backoff_ms *= 2; // Double backoff each retry + } + } + } + } + + Ok(()) + } + + fn create_transaction(&self, to: Address, nonce: u64) -> Result { + create_load_test_transaction(&self.wallet.signer, to, nonce) + } + + fn random_address(&mut self) -> Address { + let mut bytes = [0u8; 20]; + self.rng.fill(&mut bytes); + Address::from(bytes) + } +} diff --git a/crates/tips/system-tests/src/load_test/setup.rs b/crates/tips/system-tests/src/load_test/setup.rs new file mode 100644 index 0000000..6559726 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/setup.rs @@ -0,0 +1,92 @@ +use alloy_consensus::{SignableTransaction, TxEip1559}; +use alloy_primitives::U256; +use alloy_provider::Provider; +use anyhow::{Context, Result}; +use indicatif::{ProgressBar, ProgressStyle}; +use op_alloy_network::{TxSignerSync, eip2718::Encodable2718}; + +use super::{ + config::SetupArgs, + wallet::{Wallet, generate_wallets, save_wallets}, +}; +use crate::fixtures::create_optimism_provider; + +const CHAIN_ID: u64 = 13; // builder-playground local chain ID + +pub async fn run(args: SetupArgs) -> Result<()> { + let master_wallet = Wallet::from_private_key(&args.master_key) + .context("Failed to parse master wallet private key")?; + + let provider = create_optimism_provider(&args.sequencer)?; + + let master_balance = provider + .get_balance(master_wallet.address) + .await + .context("Failed to get master wallet balance")?; + + let required_balance = + U256::from((args.fund_amount * 1e18) as u64) * U256::from(args.num_wallets); + + if master_balance < required_balance { + anyhow::bail!( + "Insufficient master wallet balance. Need {} ETH, have {} ETH", + required_balance.to::() as f64 / 1e18, + master_balance.to::() as f64 / 1e18 + ); + } + + let wallets = generate_wallets(args.num_wallets, None); + + let pb = ProgressBar::new(args.num_wallets as u64); + pb.set_style( + ProgressStyle::default_bar() + .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos}/{len} {msg}") + .unwrap() + .progress_chars("##-"), + ); + + let mut nonce = provider + .get_transaction_count(master_wallet.address) + .await + .context("Failed to get master wallet nonce")?; + + let fund_amount_wei = U256::from((args.fund_amount * 1e18) as u64); + + // Send all funding transactions + let mut pending_txs = Vec::new(); + for (i, wallet) in wallets.iter().enumerate() { + let mut tx = TxEip1559 { + chain_id: CHAIN_ID, + nonce, + gas_limit: 21000, + max_fee_per_gas: 1_000_000_000, // 1 gwei + max_priority_fee_per_gas: 100_000_000, // 0.1 gwei + to: wallet.address.into(), + value: fund_amount_wei, + access_list: Default::default(), + input: Default::default(), + }; + + let signature = master_wallet.signer.sign_transaction_sync(&mut tx)?; + let envelope = op_alloy_consensus::OpTxEnvelope::Eip1559(tx.into_signed(signature)); + + let mut buf = Vec::new(); + envelope.encode_2718(&mut buf); + let pending = provider + .send_raw_transaction(buf.as_ref()) + .await + .with_context(|| format!("Failed to send funding tx for wallet {i}"))?; + + pending_txs.push(pending); + nonce += 1; + pb.set_message(format!("Sent funding tx {}", i + 1)); + pb.inc(1); + } + + pb.finish_with_message("All funding transactions sent!"); + + // Save wallets to file + save_wallets(&wallets, args.fund_amount, &args.output)?; + + Ok(()) +} diff --git a/crates/tips/system-tests/src/load_test/tracker.rs b/crates/tips/system-tests/src/load_test/tracker.rs new file mode 100644 index 0000000..4326c20 --- /dev/null +++ b/crates/tips/system-tests/src/load_test/tracker.rs @@ -0,0 +1,117 @@ +use std::{ + sync::{ + Arc, + atomic::{AtomicBool, AtomicU64, Ordering}, + }, + time::{Duration, Instant}, +}; + +use alloy_primitives::B256; +use dashmap::DashMap; + +pub struct TransactionTracker { + // Pending transactions (tx_hash -> send_time) + pending: DashMap, + + // Included transactions (succeeded) + included: DashMap, + + // Reverted transactions (included but status == false) + reverted: DashMap, + + // Timed out transactions + timed_out: DashMap, + + // Send errors (not transaction-specific) + send_errors: AtomicU64, + + // Test metadata + test_start: Instant, + test_completed: AtomicBool, +} + +impl TransactionTracker { + pub fn new(_test_duration: Duration) -> Arc { + Arc::new(Self { + pending: DashMap::new(), + included: DashMap::new(), + reverted: DashMap::new(), + timed_out: DashMap::new(), + send_errors: AtomicU64::new(0), + test_start: Instant::now(), + test_completed: AtomicBool::new(false), + }) + } + + pub fn record_sent(&self, tx_hash: B256, send_time: Instant) { + self.pending.insert(tx_hash, send_time); + } + + pub fn record_send_error(&self) { + self.send_errors.fetch_add(1, Ordering::Relaxed); + } + + /// Record a transaction that was included and succeeded (status == true) + pub fn record_included(&self, tx_hash: B256) { + self.pending.remove(&tx_hash); + self.included.insert(tx_hash, ()); + } + + /// Record a transaction that was included but reverted (status == false) + pub fn record_reverted(&self, tx_hash: B256) { + self.pending.remove(&tx_hash); + self.reverted.insert(tx_hash, ()); + } + + pub fn record_timeout(&self, tx_hash: B256) { + if self.pending.remove(&tx_hash).is_some() { + self.timed_out.insert(tx_hash, ()); + } + } + + pub fn get_pending(&self) -> Vec<(B256, Instant)> { + self.pending.iter().map(|entry| (*entry.key(), *entry.value())).collect() + } + + pub fn mark_test_completed(&self) { + self.test_completed.store(true, Ordering::Relaxed); + } + + pub fn is_test_completed(&self) -> bool { + self.test_completed.load(Ordering::Relaxed) + } + + pub fn all_resolved(&self) -> bool { + self.pending.is_empty() + } + + pub fn elapsed(&self) -> Duration { + self.test_start.elapsed() + } + + // Metrics getters + pub fn total_sent(&self) -> u64 { + (self.pending.len() + self.included.len() + self.reverted.len() + self.timed_out.len()) + as u64 + } + + pub fn total_included(&self) -> u64 { + self.included.len() as u64 + } + + pub fn total_reverted(&self) -> u64 { + self.reverted.len() as u64 + } + + pub fn total_pending(&self) -> u64 { + self.pending.len() as u64 + } + + pub fn total_timed_out(&self) -> u64 { + self.timed_out.len() as u64 + } + + pub fn total_send_errors(&self) -> u64 { + self.send_errors.load(Ordering::Relaxed) + } +} diff --git a/crates/tips/system-tests/src/load_test/wallet.rs b/crates/tips/system-tests/src/load_test/wallet.rs new file mode 100644 index 0000000..2a8070d --- /dev/null +++ b/crates/tips/system-tests/src/load_test/wallet.rs @@ -0,0 +1,79 @@ +use std::{fs, path::Path}; + +use alloy_primitives::Address; +use alloy_signer_local::PrivateKeySigner; +use anyhow::{Context, Result}; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WalletData { + pub address: String, + pub private_key: String, + pub initial_balance: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WalletsFile { + pub wallets: Vec, +} + +pub struct Wallet { + pub signer: PrivateKeySigner, + pub address: Address, +} + +impl Wallet { + pub fn from_private_key(private_key: &str) -> Result { + let signer: PrivateKeySigner = + private_key.parse().context("Failed to parse private key")?; + let address = signer.address(); + Ok(Self { signer, address }) + } + + pub fn new_random(rng: &mut ChaCha8Rng) -> Self { + let signer = PrivateKeySigner::random_with(rng); + let address = signer.address(); + Self { signer, address } + } +} + +pub fn generate_wallets(num_wallets: usize, seed: Option) -> Vec { + let mut rng = match seed { + Some(s) => ChaCha8Rng::seed_from_u64(s), + None => ChaCha8Rng::from_entropy(), + }; + + (0..num_wallets).map(|_| Wallet::new_random(&mut rng)).collect() +} + +pub fn save_wallets(wallets: &[Wallet], fund_amount: f64, path: &Path) -> Result<()> { + let wallet_data: Vec = wallets + .iter() + .map(|w| WalletData { + address: format!("{:?}", w.address), + private_key: format!("0x{}", hex::encode(w.signer.to_bytes())), + initial_balance: fund_amount.to_string(), + }) + .collect(); + + let wallets_file = WalletsFile { wallets: wallet_data }; + + let json = + serde_json::to_string_pretty(&wallets_file).context("Failed to serialize wallets")?; + fs::write(path, json).context("Failed to write wallets file")?; + + Ok(()) +} + +pub fn load_wallets(path: &Path) -> Result> { + let json = fs::read_to_string(path).context("Failed to read wallets file")?; + let wallets_file: WalletsFile = + serde_json::from_str(&json).context("Failed to parse wallets file")?; + + let wallets: Result> = + wallets_file.wallets.iter().map(|wd| Wallet::from_private_key(&wd.private_key)).collect(); + + wallets +} diff --git a/crates/tips/system-tests/tests/common/kafka.rs b/crates/tips/system-tests/tests/common/kafka.rs new file mode 100644 index 0000000..f5d2df7 --- /dev/null +++ b/crates/tips/system-tests/tests/common/kafka.rs @@ -0,0 +1,114 @@ +use std::{path::Path, time::Duration}; + +use alloy_primitives::B256; +use anyhow::{Context, Result}; +use rdkafka::{ + Message, + config::ClientConfig, + consumer::{Consumer, StreamConsumer}, + message::BorrowedMessage, +}; +use tips_audit_lib::BundleEvent; +use tips_core::{BundleExtensions, kafka::load_kafka_config_from_file}; +use tokio::time::{Instant, timeout}; +use uuid::Uuid; + +const DEFAULT_AUDIT_TOPIC: &str = "tips-audit"; +const DEFAULT_AUDIT_PROPERTIES: &str = "../../docker/host-ingress-audit-kafka-properties"; +const KAFKA_WAIT_TIMEOUT: Duration = Duration::from_secs(60); + +fn resolve_properties_path(env_key: &str, default_path: &str) -> Result { + match std::env::var(env_key) { + Ok(value) => Ok(value), + Err(_) => { + if Path::new(default_path).exists() { + Ok(default_path.to_string()) + } else { + anyhow::bail!( + "Environment variable {env_key} must be set (default path '{default_path}' not found). \ + Run `just sync` or export {env_key} before running tests." + ); + } + } + } +} + +fn build_kafka_consumer(properties_env: &str, default_path: &str) -> Result { + let props_file = resolve_properties_path(properties_env, default_path)?; + + let mut client_config = ClientConfig::from_iter(load_kafka_config_from_file(&props_file)?); + + client_config + .set("group.id", format!("tips-system-tests-{}", Uuid::new_v4())) + .set("enable.auto.commit", "false") + .set("auto.offset.reset", "earliest"); + + client_config.create().context("Failed to create Kafka consumer") +} + +async fn wait_for_kafka_message( + properties_env: &str, + default_properties: &str, + topic_env: &str, + default_topic: &str, + timeout_duration: Duration, + mut matcher: impl FnMut(BorrowedMessage<'_>) -> Option, +) -> Result { + let consumer = build_kafka_consumer(properties_env, default_properties)?; + let topic = std::env::var(topic_env).unwrap_or_else(|_| default_topic.to_string()); + consumer.subscribe(&[&topic])?; + + let deadline = Instant::now() + timeout_duration; + + loop { + let now = Instant::now(); + if now >= deadline { + anyhow::bail!( + "Timed out waiting for Kafka message on topic {topic} after {:?}", + timeout_duration + ); + } + + let remaining = deadline - now; + match timeout(remaining, consumer.recv()).await { + Ok(Ok(message)) => { + if let Some(value) = matcher(message) { + return Ok(value); + } + } + Ok(Err(err)) => { + return Err(err.into()); + } + Err(_) => { + // Timeout for this iteration, continue looping + } + } + } +} + +pub async fn wait_for_audit_event_by_hash( + expected_bundle_hash: &B256, + mut matcher: impl FnMut(&BundleEvent) -> bool, +) -> Result { + let expected_hash = *expected_bundle_hash; + wait_for_kafka_message( + "TIPS_INGRESS_KAFKA_AUDIT_PROPERTIES_FILE", + DEFAULT_AUDIT_PROPERTIES, + "TIPS_INGRESS_KAFKA_AUDIT_TOPIC", + DEFAULT_AUDIT_TOPIC, + KAFKA_WAIT_TIMEOUT, + |message| { + let payload = message.payload()?; + let event: BundleEvent = serde_json::from_slice(payload).ok()?; + // Match by bundle hash from the Received event + if let BundleEvent::Received { bundle, .. } = &event + && bundle.bundle_hash() == expected_hash + && matcher(&event) + { + return Some(event); + } + None + }, + ) + .await +} diff --git a/crates/tips/system-tests/tests/common/mod.rs b/crates/tips/system-tests/tests/common/mod.rs new file mode 100644 index 0000000..b17877c --- /dev/null +++ b/crates/tips/system-tests/tests/common/mod.rs @@ -0,0 +1 @@ +pub mod kafka; diff --git a/crates/tips/system-tests/tests/integration_tests.rs b/crates/tips/system-tests/tests/integration_tests.rs new file mode 100644 index 0000000..857b76d --- /dev/null +++ b/crates/tips/system-tests/tests/integration_tests.rs @@ -0,0 +1,328 @@ +#[path = "common/mod.rs"] +mod common; + +use alloy_network::ReceiptResponse; +use alloy_primitives::{Address, TxHash, U256, keccak256}; +use alloy_provider::{Provider, RootProvider}; +use anyhow::{Context, Result, bail}; +use common::kafka::wait_for_audit_event_by_hash; +use op_alloy_network::Optimism; +use serial_test::serial; +use tips_audit_lib::BundleEvent; +use tips_core::BundleExtensions; +use tips_system_tests::{ + client::TipsRpcClient, + fixtures::{create_funded_signer, create_optimism_provider, create_signed_transaction}, +}; +use tokio::time::{Duration, Instant, sleep}; + +/// Get the URL for integration tests against the TIPS ingress service +fn get_integration_test_url() -> String { + std::env::var("INGRESS_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()) +} + +/// Get the URL for the sequencer (for fetching nonces) +fn get_sequencer_url() -> String { + std::env::var("SEQUENCER_URL").unwrap_or_else(|_| "http://localhost:8547".to_string()) +} + +async fn wait_for_transaction_seen( + provider: &RootProvider, + tx_hash: TxHash, + timeout_secs: u64, +) -> Result<()> { + let deadline = Instant::now() + Duration::from_secs(timeout_secs); + loop { + if Instant::now() >= deadline { + bail!("Timed out waiting for transaction {} to appear on the sequencer", tx_hash); + } + + if provider.get_transaction_by_hash(tx_hash).await?.is_some() { + return Ok(()); + } + + sleep(Duration::from_millis(500)).await; + } +} + +#[tokio::test] +async fn test_client_can_connect_to_tips() -> Result<()> { + if std::env::var("INTEGRATION_TESTS").is_err() { + eprintln!( + "Skipping integration tests (set INTEGRATION_TESTS=1 and ensure TIPS infrastructure is running)" + ); + return Ok(()); + } + + let url = get_integration_test_url(); + let provider = create_optimism_provider(&url)?; + let _client = TipsRpcClient::new(provider); + Ok(()) +} + +#[tokio::test] +#[serial] +async fn test_send_raw_transaction_accepted() -> Result<()> { + if std::env::var("INTEGRATION_TESTS").is_err() { + eprintln!( + "Skipping integration tests (set INTEGRATION_TESTS=1 and ensure TIPS infrastructure is running)" + ); + return Ok(()); + } + + let url = get_integration_test_url(); + let provider = create_optimism_provider(&url)?; + let client = TipsRpcClient::new(provider); + let signer = create_funded_signer(); + + let sequencer_url = get_sequencer_url(); + let sequencer_provider = create_optimism_provider(&sequencer_url)?; + let nonce = sequencer_provider.get_transaction_count(signer.address()).await?; + + let to = Address::from([0x11; 20]); + let value = U256::from(1000); + let gas_limit = 21000; + let gas_price = 1_000_000_000; + + let signed_tx = create_signed_transaction(&signer, to, value, nonce, gas_limit, gas_price)?; + + // Send transaction to TIPS + let tx_hash = client + .send_raw_transaction(signed_tx) + .await + .context("Failed to send transaction to TIPS")?; + + // Verify TIPS accepted the transaction and returned a hash + assert!(!tx_hash.is_zero(), "Transaction hash should not be zero"); + + // Verify transaction lands on-chain + wait_for_transaction_seen(&sequencer_provider, tx_hash, 30) + .await + .context("Transaction never appeared on sequencer")?; + + // Verify transaction receipt shows success + let receipt = sequencer_provider + .get_transaction_receipt(tx_hash) + .await + .context("Failed to fetch transaction receipt")? + .expect("Transaction receipt should exist after being seen on sequencer"); + assert!(receipt.status(), "Transaction should have succeeded"); + + Ok(()) +} + +#[tokio::test] +#[serial] +async fn test_send_bundle_accepted() -> Result<()> { + if std::env::var("INTEGRATION_TESTS").is_err() { + eprintln!( + "Skipping integration tests (set INTEGRATION_TESTS=1 and ensure TIPS infrastructure is running)" + ); + return Ok(()); + } + + use tips_core::Bundle; + + let url = get_integration_test_url(); + let provider = create_optimism_provider(&url)?; + let client = TipsRpcClient::new(provider); + let signer = create_funded_signer(); + + let sequencer_url = get_sequencer_url(); + let sequencer_provider = create_optimism_provider(&sequencer_url)?; + let nonce = sequencer_provider.get_transaction_count(signer.address()).await?; + + let to = Address::from([0x11; 20]); + let value = U256::from(1000); + let gas_limit = 21000; + let gas_price = 1_000_000_000; + + let signed_tx = create_signed_transaction(&signer, to, value, nonce, gas_limit, gas_price)?; + let tx_hash = keccak256(&signed_tx); + + // First send the transaction to mempool + let _mempool_tx_hash = client + .send_raw_transaction(signed_tx.clone()) + .await + .context("Failed to send transaction to mempool")?; + + let bundle = Bundle { + txs: vec![signed_tx], + block_number: 0, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![tx_hash], + replacement_uuid: None, + dropping_tx_hashes: vec![], + flashblock_number_min: None, + flashblock_number_max: None, + }; + + // Send backrun bundle to TIPS + let bundle_hash = client + .send_backrun_bundle(bundle) + .await + .context("Failed to send backrun bundle to TIPS")?; + + // Verify TIPS accepted the bundle and returned a hash + assert!(!bundle_hash.bundle_hash.is_zero(), "Bundle hash should not be zero"); + + // Verify bundle hash is calculated correctly: keccak256(concat(tx_hashes)) + let mut concatenated = Vec::new(); + concatenated.extend_from_slice(tx_hash.as_slice()); + let expected_bundle_hash = keccak256(&concatenated); + assert_eq!( + bundle_hash.bundle_hash, expected_bundle_hash, + "Bundle hash should match keccak256(tx_hash)" + ); + + // Verify audit channel emitted a Received event for this bundle + let audit_event = wait_for_audit_event_by_hash(&bundle_hash.bundle_hash, |event| { + matches!(event, BundleEvent::Received { .. }) + }) + .await + .context("Failed to read audit event from Kafka")?; + match audit_event { + BundleEvent::Received { bundle, .. } => { + assert_eq!( + bundle.bundle_hash(), + bundle_hash.bundle_hash, + "Audit event bundle hash should match response" + ); + } + other => panic!("Expected Received audit event, got {:?}", other), + } + + // Wait for transaction to appear on sequencer + wait_for_transaction_seen(&sequencer_provider, tx_hash, 60) + .await + .context("Bundle transaction never appeared on sequencer")?; + + // Verify transaction receipt shows success + let receipt = sequencer_provider + .get_transaction_receipt(tx_hash) + .await + .context("Failed to fetch transaction receipt")? + .expect("Transaction receipt should exist after being seen on sequencer"); + assert!(receipt.status(), "Transaction should have succeeded"); + assert!(receipt.block_number().is_some(), "Transaction should be included in a block"); + + Ok(()) +} + +#[tokio::test] +#[serial] +async fn test_send_bundle_with_two_transactions() -> Result<()> { + if std::env::var("INTEGRATION_TESTS").is_err() { + eprintln!( + "Skipping integration tests (set INTEGRATION_TESTS=1 and ensure TIPS infrastructure is running)" + ); + return Ok(()); + } + + use tips_core::Bundle; + + let url = get_integration_test_url(); + let provider = create_optimism_provider(&url)?; + let client = TipsRpcClient::new(provider); + let signer = create_funded_signer(); + + let sequencer_url = get_sequencer_url(); + let sequencer_provider = create_optimism_provider(&sequencer_url)?; + let nonce = sequencer_provider.get_transaction_count(signer.address()).await?; + + // Create two transactions + let tx1 = create_signed_transaction( + &signer, + Address::from([0x33; 20]), + U256::from(1000), + nonce, + 21000, + 1_000_000_000, + )?; + + let tx2 = create_signed_transaction( + &signer, + Address::from([0x44; 20]), + U256::from(2000), + nonce + 1, + 21000, + 1_000_000_000, + )?; + + let tx1_hash = keccak256(&tx1); + let tx2_hash = keccak256(&tx2); + + // First send both transactions to mempool + client.send_raw_transaction(tx1.clone()).await.context("Failed to send tx1 to mempool")?; + client.send_raw_transaction(tx2.clone()).await.context("Failed to send tx2 to mempool")?; + + let bundle = Bundle { + txs: vec![tx1, tx2], + block_number: 0, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![tx1_hash, tx2_hash], + replacement_uuid: None, + dropping_tx_hashes: vec![], + flashblock_number_min: None, + flashblock_number_max: None, + }; + + // Send backrun bundle with 2 transactions to TIPS + let bundle_hash = client + .send_backrun_bundle(bundle) + .await + .context("Failed to send multi-transaction backrun bundle to TIPS")?; + + // Verify TIPS accepted the bundle and returned a hash + assert!(!bundle_hash.bundle_hash.is_zero(), "Bundle hash should not be zero"); + + // Verify bundle hash is calculated correctly: keccak256(concat(all tx_hashes)) + let mut concatenated = Vec::new(); + concatenated.extend_from_slice(tx1_hash.as_slice()); + concatenated.extend_from_slice(tx2_hash.as_slice()); + let expected_bundle_hash = keccak256(&concatenated); + assert_eq!( + bundle_hash.bundle_hash, expected_bundle_hash, + "Bundle hash should match keccak256(concat(tx1_hash, tx2_hash))" + ); + + // Verify audit channel emitted a Received event + let audit_event = wait_for_audit_event_by_hash(&bundle_hash.bundle_hash, |event| { + matches!(event, BundleEvent::Received { .. }) + }) + .await + .context("Failed to read audit event for 2-tx bundle")?; + match audit_event { + BundleEvent::Received { bundle, .. } => { + assert_eq!( + bundle.bundle_hash(), + bundle_hash.bundle_hash, + "Audit event bundle hash should match response" + ); + } + other => panic!("Expected Received audit event, got {:?}", other), + } + + // Wait for both transactions to appear on sequencer + wait_for_transaction_seen(&sequencer_provider, tx1_hash, 60) + .await + .context("Bundle tx1 never appeared on sequencer")?; + wait_for_transaction_seen(&sequencer_provider, tx2_hash, 60) + .await + .context("Bundle tx2 never appeared on sequencer")?; + + // Verify both transaction receipts show success + for (tx_hash, name) in [(tx1_hash, "tx1"), (tx2_hash, "tx2")] { + let receipt = sequencer_provider + .get_transaction_receipt(tx_hash) + .await + .context(format!("Failed to fetch {name} receipt"))? + .unwrap_or_else(|| panic!("{name} receipt should exist")); + assert!(receipt.status(), "{name} should have succeeded"); + assert!(receipt.block_number().is_some(), "{name} should be included in a block"); + } + + Ok(()) +} diff --git a/deny.toml b/deny.toml index 62b04cd..97fe494 100644 --- a/deny.toml +++ b/deny.toml @@ -44,9 +44,57 @@ skip = [ # Common ecosystem duplicates "getrandom", "hashbrown", + "itertools", + "foldhash", + "lru", + + # AWS SDK transitive dependencies + "aws-smithy-http", + + # HTTP ecosystem (hyper 0.14 vs 1.x transition) + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + + # TLS/crypto ecosystem + "rustls", + "rustls-webpki", + "tokio-rustls", + + # macOS security framework + "core-foundation", + "security-framework", + "openssl-probe", + + # Random number generation + "rand", + "rand_chacha", + "rand_core", + + # Error handling + "thiserror", + "thiserror-impl", + + # Networking + "socket2", + + # OP Stack crates (reth uses older versions) + "op-alloy-consensus", + "op-alloy-rpc-types", # Windows platform crates "windows-sys", + "windows-targets", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [sources] diff --git a/docker/Dockerfile.tips b/docker/Dockerfile.tips new file mode 100644 index 0000000..9442acb --- /dev/null +++ b/docker/Dockerfile.tips @@ -0,0 +1,35 @@ +FROM rust:1-bookworm AS base + +RUN apt-get update && apt-get -y upgrade && apt-get install -y libclang-dev pkg-config libsasl2-dev libssl-dev + +RUN cargo install cargo-chef --locked +WORKDIR /app + +FROM base AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM base AS builder +COPY --from=planner /app/recipe.json recipe.json + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=/app/target \ + cargo chef cook --recipe-path recipe.json + +COPY . . +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=/app/target \ + cargo build -p tips-ingress-rpc -p tips-audit && \ + cp target/debug/tips-ingress-rpc /tmp/tips-ingress-rpc && \ + cp target/debug/tips-audit /tmp/tips-audit + +FROM debian:bookworm + +RUN apt-get update && apt-get install -y libssl3 ca-certificates && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY --from=builder /tmp/tips-audit /app/tips-audit +COPY --from=builder /tmp/tips-ingress-rpc /app/tips-ingress-rpc \ No newline at end of file diff --git a/docker/docker-compose.tips-kafka.yml b/docker/docker-compose.tips-kafka.yml new file mode 100644 index 0000000..2cadfbf --- /dev/null +++ b/docker/docker-compose.tips-kafka.yml @@ -0,0 +1,75 @@ +services: + kafka: + image: confluentinc/cp-kafka:7.5.0 + container_name: tips-kafka + ports: + - "9092:9092" + - "9094:9094" + environment: + KAFKA_BROKER_ID: 1 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,PLAINTEXT_DOCKER:PLAINTEXT,CONTROLLER:PLAINTEXT + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092,PLAINTEXT_DOCKER://host.docker.internal:9094 + KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093,PLAINTEXT_HOST://0.0.0.0:9092,PLAINTEXT_DOCKER://0.0.0.0:9094 + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER + KAFKA_ZOOKEEPER_CONNECT: ' ' + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + KAFKA_PROCESS_ROLES: broker,controller + KAFKA_NODE_ID: 1 + KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 + KAFKA_LOG_DIRS: /var/lib/kafka/data + CLUSTER_ID: 4L6g3nShT-eMCtK--X86sw + volumes: + - ./data/kafka:/var/lib/kafka/data + healthcheck: + test: ["CMD", "kafka-broker-api-versions", "--bootstrap-server", "localhost:9092"] + interval: 10s + timeout: 10s + retries: 5 + kafka-setup: + image: confluentinc/cp-kafka:7.5.0 + container_name: tips-kafka-setup + depends_on: + kafka: + condition: service_healthy + command: | + sh -c " + kafka-topics --create --if-not-exists --topic tips-audit --bootstrap-server kafka:29092 --partitions 3 --replication-factor 1 + kafka-topics --create --if-not-exists --topic tips-ingress --bootstrap-server kafka:29092 --partitions 3 --replication-factor 1 + kafka-topics --create --if-not-exists --topic tips-user-operation --bootstrap-server kafka:29092 --partitions 3 --replication-factor 1 + kafka-topics --list --bootstrap-server kafka:29092 + " + + minio: + image: minio/minio:latest + container_name: tips-minio + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + ports: + - "7000:9000" + - "7001:9001" + command: server /data --console-address ":9001" + volumes: + - ./data/minio:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + minio-setup: + image: minio/mc + container_name: tips-minio-setup + depends_on: + minio: + condition: service_healthy + entrypoint: > + /bin/sh -c " + /usr/bin/mc alias set minio http://minio:9000 minioadmin minioadmin; + /usr/bin/mc mb minio/tips; + /usr/bin/mc anonymous set public minio/tips; + exit 0; + " \ No newline at end of file diff --git a/docker/docker-compose.tips.yml b/docker/docker-compose.tips.yml new file mode 100644 index 0000000..4054ccf --- /dev/null +++ b/docker/docker-compose.tips.yml @@ -0,0 +1,43 @@ +services: + ingress-rpc: + build: + context: . + dockerfile: Dockerfile + command: + - "/app/tips-ingress-rpc" + container_name: tips-ingress-rpc + ports: + - "8080:8080" + - "8081:8081" + - "9002:9002" + env_file: + - .env.docker + volumes: + - ./docker/ingress-bundles-kafka-properties:/app/docker/ingress-bundles-kafka-properties:ro + - ./docker/ingress-audit-kafka-properties:/app/docker/ingress-audit-kafka-properties:ro + - ./docker/ingress-user-operation-consumer-kafka-properties:/app/docker/ingress-user-operation-consumer-kafka-properties:ro + restart: unless-stopped + + audit: + build: + context: . + dockerfile: Dockerfile + command: + - "/app/tips-audit" + container_name: tips-audit + env_file: + - .env.docker + volumes: + - ./docker/audit-kafka-properties:/app/docker/audit-kafka-properties:ro + restart: unless-stopped + + ui: + build: + context: . + dockerfile: ui/Dockerfile + ports: + - "3000:3000" + container_name: tips-ui + env_file: + - .env.docker + restart: unless-stopped \ No newline at end of file diff --git a/docker/kafka-properties/audit-kafka-properties b/docker/kafka-properties/audit-kafka-properties new file mode 100644 index 0000000..d0ede9b --- /dev/null +++ b/docker/kafka-properties/audit-kafka-properties @@ -0,0 +1,10 @@ +# Kafka configuration properties for audit service +bootstrap.servers=host.docker.internal:9094 +message.timeout.ms=5000 +group.id=local-audit +enable.partition.eof=false +session.timeout.ms=6000 +enable.auto.commit=false +auto.offset.reset=earliest +fetch.wait.max.ms=100 +fetch.min.bytes=1 \ No newline at end of file diff --git a/docker/kafka-properties/host-ingress-audit-kafka-properties b/docker/kafka-properties/host-ingress-audit-kafka-properties new file mode 100644 index 0000000..7929f9b --- /dev/null +++ b/docker/kafka-properties/host-ingress-audit-kafka-properties @@ -0,0 +1,4 @@ +# Kafka audit configuration for host-based integration tests +bootstrap.servers=localhost:9092 +message.timeout.ms=5000 + diff --git a/docker/kafka-properties/host-ingress-bundles-kafka-properties b/docker/kafka-properties/host-ingress-bundles-kafka-properties new file mode 100644 index 0000000..3462d1f --- /dev/null +++ b/docker/kafka-properties/host-ingress-bundles-kafka-properties @@ -0,0 +1,4 @@ +# Kafka configuration properties for host-based integration tests +bootstrap.servers=localhost:9092 +message.timeout.ms=5000 + diff --git a/docker/kafka-properties/ingress-audit-kafka-properties b/docker/kafka-properties/ingress-audit-kafka-properties new file mode 100644 index 0000000..2f58fc9 --- /dev/null +++ b/docker/kafka-properties/ingress-audit-kafka-properties @@ -0,0 +1,4 @@ +# Kafka configuration properties for ingress audit events +bootstrap.servers=host.docker.internal:9094 +message.timeout.ms=5000 +compression.type=zstd \ No newline at end of file diff --git a/docker/kafka-properties/ingress-bundles-kafka-properties b/docker/kafka-properties/ingress-bundles-kafka-properties new file mode 100644 index 0000000..6b7899a --- /dev/null +++ b/docker/kafka-properties/ingress-bundles-kafka-properties @@ -0,0 +1,3 @@ +# Kafka configuration properties for ingress service +bootstrap.servers=host.docker.internal:9094 +message.timeout.ms=5000 \ No newline at end of file diff --git a/docker/kafka-properties/ingress-user-operation-consumer-kafka-properties b/docker/kafka-properties/ingress-user-operation-consumer-kafka-properties new file mode 100644 index 0000000..3bb02bf --- /dev/null +++ b/docker/kafka-properties/ingress-user-operation-consumer-kafka-properties @@ -0,0 +1,9 @@ +# Kafka configuration properties for ingress user operation consumer +bootstrap.servers=host.docker.internal:9094 +message.timeout.ms=5000 +enable.partition.eof=false +session.timeout.ms=6000 +fetch.wait.max.ms=100 +fetch.min.bytes=1 +# Note: group.id and enable.auto.commit are set programmatically + diff --git a/justfile b/justfile index 5d528eb..2041465 100644 --- a/justfile +++ b/justfile @@ -101,3 +101,11 @@ docker-image: # Run mempool rebroadcaster service run-mempool-rebroadcaster: cargo run -p mempool-rebroadcaster -- --geth-mempool-endpoint {{env_var("GETH_MEMPOOL_ENDPOINT")}} --reth-mempool-endpoint {{env_var("RETH_MEMPOOL_ENDPOINT")}} + +# Run tips audit service +run-tips-audit: + cargo run -p tips-audit + +# Run tips ingress RPC service +run-tips-ingress-rpc: + cargo run -p tips-ingress-rpc diff --git a/ui/tips/.gitignore b/ui/tips/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/ui/tips/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/ui/tips/Dockerfile b/ui/tips/Dockerfile new file mode 100644 index 0000000..8615be8 --- /dev/null +++ b/ui/tips/Dockerfile @@ -0,0 +1,40 @@ +FROM node:20-alpine AS deps +WORKDIR /app +COPY ui/package.json ui/yarn.lock ./ +RUN --mount=type=cache,target=/root/.yarn \ + yarn install --frozen-lockfile + +FROM node:20-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY ./ui . + +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN --mount=type=cache,target=/app/.next/cache \ + yarn build + +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +RUN mkdir .next +RUN chown nextjs:nodejs .next + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/ui/tips/biome.json b/ui/tips/biome.json new file mode 100644 index 0000000..31bf9c5 --- /dev/null +++ b/ui/tips/biome.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": true, + "includes": ["**", "!node_modules", "!.next", "!dist", "!build"] + }, + "css": { + "parser": { + "cssModules": true, + "tailwindDirectives": true + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noUnknownAtRules": "off" + } + }, + "domains": { + "next": "recommended", + "react": "recommended" + } + }, + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/ui/tips/next.config.ts b/ui/tips/next.config.ts new file mode 100644 index 0000000..68a6c64 --- /dev/null +++ b/ui/tips/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + output: "standalone", +}; + +export default nextConfig; diff --git a/ui/tips/package.json b/ui/tips/package.json new file mode 100644 index 0000000..54d105c --- /dev/null +++ b/ui/tips/package.json @@ -0,0 +1,28 @@ +{ + "name": "ui", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build --turbopack", + "start": "next start", + "lint": "biome check", + "format": "biome format --write" + }, + "dependencies": { + "@aws-sdk/client-s3": "3.940.0", + "next": "16.0.7", + "react": "19.2.1", + "react-dom": "19.2.1", + "viem": "2.40.3" + }, + "devDependencies": { + "@biomejs/biome": "2.3.8", + "@tailwindcss/postcss": "4.1.17", + "@types/node": "20.19.25", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", + "tailwindcss": "4.1.17", + "typescript": "5.9.3" + } +} diff --git a/ui/tips/postcss.config.mjs b/ui/tips/postcss.config.mjs new file mode 100644 index 0000000..c7bcb4b --- /dev/null +++ b/ui/tips/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/ui/tips/public/logo.svg b/ui/tips/public/logo.svg new file mode 100644 index 0000000..05f4921 --- /dev/null +++ b/ui/tips/public/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/ui/tips/src/app/api/block/[hash]/route.ts b/ui/tips/src/app/api/block/[hash]/route.ts new file mode 100644 index 0000000..aafe3c5 --- /dev/null +++ b/ui/tips/src/app/api/block/[hash]/route.ts @@ -0,0 +1,191 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { type Block, createPublicClient, type Hash, http } from "viem"; +import { mainnet } from "viem/chains"; +import { + type BlockData, + type BlockTransaction, + cacheBlockData, + getBlockFromCache, + getBundleHistory, + getTransactionMetadataByHash, + type MeterBundleResult, +} from "@/lib/s3"; + +function serializeBlockData(block: BlockData) { + return { + ...block, + number: block.number.toString(), + timestamp: block.timestamp.toString(), + gasUsed: block.gasUsed.toString(), + gasLimit: block.gasLimit.toString(), + transactions: block.transactions.map((tx) => ({ + ...tx, + gasUsed: tx.gasUsed.toString(), + })), + }; +} + +const RPC_URL = process.env.TIPS_UI_RPC_URL || "http://localhost:8545"; + +const client = createPublicClient({ + chain: mainnet, + transport: http(RPC_URL), +}); + +async function fetchBlockFromRpc( + blockHash: string, +): Promise | null> { + try { + const block = await client.getBlock({ + blockHash: blockHash as Hash, + includeTransactions: true, + }); + return block; + } catch (error) { + console.error("Failed to fetch block from RPC:", error); + return null; + } +} + +// On OP Stack, the first transaction (index 0) is the L1 attributes deposit transaction. +// This is not a perfect check (ideally we'd check tx.type === 'deposit' or type 0x7e), +// but sufficient for filtering out system transactions that don't need simulation data. +function isSystemTransaction(tx: BlockTransaction): boolean { + return tx.index === 0; +} + +async function enrichTransactionWithBundleData( + txHash: string, +): Promise<{ bundleId: string | null; executionTimeUs: number | null }> { + const metadata = await getTransactionMetadataByHash(txHash); + if (!metadata || metadata.bundle_ids.length === 0) { + return { bundleId: null, executionTimeUs: null }; + } + + const bundleId = metadata.bundle_ids[0]; + const bundleHistory = await getBundleHistory(bundleId); + if (!bundleHistory) { + return { bundleId, executionTimeUs: null }; + } + + const receivedEvent = bundleHistory.history.find( + (e) => e.event === "Received", + ); + if (!receivedEvent?.data?.bundle?.meter_bundle_response?.results) { + return { bundleId, executionTimeUs: null }; + } + + const txResult = receivedEvent.data.bundle.meter_bundle_response.results.find( + (r: MeterBundleResult) => r.txHash.toLowerCase() === txHash.toLowerCase(), + ); + + return { + bundleId, + executionTimeUs: txResult?.executionTimeUs ?? null, + }; +} + +async function refetchMissingTransactionSimulations( + block: BlockData, +): Promise<{ updatedBlock: BlockData; hasUpdates: boolean }> { + const transactionsToRefetch = block.transactions.filter( + (tx) => tx.bundleId === null && !isSystemTransaction(tx), + ); + + if (transactionsToRefetch.length === 0) { + return { updatedBlock: block, hasUpdates: false }; + } + + const refetchResults = await Promise.all( + transactionsToRefetch.map(async (tx) => { + const { bundleId, executionTimeUs } = + await enrichTransactionWithBundleData(tx.hash); + return { hash: tx.hash, bundleId, executionTimeUs }; + }), + ); + + let hasUpdates = false; + const updatedTransactions = block.transactions.map((tx) => { + const refetchResult = refetchResults.find((r) => r.hash === tx.hash); + if (refetchResult && refetchResult.bundleId !== null) { + hasUpdates = true; + return { + ...tx, + bundleId: refetchResult.bundleId, + executionTimeUs: refetchResult.executionTimeUs, + }; + } + return tx; + }); + + return { + updatedBlock: { + ...block, + transactions: updatedTransactions, + cachedAt: hasUpdates ? Date.now() : block.cachedAt, + }, + hasUpdates, + }; +} + +export async function GET( + _request: NextRequest, + { params }: { params: Promise<{ hash: string }> }, +) { + try { + const { hash } = await params; + + const cachedBlock = await getBlockFromCache(hash); + if (cachedBlock) { + const { updatedBlock, hasUpdates } = + await refetchMissingTransactionSimulations(cachedBlock); + + if (hasUpdates) { + await cacheBlockData(updatedBlock); + } + + return NextResponse.json(serializeBlockData(updatedBlock)); + } + + const rpcBlock = await fetchBlockFromRpc(hash); + if (!rpcBlock || !rpcBlock.hash || !rpcBlock.number) { + return NextResponse.json({ error: "Block not found" }, { status: 404 }); + } + + const transactions: BlockTransaction[] = await Promise.all( + rpcBlock.transactions.map(async (tx, index) => { + const { bundleId, executionTimeUs } = + await enrichTransactionWithBundleData(tx.hash); + return { + hash: tx.hash, + from: tx.from, + to: tx.to, + gasUsed: tx.gas, + executionTimeUs, + bundleId, + index, + }; + }), + ); + + const blockData: BlockData = { + hash: rpcBlock.hash, + number: rpcBlock.number, + timestamp: rpcBlock.timestamp, + transactions, + gasUsed: rpcBlock.gasUsed, + gasLimit: rpcBlock.gasLimit, + cachedAt: Date.now(), + }; + + await cacheBlockData(blockData); + + return NextResponse.json(serializeBlockData(blockData)); + } catch (error) { + console.error("Error fetching block data:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/ui/tips/src/app/api/blocks/route.ts b/ui/tips/src/app/api/blocks/route.ts new file mode 100644 index 0000000..919256a --- /dev/null +++ b/ui/tips/src/app/api/blocks/route.ts @@ -0,0 +1,105 @@ +import { NextResponse } from "next/server"; + +const RPC_URL = process.env.TIPS_UI_RPC_URL || "http://localhost:8545"; + +export interface BlockSummary { + hash: string; + number: number; + timestamp: number; + transactionCount: number; +} + +export interface BlocksResponse { + blocks: BlockSummary[]; +} + +async function fetchLatestBlockNumber(): Promise { + try { + const response = await fetch(RPC_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "eth_blockNumber", + params: [], + id: 1, + }), + }); + + const data = await response.json(); + if (data.error || !data.result) { + return null; + } + + return parseInt(data.result, 16); + } catch (error) { + console.error("Failed to fetch latest block number:", error); + return null; + } +} + +async function fetchBlockByNumber( + blockNumber: number, +): Promise { + try { + const response = await fetch(RPC_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + method: "eth_getBlockByNumber", + params: [`0x${blockNumber.toString(16)}`, false], + id: 1, + }), + }); + + const data = await response.json(); + if (data.error || !data.result) { + return null; + } + + const block = data.result; + return { + hash: block.hash, + number: parseInt(block.number, 16), + timestamp: parseInt(block.timestamp, 16), + transactionCount: block.transactions?.length ?? 0, + }; + } catch (error) { + console.error(`Failed to fetch block ${blockNumber}:`, error); + return null; + } +} + +export async function GET() { + try { + const latestBlockNumber = await fetchLatestBlockNumber(); + if (latestBlockNumber === null) { + return NextResponse.json( + { error: "Failed to fetch latest block" }, + { status: 500 }, + ); + } + + const blockNumbers = Array.from( + { length: 10 }, + (_, i) => latestBlockNumber - i, + ).filter((n) => n >= 0); + + const blocks = await Promise.all(blockNumbers.map(fetchBlockByNumber)); + + const validBlocks = blocks.filter( + (block): block is BlockSummary => block !== null, + ); + + const response: BlocksResponse = { blocks: validBlocks }; + + return NextResponse.json(response); + } catch (error) { + console.error("Error fetching blocks:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/ui/tips/src/app/api/bundle/[uuid]/route.ts b/ui/tips/src/app/api/bundle/[uuid]/route.ts new file mode 100644 index 0000000..6be37d6 --- /dev/null +++ b/ui/tips/src/app/api/bundle/[uuid]/route.ts @@ -0,0 +1,39 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { type BundleEvent, getBundleHistory } from "@/lib/s3"; + +export interface BundleHistoryResponse { + uuid: string; + history: BundleEvent[]; +} + +export async function GET( + _request: NextRequest, + { params }: { params: Promise<{ uuid: string }> }, +) { + try { + const { uuid } = await params; + + const bundle = await getBundleHistory(uuid); + if (!bundle) { + return NextResponse.json({ error: "Bundle not found" }, { status: 404 }); + } + + const history = bundle.history; + history.sort((lhs, rhs) => + lhs.data.timestamp < rhs.data.timestamp ? -1 : 1, + ); + + const response: BundleHistoryResponse = { + uuid, + history: bundle.history, + }; + + return NextResponse.json(response); + } catch (error) { + console.error("Error fetching bundle data:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/ui/tips/src/app/api/health/route.ts b/ui/tips/src/app/api/health/route.ts new file mode 100644 index 0000000..35adb41 --- /dev/null +++ b/ui/tips/src/app/api/health/route.ts @@ -0,0 +1,5 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + return NextResponse.json({ status: "ok" }, { status: 200 }); +} diff --git a/ui/tips/src/app/api/txn/[hash]/route.ts b/ui/tips/src/app/api/txn/[hash]/route.ts new file mode 100644 index 0000000..44f6824 --- /dev/null +++ b/ui/tips/src/app/api/txn/[hash]/route.ts @@ -0,0 +1,73 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { + type BundleEvent, + getBundleHistory, + getTransactionMetadataByHash, +} from "@/lib/s3"; + +export interface TransactionEvent { + type: string; + data: { + bundle_id?: string; + transactions?: Array<{ + id: { + sender: string; + nonce: string; + hash: string; + }; + data: string; + }>; + transaction_ids?: Array<{ + sender: string; + nonce: string; + hash: string; + }>; + block_number?: number; + flashblock_index?: number; + block_hash?: string; + }; +} + +export interface TransactionHistoryResponse { + hash: string; + bundle_ids: string[]; + history: BundleEvent[]; +} + +export async function GET( + _request: NextRequest, + { params }: { params: Promise<{ hash: string }> }, +) { + try { + const { hash } = await params; + + const metadata = await getTransactionMetadataByHash(hash); + + if (!metadata) { + return NextResponse.json( + { error: "Transaction not found" }, + { status: 404 }, + ); + } + + // TODO: Can be in multiple bundles + const bundle = await getBundleHistory(metadata.bundle_ids[0]); + if (!bundle) { + return NextResponse.json({ error: "Bundle not found" }, { status: 404 }); + } + + const response: TransactionHistoryResponse = { + hash, + bundle_ids: metadata.bundle_ids, + history: bundle.history, + }; + + return NextResponse.json(response); + } catch (error) { + console.error("Error fetching transaction data:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 }, + ); + } +} diff --git a/ui/tips/src/app/block/[hash]/page.tsx b/ui/tips/src/app/block/[hash]/page.tsx new file mode 100644 index 0000000..71133e0 --- /dev/null +++ b/ui/tips/src/app/block/[hash]/page.tsx @@ -0,0 +1,426 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; +import type { BlockData, BlockTransaction } from "@/lib/s3"; + +const BLOCK_EXPLORER_URL = process.env.NEXT_PUBLIC_BLOCK_EXPLORER_URL; + +interface PageProps { + params: Promise<{ hash: string }>; +} + +function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + ); +} + +function Card({ + children, + className = "", +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +

+ {children} +
+ ); +} + +function getHeatmapStyle( + executionTimeUs: number, + maxTime: number, +): { bg: string; text: string } { + if (maxTime === 0) return { bg: "bg-amber-50", text: "text-amber-700" }; + const ratio = Math.min(executionTimeUs / maxTime, 1); + if (ratio < 0.2) return { bg: "bg-amber-100", text: "text-amber-800" }; + if (ratio < 0.4) return { bg: "bg-amber-200", text: "text-amber-900" }; + if (ratio < 0.6) return { bg: "bg-orange-200", text: "text-orange-900" }; + if (ratio < 0.8) return { bg: "bg-orange-300", text: "text-orange-950" }; + return { bg: "bg-red-300", text: "text-red-950" }; +} + +function TransactionRow({ + tx, + maxExecutionTime, +}: { + tx: BlockTransaction; + maxExecutionTime: number; +}) { + const hasBundle = tx.bundleId !== null; + const hasExecutionTime = tx.executionTimeUs !== null; + const executionTime = tx.executionTimeUs ?? 0; + const heatmapStyle = hasExecutionTime + ? getHeatmapStyle(executionTime, maxExecutionTime) + : null; + + const content = ( +
+
+ {tx.index} +
+
+
+ {BLOCK_EXPLORER_URL ? ( + e.stopPropagation()} + > + {tx.hash} + + ) : ( + + {tx.hash} + + )} +
+
+ {tx.from.slice(0, 6)}...{tx.from.slice(-4)} + {tx.to && ( + <> + {" → "} + {tx.to.slice(0, 6)}...{tx.to.slice(-4)} + + )} +
+
+
+ {hasExecutionTime && heatmapStyle ? ( + + {executionTime.toLocaleString()}μs + + ) : ( +
+ )} +
+ {tx.gasUsed.toLocaleString()} gas +
+
+
+ ); + + if (hasBundle) { + return {content}; + } + + return content; +} + +function BlockStats({ block }: { block: BlockData }) { + const txsWithTime = block.transactions.filter( + (tx) => tx.executionTimeUs !== null, + ); + const totalExecutionTime = txsWithTime.reduce( + (sum, tx) => sum + (tx.executionTimeUs ?? 0), + 0, + ); + const bundleCount = block.transactions.filter( + (tx) => tx.bundleId !== null, + ).length; + + return ( + +
+
+
+
Block Number
+
+ #{block.number.toLocaleString()} +
+
+
+
Transactions
+
+ {block.transactions.length} +
+
+
+
Bundles
+
+ {bundleCount} +
+
+
+
Total Exec Time
+
+ {totalExecutionTime > 0 + ? `${totalExecutionTime.toLocaleString()}μs` + : "—"} +
+
+
+
+
+
+ Gas Used + + {block.gasUsed.toLocaleString()} + +
+
+ Gas Limit + + {block.gasLimit.toLocaleString()} + +
+
+ Timestamp + + {new Date(Number(block.timestamp) * 1000).toLocaleString()} + +
+
+
+ ); +} + +export default function BlockPage({ params }: PageProps) { + const [hash, setHash] = useState(""); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + params.then((p) => setHash(p.hash)); + }, [params]); + + useEffect(() => { + if (!hash) return; + + const fetchData = async () => { + try { + const response = await fetch(`/api/block/${hash}`); + if (!response.ok) { + setError( + response.status === 404 + ? "Block not found" + : "Failed to fetch block data", + ); + setData(null); + return; + } + setData(await response.json()); + setError(null); + } catch { + setError("Failed to fetch block data"); + setData(null); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [hash]); + + if (!hash || loading) { + return ( +
+
+
+ Loading block... +
+
+ ); + } + + const maxExecutionTime = data + ? Math.max( + ...data.transactions + .filter((tx) => tx.executionTimeUs !== null) + .map((tx) => tx.executionTimeUs ?? 0), + 0, + ) + : 0; + + return ( +
+
+
+
+ + + Back + + + +
+ + TIPS + +
+
+ + {hash.slice(0, 10)}...{hash.slice(-8)} + + + {BLOCK_EXPLORER_URL && ( + + + External link + + + + )} +
+
+
+ +
+ {error && ( + +
+
+ + Error + + +
+
+

Error

+

{error}

+
+
+
+ )} + + {data && ( +
+
+

+ Block Overview +

+ +
+ +
+

+ Transactions +

+ +
+
#
+
Transaction
+
Execution
+
+
+ {data.transactions.map((tx) => ( + + ))} +
+ {data.transactions.length === 0 && ( +
+ No transactions in this block +
+ )} +
+
+
+ )} +
+
+ ); +} diff --git a/ui/tips/src/app/bundles/[uuid]/page.tsx b/ui/tips/src/app/bundles/[uuid]/page.tsx new file mode 100644 index 0000000..0212106 --- /dev/null +++ b/ui/tips/src/app/bundles/[uuid]/page.tsx @@ -0,0 +1,592 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; +import type { BundleHistoryResponse } from "@/app/api/bundle/[uuid]/route"; +import type { BundleTransaction, MeterBundleResponse } from "@/lib/s3"; + +const WEI_PER_GWEI = 10n ** 9n; +const WEI_PER_ETH = 10n ** 18n; +const BLOCK_EXPLORER_URL = process.env.NEXT_PUBLIC_BLOCK_EXPLORER_URL; + +interface PageProps { + params: Promise<{ uuid: string }>; +} + +function formatBigInt(value: bigint, decimals: number, scale: bigint): string { + const whole = value / scale; + const frac = ((value % scale) * 10n ** BigInt(decimals)) / scale; + return `${whole}.${frac.toString().padStart(decimals, "0")}`; +} + +function formatHexValue(hex: string | undefined): string { + if (!hex) return "—"; + const value = BigInt(hex); + if (value >= WEI_PER_ETH / 10000n) { + return `${formatBigInt(value, 6, WEI_PER_ETH)} ETH`; + } + if (value >= WEI_PER_GWEI / 100n) { + return `${formatBigInt(value, 4, WEI_PER_GWEI)} Gwei`; + } + return `${value.toString()} Wei`; +} + +function formatGasPrice(hex: string | undefined): string { + if (!hex) return "—"; + const value = BigInt(hex); + return `${formatBigInt(value, 2, WEI_PER_GWEI)} Gwei`; +} + +function ExplorerLink({ + type, + value, + children, + className = "", +}: { + type: "tx" | "address"; + value: string; + children: React.ReactNode; + className?: string; +}) { + if (!BLOCK_EXPLORER_URL) { + return {children}; + } + + const path = type === "tx" ? `/tx/${value}` : `/address/${value}`; + return ( + + {children} + + ); +} + +function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + ); +} + +function Badge({ + children, + variant = "default", +}: { + children: React.ReactNode; + variant?: "default" | "success" | "warning" | "error"; +}) { + const variants = { + default: "bg-blue-50 text-blue-700 ring-blue-600/20", + success: "bg-emerald-50 text-emerald-700 ring-emerald-600/20", + warning: "bg-amber-50 text-amber-700 ring-amber-600/20", + error: "bg-red-50 text-red-700 ring-red-600/20", + }; + + return ( + + {children} + + ); +} + +function Card({ + children, + className = "", +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} + +function TransactionDetails({ + tx, + index, +}: { + tx: BundleTransaction; + index: number; +}) { + const [expanded, setExpanded] = useState(index === 0); + + return ( +
+ + + {expanded && ( + <> +
+ + + + + + + + + + + + + + + +
Hash + + + {tx.hash} + + + +
From + + + {tx.signer} + + + +
To + + + {tx.to} + + + +
+
+
+
+ Nonce + + {parseInt(tx.nonce, 16)} + +
+
+ Max Fee + + {formatGasPrice(tx.maxFeePerGas)} + +
+
+ Priority Fee + + {formatGasPrice(tx.maxPriorityFeePerGas)} + +
+
+ Type + + {tx.type === "0x2" ? "EIP-1559" : tx.type} + +
+
+ + )} +
+ ); +} + +function SimulationCard({ meter }: { meter: MeterBundleResponse }) { + return ( + +
+
+
+
Total Gas
+
+ {meter.totalGasUsed.toLocaleString()} +
+
+
+
Execution Time
+
+ {meter.results.reduce((sum, r) => sum + r.executionTimeUs, 0)}μs +
+
+
+
Gas Price
+
+ {formatGasPrice(meter.bundleGasPrice)} +
+
+
+
Coinbase Diff
+
+ {formatHexValue(meter.coinbaseDiff)} +
+
+
+
+
+
+ State Block + + #{meter.stateBlockNumber} + +
+
+ Gas Fees + + {formatHexValue(meter.gasFees)} + +
+
+ ETH to Coinbase + + {formatHexValue(meter.ethSentToCoinbase)} + +
+
+
+ ); +} + +function TimelineEventDetails({ + event, +}: { + event: BundleHistoryResponse["history"][0]; +}) { + if (event.event === "BlockIncluded" && event.data?.block_hash) { + return ( +
+ {event.event} + + Block #{event.data.block_number} + +
+ ); + } + + if (event.event === "BuilderIncluded" && event.data?.builder) { + return ( +
+ {event.event} + + {event.data.builder} (flashblock #{event.data.flashblock_index}) + +
+ ); + } + + if (event.event === "Dropped" && event.data?.reason) { + return ( +
+ {event.event} + {event.data.reason} +
+ ); + } + + return {event.event}; +} + +function Timeline({ events }: { events: BundleHistoryResponse["history"] }) { + if (events.length === 0) return null; + + return ( +
+ {events.map((event, index) => ( +
+
+
+
+
+ + +
+
+ ))} +
+ ); +} + +function SectionTitle({ children }: { children: React.ReactNode }) { + return ( +

{children}

+ ); +} + +export default function BundlePage({ params }: PageProps) { + const [uuid, setUuid] = useState(""); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + params.then((p) => setUuid(p.uuid)); + }, [params]); + + useEffect(() => { + if (!uuid) return; + + const fetchData = async () => { + try { + const response = await fetch(`/api/bundle/${uuid}`); + if (!response.ok) { + setError( + response.status === 404 + ? "Bundle not found" + : "Failed to fetch bundle data", + ); + setData(null); + return; + } + setData(await response.json()); + setError(null); + } catch { + setError("Failed to fetch bundle data"); + setData(null); + } finally { + setLoading(false); + } + }; + + fetchData(); + const interval = setInterval(fetchData, 5000); + return () => clearInterval(interval); + }, [uuid]); + + if (!uuid || (loading && !data)) { + return ( +
+
+
+ Loading bundle... +
+
+ ); + } + + const latestBundle = data?.history + .filter((e) => e.data?.bundle) + .map((e) => e.data.bundle) + .pop(); + + return ( +
+
+
+
+ + + Back + + + +
+ + TIPS + +
+
+ + {uuid} + + +
+
+
+ +
+ {error && ( + +
+
+ + Error + + +
+
+

Error

+

{error}

+
+
+
+ )} + + {data && latestBundle && ( +
+
+ Transactions +
+ {latestBundle.txs.map((tx, index) => ( + + ))} +
+
+ + {latestBundle.meter_bundle_response && ( +
+ Simulation Results + +
+ )} + +
+ Event History + + {data.history.length > 0 ? ( + + ) : ( +
+ No events recorded yet. +
+ )} +
+
+
+ )} +
+
+ ); +} diff --git a/ui/tips/src/app/globals.css b/ui/tips/src/app/globals.css new file mode 100644 index 0000000..a2dc41e --- /dev/null +++ b/ui/tips/src/app/globals.css @@ -0,0 +1,26 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/ui/tips/src/app/layout.tsx b/ui/tips/src/app/layout.tsx new file mode 100644 index 0000000..bc9a9f2 --- /dev/null +++ b/ui/tips/src/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "TIPS", + description: "A beautiful UI for interacting with TIPS", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/ui/tips/src/app/page.tsx b/ui/tips/src/app/page.tsx new file mode 100644 index 0000000..21e257f --- /dev/null +++ b/ui/tips/src/app/page.tsx @@ -0,0 +1,257 @@ +"use client"; + +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import type { BlockSummary, BlocksResponse } from "./api/blocks/route"; + +function SearchBar({ onError }: { onError: (error: string | null) => void }) { + const router = useRouter(); + const [searchHash, setSearchHash] = useState(""); + const [loading, setLoading] = useState(false); + + const handleSearch = async (e: React.FormEvent) => { + e.preventDefault(); + const hash = searchHash.trim(); + if (!hash) return; + + setLoading(true); + onError(null); + + try { + const response = await fetch(`/api/txn/${hash}`); + if (!response.ok) { + if (response.status === 404) { + onError("Transaction not found"); + } else { + onError("Failed to fetch transaction data"); + } + return; + } + const result = await response.json(); + + if (result.bundle_ids && result.bundle_ids.length > 0) { + router.push(`/bundles/${result.bundle_ids[0]}`); + } else { + onError("No bundle found for this transaction"); + } + } catch { + onError("Failed to fetch transaction data"); + } finally { + setLoading(false); + } + }; + + return ( +
+ setSearchHash(e.target.value)} + className="w-64 lg:w-80 px-3 py-1.5 text-sm border rounded-lg bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-400" + disabled={loading} + /> + +
+ ); +} + +function BlockRow({ block, index }: { block: BlockSummary; index: number }) { + const opacity = Math.max(0.3, 1 - index * 0.08); + const timeSince = Math.floor(Date.now() / 1000 - block.timestamp); + const timeAgo = + timeSince <= 0 + ? "now" + : timeSince < 60 + ? `${timeSince}s ago` + : timeSince < 3600 + ? `${Math.floor(timeSince / 60)}m ago` + : `${Math.floor(timeSince / 3600)}h ago`; + + return ( + +
+ + Block + + +
+
+
+ + #{block.number.toLocaleString()} + + {timeAgo} +
+
+ {block.hash} +
+
+
+
+ {block.transactionCount} +
+
txns
+
+ + View + + + + ); +} + +function Card({ + children, + className = "", +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} + +export default function Home() { + const [error, setError] = useState(null); + const [blocks, setBlocks] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchBlocks = async () => { + try { + const response = await fetch("/api/blocks"); + if (response.ok) { + const data: BlocksResponse = await response.json(); + setBlocks(data.blocks); + } + } catch { + console.error("Failed to fetch blocks"); + } finally { + setLoading(false); + } + }; + + fetchBlocks(); + const interval = setInterval(fetchBlocks, 2000); + return () => clearInterval(interval); + }, []); + + return ( +
+
+
+ TIPS + +
+
+ +
+ {error && ( + +
+ + Error + + + {error} + +
+
+ )} + +
+

+ Latest Blocks +

+ + + {loading ? ( +
+
+
+ Loading blocks... +
+
+ ) : blocks.length > 0 ? ( +
+ {blocks.map((block, index) => ( + + ))} +
+ ) : ( +
+ No blocks available +
+ )} + +
+
+
+ ); +} diff --git a/ui/tips/src/app/txn/[hash]/page.tsx b/ui/tips/src/app/txn/[hash]/page.tsx new file mode 100644 index 0000000..76f1dda --- /dev/null +++ b/ui/tips/src/app/txn/[hash]/page.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import type { TransactionHistoryResponse } from "@/app/api/txn/[hash]/route"; + +interface PageProps { + params: Promise<{ hash: string }>; +} + +export default function TransactionRedirectPage({ params }: PageProps) { + const router = useRouter(); + const [hash, setHash] = useState(""); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const initializeParams = async () => { + const resolvedParams = await params; + setHash(resolvedParams.hash); + }; + initializeParams(); + }, [params]); + + useEffect(() => { + if (!hash) return; + + const fetchAndRedirect = async () => { + try { + const response = await fetch(`/api/txn/${hash}`); + if (!response.ok) { + if (response.status === 404) { + setError("Transaction not found"); + } else { + setError("Failed to fetch transaction data"); + } + return; + } + const result: TransactionHistoryResponse = await response.json(); + + if (result.bundle_ids && result.bundle_ids.length > 0) { + router.push(`/bundles/${result.bundle_ids[0]}`); + } else { + setError("No bundle found for this transaction"); + } + } catch (_err) { + setError("Failed to fetch transaction data"); + } finally { + setLoading(false); + } + }; + + fetchAndRedirect(); + }, [hash, router]); + + if (!hash) { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+
+

Transaction {hash}

+ {loading && ( +
+ Redirecting to bundle page... +
+ )} + {error && ( +
{error}
+ )} +
+
+ ); +} diff --git a/ui/tips/src/lib/s3.ts b/ui/tips/src/lib/s3.ts new file mode 100644 index 0000000..0d0cde7 --- /dev/null +++ b/ui/tips/src/lib/s3.ts @@ -0,0 +1,250 @@ +import { + GetObjectCommand, + PutObjectCommand, + S3Client, + type S3ClientConfig, +} from "@aws-sdk/client-s3"; + +function createS3Client(): S3Client { + const configType = process.env.TIPS_UI_S3_CONFIG_TYPE || "aws"; + const region = process.env.TIPS_UI_AWS_REGION || "us-east-1"; + + if (configType === "manual") { + console.log("Using Manual S3 configuration"); + const config: S3ClientConfig = { + region, + forcePathStyle: true, + }; + + if (process.env.TIPS_UI_S3_ENDPOINT) { + config.endpoint = process.env.TIPS_UI_S3_ENDPOINT; + } + + if ( + process.env.TIPS_UI_S3_ACCESS_KEY_ID && + process.env.TIPS_UI_S3_SECRET_ACCESS_KEY + ) { + config.credentials = { + accessKeyId: process.env.TIPS_UI_S3_ACCESS_KEY_ID, + secretAccessKey: process.env.TIPS_UI_S3_SECRET_ACCESS_KEY, + }; + } + + return new S3Client(config); + } + + console.log("Using AWS S3 configuration"); + return new S3Client({ + region, + }); +} + +const s3Client = createS3Client(); + +const BUCKET_NAME = process.env.TIPS_UI_S3_BUCKET_NAME || "tips"; + +export interface TransactionMetadata { + bundle_ids: string[]; + sender: string; + nonce: string; +} + +async function getObjectContent(key: string): Promise { + try { + const command = new GetObjectCommand({ + Bucket: BUCKET_NAME, + Key: key, + }); + + const response = await s3Client.send(command); + const body = await response.Body?.transformToString(); + return body || null; + } catch (_error) { + return null; + } +} + +export async function getTransactionMetadataByHash( + hash: string, +): Promise { + const key = `transactions/by_hash/${hash}`; + const content = await getObjectContent(key); + + if (!content) { + return null; + } + + try { + return JSON.parse(content) as TransactionMetadata; + } catch (error) { + console.error( + `Failed to parse transaction metadata for hash ${hash}:`, + error, + ); + return null; + } +} + +export interface BundleTransaction { + signer: string; + type: string; + chainId: string; + nonce: string; + gas: string; + maxFeePerGas: string; + maxPriorityFeePerGas: string; + to: string; + value: string; + accessList: unknown[]; + input: string; + r: string; + s: string; + yParity: string; + v: string; + hash: string; +} + +export interface MeterBundleResult { + coinbaseDiff: string; + ethSentToCoinbase: string; + fromAddress: string; + gasFees: string; + gasPrice: string; + gasUsed: number; + toAddress: string; + txHash: string; + value: string; + executionTimeUs: number; +} + +export interface MeterBundleResponse { + bundleGasPrice: string; + bundleHash: string; + coinbaseDiff: string; + ethSentToCoinbase: string; + gasFees: string; + results: MeterBundleResult[]; + stateBlockNumber: number; + totalGasUsed: number; + totalExecutionTimeUs: number; +} + +export interface BundleData { + uuid: string; + txs: BundleTransaction[]; + block_number: string; + max_timestamp: number; + reverting_tx_hashes: string[]; + meter_bundle_response: MeterBundleResponse; +} + +export interface BundleEventData { + key: string; + timestamp: number; + bundle?: BundleData; + block_number?: number; + block_hash?: string; + builder?: string; + flashblock_index?: number; + reason?: string; +} + +export interface BundleEvent { + event: string; + data: BundleEventData; +} + +export interface BundleHistory { + history: BundleEvent[]; +} + +export async function getBundleHistory( + bundleId: string, +): Promise { + const key = `bundles/${bundleId}`; + const content = await getObjectContent(key); + + if (!content) { + return null; + } + + try { + return JSON.parse(content) as BundleHistory; + } catch (error) { + console.error( + `Failed to parse bundle history for bundle ${bundleId}:`, + error, + ); + return null; + } +} + +export interface BlockTransaction { + hash: string; + from: string; + to: string | null; + gasUsed: bigint; + executionTimeUs: number | null; + bundleId: string | null; + index: number; +} + +export interface BlockData { + hash: string; + number: bigint; + timestamp: bigint; + transactions: BlockTransaction[]; + gasUsed: bigint; + gasLimit: bigint; + cachedAt: number; +} + +export async function getBlockFromCache( + blockHash: string, +): Promise { + const key = `blocks/${blockHash}`; + const content = await getObjectContent(key); + + if (!content) { + return null; + } + + try { + const parsed = JSON.parse(content); + return { + ...parsed, + number: BigInt(parsed.number), + timestamp: BigInt(parsed.timestamp), + gasUsed: BigInt(parsed.gasUsed), + gasLimit: BigInt(parsed.gasLimit), + transactions: parsed.transactions.map( + (tx: BlockTransaction & { gasUsed: string }) => ({ + ...tx, + gasUsed: BigInt(tx.gasUsed), + }), + ), + } as BlockData; + } catch (error) { + console.error(`Failed to parse block data for hash ${blockHash}:`, error); + return null; + } +} + +export async function cacheBlockData(blockData: BlockData): Promise { + const key = `blocks/${blockData.hash}`; + + try { + const command = new PutObjectCommand({ + Bucket: BUCKET_NAME, + Key: key, + Body: JSON.stringify(blockData, (_, value) => + typeof value === "bigint" ? value.toString() : value, + ), + ContentType: "application/json", + }); + + await s3Client.send(command); + } catch (error) { + console.error(`Failed to cache block data for ${blockData.hash}:`, error); + } +} diff --git a/ui/tips/tsconfig.json b/ui/tips/tsconfig.json new file mode 100644 index 0000000..0f96f2a --- /dev/null +++ b/ui/tips/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/ui/tips/yarn.lock b/ui/tips/yarn.lock new file mode 100644 index 0000000..f687ad4 --- /dev/null +++ b/ui/tips/yarn.lock @@ -0,0 +1,1912 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@adraffy/ens-normalize@^1.11.0": + version "1.11.1" + resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz" + integrity sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ== + +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + +"@aws-crypto/crc32@5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz" + integrity sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/crc32c@5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz" + integrity sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/sha1-browser@5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz" + integrity sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg== + dependencies: + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-browser@5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz" + integrity sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== + dependencies: + "@aws-crypto/sha256-js" "^5.2.0" + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz" + integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/supports-web-crypto@^5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz" + integrity sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== + dependencies: + tslib "^2.6.2" + +"@aws-crypto/util@5.2.0", "@aws-crypto/util@^5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz" + integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== + dependencies: + "@aws-sdk/types" "^3.222.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-s3@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.940.0.tgz" + integrity sha512-Wi4qnBT6shRRMXuuTgjMFTU5mu2KFWisgcigEMPptjPGUtJvBVi4PTGgS64qsLoUk/obqDAyOBOfEtRZ2ddC2w== + dependencies: + "@aws-crypto/sha1-browser" "5.2.0" + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.940.0" + "@aws-sdk/credential-provider-node" "3.940.0" + "@aws-sdk/middleware-bucket-endpoint" "3.936.0" + "@aws-sdk/middleware-expect-continue" "3.936.0" + "@aws-sdk/middleware-flexible-checksums" "3.940.0" + "@aws-sdk/middleware-host-header" "3.936.0" + "@aws-sdk/middleware-location-constraint" "3.936.0" + "@aws-sdk/middleware-logger" "3.936.0" + "@aws-sdk/middleware-recursion-detection" "3.936.0" + "@aws-sdk/middleware-sdk-s3" "3.940.0" + "@aws-sdk/middleware-ssec" "3.936.0" + "@aws-sdk/middleware-user-agent" "3.940.0" + "@aws-sdk/region-config-resolver" "3.936.0" + "@aws-sdk/signature-v4-multi-region" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@aws-sdk/util-endpoints" "3.936.0" + "@aws-sdk/util-user-agent-browser" "3.936.0" + "@aws-sdk/util-user-agent-node" "3.940.0" + "@smithy/config-resolver" "^4.4.3" + "@smithy/core" "^3.18.5" + "@smithy/eventstream-serde-browser" "^4.2.5" + "@smithy/eventstream-serde-config-resolver" "^4.3.5" + "@smithy/eventstream-serde-node" "^4.2.5" + "@smithy/fetch-http-handler" "^5.3.6" + "@smithy/hash-blob-browser" "^4.2.6" + "@smithy/hash-node" "^4.2.5" + "@smithy/hash-stream-node" "^4.2.5" + "@smithy/invalid-dependency" "^4.2.5" + "@smithy/md5-js" "^4.2.5" + "@smithy/middleware-content-length" "^4.2.5" + "@smithy/middleware-endpoint" "^4.3.12" + "@smithy/middleware-retry" "^4.4.12" + "@smithy/middleware-serde" "^4.2.6" + "@smithy/middleware-stack" "^4.2.5" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/node-http-handler" "^4.4.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/smithy-client" "^4.9.8" + "@smithy/types" "^4.9.0" + "@smithy/url-parser" "^4.2.5" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-body-length-browser" "^4.2.0" + "@smithy/util-body-length-node" "^4.2.1" + "@smithy/util-defaults-mode-browser" "^4.3.11" + "@smithy/util-defaults-mode-node" "^4.2.14" + "@smithy/util-endpoints" "^3.2.5" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-retry" "^4.2.5" + "@smithy/util-stream" "^4.5.6" + "@smithy/util-utf8" "^4.2.0" + "@smithy/util-waiter" "^4.2.5" + tslib "^2.6.2" + +"@aws-sdk/client-sso@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.940.0.tgz" + integrity sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.940.0" + "@aws-sdk/middleware-host-header" "3.936.0" + "@aws-sdk/middleware-logger" "3.936.0" + "@aws-sdk/middleware-recursion-detection" "3.936.0" + "@aws-sdk/middleware-user-agent" "3.940.0" + "@aws-sdk/region-config-resolver" "3.936.0" + "@aws-sdk/types" "3.936.0" + "@aws-sdk/util-endpoints" "3.936.0" + "@aws-sdk/util-user-agent-browser" "3.936.0" + "@aws-sdk/util-user-agent-node" "3.940.0" + "@smithy/config-resolver" "^4.4.3" + "@smithy/core" "^3.18.5" + "@smithy/fetch-http-handler" "^5.3.6" + "@smithy/hash-node" "^4.2.5" + "@smithy/invalid-dependency" "^4.2.5" + "@smithy/middleware-content-length" "^4.2.5" + "@smithy/middleware-endpoint" "^4.3.12" + "@smithy/middleware-retry" "^4.4.12" + "@smithy/middleware-serde" "^4.2.6" + "@smithy/middleware-stack" "^4.2.5" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/node-http-handler" "^4.4.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/smithy-client" "^4.9.8" + "@smithy/types" "^4.9.0" + "@smithy/url-parser" "^4.2.5" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-body-length-browser" "^4.2.0" + "@smithy/util-body-length-node" "^4.2.1" + "@smithy/util-defaults-mode-browser" "^4.3.11" + "@smithy/util-defaults-mode-node" "^4.2.14" + "@smithy/util-endpoints" "^3.2.5" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-retry" "^4.2.5" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/core@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/core/-/core-3.940.0.tgz" + integrity sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA== + dependencies: + "@aws-sdk/types" "3.936.0" + "@aws-sdk/xml-builder" "3.930.0" + "@smithy/core" "^3.18.5" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/property-provider" "^4.2.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/signature-v4" "^5.3.5" + "@smithy/smithy-client" "^4.9.8" + "@smithy/types" "^4.9.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-env@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.940.0.tgz" + integrity sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/property-provider" "^4.2.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-http@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.940.0.tgz" + integrity sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/fetch-http-handler" "^5.3.6" + "@smithy/node-http-handler" "^4.4.5" + "@smithy/property-provider" "^4.2.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/smithy-client" "^4.9.8" + "@smithy/types" "^4.9.0" + "@smithy/util-stream" "^4.5.6" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-ini@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.940.0.tgz" + integrity sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/credential-provider-env" "3.940.0" + "@aws-sdk/credential-provider-http" "3.940.0" + "@aws-sdk/credential-provider-login" "3.940.0" + "@aws-sdk/credential-provider-process" "3.940.0" + "@aws-sdk/credential-provider-sso" "3.940.0" + "@aws-sdk/credential-provider-web-identity" "3.940.0" + "@aws-sdk/nested-clients" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/credential-provider-imds" "^4.2.5" + "@smithy/property-provider" "^4.2.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-login@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.940.0.tgz" + integrity sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/nested-clients" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/property-provider" "^4.2.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-node@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.940.0.tgz" + integrity sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g== + dependencies: + "@aws-sdk/credential-provider-env" "3.940.0" + "@aws-sdk/credential-provider-http" "3.940.0" + "@aws-sdk/credential-provider-ini" "3.940.0" + "@aws-sdk/credential-provider-process" "3.940.0" + "@aws-sdk/credential-provider-sso" "3.940.0" + "@aws-sdk/credential-provider-web-identity" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/credential-provider-imds" "^4.2.5" + "@smithy/property-provider" "^4.2.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-process@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.940.0.tgz" + integrity sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/property-provider" "^4.2.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-sso@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.940.0.tgz" + integrity sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw== + dependencies: + "@aws-sdk/client-sso" "3.940.0" + "@aws-sdk/core" "3.940.0" + "@aws-sdk/token-providers" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/property-provider" "^4.2.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-web-identity@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.940.0.tgz" + integrity sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/nested-clients" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/property-provider" "^4.2.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-bucket-endpoint@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.936.0.tgz" + integrity sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg== + dependencies: + "@aws-sdk/types" "3.936.0" + "@aws-sdk/util-arn-parser" "3.893.0" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + "@smithy/util-config-provider" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-expect-continue@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.936.0.tgz" + integrity sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA== + dependencies: + "@aws-sdk/types" "3.936.0" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-flexible-checksums@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.940.0.tgz" + integrity sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ== + dependencies: + "@aws-crypto/crc32" "5.2.0" + "@aws-crypto/crc32c" "5.2.0" + "@aws-crypto/util" "5.2.0" + "@aws-sdk/core" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/is-array-buffer" "^4.2.0" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-stream" "^4.5.6" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-host-header@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz" + integrity sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw== + dependencies: + "@aws-sdk/types" "3.936.0" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-location-constraint@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.936.0.tgz" + integrity sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw== + dependencies: + "@aws-sdk/types" "3.936.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-logger@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz" + integrity sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw== + dependencies: + "@aws-sdk/types" "3.936.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-recursion-detection@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz" + integrity sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA== + dependencies: + "@aws-sdk/types" "3.936.0" + "@aws/lambda-invoke-store" "^0.2.0" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-sdk-s3@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.940.0.tgz" + integrity sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@aws-sdk/util-arn-parser" "3.893.0" + "@smithy/core" "^3.18.5" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/signature-v4" "^5.3.5" + "@smithy/smithy-client" "^4.9.8" + "@smithy/types" "^4.9.0" + "@smithy/util-config-provider" "^4.2.0" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-stream" "^4.5.6" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-ssec@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.936.0.tgz" + integrity sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA== + dependencies: + "@aws-sdk/types" "3.936.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-user-agent@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.940.0.tgz" + integrity sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@aws-sdk/util-endpoints" "3.936.0" + "@smithy/core" "^3.18.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/nested-clients@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.940.0.tgz" + integrity sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.940.0" + "@aws-sdk/middleware-host-header" "3.936.0" + "@aws-sdk/middleware-logger" "3.936.0" + "@aws-sdk/middleware-recursion-detection" "3.936.0" + "@aws-sdk/middleware-user-agent" "3.940.0" + "@aws-sdk/region-config-resolver" "3.936.0" + "@aws-sdk/types" "3.936.0" + "@aws-sdk/util-endpoints" "3.936.0" + "@aws-sdk/util-user-agent-browser" "3.936.0" + "@aws-sdk/util-user-agent-node" "3.940.0" + "@smithy/config-resolver" "^4.4.3" + "@smithy/core" "^3.18.5" + "@smithy/fetch-http-handler" "^5.3.6" + "@smithy/hash-node" "^4.2.5" + "@smithy/invalid-dependency" "^4.2.5" + "@smithy/middleware-content-length" "^4.2.5" + "@smithy/middleware-endpoint" "^4.3.12" + "@smithy/middleware-retry" "^4.4.12" + "@smithy/middleware-serde" "^4.2.6" + "@smithy/middleware-stack" "^4.2.5" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/node-http-handler" "^4.4.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/smithy-client" "^4.9.8" + "@smithy/types" "^4.9.0" + "@smithy/url-parser" "^4.2.5" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-body-length-browser" "^4.2.0" + "@smithy/util-body-length-node" "^4.2.1" + "@smithy/util-defaults-mode-browser" "^4.3.11" + "@smithy/util-defaults-mode-node" "^4.2.14" + "@smithy/util-endpoints" "^3.2.5" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-retry" "^4.2.5" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@aws-sdk/region-config-resolver@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz" + integrity sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw== + dependencies: + "@aws-sdk/types" "3.936.0" + "@smithy/config-resolver" "^4.4.3" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/signature-v4-multi-region@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.940.0.tgz" + integrity sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw== + dependencies: + "@aws-sdk/middleware-sdk-s3" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/protocol-http" "^5.3.5" + "@smithy/signature-v4" "^5.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/token-providers@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.940.0.tgz" + integrity sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg== + dependencies: + "@aws-sdk/core" "3.940.0" + "@aws-sdk/nested-clients" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/property-provider" "^4.2.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/types@3.936.0", "@aws-sdk/types@^3.222.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz" + integrity sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/util-arn-parser@3.893.0": + version "3.893.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz" + integrity sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-endpoints@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz" + integrity sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w== + dependencies: + "@aws-sdk/types" "3.936.0" + "@smithy/types" "^4.9.0" + "@smithy/url-parser" "^4.2.5" + "@smithy/util-endpoints" "^3.2.5" + tslib "^2.6.2" + +"@aws-sdk/util-locate-window@^3.0.0": + version "3.893.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz" + integrity sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-browser@3.936.0": + version "3.936.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz" + integrity sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw== + dependencies: + "@aws-sdk/types" "3.936.0" + "@smithy/types" "^4.9.0" + bowser "^2.11.0" + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-node@3.940.0": + version "3.940.0" + resolved "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.940.0.tgz" + integrity sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A== + dependencies: + "@aws-sdk/middleware-user-agent" "3.940.0" + "@aws-sdk/types" "3.936.0" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@aws-sdk/xml-builder@3.930.0": + version "3.930.0" + resolved "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz" + integrity sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA== + dependencies: + "@smithy/types" "^4.9.0" + fast-xml-parser "5.2.5" + tslib "^2.6.2" + +"@aws/lambda-invoke-store@^0.2.0": + version "0.2.2" + resolved "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz" + integrity sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg== + +"@biomejs/biome@2.3.8": + version "2.3.8" + resolved "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.8.tgz" + integrity sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "2.3.8" + "@biomejs/cli-darwin-x64" "2.3.8" + "@biomejs/cli-linux-arm64" "2.3.8" + "@biomejs/cli-linux-arm64-musl" "2.3.8" + "@biomejs/cli-linux-x64" "2.3.8" + "@biomejs/cli-linux-x64-musl" "2.3.8" + "@biomejs/cli-win32-arm64" "2.3.8" + "@biomejs/cli-win32-x64" "2.3.8" + +"@biomejs/cli-darwin-arm64@2.3.8": + version "2.3.8" + resolved "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.8.tgz" + integrity sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww== + +"@biomejs/cli-darwin-x64@2.3.8": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.8.tgz#5765d66ac9e92d80469ab5c24344ab6d3410ff75" + integrity sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA== + +"@biomejs/cli-linux-arm64-musl@2.3.8": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.8.tgz#63ff811a3acc74c885e989a4e6e8f54399425e3a" + integrity sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA== + +"@biomejs/cli-linux-arm64@2.3.8": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.8.tgz#c6275cf8bc8592ef4ea840f5b1938606d3f3a721" + integrity sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g== + +"@biomejs/cli-linux-x64-musl@2.3.8": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.8.tgz#95da386940356ef21d71d724587213915a6e7fef" + integrity sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA== + +"@biomejs/cli-linux-x64@2.3.8": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.8.tgz#65fddc3feb27a6817763306b73506308cbdb1dcc" + integrity sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw== + +"@biomejs/cli-win32-arm64@2.3.8": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.8.tgz#86385f8ff7abec220af11a7ddf9e57c39e317277" + integrity sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg== + +"@biomejs/cli-win32-x64@2.3.8": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.8.tgz#dca59fbf4c6f871f22f567ade203e996c7867206" + integrity sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w== + +"@emnapi/core@^1.6.0", "@emnapi/core@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.7.1.tgz#3a79a02dbc84f45884a1806ebb98e5746bdfaac4" + integrity sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg== + dependencies: + "@emnapi/wasi-threads" "1.1.0" + tslib "^2.4.0" + +"@emnapi/runtime@^1.6.0", "@emnapi/runtime@^1.7.0", "@emnapi/runtime@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.7.1.tgz#a73784e23f5d57287369c808197288b52276b791" + integrity sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.1.0", "@emnapi/wasi-threads@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== + dependencies: + tslib "^2.4.0" + +"@img/colour@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz" + integrity sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw== + +"@img/sharp-darwin-arm64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz#6e0732dcade126b6670af7aa17060b926835ea86" + integrity sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.2.4" + +"@img/sharp-darwin-x64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz#19bc1dd6eba6d5a96283498b9c9f401180ee9c7b" + integrity sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.2.4" + +"@img/sharp-libvips-darwin-arm64@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz#2894c0cb87d42276c3889942e8e2db517a492c43" + integrity sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g== + +"@img/sharp-libvips-darwin-x64@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz#e63681f4539a94af9cd17246ed8881734386f8cc" + integrity sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg== + +"@img/sharp-libvips-linux-arm64@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz#b1b288b36864b3bce545ad91fa6dadcf1a4ad318" + integrity sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw== + +"@img/sharp-libvips-linux-arm@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz#b9260dd1ebe6f9e3bdbcbdcac9d2ac125f35852d" + integrity sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A== + +"@img/sharp-libvips-linux-ppc64@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz#4b83ecf2a829057222b38848c7b022e7b4d07aa7" + integrity sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA== + +"@img/sharp-libvips-linux-riscv64@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz#880b4678009e5a2080af192332b00b0aaf8a48de" + integrity sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA== + +"@img/sharp-libvips-linux-s390x@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz#74f343c8e10fad821b38f75ced30488939dc59ec" + integrity sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ== + +"@img/sharp-libvips-linux-x64@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz#df4183e8bd8410f7d61b66859a35edeab0a531ce" + integrity sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw== + +"@img/sharp-libvips-linuxmusl-arm64@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz#c8d6b48211df67137541007ee8d1b7b1f8ca8e06" + integrity sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw== + +"@img/sharp-libvips-linuxmusl-x64@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz#be11c75bee5b080cbee31a153a8779448f919f75" + integrity sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg== + +"@img/sharp-linux-arm64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz#7aa7764ef9c001f15e610546d42fce56911790cc" + integrity sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.2.4" + +"@img/sharp-linux-arm@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz#5fb0c3695dd12522d39c3ff7a6bc816461780a0d" + integrity sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.2.4" + +"@img/sharp-linux-ppc64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz#9c213a81520a20caf66978f3d4c07456ff2e0813" + integrity sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA== + optionalDependencies: + "@img/sharp-libvips-linux-ppc64" "1.2.4" + +"@img/sharp-linux-riscv64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz#cdd28182774eadbe04f62675a16aabbccb833f60" + integrity sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw== + optionalDependencies: + "@img/sharp-libvips-linux-riscv64" "1.2.4" + +"@img/sharp-linux-s390x@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz#93eac601b9f329bb27917e0e19098c722d630df7" + integrity sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.2.4" + +"@img/sharp-linux-x64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz#55abc7cd754ffca5002b6c2b719abdfc846819a8" + integrity sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.2.4" + +"@img/sharp-linuxmusl-arm64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz#d6515ee971bb62f73001a4829b9d865a11b77086" + integrity sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.2.4" + +"@img/sharp-linuxmusl-x64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz#d97978aec7c5212f999714f2f5b736457e12ee9f" + integrity sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.2.4" + +"@img/sharp-wasm32@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz#2f15803aa626f8c59dd7c9d0bbc766f1ab52cfa0" + integrity sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw== + dependencies: + "@emnapi/runtime" "^1.7.0" + +"@img/sharp-win32-arm64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz#3706e9e3ac35fddfc1c87f94e849f1b75307ce0a" + integrity sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g== + +"@img/sharp-win32-ia32@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz#0b71166599b049e032f085fb9263e02f4e4788de" + integrity sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg== + +"@img/sharp-win32-x64@0.34.5": + version "0.34.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz#a81ffb00e69267cd0a1d626eaedb8a8430b2b2f8" + integrity sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.4": + version "2.3.5" + resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@napi-rs/wasm-runtime@^1.0.7": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz#c0180393d7862cff0d412e3e1a7c3bd5ea6d9b2f" + integrity sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA== + dependencies: + "@emnapi/core" "^1.7.1" + "@emnapi/runtime" "^1.7.1" + "@tybys/wasm-util" "^0.10.1" + +"@next/env@16.0.7": + version "16.0.7" + resolved "https://registry.npmjs.org/@next/env/-/env-16.0.7.tgz" + integrity sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw== + +"@next/swc-darwin-arm64@16.0.7": + version "16.0.7" + resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.7.tgz" + integrity sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg== + +"@next/swc-darwin-x64@16.0.7": + version "16.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.7.tgz#ec5fd15ae391e1af9f152881f559bffaa24524e7" + integrity sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA== + +"@next/swc-linux-arm64-gnu@16.0.7": + version "16.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.7.tgz#22be26ee7544f68aa916159b823899b67b09f0a6" + integrity sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww== + +"@next/swc-linux-arm64-musl@16.0.7": + version "16.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.7.tgz#1b34103951456e5d6766710e4f16dadbd7745bed" + integrity sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g== + +"@next/swc-linux-x64-gnu@16.0.7": + version "16.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.7.tgz#b45e1fa7aaf844269e0932c4cde49bb6b0817ce9" + integrity sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA== + +"@next/swc-linux-x64-musl@16.0.7": + version "16.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.7.tgz#c5376fd78ca97a911e2bbfb9b26dc623b54edfab" + integrity sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w== + +"@next/swc-win32-arm64-msvc@16.0.7": + version "16.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.7.tgz#2cf27e275ddd8d04f89391a9cdf6790cccb3bf09" + integrity sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q== + +"@next/swc-win32-x64-msvc@16.0.7": + version "16.0.7" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.7.tgz#e9e38fcca752902542c25c527e532af71e9ca812" + integrity sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug== + +"@noble/ciphers@^1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz" + integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== + +"@noble/curves@1.9.1", "@noble/curves@~1.9.0": + version "1.9.1" + resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz" + integrity sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA== + dependencies: + "@noble/hashes" "1.8.0" + +"@noble/hashes@1.8.0", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0": + version "1.8.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + +"@scure/base@~1.2.5": + version "1.2.6" + resolved "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz" + integrity sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg== + +"@scure/bip32@1.7.0", "@scure/bip32@^1.7.0": + version "1.7.0" + resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz" + integrity sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw== + dependencies: + "@noble/curves" "~1.9.0" + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + +"@scure/bip39@1.6.0", "@scure/bip39@^1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz" + integrity sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A== + dependencies: + "@noble/hashes" "~1.8.0" + "@scure/base" "~1.2.5" + +"@smithy/abort-controller@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz" + integrity sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/chunked-blob-reader-native@^4.2.1": + version "4.2.1" + resolved "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz" + integrity sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ== + dependencies: + "@smithy/util-base64" "^4.3.0" + tslib "^2.6.2" + +"@smithy/chunked-blob-reader@^5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz" + integrity sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA== + dependencies: + tslib "^2.6.2" + +"@smithy/config-resolver@^4.4.3": + version "4.4.3" + resolved "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz" + integrity sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw== + dependencies: + "@smithy/node-config-provider" "^4.3.5" + "@smithy/types" "^4.9.0" + "@smithy/util-config-provider" "^4.2.0" + "@smithy/util-endpoints" "^3.2.5" + "@smithy/util-middleware" "^4.2.5" + tslib "^2.6.2" + +"@smithy/core@^3.18.5", "@smithy/core@^3.18.6": + version "3.18.6" + resolved "https://registry.npmjs.org/@smithy/core/-/core-3.18.6.tgz" + integrity sha512-8Q/ugWqfDUEU1Exw71+DoOzlONJ2Cn9QA8VeeDzLLjzO/qruh9UKFzbszy4jXcIYgGofxYiT0t1TT6+CT/GupQ== + dependencies: + "@smithy/middleware-serde" "^4.2.6" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-body-length-browser" "^4.2.0" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-stream" "^4.5.6" + "@smithy/util-utf8" "^4.2.0" + "@smithy/uuid" "^1.1.0" + tslib "^2.6.2" + +"@smithy/credential-provider-imds@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz" + integrity sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ== + dependencies: + "@smithy/node-config-provider" "^4.3.5" + "@smithy/property-provider" "^4.2.5" + "@smithy/types" "^4.9.0" + "@smithy/url-parser" "^4.2.5" + tslib "^2.6.2" + +"@smithy/eventstream-codec@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.5.tgz" + integrity sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA== + dependencies: + "@aws-crypto/crc32" "5.2.0" + "@smithy/types" "^4.9.0" + "@smithy/util-hex-encoding" "^4.2.0" + tslib "^2.6.2" + +"@smithy/eventstream-serde-browser@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.5.tgz" + integrity sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw== + dependencies: + "@smithy/eventstream-serde-universal" "^4.2.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/eventstream-serde-config-resolver@^4.3.5": + version "4.3.5" + resolved "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.5.tgz" + integrity sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/eventstream-serde-node@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.5.tgz" + integrity sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg== + dependencies: + "@smithy/eventstream-serde-universal" "^4.2.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/eventstream-serde-universal@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.5.tgz" + integrity sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q== + dependencies: + "@smithy/eventstream-codec" "^4.2.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/fetch-http-handler@^5.3.6": + version "5.3.6" + resolved "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz" + integrity sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg== + dependencies: + "@smithy/protocol-http" "^5.3.5" + "@smithy/querystring-builder" "^4.2.5" + "@smithy/types" "^4.9.0" + "@smithy/util-base64" "^4.3.0" + tslib "^2.6.2" + +"@smithy/hash-blob-browser@^4.2.6": + version "4.2.6" + resolved "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.6.tgz" + integrity sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw== + dependencies: + "@smithy/chunked-blob-reader" "^5.2.0" + "@smithy/chunked-blob-reader-native" "^4.2.1" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/hash-node@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz" + integrity sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA== + dependencies: + "@smithy/types" "^4.9.0" + "@smithy/util-buffer-from" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@smithy/hash-stream-node@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.5.tgz" + integrity sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q== + dependencies: + "@smithy/types" "^4.9.0" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@smithy/invalid-dependency@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz" + integrity sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/is-array-buffer@^2.2.0": + version "2.2.0" + resolved "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz" + integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== + dependencies: + tslib "^2.6.2" + +"@smithy/is-array-buffer@^4.2.0": + version "4.2.0" + resolved "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz" + integrity sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ== + dependencies: + tslib "^2.6.2" + +"@smithy/md5-js@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.5.tgz" + integrity sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg== + dependencies: + "@smithy/types" "^4.9.0" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@smithy/middleware-content-length@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz" + integrity sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A== + dependencies: + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/middleware-endpoint@^4.3.12", "@smithy/middleware-endpoint@^4.3.13": + version "4.3.13" + resolved "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.13.tgz" + integrity sha512-X4za1qCdyx1hEVVXuAWlZuK6wzLDv1uw1OY9VtaYy1lULl661+frY7FeuHdYdl7qAARUxH2yvNExU2/SmRFfcg== + dependencies: + "@smithy/core" "^3.18.6" + "@smithy/middleware-serde" "^4.2.6" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + "@smithy/url-parser" "^4.2.5" + "@smithy/util-middleware" "^4.2.5" + tslib "^2.6.2" + +"@smithy/middleware-retry@^4.4.12": + version "4.4.13" + resolved "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.13.tgz" + integrity sha512-RzIDF9OrSviXX7MQeKOm8r/372KTyY8Jmp6HNKOOYlrguHADuM3ED/f4aCyNhZZFLG55lv5beBin7nL0Nzy1Dw== + dependencies: + "@smithy/node-config-provider" "^4.3.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/service-error-classification" "^4.2.5" + "@smithy/smithy-client" "^4.9.9" + "@smithy/types" "^4.9.0" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-retry" "^4.2.5" + "@smithy/uuid" "^1.1.0" + tslib "^2.6.2" + +"@smithy/middleware-serde@^4.2.6": + version "4.2.6" + resolved "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz" + integrity sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ== + dependencies: + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/middleware-stack@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz" + integrity sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/node-config-provider@^4.3.5": + version "4.3.5" + resolved "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz" + integrity sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg== + dependencies: + "@smithy/property-provider" "^4.2.5" + "@smithy/shared-ini-file-loader" "^4.4.0" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/node-http-handler@^4.4.5": + version "4.4.5" + resolved "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz" + integrity sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw== + dependencies: + "@smithy/abort-controller" "^4.2.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/querystring-builder" "^4.2.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/property-provider@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz" + integrity sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/protocol-http@^5.3.5": + version "5.3.5" + resolved "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz" + integrity sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/querystring-builder@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz" + integrity sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg== + dependencies: + "@smithy/types" "^4.9.0" + "@smithy/util-uri-escape" "^4.2.0" + tslib "^2.6.2" + +"@smithy/querystring-parser@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz" + integrity sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/service-error-classification@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz" + integrity sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ== + dependencies: + "@smithy/types" "^4.9.0" + +"@smithy/shared-ini-file-loader@^4.4.0": + version "4.4.0" + resolved "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz" + integrity sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/signature-v4@^5.3.5": + version "5.3.5" + resolved "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz" + integrity sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w== + dependencies: + "@smithy/is-array-buffer" "^4.2.0" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + "@smithy/util-hex-encoding" "^4.2.0" + "@smithy/util-middleware" "^4.2.5" + "@smithy/util-uri-escape" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@smithy/smithy-client@^4.9.8", "@smithy/smithy-client@^4.9.9": + version "4.9.9" + resolved "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.9.tgz" + integrity sha512-SUnZJMMo5yCmgjopJbiNeo1vlr8KvdnEfIHV9rlD77QuOGdRotIVBcOrBuMr+sI9zrnhtDtLP054bZVbpZpiQA== + dependencies: + "@smithy/core" "^3.18.6" + "@smithy/middleware-endpoint" "^4.3.13" + "@smithy/middleware-stack" "^4.2.5" + "@smithy/protocol-http" "^5.3.5" + "@smithy/types" "^4.9.0" + "@smithy/util-stream" "^4.5.6" + tslib "^2.6.2" + +"@smithy/types@^4.9.0": + version "4.9.0" + resolved "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz" + integrity sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA== + dependencies: + tslib "^2.6.2" + +"@smithy/url-parser@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz" + integrity sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ== + dependencies: + "@smithy/querystring-parser" "^4.2.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/util-base64@^4.3.0": + version "4.3.0" + resolved "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz" + integrity sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ== + dependencies: + "@smithy/util-buffer-from" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-body-length-browser@^4.2.0": + version "4.2.0" + resolved "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz" + integrity sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg== + dependencies: + tslib "^2.6.2" + +"@smithy/util-body-length-node@^4.2.1": + version "4.2.1" + resolved "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz" + integrity sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA== + dependencies: + tslib "^2.6.2" + +"@smithy/util-buffer-from@^2.2.0": + version "2.2.0" + resolved "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz" + integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== + dependencies: + "@smithy/is-array-buffer" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-buffer-from@^4.2.0": + version "4.2.0" + resolved "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz" + integrity sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew== + dependencies: + "@smithy/is-array-buffer" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-config-provider@^4.2.0": + version "4.2.0" + resolved "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz" + integrity sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q== + dependencies: + tslib "^2.6.2" + +"@smithy/util-defaults-mode-browser@^4.3.11": + version "4.3.12" + resolved "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.12.tgz" + integrity sha512-TKc6FnOxFULKxLgTNHYjcFqdOYzXVPFFVm5JhI30F3RdhT7nYOtOsjgaOwfDRmA/3U66O9KaBQ3UHoXwayRhAg== + dependencies: + "@smithy/property-provider" "^4.2.5" + "@smithy/smithy-client" "^4.9.9" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/util-defaults-mode-node@^4.2.14": + version "4.2.15" + resolved "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.15.tgz" + integrity sha512-94NqfQVo+vGc5gsQ9SROZqOvBkGNMQu6pjXbnn8aQvBUhc31kx49gxlkBEqgmaZQHUUfdRUin5gK/HlHKmbAwg== + dependencies: + "@smithy/config-resolver" "^4.4.3" + "@smithy/credential-provider-imds" "^4.2.5" + "@smithy/node-config-provider" "^4.3.5" + "@smithy/property-provider" "^4.2.5" + "@smithy/smithy-client" "^4.9.9" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/util-endpoints@^3.2.5": + version "3.2.5" + resolved "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz" + integrity sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A== + dependencies: + "@smithy/node-config-provider" "^4.3.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/util-hex-encoding@^4.2.0": + version "4.2.0" + resolved "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz" + integrity sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw== + dependencies: + tslib "^2.6.2" + +"@smithy/util-middleware@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz" + integrity sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA== + dependencies: + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/util-retry@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz" + integrity sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg== + dependencies: + "@smithy/service-error-classification" "^4.2.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/util-stream@^4.5.6": + version "4.5.6" + resolved "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz" + integrity sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ== + dependencies: + "@smithy/fetch-http-handler" "^5.3.6" + "@smithy/node-http-handler" "^4.4.5" + "@smithy/types" "^4.9.0" + "@smithy/util-base64" "^4.3.0" + "@smithy/util-buffer-from" "^4.2.0" + "@smithy/util-hex-encoding" "^4.2.0" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-uri-escape@^4.2.0": + version "4.2.0" + resolved "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz" + integrity sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA== + dependencies: + tslib "^2.6.2" + +"@smithy/util-utf8@^2.0.0": + version "2.3.0" + resolved "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz" + integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== + dependencies: + "@smithy/util-buffer-from" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-utf8@^4.2.0": + version "4.2.0" + resolved "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz" + integrity sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw== + dependencies: + "@smithy/util-buffer-from" "^4.2.0" + tslib "^2.6.2" + +"@smithy/util-waiter@^4.2.5": + version "4.2.5" + resolved "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz" + integrity sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g== + dependencies: + "@smithy/abort-controller" "^4.2.5" + "@smithy/types" "^4.9.0" + tslib "^2.6.2" + +"@smithy/uuid@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz" + integrity sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw== + dependencies: + tslib "^2.6.2" + +"@swc/helpers@0.5.15": + version "0.5.15" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== + dependencies: + tslib "^2.8.0" + +"@tailwindcss/node@4.1.17": + version "4.1.17" + resolved "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz" + integrity sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg== + dependencies: + "@jridgewell/remapping" "^2.3.4" + enhanced-resolve "^5.18.3" + jiti "^2.6.1" + lightningcss "1.30.2" + magic-string "^0.30.21" + source-map-js "^1.2.1" + tailwindcss "4.1.17" + +"@tailwindcss/oxide-android-arm64@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz#17f0dc901f88a979c5bff618181bce596dff596d" + integrity sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ== + +"@tailwindcss/oxide-darwin-arm64@4.1.17": + version "4.1.17" + resolved "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz" + integrity sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg== + +"@tailwindcss/oxide-darwin-x64@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz#6dad270d2777508f55e2b73eca0eaef625bc45a7" + integrity sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog== + +"@tailwindcss/oxide-freebsd-x64@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz#e7628b4602ac7d73c11a9922ecb83c24337eff55" + integrity sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g== + +"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz#4d96a6fe4c7ed20e7a013101ee46f46caca2233e" + integrity sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ== + +"@tailwindcss/oxide-linux-arm64-gnu@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz#adc3c01cd73610870bfc21db5713571e08fb2210" + integrity sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ== + +"@tailwindcss/oxide-linux-arm64-musl@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz#39ceda30407af56a1ee125b2c5ce856c6d29250f" + integrity sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg== + +"@tailwindcss/oxide-linux-x64-gnu@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz#a3d4bd876c04d09856af0c394f5095fbc8a2b14c" + integrity sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ== + +"@tailwindcss/oxide-linux-x64-musl@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz#bdc20aa4fb2d28cc928f2cfffff7a9cd03a51d5b" + integrity sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ== + +"@tailwindcss/oxide-wasm32-wasi@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz#7c0804748935928751838f86ff4f879c38f8a6d7" + integrity sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg== + dependencies: + "@emnapi/core" "^1.6.0" + "@emnapi/runtime" "^1.6.0" + "@emnapi/wasi-threads" "^1.1.0" + "@napi-rs/wasm-runtime" "^1.0.7" + "@tybys/wasm-util" "^0.10.1" + tslib "^2.4.0" + +"@tailwindcss/oxide-win32-arm64-msvc@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz#7222fc2ceee9d45ebe5aebf38707ee9833a20475" + integrity sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A== + +"@tailwindcss/oxide-win32-x64-msvc@4.1.17": + version "4.1.17" + resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz#ac79087f451dfcd5c3099589027a5732b045a3bf" + integrity sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw== + +"@tailwindcss/oxide@4.1.17": + version "4.1.17" + resolved "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz" + integrity sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA== + optionalDependencies: + "@tailwindcss/oxide-android-arm64" "4.1.17" + "@tailwindcss/oxide-darwin-arm64" "4.1.17" + "@tailwindcss/oxide-darwin-x64" "4.1.17" + "@tailwindcss/oxide-freebsd-x64" "4.1.17" + "@tailwindcss/oxide-linux-arm-gnueabihf" "4.1.17" + "@tailwindcss/oxide-linux-arm64-gnu" "4.1.17" + "@tailwindcss/oxide-linux-arm64-musl" "4.1.17" + "@tailwindcss/oxide-linux-x64-gnu" "4.1.17" + "@tailwindcss/oxide-linux-x64-musl" "4.1.17" + "@tailwindcss/oxide-wasm32-wasi" "4.1.17" + "@tailwindcss/oxide-win32-arm64-msvc" "4.1.17" + "@tailwindcss/oxide-win32-x64-msvc" "4.1.17" + +"@tailwindcss/postcss@4.1.17": + version "4.1.17" + resolved "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.17.tgz" + integrity sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw== + dependencies: + "@alloc/quick-lru" "^5.2.0" + "@tailwindcss/node" "4.1.17" + "@tailwindcss/oxide" "4.1.17" + postcss "^8.4.41" + tailwindcss "4.1.17" + +"@tybys/wasm-util@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + +"@types/node@20.19.25": + version "20.19.25" + resolved "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz" + integrity sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ== + dependencies: + undici-types "~6.21.0" + +"@types/react-dom@19.2.3": + version "19.2.3" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz" + integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== + +"@types/react@19.2.7": + version "19.2.7" + resolved "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz" + integrity sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg== + dependencies: + csstype "^3.2.2" + +abitype@1.1.0, abitype@^1.0.9: + version "1.1.0" + resolved "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz" + integrity sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A== + +bowser@^2.11.0: + version "2.13.1" + resolved "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz" + integrity sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw== + +caniuse-lite@^1.0.30001579: + version "1.0.30001759" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz#d569e7b010372c6b0ca3946e30dada0a2e9d5006" + integrity sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw== + +client-only@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + +csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + +detect-libc@^2.0.3, detect-libc@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + +enhanced-resolve@^5.18.3: + version "5.18.3" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz" + integrity sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +eventemitter3@5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +fast-xml-parser@5.2.5: + version "5.2.5" + resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz" + integrity sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ== + dependencies: + strnum "^2.1.0" + +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +isows@1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz" + integrity sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg== + +jiti@^2.6.1: + version "2.6.1" + resolved "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz" + integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== + +lightningcss-android-arm64@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz#6966b7024d39c94994008b548b71ab360eb3a307" + integrity sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A== + +lightningcss-darwin-arm64@1.30.2: + version "1.30.2" + resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz" + integrity sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA== + +lightningcss-darwin-x64@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz#5ce87e9cd7c4f2dcc1b713f5e8ee185c88d9b7cd" + integrity sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ== + +lightningcss-freebsd-x64@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz#6ae1d5e773c97961df5cff57b851807ef33692a5" + integrity sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA== + +lightningcss-linux-arm-gnueabihf@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz#62c489610c0424151a6121fa99d77731536cdaeb" + integrity sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA== + +lightningcss-linux-arm64-gnu@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz#2a3661b56fe95a0cafae90be026fe0590d089298" + integrity sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A== + +lightningcss-linux-arm64-musl@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz#d7ddd6b26959245e026bc1ad9eb6aa983aa90e6b" + integrity sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA== + +lightningcss-linux-x64-gnu@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz#5a89814c8e63213a5965c3d166dff83c36152b1a" + integrity sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w== + +lightningcss-linux-x64-musl@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz#808c2e91ce0bf5d0af0e867c6152e5378c049728" + integrity sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA== + +lightningcss-win32-arm64-msvc@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz#ab4a8a8a2e6a82a4531e8bbb6bf0ff161ee6625a" + integrity sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ== + +lightningcss-win32-x64-msvc@1.30.2: + version "1.30.2" + resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz#f01f382c8e0a27e1c018b0bee316d210eac43b6e" + integrity sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw== + +lightningcss@1.30.2: + version "1.30.2" + resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz" + integrity sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ== + dependencies: + detect-libc "^2.0.3" + optionalDependencies: + lightningcss-android-arm64 "1.30.2" + lightningcss-darwin-arm64 "1.30.2" + lightningcss-darwin-x64 "1.30.2" + lightningcss-freebsd-x64 "1.30.2" + lightningcss-linux-arm-gnueabihf "1.30.2" + lightningcss-linux-arm64-gnu "1.30.2" + lightningcss-linux-arm64-musl "1.30.2" + lightningcss-linux-x64-gnu "1.30.2" + lightningcss-linux-x64-musl "1.30.2" + lightningcss-win32-arm64-msvc "1.30.2" + lightningcss-win32-x64-msvc "1.30.2" + +magic-string@^0.30.21: + version "0.30.21" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +nanoid@^3.3.11, nanoid@^3.3.6: + version "3.3.11" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +next@16.0.7: + version "16.0.7" + resolved "https://registry.npmjs.org/next/-/next-16.0.7.tgz" + integrity sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A== + dependencies: + "@next/env" "16.0.7" + "@swc/helpers" "0.5.15" + caniuse-lite "^1.0.30001579" + postcss "8.4.31" + styled-jsx "5.1.6" + optionalDependencies: + "@next/swc-darwin-arm64" "16.0.7" + "@next/swc-darwin-x64" "16.0.7" + "@next/swc-linux-arm64-gnu" "16.0.7" + "@next/swc-linux-arm64-musl" "16.0.7" + "@next/swc-linux-x64-gnu" "16.0.7" + "@next/swc-linux-x64-musl" "16.0.7" + "@next/swc-win32-arm64-msvc" "16.0.7" + "@next/swc-win32-x64-msvc" "16.0.7" + sharp "^0.34.4" + +ox@0.9.6: + version "0.9.6" + resolved "https://registry.npmjs.org/ox/-/ox-0.9.6.tgz" + integrity sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg== + dependencies: + "@adraffy/ens-normalize" "^1.11.0" + "@noble/ciphers" "^1.3.0" + "@noble/curves" "1.9.1" + "@noble/hashes" "^1.8.0" + "@scure/bip32" "^1.7.0" + "@scure/bip39" "^1.6.0" + abitype "^1.0.9" + eventemitter3 "5.0.1" + +picocolors@^1.0.0, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +postcss@8.4.31: + version "8.4.31" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.4.41: + version "8.5.6" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +react-dom@19.2.1: + version "19.2.1" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz" + integrity sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg== + dependencies: + scheduler "^0.27.0" + +react@19.2.1: + version "19.2.1" + resolved "https://registry.npmjs.org/react/-/react-19.2.1.tgz" + integrity sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw== + +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== + +semver@^7.7.3: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + +sharp@^0.34.4: + version "0.34.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.5.tgz#b6f148e4b8c61f1797bde11a9d1cfebbae2c57b0" + integrity sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg== + dependencies: + "@img/colour" "^1.0.0" + detect-libc "^2.1.2" + semver "^7.7.3" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.34.5" + "@img/sharp-darwin-x64" "0.34.5" + "@img/sharp-libvips-darwin-arm64" "1.2.4" + "@img/sharp-libvips-darwin-x64" "1.2.4" + "@img/sharp-libvips-linux-arm" "1.2.4" + "@img/sharp-libvips-linux-arm64" "1.2.4" + "@img/sharp-libvips-linux-ppc64" "1.2.4" + "@img/sharp-libvips-linux-riscv64" "1.2.4" + "@img/sharp-libvips-linux-s390x" "1.2.4" + "@img/sharp-libvips-linux-x64" "1.2.4" + "@img/sharp-libvips-linuxmusl-arm64" "1.2.4" + "@img/sharp-libvips-linuxmusl-x64" "1.2.4" + "@img/sharp-linux-arm" "0.34.5" + "@img/sharp-linux-arm64" "0.34.5" + "@img/sharp-linux-ppc64" "0.34.5" + "@img/sharp-linux-riscv64" "0.34.5" + "@img/sharp-linux-s390x" "0.34.5" + "@img/sharp-linux-x64" "0.34.5" + "@img/sharp-linuxmusl-arm64" "0.34.5" + "@img/sharp-linuxmusl-x64" "0.34.5" + "@img/sharp-wasm32" "0.34.5" + "@img/sharp-win32-arm64" "0.34.5" + "@img/sharp-win32-ia32" "0.34.5" + "@img/sharp-win32-x64" "0.34.5" + +source-map-js@^1.0.2, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +strnum@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz" + integrity sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw== + +styled-jsx@5.1.6: + version "5.1.6" + resolved "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz" + integrity sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA== + dependencies: + client-only "0.0.1" + +tailwindcss@4.1.17: + version "4.1.17" + resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz" + integrity sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q== + +tapable@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz" + integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== + +tslib@^2.4.0, tslib@^2.6.2, tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +typescript@5.9.3: + version "5.9.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +viem@2.40.3: + version "2.40.3" + resolved "https://registry.npmjs.org/viem/-/viem-2.40.3.tgz" + integrity sha512-feYfEpbgjRkZYQpwcgxqkWzjxHI5LSDAjcGetHHwDRuX9BRQHUdV8ohrCosCYpdEhus/RknD3/bOd4qLYVPPuA== + dependencies: + "@noble/curves" "1.9.1" + "@noble/hashes" "1.8.0" + "@scure/bip32" "1.7.0" + "@scure/bip39" "1.6.0" + abitype "1.1.0" + isows "1.0.7" + ox "0.9.6" + ws "8.18.3" + +ws@8.18.3: + version "8.18.3" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==