diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 5f4d1970c..0be051938 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -23,6 +23,7 @@ jobs: - nightly features: - "" + steps: - name: Checkout sources uses: actions/checkout@v4 @@ -66,13 +67,10 @@ jobs: - name: Compile op-rbuilder run: cargo build -p op-rbuilder --bin op-rbuilder - - name: Download op-reth - run: | - ./scripts/ci/download-op-reth.sh - echo "$(pwd)" >> $GITHUB_PATH - - name: Lint run: make lint - name: Test run: make test + env: + TESTS_TEMP_DIR: ${{ github.workspace }} diff --git a/Cargo.lock b/Cargo.lock index 840a4cc2e..d9dc6c20d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,15 +54,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom 0.3.3", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -97,9 +97,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7734aecfc58a597dde036e4c5cace2ae43e2f8bf3d406b022a1ef34da178dd49" +checksum = "d6967ca1ed656766e471bc323da42fb0db320ca5e1418b408650e98e4757b3d2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -135,9 +135,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d0d4b81bd538d023236b5301582c962aa2f2043d1b3a1373ea88fbee82a8e0" +checksum = "142daffb15d5be1a2b20d2cd540edbcef03037b55d4ff69dc06beb4d06286dba" dependencies = [ "alloy-consensus", "alloy-eips", @@ -150,15 +150,14 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f90b63261b7744642f6075ed17db6de118eecbe9516ea6c6ffd444b80180b75" +checksum = "f9135eb501feccf7f4cb8a183afd406a65483fdad7bbd7332d0470e5d725c92f" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-type-parser", "alloy-sol-types", - "const-hex", "derive_more", "itoa", "serde", @@ -230,7 +229,7 @@ dependencies = [ "ethereum_ssz", "ethereum_ssz_derive", "serde", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -267,9 +266,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbff8445282ec080c2673692062bd4930d7a0d6bda257caf138cfc650c503000" +checksum = "977d2492ce210e34baf7b36afaacea272c96fbe6774c47e23f97d14033c0e94f" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -281,9 +280,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0068ae277f5ee3153a95eaea8ff10e188ed8ccde9b7f9926305415a2c0ab2442" +checksum = "8b26fdd571915bafe857fccba4ee1a4f352965800e46a53e4a5f50187b7776fa" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -363,9 +362,9 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ddfbb5cc9f614efa5d56e0d7226214bb67b29271d44b6ddfcbbe25eb0ff898b" +checksum = "08b147547aff595aa3d4c2fc2c8146263e18d3372909def423619ed631ecbcfa" dependencies = [ "alloy-hardforks", "auto_impl", @@ -373,9 +372,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "a326d47106039f38b811057215a92139f46eef7983a4b77b10930a0ea5685b1e" dependencies = [ "alloy-rlp", "arbitrary", @@ -385,8 +384,8 @@ dependencies = [ "derive_arbitrary", "derive_more", "foldhash", - "getrandom 0.3.2", - "hashbrown 0.15.2", + "getrandom 0.3.3", + "hashbrown 0.15.3", "indexmap 2.9.0", "itoa", "k256", @@ -394,7 +393,7 @@ dependencies = [ "paste", "proptest", "proptest-derive", - "rand 0.9.0", + "rand 0.9.1", "ruint", "rustc-hash 2.1.1", "serde", @@ -448,9 +447,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfbb328bc2538e10b17570eae25e5ecb2cdb97b729caa115617e124b05e9463e" +checksum = "8550f7306e0230fc835eb2ff4af0a96362db4b6fc3f25767d161e0ad0ac765bf" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -469,9 +468,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -480,13 +479,13 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -556,9 +555,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518c67d8465f885c7524f0fe2cc32861635e9409a6f2efc015e306ca8d73f377" +checksum = "508b2fbe66d952089aa694e53802327798806498cd29ff88c75135770ecaabfc" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -720,23 +719,23 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3ef8e0d622453d969ba3cded54cf6800efdc85cb929fe22c5bdf8335666757" +checksum = "d4be1ce1274ddd7fdfac86e5ece1b225e9bba1f2327e20fbb30ee6b9cc1423fe" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e84bd0693c69a8fbe3ec0008465e029c6293494df7cb07580bf4a33eff52e1" +checksum = "01e92f3708ea4e0d9139001c86c051c538af0146944a2a9c7181753bd944bf57" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -745,16 +744,16 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3de663412dadf9b64f4f92f507f78deebcc92339d12cf15f88ded65d41c7935" +checksum = "9afe1bd348a41f8c9b4b54dfb314886786d6201235b0b3f47198b9d910c86bb2" dependencies = [ "const-hex", "dunce", @@ -762,15 +761,15 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251273c5aa1abb590852f795c938730fa641832fc8fa77b5478ed1bf11b6097e" +checksum = "d6195df2acd42df92a380a8db6205a5c7b41282d0ce3f4c665ecf7911ac292f1" dependencies = [ "serde", "winnow", @@ -778,14 +777,13 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5460a975434ae594fe2b91586253c1beb404353b78f0a55bf124abcd79557b15" +checksum = "6185e98a79cf19010722f48a74b5a65d153631d2f038cabd250f4b9e9813b8ad" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-macro", - "const-hex", "serde", ] @@ -829,9 +827,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed6f489a90092929a480cbe30d16a147fa89f859f8d9ef26f60555681503c8b" +checksum = "606af17a7e064d219746f6d2625676122c79d78bf73dfe746d6db9ecd7dbcb85" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -849,9 +847,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.3" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30aa80247a43d71bf683294907e74c634be7b4307d02530c856fac7fc4c2566a" +checksum = "e0c6f9b37cd8d44aab959613966cc9d4d7a9b429c575cec43b3e5b46ea109a79" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -902,9 +900,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -917,36 +915,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -967,7 +965,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1016,7 +1014,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "itertools 0.13.0", "num-bigint", "num-integer", @@ -1109,7 +1107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1147,7 +1145,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1162,7 +1160,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -1236,7 +1234,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1292,9 +1290,9 @@ checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" [[package]] name = "async-compression" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a194f9d963d8099596278594b3107448656ba73831c9d8c783e613ce86da64" +checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" dependencies = [ "brotli", "flate2", @@ -1317,17 +1315,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -1347,7 +1334,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1358,7 +1345,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1396,7 +1383,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1407,9 +1394,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" dependencies = [ "aws-lc-sys", "zeroize", @@ -1417,9 +1404,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" dependencies = [ "bindgen 0.69.5", "cc", @@ -1477,9 +1464,9 @@ dependencies = [ [[package]] name = "backon" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0b50b1b78dbadd44ab18b3c794e496f3a139abb9fbc27d9c94c4eebbb96496" +checksum = "302eaff5357a264a2c42f127ecb8bac761cf99749fc3dc95677e2743991f99e7" dependencies = [ "fastrand", "tokio", @@ -1487,9 +1474,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -1520,9 +1507,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bimap" @@ -1545,10 +1532,10 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -1558,7 +1545,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.100", + "syn 2.0.101", "which", ] @@ -1568,7 +1555,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1577,7 +1564,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1619,9 +1606,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -1641,9 +1628,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", "arrayvec", @@ -1681,9 +1668,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -1697,7 +1684,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c340fe0f0b267787095cbe35240c6786ff19da63ec7b69367ba338eace8169b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "boa_interner", "boa_macros", "boa_string", @@ -1713,7 +1700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f620c3f06f51e65c0504ddf04978be1b814ac6586f0b45f6019801ab5efd37f9" dependencies = [ "arrayvec", - "bitflags 2.9.0", + "bitflags 2.9.1", "boa_ast", "boa_gc", "boa_interner", @@ -1725,8 +1712,8 @@ dependencies = [ "cfg-if", "dashmap 6.1.0", "fast-float2", - "hashbrown 0.15.2", - "icu_normalizer", + "hashbrown 0.15.3", + "icu_normalizer 1.5.0", "indexmap 2.9.0", "intrusive-collections", "itertools 0.13.0", @@ -1760,7 +1747,7 @@ dependencies = [ "boa_macros", "boa_profiler", "boa_string", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "thin-vec", ] @@ -1772,7 +1759,7 @@ checksum = "42407a3b724cfaecde8f7d4af566df4b56af32a2f11f0956f5570bb974e7f749" dependencies = [ "boa_gc", "boa_macros", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "indexmap 2.9.0", "once_cell", "phf", @@ -1788,7 +1775,7 @@ checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] @@ -1798,13 +1785,13 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cc142dac798cdc6e2dbccfddeb50f36d2523bb977a976e19bdb3ae19b740804" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "boa_ast", "boa_interner", "boa_macros", "boa_profiler", "fast-float2", - "icu_properties", + "icu_properties 1.5.1", "num-bigint", "num-traits", "regress", @@ -1830,6 +1817,51 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "bollard" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af706e9dc793491dd382c99c22fde6e9934433d4cc0d6a4b34eb2cdc57a5c917" +dependencies = [ + "base64", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.12", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.48.2-rc.28.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cdf0fccd5341b38ae0be74b74410bdd5eceeea8876dc149a13edfe57e3b259" +dependencies = [ + "serde", + "serde_json", + "serde_repr", + "serde_with", +] + [[package]] name = "boyer-moore-magiclen" version = "0.2.20" @@ -1841,9 +1873,9 @@ dependencies = [ [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1852,9 +1884,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1882,9 +1914,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byte-slice-cast" @@ -1894,15 +1926,15 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytecount" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" dependencies = [ "bytemuck_derive", ] @@ -1915,7 +1947,7 @@ checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1951,9 +1983,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] @@ -2049,9 +2081,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2085,9 +2117,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.36" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", "clap_derive", @@ -2095,9 +2127,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.36" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -2114,7 +2146,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2134,9 +2166,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "combine" @@ -2205,9 +2237,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" dependencies = [ "cfg-if", "cpufeatures", @@ -2269,9 +2301,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -2303,9 +2335,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -2371,7 +2403,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "crossterm_winapi", "mio", "parking_lot", @@ -2420,15 +2452,21 @@ dependencies = [ ] [[package]] -name = "crypto-mac" -version = "0.8.0" +name = "ctor" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "a4735f265ba6a1188052ca32d461028a7d1125868be18e287e756019da7607b5" dependencies = [ - "generic-array", - "subtle", + "ctor-proc-macro", + "dtor", ] +[[package]] +name = "ctor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" + [[package]] name = "ctr" version = "0.9.2" @@ -2462,7 +2500,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2486,7 +2524,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2497,7 +2535,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2550,7 +2588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2572,9 +2610,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "zeroize", @@ -2603,13 +2641,13 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.2.7" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2620,7 +2658,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2641,7 +2679,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2651,7 +2689,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2672,7 +2710,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "unicode-xid", ] @@ -2786,7 +2824,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2801,6 +2839,21 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dtor" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + [[package]] name = "dunce" version = "1.0.5" @@ -2848,7 +2901,7 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "subtle", "zeroize", ] @@ -2862,7 +2915,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2935,7 +2988,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2955,7 +3008,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2966,9 +3019,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -2991,7 +3044,7 @@ checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" dependencies = [ "cpufeatures", "ring", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -3031,7 +3084,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3272,7 +3325,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3323,15 +3376,16 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "generator" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ + "cc", "cfg-if", "libc", "log", "rustversion", - "windows 0.58.0", + "windows 0.61.1", ] [[package]] @@ -3348,9 +3402,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", @@ -3361,9 +3415,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", @@ -3391,11 +3445,11 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git2" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5220b8ba44c68a9a7f7a7659e864dd73692e417ef0211bea133c7b74e031eeb9" +checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "libgit2-sys", "log", @@ -3467,9 +3521,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", @@ -3513,9 +3567,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -3550,9 +3604,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -3574,14 +3628,12 @@ dependencies = [ [[package]] name = "hickory-proto" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d844af74f7b799e41c78221be863bade11c430d46042c3b49ca8ae0c6d27287" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ - "async-recursion", "async-trait", "cfg-if", - "critical-section", "data-encoding", "enum-as-inner", "futures-channel", @@ -3590,7 +3642,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.0", + "rand 0.9.1", "ring", "serde", "thiserror 2.0.12", @@ -3602,9 +3654,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a128410b38d6f931fcc6ca5c107a3b02cabd6c05967841269a4ad65d23c44331" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ "cfg-if", "futures-util", @@ -3613,7 +3665,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.0", + "rand 0.9.1", "resolv-conf", "serde", "smallvec", @@ -3628,17 +3680,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac", - "digest 0.9.0", + "hmac", ] [[package]] @@ -3650,17 +3692,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array", - "hmac 0.8.1", -] - [[package]] name = "home" version = "0.5.11" @@ -3670,17 +3701,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "hostname" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" -dependencies = [ - "cfg-if", - "libc", - "windows-link", -] - [[package]] name = "http" version = "1.3.1" @@ -3776,13 +3796,27 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", @@ -3793,7 +3827,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots", + "webpki-roots 1.0.0", ] [[package]] @@ -3827,17 +3861,21 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ + "base64", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -3845,6 +3883,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -3857,7 +3910,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core 0.61.2", ] [[package]] @@ -3876,9 +3929,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", - "yoke", + "yoke 0.7.5", + "zerofrom", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke 0.8.0", "zerofrom", - "zerovec", + "zerovec 0.11.2", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap 0.8.0", + "tinystr 0.8.1", + "writeable 0.6.1", + "zerovec 0.11.2", ] [[package]] @@ -3888,10 +3967,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "litemap 0.7.5", + "tinystr 0.7.6", + "writeable 0.5.5", + "zerovec 0.10.4", ] [[package]] @@ -3903,9 +3982,9 @@ dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", + "icu_provider 1.5.0", + "tinystr 0.7.6", + "zerovec 0.10.4", ] [[package]] @@ -3921,15 +4000,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", + "icu_collections 1.5.0", + "icu_normalizer_data 1.5.1", + "icu_properties 1.5.1", + "icu_provider 1.5.0", "smallvec", "utf16_iter", "utf8_iter", "write16", - "zerovec", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections 2.0.0", + "icu_normalizer_data 2.0.0", + "icu_properties 2.0.1", + "icu_provider 2.0.0", + "smallvec", + "zerovec 0.11.2", ] [[package]] @@ -3938,6 +4032,12 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + [[package]] name = "icu_properties" version = "1.5.1" @@ -3945,12 +4045,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", - "icu_collections", + "icu_collections 1.5.0", "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", + "icu_properties_data 1.5.1", + "icu_provider 1.5.0", + "tinystr 0.7.6", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections 2.0.0", + "icu_locale_core", + "icu_properties_data 2.0.1", + "icu_provider 2.0.0", + "potential_utf", + "zerotrie", + "zerovec 0.11.2", ] [[package]] @@ -3959,6 +4075,12 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + [[package]] name = "icu_provider" version = "1.5.0" @@ -3969,11 +4091,28 @@ dependencies = [ "icu_locid", "icu_provider_macros", "stable_deref_trait", - "tinystr", - "writeable", - "yoke", + "tinystr 0.7.6", + "writeable 0.5.5", + "yoke 0.7.5", + "zerofrom", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr 0.8.1", + "writeable 0.6.1", + "yoke 0.8.0", "zerofrom", - "zerovec", + "zerotrie", + "zerovec 0.11.2", ] [[package]] @@ -3984,7 +4123,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4006,12 +4145,12 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "icu_normalizer", - "icu_properties", + "icu_normalizer 2.0.0", + "icu_properties 2.0.1", ] [[package]] @@ -4041,7 +4180,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4088,7 +4227,7 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", ] @@ -4104,7 +4243,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "inotify-sys", "libc", ] @@ -4138,7 +4277,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4208,6 +4347,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -4260,7 +4408,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -4347,7 +4495,7 @@ dependencies = [ "jsonrpsee-types 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot", "pin-project", - "rand 0.9.0", + "rand 0.9.1", "rustc-hash 2.1.1", "serde", "serde_json", @@ -4373,7 +4521,7 @@ dependencies = [ "jsonrpsee-types 0.25.1 (git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff)", "parking_lot", "pin-project", - "rand 0.9.0", + "rand 0.9.1", "rustc-hash 2.1.1", "serde", "serde_json", @@ -4438,7 +4586,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4450,7 +4598,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4581,7 +4729,7 @@ dependencies = [ "elliptic-curve", "once_cell", "serdect", - "sha2 0.10.8", + "sha2 0.10.9", "signature", ] @@ -4606,9 +4754,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", @@ -4656,35 +4804,35 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libp2p-identity" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b5621d159b32282eac446bed6670c39c7dc68a200a992d8f056afa0066f6d" +checksum = "fbb68ea10844211a59ce46230909fd0ea040e8a192454d4cc2ee0d53e12280eb" dependencies = [ "asn1_der", "bs58", "ed25519-dalek", "hkdf", - "libsecp256k1", + "k256", "multihash", "quick-protobuf", - "sha2 0.10.8", - "thiserror 1.0.69", + "sha2 0.10.9", + "thiserror 2.0.12", "tracing", "zeroize", ] @@ -4706,7 +4854,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "redox_syscall", ] @@ -4720,14 +4868,12 @@ dependencies = [ "arrayref", "base64", "digest 0.9.0", - "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", "serde", "sha2 0.9.9", - "typenum", ] [[package]] @@ -4805,11 +4951,17 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -4841,7 +4993,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -4850,9 +5002,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lz4" version = "1.28.1" @@ -4895,7 +5053,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4939,9 +5097,9 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7deb012b3b2767169ff203fadb4c6b0b82b947512e5eb9e0b78c2e186ad9e3" +checksum = "25dea7ac8057892855ec285c440160265225438c3c45072613c25a4b26e98ef5" dependencies = [ "ahash", "portable-atomic", @@ -4956,7 +5114,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4998,20 +5156,20 @@ dependencies = [ [[package]] name = "metrics-util" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbd4884b1dd24f7d6628274a2f5ae22465c337c5ba065ec9b6edccddf8acc673" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" dependencies = [ "aho-corasick", "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "indexmap 2.9.0", "metrics", "ordered-float", "quanta", "radix_trie", - "rand 0.8.5", + "rand 0.9.1", "rand_xoshiro", "sketches-ddsketch", ] @@ -5064,14 +5222,14 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5163,6 +5321,15 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -5205,7 +5372,7 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "filetime", "fsevent-sys", "inotify", @@ -5326,9 +5493,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", @@ -5352,7 +5519,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -5397,6 +5564,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "op-alloy-consensus" version = "0.17.2" @@ -5506,9 +5679,12 @@ dependencies = [ "alloy-transport-http", "anyhow", "async-trait", + "bollard", "chrono", "clap", "clap_builder", + "ctor", + "dashmap 6.1.0", "derive_more", "eyre", "futures", @@ -5519,6 +5695,7 @@ dependencies = [ "jsonrpsee-types 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "metrics", "moka", + "nanoid", "op-alloy-consensus", "op-alloy-flz", "op-alloy-network", @@ -5526,7 +5703,7 @@ dependencies = [ "op-alloy-rpc-types-engine", "op-revm", "parking_lot", - "rand 0.9.0", + "rand 0.9.1", "reth", "reth-basic-payload-builder", "reth-chain-state", @@ -5538,6 +5715,7 @@ dependencies = [ "reth-evm", "reth-execution-types", "reth-exex", + "reth-ipc", "reth-metrics", "reth-network-peers", "reth-node-api", @@ -5580,6 +5758,7 @@ dependencies = [ "serde_with", "serde_yaml", "shellexpand", + "tar", "tempfile", "thiserror 1.0.69", "tikv-jemallocator", @@ -5616,11 +5795,11 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -5637,7 +5816,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -5648,9 +5827,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -5820,7 +5999,7 @@ dependencies = [ "glob", "opentelemetry 0.29.1", "percent-encoding", - "rand 0.9.0", + "rand 0.9.1", "serde_json", "thiserror 2.0.12", "tracing", @@ -5856,7 +6035,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -5871,9 +6050,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ "arbitrary", "arrayvec", @@ -5889,14 +6068,14 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -5907,9 +6086,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -5917,9 +6096,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -6002,7 +6181,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -6031,7 +6210,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -6091,9 +6270,18 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec 0.11.2", +] [[package]] name = "powerfmt" @@ -6107,7 +6295,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -6122,12 +6310,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" dependencies = [ "proc-macro2", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -6178,7 +6366,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -6196,7 +6384,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "chrono", "flate2", "hex", @@ -6210,7 +6398,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "chrono", "hex", ] @@ -6223,7 +6411,7 @@ checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.0", + "bitflags 2.9.1", "lazy_static", "num-traits", "rand 0.8.5", @@ -6253,7 +6441,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -6276,7 +6464,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -6285,7 +6473,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "memchr", "unicase", ] @@ -6322,9 +6510,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", "cfg_aliases", @@ -6342,13 +6530,14 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.10" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.2", - "rand 0.9.0", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", "ring", "rustc-hash 2.1.1", "rustls", @@ -6362,9 +6551,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ "cfg_aliases", "libc", @@ -6419,14 +6608,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", "serde", - "zerocopy 0.8.24", ] [[package]] @@ -6455,7 +6643,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -6464,7 +6652,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "serde", ] @@ -6479,11 +6667,11 @@ dependencies = [ [[package]] name = "rand_xoshiro" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] @@ -6492,7 +6680,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cassowary", "compact_str", "crossterm", @@ -6513,7 +6701,7 @@ version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -6544,11 +6732,11 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -6557,7 +6745,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] @@ -6568,7 +6756,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 2.0.12", ] @@ -6623,15 +6811,15 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ef7fa9ed0256d64a688a3747d0fef7a88851c18a5e1d57f115f38ec2e09366" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", "memchr", ] [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" dependencies = [ "base64", "bytes", @@ -6656,7 +6844,6 @@ dependencies = [ "quinn", "rustls", "rustls-native-certs", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -6667,24 +6854,21 @@ dependencies = [ "tokio-rustls", "tokio-util", "tower 0.5.2", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", - "windows-registry", + "webpki-roots 1.0.0", ] [[package]] name = "resolv-conf" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48375394603e3dd4b2d64371f7148fd8c7baa2680e28741f2cb8d23b59e3d4c4" -dependencies = [ - "hostname", -] +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] name = "reth" @@ -6770,7 +6954,7 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.9.0", + "rand 0.9.1", "reth-chainspec", "reth-errors", "reth-ethereum-primitives", @@ -6948,7 +7132,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -7151,7 +7335,7 @@ dependencies = [ "futures", "itertools 0.14.0", "metrics", - "rand 0.9.0", + "rand 0.9.1", "reth-chainspec", "reth-ethereum-forks", "reth-metrics", @@ -7203,12 +7387,17 @@ dependencies = [ "rayon", "reth-config", "reth-consensus", + "reth-db", + "reth-db-api", + "reth-ethereum-primitives", "reth-metrics", "reth-network-p2p", "reth-network-peers", "reth-primitives-traits", "reth-storage-api", "reth-tasks", + "reth-testing-utils", + "tempfile", "thiserror 2.0.12", "tokio", "tokio-stream", @@ -7232,12 +7421,12 @@ dependencies = [ "digest 0.10.7", "futures", "generic-array", - "hmac 0.12.1", + "hmac", "pin-project", "rand 0.8.5", "reth-network-peers", "secp256k1", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "thiserror 2.0.12", "tokio", @@ -7344,6 +7533,7 @@ dependencies = [ "parking_lot", "rayon", "reth-chain-state", + "reth-chainspec", "reth-consensus", "reth-db", "reth-engine-primitives", @@ -7357,9 +7547,13 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-prune", + "reth-prune-types", "reth-revm", + "reth-stages", "reth-stages-api", + "reth-static-file", "reth-tasks", + "reth-tracing", "reth-trie", "reth-trie-db", "reth-trie-parallel", @@ -7426,7 +7620,7 @@ dependencies = [ "futures-util", "reqwest", "reth-fs-util", - "sha2 0.10.8", + "sha2 0.10.9", "tokio", ] @@ -7599,7 +7793,7 @@ dependencies = [ "reth-payload-primitives", "reth-primitives-traits", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "thiserror 2.0.12", ] @@ -7858,7 +8052,7 @@ name = "reth-libmdbx" version = "1.4.8" source = "git+https://github.com/paradigmxyz/reth?tag=v1.4.8#127595e23079de2c494048d0821ea1f1107eb624" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "dashmap 6.1.0", "derive_more", @@ -7933,7 +8127,7 @@ dependencies = [ "parking_lot", "pin-project", "rand 0.8.5", - "rand 0.9.0", + "rand 0.9.1", "reth-chainspec", "reth-consensus", "reth-discv4", @@ -8002,6 +8196,7 @@ dependencies = [ "auto_impl", "derive_more", "futures", + "parking_lot", "reth-consensus", "reth-eth-wire-types", "reth-ethereum-primitives", @@ -8163,7 +8358,7 @@ dependencies = [ "eyre", "futures", "humantime", - "rand 0.9.0", + "rand 0.9.1", "reth-chainspec", "reth-cli-util", "reth-config", @@ -8507,7 +8702,7 @@ dependencies = [ "reth-transaction-pool", "revm", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "thiserror 2.0.12", "tracing", ] @@ -8632,6 +8827,7 @@ version = "1.4.8" source = "git+https://github.com/paradigmxyz/reth?tag=v1.4.8#127595e23079de2c494048d0821ea1f1107eb624" dependencies = [ "alloy-consensus", + "alloy-primitives", "alloy-rpc-types", "futures-util", "metrics", @@ -8955,7 +9151,7 @@ dependencies = [ "revm-primitives", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "thiserror 2.0.12", "tokio", "tokio-stream", @@ -9119,7 +9315,7 @@ dependencies = [ "jsonrpsee-core 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpsee-types 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "metrics", - "rand 0.9.0", + "rand 0.9.1", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -9203,11 +9399,13 @@ dependencies = [ "num-traits", "rayon", "reqwest", + "reth-chainspec", "reth-codecs", "reth-config", "reth-consensus", "reth-db", "reth-db-api", + "reth-ethereum-primitives", "reth-etl", "reth-evm", "reth-execution-types", @@ -9222,9 +9420,11 @@ dependencies = [ "reth-stages-api", "reth-static-file-types", "reth-storage-errors", + "reth-testing-utils", "reth-trie", "reth-trie-db", "serde", + "tempfile", "thiserror 2.0.12", "tokio", "tracing", @@ -9371,7 +9571,7 @@ dependencies = [ "alloy-genesis", "alloy-primitives", "rand 0.8.5", - "rand 0.9.0", + "rand 0.9.1", "reth-ethereum-primitives", "reth-primitives-traits", "secp256k1", @@ -9427,11 +9627,12 @@ dependencies = [ "alloy-rlp", "aquamarine", "auto_impl", - "bitflags 2.9.0", + "bitflags 2.9.1", "futures-util", "metrics", "parking_lot", - "rand 0.9.0", + "paste", + "rand 0.9.1", "reth-chain-state", "reth-chainspec", "reth-eth-wire-types", @@ -9748,7 +9949,7 @@ dependencies = [ "revm-primitives", "ripemd", "secp256k1", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -9768,7 +9969,7 @@ version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0040c61c30319254b34507383ba33d85f92949933adf6525a2cede05d165e1fa" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "revm-bytecode", "revm-primitives", "serde", @@ -9780,7 +9981,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -9792,7 +9993,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -9876,7 +10077,7 @@ dependencies = [ [[package]] name = "rollup-boost" version = "0.1.0" -source = "git+http://github.com/flashbots/rollup-boost?branch=main#baebd910c33e9a821ba8640082d33a507a62dc46" +source = "git+http://github.com/flashbots/rollup-boost?branch=main#9fdd722ced0c8430097e7edc5a9024f7aa27c5ad" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -9924,9 +10125,9 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "ruint" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a46eb779843b2c4f21fac5773e25d6d5b7c8f0922876c91541790d2ca27eef" +checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" dependencies = [ "alloy-rlp", "arbitrary", @@ -9942,7 +10143,7 @@ dependencies = [ "primitive-types", "proptest", "rand 0.8.5", - "rand 0.9.0", + "rand 0.9.1", "rlp", "ruint-macro", "serde", @@ -10007,7 +10208,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -10016,11 +10217,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", @@ -10029,9 +10230,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "aws-lc-rs", "log", @@ -10055,31 +10256,23 @@ dependencies = [ "security-framework 3.2.0", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] name = "rustls-platform-verifier" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5467026f437b4cb2a533865eaa73eb840019a0916f4b9ec563c6e617e086c9" +checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ - "core-foundation 0.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", @@ -10090,7 +10283,7 @@ dependencies = [ "rustls-webpki", "security-framework 3.2.0", "security-framework-sys", - "webpki-root-certs", + "webpki-root-certs 0.26.11", "windows-sys 0.59.0", ] @@ -10102,9 +10295,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "aws-lc-rs", "ring", @@ -10114,9 +10307,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rusty-fork" @@ -10225,7 +10418,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -10238,8 +10431,8 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -10311,7 +10504,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -10327,6 +10520,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -10375,7 +10579,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -10433,9 +10637,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -10488,9 +10692,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -10509,9 +10713,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -10613,9 +10817,9 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -10699,7 +10903,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -10712,7 +10916,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -10734,9 +10938,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -10745,14 +10949,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0f0d4760f4c2a0823063b2c70e97aa2ad185f57be195172ccc0e23c4b787c4" +checksum = "14c8c8f496c33dc6343dac05b4be8d9e0bca180a4caa81d7b8416b10cc2273cd" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -10766,13 +10970,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -10817,7 +11021,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15574aa79d3c04a12f3cb53ff976d5571e53b9d8e0bdbe4021df0a06473dd1c9" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "log", "memchr", "num-traits", @@ -10830,9 +11034,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.5", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -10868,7 +11072,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -10879,7 +11083,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -10982,7 +11186,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", - "zerovec", + "zerovec 0.10.4", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec 0.11.2", ] [[package]] @@ -11002,9 +11216,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -11026,7 +11240,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -11076,14 +11290,14 @@ dependencies = [ "tokio-native-tls", "tokio-rustls", "tungstenite", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -11096,9 +11310,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -11108,26 +11322,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tonic" version = "0.12.3" @@ -11206,7 +11427,7 @@ checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", "base64", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "futures-core", "futures-util", @@ -11267,20 +11488,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -11430,7 +11651,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -11467,7 +11688,7 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.9.0", + "rand 0.9.1", "rustls", "rustls-pki-types", "sha1", @@ -11630,13 +11851,15 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", + "js-sys", "serde", "sha1_smol", + "wasm-bindgen", ] [[package]] @@ -11706,7 +11929,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -11774,7 +11997,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -11809,7 +12032,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -11872,18 +12095,36 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "0.26.8" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.0", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" +checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] @@ -11957,6 +12198,28 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + [[package]] name = "windows-core" version = "0.57.0" @@ -11984,15 +12247,26 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", "windows-link", - "windows-result 0.3.2", - "windows-strings 0.4.0", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link", + "windows-threading", ] [[package]] @@ -12003,7 +12277,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -12014,7 +12288,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -12025,7 +12299,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -12036,7 +12310,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -12047,7 +12321,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -12058,7 +12332,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -12068,14 +12342,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] -name = "windows-registry" -version = "0.4.0" +name = "windows-numerics" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-result 0.3.2", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-core 0.61.2", + "windows-link", ] [[package]] @@ -12098,9 +12371,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] @@ -12117,18 +12390,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -12231,6 +12495,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -12413,9 +12686,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -12436,7 +12709,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -12451,6 +12724,12 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -12486,7 +12765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" dependencies = [ "libc", - "rustix 1.0.5", + "rustix 1.0.7", ] [[package]] @@ -12503,7 +12782,19 @@ checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", - "yoke-derive", + "yoke-derive 0.7.5", + "zerofrom", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive 0.8.0", "zerofrom", ] @@ -12515,48 +12806,40 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] [[package]] -name = "zerocopy" -version = "0.7.35" +name = "yoke-derive" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ - "zerocopy-derive 0.7.35", + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" -dependencies = [ - "zerocopy-derive 0.8.24", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -12576,7 +12859,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "synstructure", ] @@ -12597,7 +12880,18 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke 0.8.0", + "zerofrom", ] [[package]] @@ -12606,9 +12900,20 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ - "yoke", + "yoke 0.7.5", + "zerofrom", + "zerovec-derive 0.10.3", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke 0.8.0", "zerofrom", - "zerovec-derive", + "zerovec-derive 0.11.1", ] [[package]] @@ -12619,7 +12924,18 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1c29d5c93..9106e0916 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8", features = [ "test-utils", ] } + reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8" } reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8" } reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8" } @@ -83,6 +84,7 @@ reth-testing-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4. reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8" } reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8" } reth-tracing-otlp = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8" } +reth-ipc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8" } # reth optimism reth-optimism-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.4.8" } diff --git a/crates/op-rbuilder/Cargo.toml b/crates/op-rbuilder/Cargo.toml index 37d5855f1..5e8287065 100644 --- a/crates/op-rbuilder/Cargo.toml +++ b/crates/op-rbuilder/Cargo.toml @@ -116,6 +116,13 @@ http = "1.0" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } +dashmap = { version = "6.1", optional = true } +nanoid = { version = "0.4", optional = true } +reth-ipc = { workspace = true, optional = true } +bollard = { version = "0.19", optional = true } +tar = { version = "0.4", optional = true } +ctor = { version = "0.4.2", optional = true } + [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -129,6 +136,13 @@ alloy-provider = { workspace = true, default-features = true, features = [ ] } tempfile = "3.8" +dashmap = { version = "6.1" } +nanoid = { version = "0.4" } +reth-ipc = { workspace = true } +reth-node-builder = { workspace = true, features = ["test-utils"] } +bollard = "0.19" +ctor = "0.4.2" + [features] default = ["jemalloc"] @@ -150,7 +164,16 @@ min-info-logs = ["tracing/release_max_level_info"] min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] -testing = [] + +testing = [ + "dashmap", + "nanoid", + "reth-ipc", + "reth-node-builder/test-utils", + "bollard", + "ctor", +] + interop = [] diff --git a/crates/op-rbuilder/src/args/mod.rs b/crates/op-rbuilder/src/args/mod.rs index 20f716f97..9048fde70 100644 --- a/crates/op-rbuilder/src/args/mod.rs +++ b/crates/op-rbuilder/src/args/mod.rs @@ -3,7 +3,7 @@ use crate::{ metrics::{CARGO_PKG_VERSION, VERGEN_GIT_SHA}, }; use clap_builder::{CommandFactory, FromArgMatches}; -pub use op::OpRbuilderArgs; +pub use op::{FlashblocksArgs, OpRbuilderArgs}; use playground::PlaygroundOptions; use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; diff --git a/crates/op-rbuilder/src/args/op.rs b/crates/op-rbuilder/src/args/op.rs index 777baa919..4f54adaa5 100644 --- a/crates/op-rbuilder/src/args/op.rs +++ b/crates/op-rbuilder/src/args/op.rs @@ -3,13 +3,16 @@ //! Copied from OptimismNode to allow easy extension. //! clap [Args](clap::Args) for optimism rollup configuration + use crate::tx_signer::Signer; use anyhow::{anyhow, Result}; +use clap::Parser; +use reth_optimism_cli::commands::Commands; use reth_optimism_node::args::RollupArgs; use std::path::PathBuf; /// Parameters for rollup configuration -#[derive(Debug, Clone, Default, clap::Args)] +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] #[command(next_help_heading = "Rollup")] pub struct OpRbuilderArgs { /// Rollup configuration @@ -51,6 +54,16 @@ pub struct OpRbuilderArgs { pub flashblocks: FlashblocksArgs, } +impl Default for OpRbuilderArgs { + fn default() -> Self { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext + } +} + fn expand_path(s: &str) -> Result { shellexpand::full(s) .map_err(|e| anyhow!("expansion error for `{s}`: {e}"))? @@ -63,7 +76,7 @@ fn expand_path(s: &str) -> Result { /// The names in the struct are prefixed with `flashblocks` to avoid conflicts /// with the standard block building configuration since these args are flattened /// into the main `OpRbuilderArgs` struct with the other rollup/node args. -#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] pub struct FlashblocksArgs { /// When set to true, the builder will build flashblocks /// and will build standard blocks at the chain block time. @@ -101,3 +114,13 @@ pub struct FlashblocksArgs { )] pub flashblocks_block_time: u64, } + +impl Default for FlashblocksArgs { + fn default() -> Self { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext.flashblocks + } +} diff --git a/crates/op-rbuilder/src/bin/tester/main.rs b/crates/op-rbuilder/src/bin/tester/main.rs index 10768caa9..b011beb6a 100644 --- a/crates/op-rbuilder/src/bin/tester/main.rs +++ b/crates/op-rbuilder/src/bin/tester/main.rs @@ -1,5 +1,7 @@ use alloy_primitives::Address; +use alloy_provider::{Identity, ProviderBuilder}; use clap::Parser; +use op_alloy_network::Optimism; use op_rbuilder::tests::*; /// CLI Commands @@ -49,69 +51,38 @@ async fn main() -> eyre::Result<()> { match cli.command { Commands::Genesis { output } => generate_genesis(output).await, - Commands::Run { - validation, - no_tx_pool, - block_time_secs, - flashblocks_endpoint, - no_sleep, - } => { - run_system( - validation, - no_tx_pool, - block_time_secs, - flashblocks_endpoint, - no_sleep, - ) - .await - } + Commands::Run { validation, .. } => run_system(validation).await, Commands::Deposit { address, amount } => { - let engine_api = EngineApi::builder().build().unwrap(); - let mut generator = BlockGenerator::new(engine_api, None, false, 1, None); - - generator.init().await?; - - let block_generated = generator.deposit(address, amount).await?; - println!( - "Deposit transaction included in block: {:?}", - block_generated.block_hash() - ); + let engine_api = EngineApi::with_http("http://localhost:4444"); + let provider = ProviderBuilder::::default() + .connect_http("http://localhost:2222".try_into()?); + let driver = ChainDriver::::remote(provider, engine_api); + let block_hash = driver.fund(address, amount).await?; + println!("Deposit transaction included in block: {block_hash}"); Ok(()) } } } #[allow(dead_code)] -pub async fn run_system( - validation: bool, - no_tx_pool: bool, - block_time_secs: u64, - flashblocks_endpoint: Option, - no_sleep: bool, -) -> eyre::Result<()> { - println!("Validation: {validation}"); +pub async fn run_system(validation: bool) -> eyre::Result<()> { + println!("Validation node enabled: {validation}"); - let engine_api = EngineApi::new("http://localhost:4444").unwrap(); - let validation_api = if validation { - Some(EngineApi::new("http://localhost:5555").unwrap()) - } else { - None - }; + let engine_api = EngineApi::with_http("http://localhost:4444"); + let provider = ProviderBuilder::::default() + .connect_http("http://localhost:2222".try_into()?); + let mut driver = ChainDriver::::remote(provider, engine_api); - let mut generator = BlockGenerator::new( - engine_api, - validation_api, - no_tx_pool, - block_time_secs, - flashblocks_endpoint, - ); - - generator.init().await?; + if validation { + driver = driver + .with_validation_node(ExternalNode::reth().await?) + .await?; + } // Infinite loop generating blocks loop { println!("Generating new block..."); - let block_generated = generator.submit_payload(None, 0, no_sleep).await?; - println!("Generated block: {:?}", block_generated.block_hash()); + let block = driver.build_new_block().await?; + println!("Generated block: {:?}", block.header.hash); } } diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index 2b7484f93..218596ec8 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -35,7 +35,7 @@ use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; use revm::{context::result::ResultAndState, Database, DatabaseCommit}; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; -use tracing::{info, trace, warn}; +use tracing::{debug, info, trace, warn}; use crate::{ metrics::OpRBuilderMetrics, @@ -367,7 +367,7 @@ impl OpPayloadBuilderCtx { reverted_hashes.is_some() && !reverted_hashes.unwrap().contains(&tx_hash); let log_txn = |result: TxnExecutionResult| { - info!(target: "payload_builder", tx_hash = ?tx_hash, tx_da_size = ?tx_da_size, exclude_reverting_txs = ?exclude_reverting_txs, result = %result, "Considering transaction"); + debug!(target: "payload_builder", tx_hash = ?tx_hash, tx_da_size = ?tx_da_size, exclude_reverting_txs = ?exclude_reverting_txs, result = %result, "Considering transaction"); }; if let Some(conditional) = conditional { diff --git a/crates/op-rbuilder/src/lib.rs b/crates/op-rbuilder/src/lib.rs index 421220dac..0305ed08c 100644 --- a/crates/op-rbuilder/src/lib.rs +++ b/crates/op-rbuilder/src/lib.rs @@ -1,16 +1,13 @@ +pub mod args; +pub mod builders; +pub mod launcher; +pub mod metrics; +mod monitor_tx_pool; pub mod primitives; +pub mod revert_protection; +pub mod traits; +pub mod tx; pub mod tx_signer; #[cfg(any(test, feature = "testing"))] pub mod tests; - -pub mod traits; -pub mod tx; - -/// CLI argument parsing. -pub mod args; -mod builders; -pub mod launcher; -mod metrics; -mod monitor_tx_pool; -mod revert_protection; diff --git a/crates/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/op-rbuilder/src/tests/flashblocks/smoke.rs index f48a0f730..5a98b5d0d 100644 --- a/crates/op-rbuilder/src/tests/flashblocks/smoke.rs +++ b/crates/op-rbuilder/src/tests/flashblocks/smoke.rs @@ -6,26 +6,39 @@ use tokio::task::JoinHandle; use tokio_tungstenite::{connect_async, tungstenite::Message}; use tokio_util::sync::CancellationToken; -use crate::tests::TestHarnessBuilder; +use crate::{ + args::{FlashblocksArgs, OpRbuilderArgs}, + builders::FlashblocksBuilder, + tests::{ChainDriverExt, LocalInstance, TransactionBuilderExt}, +}; #[tokio::test] async fn chain_produces_blocks() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("flashbots_chain_produces_blocks") - .with_flashblocks_port(1239) - .with_chain_block_time(2000) - .with_flashbots_block_time(200) - .build() - .await?; + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + chain_block_time: 2000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + }, + ..Default::default() + }) + .await?; + + let driver = rbuilder.driver().await?; + driver.fund_default_accounts().await?; // Create a struct to hold received messages let received_messages = Arc::new(Mutex::new(Vec::new())); let messages_clone = received_messages.clone(); let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); // Spawn WebSocket listener task let cancellation_token_clone = cancellation_token.clone(); let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async("ws://localhost:1239").await?; + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; let (_, mut read) = ws_stream.split(); loop { @@ -40,22 +53,25 @@ async fn chain_produces_blocks() -> eyre::Result<()> { } }); - let mut generator = harness.block_generator().await?; - for _ in 0..10 { for _ in 0..5 { // send a valid transaction - let _ = harness.send_valid_transaction().await?; + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; } - let generated_block = generator.generate_block().await?; - assert_eq!(generated_block.num_transactions(), 8); // 5 normal txn + deposit + 2 builder txn + let block = driver.build_new_block().await?; + assert_eq!(block.transactions.len(), 8); // 5 normal txn + deposit + 2 builder txn tokio::time::sleep(std::time::Duration::from_secs(1)).await; } cancellation_token.cancel(); assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + assert!( !received_messages .lock() diff --git a/crates/op-rbuilder/src/tests/framework/apis.rs b/crates/op-rbuilder/src/tests/framework/apis.rs index 19b63e876..d93572070 100644 --- a/crates/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/op-rbuilder/src/tests/framework/apis.rs @@ -1,8 +1,9 @@ use super::DEFAULT_JWT_TOKEN; use alloy_eips::{eip7685::Requests, BlockNumberOrTag}; use alloy_primitives::B256; + use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadStatus}; -use http::Uri; +use core::{future::Future, marker::PhantomData}; use jsonrpsee::{ core::{client::SubscriptionClientT, RpcResult}, proc_macros::rpc, @@ -15,84 +16,143 @@ use reth_optimism_rpc::engine::OpEngineApiClient; use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, JwtSecret}; use serde_json::Value; -use std::str::FromStr; +use tracing::debug; -/// Helper for engine api operations -pub struct EngineApi { - pub url: Uri, - pub jwt_secret: JwtSecret, +#[derive(Clone, Debug)] +pub enum Address { + Ipc(String), + Http(url::Url), +} + +pub trait Protocol { + fn client( + jwt: JwtSecret, + address: Address, + ) -> impl Future; +} + +pub struct Http; +impl Protocol for Http { + async fn client( + jwt: JwtSecret, + address: Address, + ) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + let Address::Http(url) = address else { + unreachable!(); + }; + + let secret_layer = AuthClientLayer::new(jwt); + let middleware = tower::ServiceBuilder::default().layer(secret_layer); + jsonrpsee::http_client::HttpClientBuilder::default() + .set_http_middleware(middleware) + .build(url) + .expect("Failed to create http client") + } +} + +pub struct Ipc; +impl Protocol for Ipc { + async fn client( + _: JwtSecret, // ipc does not use JWT + address: Address, + ) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + let Address::Ipc(path) = address else { + unreachable!(); + }; + reth_ipc::client::IpcClientBuilder::default() + .build(&path) + .await + .expect("Failed to create ipc client") + } } -/// Builder for EngineApi configuration -pub struct EngineApiBuilder { - url: String, - jwt_secret: String, +/// Helper for engine api operations +pub struct EngineApi { + address: Address, + jwt_secret: JwtSecret, + _tag: PhantomData

, } -impl Default for EngineApiBuilder { - fn default() -> Self { - Self::new() +impl EngineApi

{ + async fn client(&self) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + P::client(self.jwt_secret, self.address.clone()).await } } -impl EngineApiBuilder { - pub fn new() -> Self { - Self { - url: String::from("http://localhost:8551"), - jwt_secret: String::from(DEFAULT_JWT_TOKEN), +// http specific +impl EngineApi { + pub fn with_http(url: &str) -> EngineApi { + EngineApi:: { + address: Address::Http(url.parse().expect("Invalid URL")), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, } } - pub fn with_url(mut self, url: &str) -> Self { - self.url = url.to_string(); - self + pub fn with_localhost_port(port: u16) -> EngineApi { + EngineApi:: { + address: Address::Http( + format!("http://localhost:{}", port) + .parse() + .expect("Invalid URL"), + ), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, + } } - pub fn build(self) -> Result> { - Ok(EngineApi { - url: self.url.parse()?, - jwt_secret: JwtSecret::from_str(&self.jwt_secret)?, - }) + pub fn with_port(mut self, port: u16) -> Self { + let Address::Http(url) = &mut self.address else { + unreachable!(); + }; + + url.set_port(Some(port)).expect("Invalid port"); + self } -} -impl EngineApi { - pub fn builder() -> EngineApiBuilder { - EngineApiBuilder::new() + pub fn with_jwt_secret(mut self, jwt_secret: &str) -> Self { + self.jwt_secret = jwt_secret.parse().expect("Invalid JWT"); + self } - pub fn new(url: &str) -> Result> { - Self::builder().with_url(url).build() + pub fn url(&self) -> &url::Url { + let Address::Http(url) = &self.address else { + unreachable!(); + }; + url } +} - pub fn new_with_port(port: u16) -> Result> { - Self::builder() - .with_url(&format!("http://localhost:{port}")) - .build() +// ipc specific +impl EngineApi { + pub fn with_ipc(path: &str) -> EngineApi { + EngineApi:: { + address: Address::Ipc(path.into()), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, + } } - pub fn http_client(&self) -> impl SubscriptionClientT + Clone + Send + Sync + Unpin + 'static { - // Create a middleware that adds a new JWT token to every request. - let secret_layer = AuthClientLayer::new(self.jwt_secret); - let middleware = tower::ServiceBuilder::default().layer(secret_layer); - jsonrpsee::http_client::HttpClientBuilder::default() - .set_http_middleware(middleware) - .build(&self.url.to_string()) - .expect("Failed to create http client") + pub fn path(&self) -> &str { + let Address::Ipc(path) = &self.address else { + unreachable!(); + }; + path } +} +impl EngineApi

{ pub async fn get_payload( &self, payload_id: PayloadId, ) -> eyre::Result<::ExecutionPayloadEnvelopeV4> { - println!( + debug!( "Fetching payload with id: {} at {}", payload_id, chrono::Utc::now() ); - Ok( - OpEngineApiClient::::get_payload_v4(&self.http_client(), payload_id) + OpEngineApiClient::::get_payload_v4(&self.client().await, payload_id) .await?, ) } @@ -104,10 +164,9 @@ impl EngineApi { parent_beacon_block_root: B256, execution_requests: Requests, ) -> eyre::Result { - println!("Submitting new payload at {}...", chrono::Utc::now()); - + debug!("Submitting new payload at {}...", chrono::Utc::now()); Ok(OpEngineApiClient::::new_payload_v4( - &self.http_client(), + &self.client().await, payload, versioned_hashes, parent_beacon_block_root, @@ -122,10 +181,9 @@ impl EngineApi { new_head: B256, payload_attributes: Option<::PayloadAttributes>, ) -> eyre::Result { - println!("Updating forkchoice at {}...", chrono::Utc::now()); - + debug!("Updating forkchoice at {}...", chrono::Utc::now()); Ok(OpEngineApiClient::::fork_choice_updated_v3( - &self.http_client(), + &self.client().await, ForkchoiceState { head_block_hash: new_head, safe_block_hash: current_head, @@ -135,19 +193,6 @@ impl EngineApi { ) .await?) } - - pub async fn latest(&self) -> eyre::Result> { - self.get_block_by_number(BlockNumberOrTag::Latest, false) - .await - } - - pub async fn get_block_by_number( - &self, - number: BlockNumberOrTag, - include_txs: bool, - ) -> eyre::Result> { - Ok(BlockApiClient::get_block_by_number(&self.http_client(), number, include_txs).await?) - } } #[rpc(server, client, namespace = "eth")] @@ -177,9 +222,9 @@ pub async fn generate_genesis(output: Option) -> eyre::Result<()> { // Write the result to the output file if let Some(output) = output { std::fs::write(&output, serde_json::to_string_pretty(&genesis)?)?; - println!("Generated genesis file at: {output}"); + debug!("Generated genesis file at: {output}"); } else { - println!("{}", serde_json::to_string_pretty(&genesis)?); + debug!("{}", serde_json::to_string_pretty(&genesis)?); } Ok(()) diff --git a/crates/op-rbuilder/src/tests/framework/blocks.rs b/crates/op-rbuilder/src/tests/framework/blocks.rs deleted file mode 100644 index f6094679f..000000000 --- a/crates/op-rbuilder/src/tests/framework/blocks.rs +++ /dev/null @@ -1,425 +0,0 @@ -use std::{ - net::{IpAddr, SocketAddr}, - str::FromStr, -}; - -use crate::tx_signer::Signer; -use alloy_eips::{eip2718::Encodable2718, eip7685::Requests, BlockNumberOrTag}; -use alloy_primitives::{address, hex, Address, Bytes, TxKind, B256, U256}; -use alloy_rpc_types_engine::{ - ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadAttributes, - PayloadStatusEnum, -}; -use alloy_rpc_types_eth::Block; -use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; -use op_alloy_rpc_types_engine::{OpExecutionPayloadV4, OpPayloadAttributes}; -use rollup_boost::{ - Flashblocks, FlashblocksService, OpExecutionPayloadEnvelope, PayloadSource, PayloadVersion, - RpcClient, -}; -use url::Url; - -use super::apis::EngineApi; - -// L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) -// -// https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1 -const FJORD_DATA: &[u8] = &hex!("440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"); - -/// A system that continuously generates blocks using the engine API -pub struct BlockGenerator { - engine_api: EngineApi, - validation_api: Option, - latest_hash: B256, - no_tx_pool: bool, - block_time_secs: u64, - timestamp: u64, - // flashblocks service - flashblocks_endpoint: Option, - flashblocks_service: Option, -} - -impl BlockGenerator { - pub fn new( - engine_api: EngineApi, - validation_api: Option, - no_tx_pool: bool, - block_time_secs: u64, - flashblocks_endpoint: Option, - ) -> Self { - Self { - engine_api, - validation_api, - latest_hash: B256::ZERO, // temporary value - no_tx_pool, - timestamp: 0, - block_time_secs, - flashblocks_endpoint, - flashblocks_service: None, - } - } - - /// Initialize the block generator by fetching the latest block - pub async fn init(&mut self) -> eyre::Result { - let latest_block = self.engine_api.latest().await?.expect("block not found"); - self.latest_hash = latest_block.header.hash; - self.timestamp = latest_block.header.timestamp; - - // Sync validation node if it exists - if let Some(validation_api) = &self.validation_api { - self.sync_validation_node(validation_api).await?; - } - - // Initialize flashblocks service - if let Some(flashblocks_endpoint) = &self.flashblocks_endpoint { - println!("Initializing flashblocks service at {flashblocks_endpoint}"); - let builder_client = RpcClient::new( - self.engine_api.url.clone(), - self.engine_api.jwt_secret.clone(), - 10, - PayloadSource::Builder, - )?; - - self.flashblocks_service = Some(Flashblocks::run( - builder_client, - Url::from_str(flashblocks_endpoint)?, - SocketAddr::new(IpAddr::from_str("127.0.0.1")?, 1112), // output address for the preconfirmations from rb - )?); - } - - Ok(latest_block) - } - - /// Sync the validation node to the current state - async fn sync_validation_node(&self, validation_api: &EngineApi) -> eyre::Result<()> { - let latest_validation_block = validation_api.latest().await?.expect("block not found"); - let latest_block = self.engine_api.latest().await?.expect("block not found"); - - if latest_validation_block.header.number > latest_block.header.number { - return Err(eyre::eyre!("validation node is ahead of the builder")); - } - - if latest_validation_block.header.number < latest_block.header.number { - println!( - "validation node {} is behind the builder {}, syncing up", - latest_validation_block.header.number, latest_block.header.number - ); - - let mut latest_hash = latest_validation_block.header.hash; - - for i in (latest_validation_block.header.number + 1)..=latest_block.header.number { - println!("syncing block {i}"); - - let block = self - .engine_api - .get_block_by_number(BlockNumberOrTag::Number(i), true) - .await? - .expect("block not found"); - - if block.header.parent_hash != latest_hash { - return Err(eyre::eyre!("unexpected parent hash during sync")); - } - - let payload_request = OpExecutionPayloadV4 { - payload_inner: ExecutionPayloadV3 { - payload_inner: ExecutionPayloadV2 { - payload_inner: ExecutionPayloadV1 { - parent_hash: block.header.parent_hash, - fee_recipient: block.header.beneficiary, - state_root: block.header.state_root, - receipts_root: block.header.receipts_root, - logs_bloom: block.header.logs_bloom, - prev_randao: B256::ZERO, - block_number: block.header.number, - gas_limit: block.header.gas_limit, - gas_used: block.header.gas_used, - timestamp: block.header.timestamp, - extra_data: block.header.extra_data.clone(), - base_fee_per_gas: U256::from( - block.header.base_fee_per_gas.unwrap(), - ), - block_hash: block.header.hash, - transactions: vec![], // there are no txns yet - }, - withdrawals: block.withdrawals.unwrap().to_vec(), - }, - blob_gas_used: block.header.inner.blob_gas_used.unwrap(), - excess_blob_gas: block.header.inner.excess_blob_gas.unwrap(), - }, - withdrawals_root: Default::default(), - }; - - let validation_status = validation_api - .new_payload(payload_request, vec![], B256::ZERO, Requests::default()) - .await?; - - if validation_status.status != PayloadStatusEnum::Valid { - return Err(eyre::eyre!("invalid payload status during sync")); - } - - let new_chain_hash = validation_status - .latest_valid_hash - .ok_or_else(|| eyre::eyre!("missing latest valid hash"))?; - - if new_chain_hash != block.header.hash { - return Err(eyre::eyre!("hash mismatch during sync")); - } - - validation_api - .update_forkchoice(latest_hash, new_chain_hash, None) - .await?; - - latest_hash = new_chain_hash; - } - } - - Ok(()) - } - - /// Helper function to submit a payload and update chain state - pub async fn submit_payload( - &mut self, - transactions: Option>, - block_building_delay_secs: u64, - no_sleep: bool, // TODO: Change this, too many parameters we can tweak here to put as a function arguments - ) -> eyre::Result { - let timestamp = self.timestamp + self.block_time_secs; - - // Add L1 block info as the first transaction in every L2 block - // This deposit transaction contains L1 block metadata required by the L2 chain - // Currently using hardcoded data from L1 block 124665056 - // If this info is not provided, Reth cannot decode the receipt for any transaction - // in the block since it also includes this info as part of the result. - // It does not matter if the to address (4200000000000000000000000000000000000015) is - // not deployed on the L2 chain since Reth queries the block to get the info and not the contract. - let block_info_tx: Bytes = { - let deposit_tx = TxDeposit { - source_hash: B256::default(), - from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"), - to: TxKind::Call(address!("4200000000000000000000000000000000000015")), - mint: 0, - value: U256::default(), - gas_limit: 210000, - is_system_transaction: false, - input: FJORD_DATA.into(), - }; - - // Create a temporary signer for the deposit - let signer = Signer::random(); - let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; - signed_tx.encoded_2718().into() - }; - - let transactions = if let Some(transactions) = transactions { - // prepend the block info transaction - let mut all_transactions = vec![block_info_tx]; - all_transactions.extend(transactions.into_iter()); - all_transactions - } else { - vec![block_info_tx] - }; - - let result = self - .engine_api - .update_forkchoice( - self.latest_hash, - self.latest_hash, - Some(OpPayloadAttributes { - payload_attributes: PayloadAttributes { - withdrawals: Some(vec![]), - parent_beacon_block_root: Some(B256::ZERO), - timestamp, - prev_randao: B256::ZERO, - suggested_fee_recipient: Default::default(), - }, - transactions: Some(transactions), - no_tx_pool: Some(self.no_tx_pool), - gas_limit: Some(10000000), - eip_1559_params: None, - }), - ) - .await?; - - if result.payload_status.status != PayloadStatusEnum::Valid { - return Err(eyre::eyre!("Invalid payload status")); - } - - let payload_id = result.payload_id.unwrap(); - - // update the payload id in the flashblocks service if present - if let Some(flashblocks_service) = &self.flashblocks_service { - flashblocks_service.set_current_payload_id(payload_id).await; - } - - if !self.no_tx_pool && !no_sleep { - let sleep_time = self.block_time_secs + block_building_delay_secs; - tokio::time::sleep(tokio::time::Duration::from_secs(sleep_time)).await; - } - - let payload = if let Some(flashblocks_service) = &self.flashblocks_service { - flashblocks_service - .get_best_payload(PayloadVersion::V4) - .await? - .unwrap() - } else { - OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?) - }; - - let execution_payload = match payload { - OpExecutionPayloadEnvelope::V4(execution_payload) => { - execution_payload.execution_payload - } - _ => { - return Err(eyre::eyre!("execution_payload should be V4")); - } - }; - - // Validate with builder node - let validation_status = self - .engine_api - .new_payload( - execution_payload.clone(), - vec![], - B256::ZERO, - Requests::default(), - ) - .await?; - - if validation_status.status != PayloadStatusEnum::Valid { - return Err(eyre::eyre!("Invalid validation status from builder")); - } - - // Validate with validation node if present - if let Some(validation_api) = &self.validation_api { - let validation_status = validation_api - .new_payload( - execution_payload.clone(), - vec![], - B256::ZERO, - Requests::default(), - ) - .await?; - - if validation_status.status != PayloadStatusEnum::Valid { - return Err(eyre::eyre!("Invalid validation status from validator")); - } - } - - let new_block_hash = execution_payload - .payload_inner - .payload_inner - .payload_inner - .block_hash; - - // Update forkchoice on builder - self.engine_api - .update_forkchoice(self.latest_hash, new_block_hash, None) - .await?; - - // Update forkchoice on validator if present - if let Some(validation_api) = &self.validation_api { - validation_api - .update_forkchoice(self.latest_hash, new_block_hash, None) - .await?; - } - - // Update internal state - self.latest_hash = new_block_hash; - self.timestamp = execution_payload.payload_inner.timestamp(); - - let block = self - .engine_api - .get_block_by_number(BlockNumberOrTag::Latest, false) - .await? - .expect("block not found"); - - assert_eq!(block.header.hash, new_block_hash); - - let generated_block = BlockGenerated { block }; - Ok(generated_block) - } - - /// Generate a single new block and return its hash - pub async fn generate_block(&mut self) -> eyre::Result { - self.submit_payload(None, 0, false).await - } - - pub async fn generate_block_with_delay(&mut self, delay: u64) -> eyre::Result { - self.submit_payload(None, delay, false).await - } - - /// Submit a deposit transaction to seed an account with ETH - pub async fn deposit(&mut self, address: Address, value: u128) -> eyre::Result { - // Create deposit transaction - let deposit_tx = TxDeposit { - source_hash: B256::default(), - from: address, // Set the sender to the address of the account to seed - to: TxKind::Create, - mint: value, // Amount to deposit - value: U256::default(), - gas_limit: 210000, - is_system_transaction: false, - input: Bytes::default(), - }; - - // Create a temporary signer for the deposit - let signer = Signer::random(); - let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; - let signed_tx_rlp = signed_tx.encoded_2718(); - - self.submit_payload(Some(vec![signed_tx_rlp.into()]), 0, false) - .await - } - - pub async fn create_funded_accounts( - &mut self, - count: usize, - amount: u128, - ) -> eyre::Result> { - let mut signers = Vec::with_capacity(count); - - for _ in 0..count { - // Create a new signer - let signer = Signer::random(); - let address = signer.address; - - // Deposit funds to the new account - self.deposit(address, amount).await?; - - signers.push(signer); - } - - Ok(signers) - } -} - -#[derive(Debug)] -pub struct BlockGenerated { - pub block: Block, -} - -impl BlockGenerated { - pub fn block_hash(&self) -> B256 { - self.block.header.hash - } - - pub fn not_includes(&self, tx_hash: B256) -> bool { - !self.includes(tx_hash) - } - - pub fn includes(&self, tx_hash: B256) -> bool { - self.block.transactions.hashes().any(|hash| hash == tx_hash) - } - - pub fn includes_vec(&self, tx_hashes: Vec) -> bool { - tx_hashes.iter().all(|hash| self.includes(*hash)) - } - - pub fn not_includes_vec(&self, tx_hashes: Vec) -> bool { - tx_hashes.iter().all(|hash| self.not_includes(*hash)) - } - - pub fn num_transactions(&self) -> usize { - self.block.transactions.len() - } -} diff --git a/crates/op-rbuilder/src/tests/framework/driver.rs b/crates/op-rbuilder/src/tests/framework/driver.rs new file mode 100644 index 000000000..493213665 --- /dev/null +++ b/crates/op-rbuilder/src/tests/framework/driver.rs @@ -0,0 +1,289 @@ +use core::time::Duration; +use std::time::SystemTime; + +use alloy_eips::{eip7685::Requests, BlockNumberOrTag, Encodable2718}; +use alloy_primitives::{address, hex, Bytes, TxKind, B256, U256}; +use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadAttributes, PayloadStatusEnum}; +use alloy_rpc_types_eth::Block; +use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; +use op_alloy_network::Optimism; +use op_alloy_rpc_types::Transaction; +use reth_optimism_node::OpPayloadAttributes; +use rollup_boost::OpExecutionPayloadEnvelope; + +use super::{EngineApi, Ipc, LocalInstance, TransactionBuilder}; +use crate::{ + args::OpRbuilderArgs, + tests::{ExternalNode, Protocol}, + tx_signer::Signer, +}; + +const DEFAULT_GAS_LIMIT: u64 = 10_000_000; + +/// The ChainDriver is a type that allows driving the op builder node to build new blocks manually +/// by calling the `build_new_block` method. It uses the Engine API to interact with the node +/// and the provider to fetch blocks and transactions. +pub struct ChainDriver { + engine_api: EngineApi, + provider: RootProvider, + signer: Option, + gas_limit: Option, + args: OpRbuilderArgs, + validation_nodes: Vec, +} + +// instantiation and configuration +impl ChainDriver { + const MIN_BLOCK_TIME: Duration = Duration::from_secs(1); + + /// Creates a new ChainDriver instance for a local instance of RBuilder running in-process + /// communicating over IPC. + pub async fn local(instance: &LocalInstance) -> eyre::Result> { + Ok(ChainDriver:: { + engine_api: instance.engine_api(), + provider: instance.provider().await?, + signer: Default::default(), + gas_limit: None, + args: instance.args().clone(), + validation_nodes: vec![], + }) + } + + /// Creates a new ChainDriver for some EL node instance. + pub fn remote( + provider: RootProvider, + engine_api: EngineApi, + ) -> ChainDriver { + ChainDriver { + engine_api, + provider, + signer: Default::default(), + gas_limit: None, + args: OpRbuilderArgs::default(), + validation_nodes: vec![], + } + } + + /// Specifies the block builder signing key used to sign builder transactions. + /// If not specified, a random signer will be used. + pub fn with_signer(mut self, signer: Signer) -> Self { + self.signer = Some(signer); + self + } + + /// Specifies a custom gas limit for blocks being built, otherwise the limit is + /// set to a default value of 10_000_000. + pub fn with_gas_limit(mut self, gas_limit: u64) -> Self { + self.gas_limit = Some(gas_limit); + self + } + + /// Adds an external Optimism execution client node that will receive all newly built + /// blocks by this driver and ensure that they are valid. This validation process is + /// transparent and happens in the background when building new blocks. + /// + /// If there are validation nodes specified any newly built block will be submitted to + /// the validation EL and the driver will fail if the block is rejected by the + /// validation node. + pub async fn with_validation_node(mut self, node: ExternalNode) -> eyre::Result { + node.catch_up_with(self.provider()).await?; + self.validation_nodes.push(node); + Ok(self) + } +} + +// public test api +impl ChainDriver { + /// Builds a new block using the current state of the chain and the transactions in the pool. + pub async fn build_new_block(&self) -> eyre::Result> { + self.build_new_block_with_txs(vec![]).await + } + + /// Builds a new block using the current state of the chain and the transactions in the pool with a list + /// of mandatory builder transactions. Those are usually deposit transactions. + pub async fn build_new_block_with_txs( + &self, + txs: Vec, + ) -> eyre::Result> { + let latest = self.latest().await?; + let latest_timestamp = Duration::from_secs(latest.header.timestamp); + let actual_timestamp = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_err(|_| eyre::eyre!("Failed to get current system time"))?; + + // block timestamp will be the max of the actual timestamp and the latest block + // timestamp plus the minimum block time. This ensures that blocks don't break any + // assumptions, but also gives the test author the ability to control the block time + // in the test. + let block_timestamp = actual_timestamp.max(latest_timestamp + Self::MIN_BLOCK_TIME); + + // Add L1 block info as the first transaction in every L2 block + // This deposit transaction contains L1 block metadata required by the L2 chain + // Currently using hardcoded data from L1 block 124665056 + // If this info is not provided, Reth cannot decode the receipt for any transaction + // in the block since it also includes this info as part of the result. + // It does not matter if the to address (4200000000000000000000000000000000000015) is + // not deployed on the L2 chain since Reth queries the block to get the info and not the contract. + let block_info_tx: Bytes = { + let deposit_tx = TxDeposit { + source_hash: B256::default(), + from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"), + to: TxKind::Call(address!("4200000000000000000000000000000000000015")), + mint: 0, + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: FJORD_DATA.into(), + }; + + // Create a temporary signer for the deposit + let signer = self.signer.unwrap_or_else(Signer::random); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; + signed_tx.encoded_2718().into() + }; + + let fcu_result = self + .fcu(OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: block_timestamp.as_secs(), + parent_beacon_block_root: Some(B256::ZERO), + withdrawals: Some(vec![]), + ..Default::default() + }, + transactions: Some(vec![block_info_tx].into_iter().chain(txs).collect()), + gas_limit: Some(self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)), + ..Default::default() + }) + .await?; + + if fcu_result.payload_status.is_invalid() { + return Err(eyre::eyre!("Forkchoice update failed: {fcu_result:?}")); + } + + let payload_id = fcu_result + .payload_id + .ok_or_else(|| eyre::eyre!("Forkchoice update did not return a payload ID"))?; + + // wait for the block to be built for the specified chain block time + tokio::time::sleep( + Duration::from_millis(self.args.chain_block_time).max(Self::MIN_BLOCK_TIME), + ) + .await; + + let payload = + OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?); + let OpExecutionPayloadEnvelope::V4(payload) = payload else { + return Err(eyre::eyre!("Expected V4 payload, got something else")); + }; + let payload = payload.execution_payload; + + if self + .engine_api + .new_payload(payload.clone(), vec![], B256::ZERO, Requests::default()) + .await? + .status + != PayloadStatusEnum::Valid + { + return Err(eyre::eyre!("Invalid validation status from builder")); + } + + let new_block_hash = payload.payload_inner.payload_inner.payload_inner.block_hash; + self.engine_api + .update_forkchoice(latest.header.hash, new_block_hash, None) + .await?; + + let block = self + .provider + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .full() + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest block after building new block"))?; + + assert_eq!( + block.header.hash, new_block_hash, + "New block hash does not match expected hash" + ); + + for validation_node in &self.validation_nodes { + // optionally for each external validation node, ensure + // that they consider the block valid before returning it + // to the test author. + validation_node.post_block(&payload).await?; + } + + Ok(block) + } + + /// Retreives the latest built block and returns only a list of transaction + /// hashes from its body. + pub async fn latest(&self) -> eyre::Result> { + self.provider + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest block")) + } + + /// Retreives the latest built block and returns a list of full transaction + /// contents in its body. + pub async fn latest_full(&self) -> eyre::Result> { + self.provider + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .full() + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest full block")) + } + + /// retreives a specific block by its number or tag and returns a list of transaction + /// hashes from its body. + pub async fn get_block( + &self, + number: BlockNumberOrTag, + ) -> eyre::Result>> { + Ok(self.provider.get_block_by_number(number).await?) + } + + /// retreives a specific block by its number or tag and returns a list of full transaction + /// contents in its body. + pub async fn get_block_full( + &self, + number: BlockNumberOrTag, + ) -> eyre::Result>> { + Ok(self.provider.get_block_by_number(number).full().await?) + } + + /// Returns a transaction builder that can be used to create and send transactions. + pub fn create_transaction(&self) -> TransactionBuilder { + TransactionBuilder::new(self.provider.clone()) + } + + /// Returns a reference to the underlying alloy provider that is used to + /// interact with the chain. + pub const fn provider(&self) -> &RootProvider { + &self.provider + } +} + +// internal methods +impl ChainDriver { + async fn fcu(&self, attribs: OpPayloadAttributes) -> eyre::Result { + let latest = self.latest().await?.header.hash; + let response = self + .engine_api + .update_forkchoice(latest, latest, Some(attribs)) + .await?; + + Ok(response) + } +} + +// L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) +// +// https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1 +const FJORD_DATA: &[u8] = &hex!( + "440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a + 3000000000000000000000000000000000000000000000000000000003ef12787000000 + 00000000000000000000000000000000000000000000000000000000012fdf87b89884a + 61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a5900000000000000000000 + 00006887246668a3b87f54deb3b94ba47a6f63f32985" +); diff --git a/crates/op-rbuilder/src/tests/framework/external.rs b/crates/op-rbuilder/src/tests/framework/external.rs new file mode 100644 index 000000000..e67fcfcc3 --- /dev/null +++ b/crates/op-rbuilder/src/tests/framework/external.rs @@ -0,0 +1,528 @@ +use alloy_consensus::constants::EMPTY_WITHDRAWALS; +use alloy_eips::{eip7685::Requests, BlockNumberOrTag, Encodable2718}; +use alloy_primitives::{keccak256, private::alloy_rlp::Encodable, B256, U256}; +use alloy_provider::{Identity, Provider, ProviderBuilder, RootProvider}; +use alloy_rpc_types_engine::{ + ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadStatusEnum, +}; +use bollard::{ + exec::{CreateExecOptions, StartExecResults}, + query_parameters::{ + AttachContainerOptions, CreateContainerOptions, CreateImageOptions, RemoveContainerOptions, + StartContainerOptions, StopContainerOptions, + }, + secret::{ContainerCreateBody, ContainerCreateResponse, HostConfig}, + Docker, +}; +use futures::{StreamExt, TryStreamExt}; +use op_alloy_network::Optimism; +use op_alloy_rpc_types_engine::OpExecutionPayloadV4; +use std::path::{Path, PathBuf}; +use tokio::signal; +use tracing::{debug, warn}; + +use crate::tests::{EngineApi, Ipc}; + +const AUTH_CONTAINER_IPC_PATH: &str = "/home/op-reth-shared/auth.ipc"; +const RPC_CONTAINER_IPC_PATH: &str = "/home/op-reth-shared/rpc.ipc"; + +/// This type represents an Optimism execution client node that is running inside a +/// docker container. This node is used to validate the correctness of the blocks built +/// by op-rbuilder. +/// +/// When this node is attached to a `ChainDriver`, it will automatically catch up with the +/// provided chain and will transparently ingest all newly built blocks by the driver. +/// +/// If the built payload fails to validate, then the driver block production function will +/// return an error during `ChainDriver::build_new_block`. +pub struct ExternalNode { + engine_api: EngineApi, + provider: RootProvider, + docker: Docker, + tempdir: PathBuf, + container_id: String, +} + +impl ExternalNode { + /// Creates a new instance of `ExternalNode` that runs the `op-reth` client in a Docker container + /// using the specified version tag. + pub async fn reth_version(version_tag: &str) -> eyre::Result { + let docker = Docker::connect_with_local_defaults()?; + + let tempdir = std::env::var("TESTS_TEMP_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::temp_dir()); + + let tempdir = tempdir.join(format!("reth-shared-{}", nanoid::nanoid!())); + let auth_ipc = tempdir.join("auth.ipc").to_string_lossy().to_string(); + let rpc_ipc = tempdir.join("rpc.ipc").to_string_lossy().to_string(); + + std::fs::create_dir_all(&tempdir) + .map_err(|_| eyre::eyre!("Failed to create temporary directory"))?; + + std::fs::write( + tempdir.join("genesis.json"), + include_str!("./artifacts/genesis.json.tmpl"), + ) + .map_err(|_| eyre::eyre!("Failed to write genesis file"))?; + + // Create Docker container with reth EL client + let container = create_container(&tempdir, &docker, version_tag).await?; + + docker + .start_container(&container.id, None::) + .await?; + + // Wait for the container to be ready and IPCs to be created + await_ipc_readiness(&docker, &container.id).await?; + + // IPC files created by the container have restrictive permissions, + // so we need to relax them to allow the host to access them. + relax_permissions(&docker, &container.id, AUTH_CONTAINER_IPC_PATH).await?; + relax_permissions(&docker, &container.id, RPC_CONTAINER_IPC_PATH).await?; + + // Connect to the IPCs + let engine_api = EngineApi::with_ipc(&auth_ipc); + let provider = ProviderBuilder::::default() + .connect_ipc(rpc_ipc.into()) + .await?; + + // spin up a task that will clean up the container on ctrl-c + tokio::spawn({ + let docker = docker.clone(); + let container_id = container.id.clone(); + let tempdir = tempdir.clone(); + + async move { + if signal::ctrl_c().await.is_ok() { + cleanup(tempdir.clone(), docker.clone(), container_id.clone()).await; + } + } + }); + + Ok(Self { + engine_api, + provider, + docker, + tempdir, + container_id: container.id, + }) + } + + /// Creates a new instance of `ExternalNode` that runs the `op-reth` client in a Docker container + /// using the latest version. + pub async fn reth() -> eyre::Result { + Self::reth_version("latest").await + } +} + +impl ExternalNode { + /// Access to the RPC API of the validation node. + pub fn provider(&self) -> &RootProvider { + &self.provider + } + + /// Access to the Engine API of the validation node. + pub fn engine_api(&self) -> &EngineApi { + &self.engine_api + } +} + +impl ExternalNode { + /// Catches up this node with another node. + /// + /// This method will fail if this node is ahead of the provided chain or they do not + /// share the same genesis block. + pub async fn catch_up_with(&self, chain: &RootProvider) -> eyre::Result<()> { + // check if we need to catch up + let (latest_hash, latest_number) = chain.latest_block_hash_and_number().await?; + let (our_latest_hash, our_latest_number) = + self.provider.latest_block_hash_and_number().await?; + + // check if we can sync in the first place + match (our_latest_number, latest_number) { + (we, them) if we == them && our_latest_hash == latest_hash => { + // we are already caught up and in sync with the provided chain + return Ok(()); + } + (we, them) if we == them && our_latest_hash != latest_hash => { + // divergent histories, can't sync + return Err(eyre::eyre!( + "External node is at the same height but has a different latest block hash: \ + {we} == {them}, {our_latest_hash} != {latest_hash}", + )); + } + (we, them) if we > them => { + return Err(eyre::eyre!( + "External node is ahead of the provided chain: {we} > {them}", + )); + } + (we, them) if we < them => { + debug!("external node is behind the local chain: {we} < {them}, catching up..."); + + // make sure that we share common history with the provided chain + let hash_at_height = chain.hash_at_height(we).await?; + if hash_at_height != our_latest_hash { + return Err(eyre::eyre!( + "External node does not share the same genesis block or history with \ + the provided chain: {} != {} at height {}", + hash_at_height, + our_latest_hash, + we + )); + } + } + _ => {} + }; + + // we are behind, let's catch up + let mut our_current_height = our_latest_number + 1; + + while our_current_height <= latest_number { + let payload = chain + .execution_payload_for_block(our_current_height) + .await?; + + let (latest_hash, _) = self.provider().latest_block_hash_and_number().await?; + + let status = self + .engine_api() + .new_payload(payload, vec![], B256::ZERO, Requests::default()) + .await?; + + if status.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!( + "Failed to import block at height {our_current_height} into external validation node: {:?}", + status.status + )); + } + + let new_chain_hash = status.latest_valid_hash.unwrap_or_default(); + self.engine_api() + .update_forkchoice(latest_hash, new_chain_hash, None) + .await?; + + our_current_height += 1; + } + + // sync complete, double check that we are in sync + let (final_hash, final_number) = self.provider().latest_block_hash_and_number().await?; + + if final_hash != latest_hash || final_number != latest_number { + return Err(eyre::eyre!( + "Failed to sync external validation node: {:?} != {:?}, {:?} != {:?}", + final_hash, + latest_hash, + final_number, + latest_number + )); + } + + Ok(()) + } + + /// Posts a block to the external validation node for validation and sets it as the latest block + /// in the fork choice. + pub async fn post_block(&self, payload: &OpExecutionPayloadV4) -> eyre::Result<()> { + let result = self + .engine_api + .new_payload(payload.clone(), vec![], B256::ZERO, Requests::default()) + .await?; + + let new_block_hash = payload.payload_inner.payload_inner.payload_inner.block_hash; + debug!( + "external validation node payload status for block {new_block_hash}: {:?}", + result.status + ); + + if result.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!( + "Failed to validate block {new_block_hash} with external validation node." + )); + } + + let (latest_hash, _) = self.provider.latest_block_hash_and_number().await?; + + self.engine_api + .update_forkchoice(latest_hash, new_block_hash, None) + .await?; + + Ok(()) + } +} + +impl Drop for ExternalNode { + fn drop(&mut self) { + // Block on cleaning up the container + let docker = self.docker.clone(); + let container_id = self.container_id.clone(); + let tempdir = self.tempdir.clone(); + tokio::spawn(async move { + cleanup(tempdir, docker, container_id).await; + }); + } +} + +async fn create_container( + tempdir: &Path, + docker: &Docker, + version_tag: &str, +) -> eyre::Result { + let host_config = HostConfig { + binds: Some(vec![format!( + "{}:/home/op-reth-shared:rw", + tempdir.display() + )]), + ..Default::default() + }; + + // first pull the image locally + let mut pull_stream = docker.create_image( + Some(CreateImageOptions { + from_image: Some("ghcr.io/paradigmxyz/op-reth".to_string()), + tag: Some(version_tag.into()), + ..Default::default() + }), + None, + None, + ); + + while let Some(pull_result) = pull_stream.try_next().await? { + debug!( + "Pulling 'ghcr.io/paradigmxyz/op-reth:{version_tag}' locally: {:?}", + pull_result + ); + } + + // Don't expose any ports, as we will only use IPC for communication. + let container_config = ContainerCreateBody { + image: Some(format!("ghcr.io/paradigmxyz/op-reth:{version_tag}")), + entrypoint: Some(vec!["op-reth".to_string()]), + cmd: Some( + vec![ + "node", + "--chain=/home/op-reth-shared/genesis.json", + "--auth-ipc", + &format!("--auth-ipc.path={AUTH_CONTAINER_IPC_PATH}"), + &format!("--ipcpath={RPC_CONTAINER_IPC_PATH}"), + "--disable-discovery", + "--no-persist-peers", + "--max-outbound-peers=0", + "--max-inbound-peers=0", + "--trusted-only", + ] + .into_iter() + .map(String::from) + .collect(), + ), + host_config: Some(host_config), + ..Default::default() + }; + + Ok(docker + .create_container(Some(CreateContainerOptions::default()), container_config) + .await?) +} + +async fn relax_permissions(docker: &Docker, container: &str, path: &str) -> eyre::Result<()> { + let exec = docker + .create_exec( + container, + CreateExecOptions { + cmd: Some(vec!["chmod", "777", path]), + attach_stdout: Some(true), + attach_stderr: Some(true), + ..Default::default() + }, + ) + .await?; + + let StartExecResults::Attached { mut output, .. } = docker.start_exec(&exec.id, None).await? + else { + return Err(eyre::eyre!("Failed to start exec for relaxing permissions")); + }; + + while let Some(Ok(output)) = output.next().await { + use bollard::container::LogOutput::*; + match output { + StdErr { message } => { + return Err(eyre::eyre!( + "Failed to relax permissions for {path}: {}", + String::from_utf8_lossy(&message) + )) + } + _ => continue, + }; + } + + Ok(()) +} + +async fn await_ipc_readiness(docker: &Docker, container: &str) -> eyre::Result<()> { + let mut attach_stream = docker + .attach_container( + container, + Some(AttachContainerOptions { + stdout: true, + stderr: true, + stream: true, + logs: true, + ..Default::default() + }), + ) + .await?; + + let mut rpc_ipc_started = false; + let mut auth_ipc_started = false; + + // wait for the node to start and signal that IPCs are ready + while let Some(Ok(output)) = attach_stream.output.next().await { + use bollard::container::LogOutput; + match output { + LogOutput::StdOut { message } | LogOutput::StdErr { message } => { + let message = String::from_utf8_lossy(&message); + if message.contains(AUTH_CONTAINER_IPC_PATH) { + auth_ipc_started = true; + } + + if message.contains(RPC_CONTAINER_IPC_PATH) { + rpc_ipc_started = true; + } + + if message.to_lowercase().contains("error") { + return Err(eyre::eyre!("Failed to start op-reth container: {message}.")); + } + } + LogOutput::StdIn { .. } | LogOutput::Console { .. } => {} + } + + if auth_ipc_started && rpc_ipc_started { + break; + } + } + + if !auth_ipc_started || !rpc_ipc_started { + return Err(eyre::eyre!( + "Failed to start op-reth container: IPCs not ready" + )); + } + + Ok(()) +} + +async fn cleanup(tempdir: PathBuf, docker: Docker, container_id: String) { + // This is a no-op function that will be spawned to clean up the container on ctrl-c + // or Drop. + debug!( + "Cleaning up external node resources at {} [{container_id}]...", + tempdir.display() + ); + + if !tempdir.exists() { + return; // If the tempdir does not exist, there's nothing to clean up. + } + + // Block on cleaning up the container + if let Err(e) = docker + .stop_container(&container_id, None::) + .await + { + warn!("Failed to stop container {}: {}", container_id, e); + } + + if let Err(e) = docker + .remove_container( + &container_id, + Some(RemoveContainerOptions { + force: true, + ..Default::default() + }), + ) + .await + { + warn!("Failed to remove container {}: {}", container_id, e); + } + + // Clean up the temporary directory + std::fs::remove_dir_all(&tempdir).expect("Failed to remove temporary directory"); +} + +trait OptimismProviderExt { + async fn hash_at_height(&self, height: u64) -> eyre::Result; + async fn latest_block_hash_and_number(&self) -> eyre::Result<(B256, u64)>; + async fn execution_payload_for_block(&self, number: u64) -> eyre::Result; +} + +impl OptimismProviderExt for RootProvider { + async fn hash_at_height(&self, height: u64) -> eyre::Result { + let block = self + .get_block_by_number(BlockNumberOrTag::Number(height)) + .await? + .ok_or_else(|| eyre::eyre!("No block found at height {}", height))?; + Ok(block.header.hash) + } + + async fn latest_block_hash_and_number(&self) -> eyre::Result<(B256, u64)> { + let block = self + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .ok_or_else(|| eyre::eyre!("No latest block found"))?; + Ok((block.header.hash, block.header.number)) + } + + async fn execution_payload_for_block(&self, number: u64) -> eyre::Result { + let block = self + .get_block_by_number(BlockNumberOrTag::Number(number)) + .full() + .await? + .ok_or_else(|| eyre::eyre!("No block found at height {}", number))?; + + let withdrawals = block.withdrawals.clone().unwrap_or_default(); + + // Calculate the withdrawals root properly + let withdrawals_root = if withdrawals.is_empty() { + EMPTY_WITHDRAWALS + } else { + // Calculate the Merkle Patricia Trie root of the withdrawals + let mut buf = Vec::new(); + withdrawals.encode(&mut buf); + keccak256(&buf) + }; + + let payload = OpExecutionPayloadV4 { + payload_inner: ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: block.header.parent_hash, + fee_recipient: block.header.beneficiary, + state_root: block.header.state_root, + receipts_root: block.header.receipts_root, + logs_bloom: block.header.logs_bloom, + prev_randao: block.header.mix_hash, + block_number: block.header.number, + gas_limit: block.header.gas_limit, + gas_used: block.header.gas_used, + timestamp: block.header.timestamp, + extra_data: block.header.extra_data.clone(), + base_fee_per_gas: U256::from( + block.header.base_fee_per_gas.unwrap_or_default(), + ), + block_hash: block.header.hash, + transactions: block + .transactions + .into_transactions_vec() + .into_iter() + .map(|tx| tx.as_ref().encoded_2718().into()) + .collect(), + }, + withdrawals: block.withdrawals.unwrap_or_default().to_vec(), + }, + blob_gas_used: block.header.inner.blob_gas_used.unwrap_or_default(), + excess_blob_gas: block.header.inner.excess_blob_gas.unwrap_or_default(), + }, + withdrawals_root, + }; + + Ok(payload) + } +} diff --git a/crates/op-rbuilder/src/tests/framework/harness.rs b/crates/op-rbuilder/src/tests/framework/harness.rs deleted file mode 100644 index cd5ee0ee9..000000000 --- a/crates/op-rbuilder/src/tests/framework/harness.rs +++ /dev/null @@ -1,357 +0,0 @@ -use super::{ - apis::EngineApi, - blocks::BlockGenerator, - op::{OpRbuilderConfig, OpRethConfig}, - service::{self, Service, ServiceInstance}, - TransactionBuilder, BUILDER_PRIVATE_KEY, -}; -use alloy_eips::BlockNumberOrTag; -use alloy_network::Network; -use alloy_primitives::{hex, B256}; -use alloy_provider::{ - ext::TxPoolApi, Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, -}; -use op_alloy_network::Optimism; -use parking_lot::Mutex; -use std::{ - collections::HashSet, net::TcpListener, path::PathBuf, sync::LazyLock, time::SystemTime, -}; -use time::{format_description, OffsetDateTime}; -use uuid::Uuid; - -pub struct TestHarnessBuilder { - name: String, - use_revert_protection: bool, - flashblocks_port: Option, - chain_block_time: Option, - flashbots_block_time: Option, - namespaces: Option, - extra_params: Option, -} - -impl TestHarnessBuilder { - pub fn new(name: &str) -> Self { - Self { - name: name.to_string(), - use_revert_protection: false, - flashblocks_port: None, - chain_block_time: None, - flashbots_block_time: None, - namespaces: None, - extra_params: None, - } - } - - pub fn with_revert_protection(mut self) -> Self { - self.use_revert_protection = true; - self - } - - pub fn with_flashblocks_port(mut self, port: u16) -> Self { - self.flashblocks_port = Some(port); - self - } - - pub fn with_chain_block_time(mut self, block_time: u64) -> Self { - self.chain_block_time = Some(block_time); - self - } - - pub fn with_flashbots_block_time(mut self, block_time: u64) -> Self { - self.flashbots_block_time = Some(block_time); - self - } - - pub fn with_namespaces(mut self, namespaces: &str) -> Self { - self.namespaces = Some(namespaces.to_string()); - self - } - - pub fn with_extra_params(mut self, extra_params: &str) -> Self { - self.extra_params = Some(extra_params.to_string()); - self - } - - pub async fn build(self) -> eyre::Result { - let mut framework = IntegrationFramework::new(&self.name).unwrap(); - - // we are going to use the fixture genesis and copy it to each test folder - let genesis = include_str!("artifacts/genesis.json.tmpl"); - - let mut genesis_path = framework.test_dir.clone(); - genesis_path.push("genesis.json"); - std::fs::write(&genesis_path, genesis)?; - - // create the builder - let builder_data_dir: PathBuf = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let builder_auth_rpc_port = get_available_port(); - let builder_http_port = get_available_port(); - let mut op_rbuilder_config = OpRbuilderConfig::new() - .chain_config_path(genesis_path.clone()) - .data_dir(builder_data_dir) - .auth_rpc_port(builder_auth_rpc_port) - .network_port(get_available_port()) - .http_port(builder_http_port) - .with_builder_private_key(BUILDER_PRIVATE_KEY) - .with_revert_protection(self.use_revert_protection) - .with_namespaces(self.namespaces) - .with_extra_params(self.extra_params); - if let Some(flashblocks_port) = self.flashblocks_port { - op_rbuilder_config = op_rbuilder_config.with_flashblocks_port(flashblocks_port); - } - - if let Some(chain_block_time) = self.chain_block_time { - op_rbuilder_config = op_rbuilder_config.with_chain_block_time(chain_block_time); - } - - if let Some(flashbots_block_time) = self.flashbots_block_time { - op_rbuilder_config = op_rbuilder_config.with_flashbots_block_time(flashbots_block_time); - } - - // create the validation reth node - - let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let validator_auth_rpc_port = get_available_port(); - let reth = OpRethConfig::new() - .chain_config_path(genesis_path) - .data_dir(reth_data_dir) - .auth_rpc_port(validator_auth_rpc_port) - .network_port(get_available_port()); - - framework.start("op-reth", &reth).await.unwrap(); - - let builder = framework - .start("op-rbuilder", &op_rbuilder_config) - .await - .unwrap(); - - let builder_log_path = builder.log_path.clone(); - - Ok(TestHarness { - framework: framework, - builder_auth_rpc_port, - builder_http_port, - validator_auth_rpc_port, - builder_log_path, - chain_block_time: self.chain_block_time, - }) - } -} - -pub struct TestHarness { - framework: IntegrationFramework, - builder_auth_rpc_port: u16, - builder_http_port: u16, - validator_auth_rpc_port: u16, - builder_log_path: PathBuf, - chain_block_time: Option, -} - -impl TestHarness { - pub async fn send_valid_transaction( - &self, - ) -> eyre::Result> { - self.create_transaction().send().await - } - - pub async fn send_revert_transaction( - &self, - ) -> eyre::Result> { - self.create_transaction() - .with_input(hex!("60006000fd").into()) // PUSH1 0x00 PUSH1 0x00 REVERT - .send() - .await - } - - pub fn provider(&self) -> eyre::Result> { - let url = format!("http://localhost:{}", self.builder_http_port); - let provider = - ProviderBuilder::::default().connect_http(url.parse()?); - - Ok(provider) - } - - pub async fn block_generator(&self) -> eyre::Result { - let engine_api = EngineApi::new_with_port(self.builder_auth_rpc_port).unwrap(); - let validation_api = Some(EngineApi::new_with_port(self.validator_auth_rpc_port).unwrap()); - - let mut generator = BlockGenerator::new( - engine_api, - validation_api, - false, - self.chain_block_time.map_or(1, |time| time / 1000), // in seconds - None, - ); - generator.init().await?; - - Ok(generator) - } - - pub fn create_transaction(&self) -> TransactionBuilder { - TransactionBuilder::new(self.provider().expect("provider not available")) - } - - pub async fn latest_block(&self) -> ::BlockResponse { - self.provider() - .expect("provider not available") - .get_block_by_number(BlockNumberOrTag::Latest) - .full() - .await - .expect("failed to get latest block by hash") - .expect("latest block should exist") - } - - pub async fn latest_base_fee(&self) -> u128 { - self.latest_block() - .await - .header - .base_fee_per_gas - .expect("Base fee per gas not found in the latest block header") as u128 - } - - pub const fn builder_private_key() -> &'static str { - BUILDER_PRIVATE_KEY - } - - pub async fn check_tx_in_pool(&self, tx_hash: B256) -> eyre::Result { - let pool_inspect = self - .provider() - .expect("provider not available") - .txpool_content() - .await?; - - let is_pending = pool_inspect.pending.iter().any(|pending_account_map| { - pending_account_map - .1 - .iter() - .any(|(_, tx)| tx.as_recovered().hash() == *tx_hash) - }); - if is_pending { - return Ok(TransactionStatus::Pending); - } - - let is_queued = pool_inspect.queued.iter().any(|queued_account_map| { - queued_account_map - .1 - .iter() - .any(|(_, tx)| tx.as_recovered().hash() == *tx_hash) - }); - if is_queued { - return Ok(TransactionStatus::Queued); - } - - // check that the builder emitted logs for the reverted transactions with the monitoring logic - // this will tell us whether the builder dropped the transaction - // TODO: this is not ideal, lets find a different way to detect this - // Each time a transaction is dropped, it emits a log like this - // Note that this does not tell us the reason why the transaction was dropped. Ideally - // we should know it at this point. - // 'Transaction event received target="monitoring" tx_hash="" kind="discarded"' - let builder_logs = std::fs::read_to_string(&self.builder_log_path)?; - let txn_log = format!( - "Transaction event received target=\"monitoring\" tx_hash=\"{}\" kind=\"discarded\"", - tx_hash, - ); - if builder_logs.contains(txn_log.as_str()) { - return Ok(TransactionStatus::Dropped); - } - - Ok(TransactionStatus::NotFound) - } -} - -impl Drop for TestHarness { - fn drop(&mut self) { - for service in &mut self.framework.services { - let res = service.stop(); - if let Err(e) = res { - println!("Failed to stop service: {}", e); - } - } - } -} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TransactionStatus { - NotFound, - Pending, - Queued, - Dropped, -} - -impl TransactionStatus { - pub fn is_pending(&self) -> bool { - matches!(self, TransactionStatus::Pending) - } - - pub fn is_queued(&self) -> bool { - matches!(self, TransactionStatus::Queued) - } - - pub fn is_dropped(&self) -> bool { - matches!(self, TransactionStatus::Dropped) - } -} - -pub fn get_available_port() -> u16 { - static CLAIMED_PORTS: LazyLock>> = - LazyLock::new(|| Mutex::new(HashSet::new())); - loop { - let port: u16 = rand::random_range(1000..20000); - if TcpListener::bind(("127.0.0.1", port)).is_ok() && CLAIMED_PORTS.lock().insert(port) { - return port; - } - } -} - -#[derive(Debug)] -pub enum IntegrationError { - SpawnError, - BinaryNotFound, - SetupError, - LogError, - ServiceAlreadyRunning, -} - -struct IntegrationFramework { - test_dir: PathBuf, - services: Vec, -} - -impl IntegrationFramework { - pub fn new(test_name: &str) -> Result { - let dt: OffsetDateTime = SystemTime::now().into(); - let format = format_description::parse("[year]_[month]_[day]_[hour]_[minute]_[second]") - .map_err(|_| IntegrationError::SetupError)?; - - let date_format = dt - .format(&format) - .map_err(|_| IntegrationError::SetupError)?; - - let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - test_dir.push("../../integration_logs"); - test_dir.push(format!("{date_format}_{test_name}")); - - std::fs::create_dir_all(&test_dir).map_err(|_| IntegrationError::SetupError)?; - - Ok(Self { - test_dir, - services: Vec::new(), - }) - } - - pub async fn start( - &mut self, - name: &str, - config: &T, - ) -> Result<&mut ServiceInstance, service::Error> { - let service = self.create_service(name)?; - service.start_with_config(config).await?; - Ok(service) - } - - pub fn create_service(&mut self, name: &str) -> Result<&mut ServiceInstance, service::Error> { - let service = ServiceInstance::new(name.to_string(), self.test_dir.clone()); - self.services.push(service); - Ok(self.services.last_mut().unwrap()) - } -} diff --git a/crates/op-rbuilder/src/tests/framework/instance.rs b/crates/op-rbuilder/src/tests/framework/instance.rs new file mode 100644 index 000000000..7c482c939 --- /dev/null +++ b/crates/op-rbuilder/src/tests/framework/instance.rs @@ -0,0 +1,355 @@ +use crate::{ + args::OpRbuilderArgs, + builders::{BuilderConfig, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, + primitives::reth::engine_api_builder::OpEngineApiBuilder, + revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}, + tests::{ + framework::{driver::ChainDriver, BUILDER_PRIVATE_KEY}, + ChainDriverExt, EngineApi, Ipc, TransactionPoolObserver, + }, + tx::FBPooledTransaction, + tx_signer::Signer, +}; +use alloy_provider::{Identity, ProviderBuilder, RootProvider}; +use clap::Parser; +use core::{ + any::Any, + future::Future, + net::Ipv4Addr, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; +use futures::FutureExt; +use moka::future::Cache; +use nanoid::nanoid; +use op_alloy_network::Optimism; +use reth::{ + args::{DatadirArgs, NetworkArgs, RpcServerArgs}, + core::exit::NodeExitFuture, + tasks::TaskManager, +}; +use reth_node_builder::{NodeBuilder, NodeConfig}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_cli::commands::Commands; +use reth_optimism_node::{ + node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, + OpNode, +}; +use reth_transaction_pool::{AllTransactionsEvents, TransactionPool}; +use std::sync::{Arc, LazyLock}; +use tokio::sync::oneshot; + +/// Represents a type that emulates a local in-process instance of the OP builder node. +/// This node uses IPC as the communication channel for the RPC server Engine API. +pub struct LocalInstance { + signer: Signer, + config: NodeConfig, + args: OpRbuilderArgs, + task_manager: Option, + exit_future: NodeExitFuture, + _node_handle: Box, + pool_observer: TransactionPoolObserver, +} + +impl LocalInstance { + /// Creates a new local instance of the OP builder node with the given arguments, + /// with the default Reth node configuration. + /// + /// This method does not prefund any accounts, so before sending any transactions + /// make sure that sender accounts are funded. + pub async fn new(args: OpRbuilderArgs) -> eyre::Result { + Self::new_with_config::

(args, default_node_config()).await + } + + /// Creates a new local instance of the OP builder node with the given arguments, + /// with a given Reth node configuration. + /// + /// This method does not prefund any accounts, so before sending any transactions + /// make sure that sender accounts are funded. + pub async fn new_with_config( + args: OpRbuilderArgs, + config: NodeConfig, + ) -> eyre::Result { + let mut args = args; + let task_manager = task_manager(); + let op_node = OpNode::new(args.rollup_args.clone()); + let reverted_cache = Cache::builder().max_capacity(100).build(); + let reverted_cache_clone = reverted_cache.clone(); + + let (rpc_ready_tx, rpc_ready_rx) = oneshot::channel::<()>(); + let (txpool_ready_tx, txpool_ready_rx) = + oneshot::channel::>(); + + let signer = args.builder_signer.unwrap_or_else(|| { + Signer::try_from_secret( + BUILDER_PRIVATE_KEY + .parse() + .expect("Invalid builder private key"), + ) + .expect("Failed to create signer from private key") + }); + args.builder_signer = Some(signer); + args.rollup_args.enable_tx_conditional = true; + + let builder_config = BuilderConfig::::try_from(args.clone()) + .expect("Failed to convert rollup args to builder config"); + let da_config = builder_config.da_config.clone(); + + let addons: OpAddOns< + _, + _, + OpEngineValidatorBuilder, + OpEngineApiBuilder, + > = OpAddOnsBuilder::default() + .with_sequencer(args.rollup_args.sequencer.clone()) + .with_enable_tx_conditional(args.rollup_args.enable_tx_conditional) + .with_da_config(da_config) + .build(); + + let node_builder = NodeBuilder::<_, OpChainSpec>::new(config.clone()) + .testing_node(task_manager.executor()) + .with_types::() + .with_components( + op_node + .components() + .pool(pool_component(&args)) + .payload(P::new_service(builder_config)?), + ) + .with_add_ons(addons) + .extend_rpc_modules(move |ctx| { + if args.enable_revert_protection { + tracing::info!("Revert protection enabled"); + + let pool = ctx.pool().clone(); + let provider = ctx.provider().clone(); + let revert_protection_ext: RevertProtectionExt< + _, + _, + _, + op_alloy_network::Optimism, + > = RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); + + ctx.modules + .merge_configured(revert_protection_ext.bundle_api().into_rpc())?; + + ctx.modules.replace_configured( + revert_protection_ext.eth_api(reverted_cache).into_rpc(), + )?; + } + + Ok(()) + }) + .on_rpc_started(move |_, _| { + let _ = rpc_ready_tx.send(()); + Ok(()) + }) + .on_node_started(move |ctx| { + txpool_ready_tx + .send(ctx.pool.all_transactions_event_listener()) + .expect("Failed to send txpool ready signal"); + + Ok(()) + }); + + let node_handle = node_builder.launch().await?; + let exit_future = node_handle.node_exit_future; + let boxed_handle = Box::new(node_handle.node); + let node_handle: Box = boxed_handle; + + // Wait for all required components to be ready + rpc_ready_rx.await.expect("Failed to receive ready signal"); + let pool_monitor = txpool_ready_rx + .await + .expect("Failed to receive txpool ready signal"); + + Ok(Self { + args, + signer, + config, + exit_future, + _node_handle: node_handle, + task_manager: Some(task_manager), + pool_observer: TransactionPoolObserver::new(pool_monitor, reverted_cache_clone), + }) + } + + /// Creates new local instance of the OP builder node with the standard builder configuration. + /// This method prefunds the default accounts with 1 ETH each. + pub async fn standard() -> eyre::Result { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(ref node_command) = args.command else { + unreachable!() + }; + let instance = Self::new::(node_command.ext.clone()).await?; + let driver = ChainDriver::::local(&instance).await?; + driver.fund_default_accounts().await?; + Ok(instance) + } + + /// Creates new local instance of the OP builder node with the flashblocks builder configuration. + /// This method prefunds the default accounts with 1 ETH each. + pub async fn flashblocks() -> eyre::Result { + let mut args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(ref mut node_command) = args.command else { + unreachable!() + }; + node_command.ext.flashblocks.enabled = true; + node_command.ext.flashblocks.flashblocks_port = 0; // use random os assigned port + let instance = Self::new::(node_command.ext.clone()).await?; + let driver = ChainDriver::::local(&instance).await?; + driver.fund_default_accounts().await?; + Ok(instance) + } + + pub const fn config(&self) -> &NodeConfig { + &self.config + } + + pub const fn args(&self) -> &OpRbuilderArgs { + &self.args + } + + pub const fn signer(&self) -> &Signer { + &self.signer + } + + pub fn flashblocks_ws_url(&self) -> String { + let ipaddr: Ipv4Addr = self + .args + .flashblocks + .flashblocks_addr + .parse() + .expect("Failed to parse flashblocks IP address"); + + let ipaddr = if ipaddr.is_unspecified() { + Ipv4Addr::LOCALHOST + } else { + ipaddr + }; + + let port = self.args.flashblocks.flashblocks_port; + + format!("ws://{ipaddr}:{port}/") + } + + pub fn rpc_ipc(&self) -> &str { + &self.config.rpc.ipcpath + } + + pub fn auth_ipc(&self) -> &str { + &self.config.rpc.auth_ipc_path + } + + pub fn engine_api(&self) -> EngineApi { + EngineApi::::with_ipc(self.auth_ipc()) + } + + pub const fn pool(&self) -> &TransactionPoolObserver { + &self.pool_observer + } + + pub async fn driver(&self) -> eyre::Result> { + ChainDriver::::local(self).await + } + + pub async fn provider(&self) -> eyre::Result> { + ProviderBuilder::::default() + .connect_ipc(self.rpc_ipc().to_string().into()) + .await + .map_err(|e| eyre::eyre!("Failed to connect to provider: {e}")) + } +} + +impl Drop for LocalInstance { + fn drop(&mut self) { + if let Some(task_manager) = self.task_manager.take() { + task_manager.graceful_shutdown_with_timeout(Duration::from_secs(3)); + std::fs::remove_dir_all(self.config().datadir().to_string()).unwrap_or_else(|e| { + panic!( + "Failed to remove temporary data directory {}: {e}", + self.config().datadir() + ) + }); + } + } +} + +impl Future for LocalInstance { + type Output = eyre::Result<()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.get_mut().exit_future.poll_unpin(cx) + } +} + +pub fn default_node_config() -> NodeConfig { + let tempdir = std::env::temp_dir(); + let random_id = nanoid!(); + + let data_path = tempdir + .join(format!("rbuilder.{random_id}.datadir")) + .to_path_buf(); + + std::fs::create_dir_all(&data_path).expect("Failed to create temporary data directory"); + + let rpc_ipc_path = tempdir + .join(format!("rbuilder.{random_id}.rpc-ipc")) + .to_path_buf(); + + let auth_ipc_path = tempdir + .join(format!("rbuilder.{random_id}.auth-ipc")) + .to_path_buf(); + + let mut rpc = RpcServerArgs::default().with_auth_ipc(); + rpc.ws = false; + rpc.http = false; + rpc.auth_port = 0; + rpc.ipcpath = rpc_ipc_path.to_string_lossy().into(); + rpc.auth_ipc_path = auth_ipc_path.to_string_lossy().into(); + + let mut network = NetworkArgs::default().with_unused_ports(); + network.discovery.disable_discovery = true; + + let datadir = DatadirArgs { + datadir: data_path + .to_string_lossy() + .parse() + .expect("Failed to parse data dir path"), + static_files_path: None, + }; + + NodeConfig::::new(chain_spec()) + .with_datadir_args(datadir) + .with_rpc(rpc) + .with_network(network) +} + +fn chain_spec() -> Arc { + static CHAIN_SPEC: LazyLock> = LazyLock::new(|| { + let genesis = include_str!("./artifacts/genesis.json.tmpl"); + let genesis = serde_json::from_str(genesis).expect("invalid genesis JSON"); + let chain_spec = OpChainSpec::from_genesis(genesis); + Arc::new(chain_spec) + }); + + CHAIN_SPEC.clone() +} + +fn task_manager() -> TaskManager { + TaskManager::new(tokio::runtime::Handle::current()) +} + +fn pool_component(args: &OpRbuilderArgs) -> OpPoolBuilder { + let rollup_args = &args.rollup_args; + OpPoolBuilder::::default() + .with_enable_tx_conditional( + // Revert protection uses the same internal pool logic as conditional transactions + // to garbage collect transactions out of the bundle range. + rollup_args.enable_tx_conditional || args.enable_revert_protection, + ) + .with_supervisor( + rollup_args.supervisor_http.clone(), + rollup_args.supervisor_safety_level, + ) +} diff --git a/crates/op-rbuilder/src/tests/framework/mod.rs b/crates/op-rbuilder/src/tests/framework/mod.rs index d8ffff3c0..e8488d99b 100644 --- a/crates/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/op-rbuilder/src/tests/framework/mod.rs @@ -1,16 +1,16 @@ mod apis; -mod blocks; -mod harness; -mod op; -mod service; +mod driver; +mod external; +mod instance; mod txs; +mod utils; pub use apis::*; -pub use blocks::*; -pub use harness::*; -pub use op::*; -pub use service::*; +pub use driver::*; +pub use external::*; +pub use instance::*; pub use txs::*; +pub use utils::*; const BUILDER_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; @@ -22,3 +22,34 @@ pub const DEFAULT_JWT_TOKEN: &str = "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; pub const ONE_ETH: u128 = 1_000_000_000_000_000_000; + +/// This gets invoked before any tests, when the cargo test framework loads the test library. +/// It injects itself into +#[ctor::ctor] +fn init_tests_logging() { + use tracing_subscriber::{filter::filter_fn, prelude::*}; + if let Ok(v) = std::env::var("TEST_TRACE") { + let level = match v.as_str() { + "false" | "off" => return, + "true" | "debug" | "on" => tracing::Level::DEBUG, + "trace" => tracing::Level::TRACE, + "info" => tracing::Level::INFO, + "warn" => tracing::Level::WARN, + "error" => tracing::Level::ERROR, + _ => return, + }; + + // let prefix_blacklist = &["alloy_transport_ipc", "storage::db::mdbx"]; + let prefix_blacklist = &["storage::db::mdbx"]; + + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with(filter_fn(move |metadata| { + metadata.level() <= &level + && !prefix_blacklist + .iter() + .any(|prefix| metadata.target().starts_with(prefix)) + })) + .init(); + } +} diff --git a/crates/op-rbuilder/src/tests/framework/op.rs b/crates/op-rbuilder/src/tests/framework/op.rs deleted file mode 100644 index f007b33d0..000000000 --- a/crates/op-rbuilder/src/tests/framework/op.rs +++ /dev/null @@ -1,325 +0,0 @@ -use std::{ - fs::File, - future::Future, - io::{ErrorKind, Read}, - path::{Path, PathBuf}, - process::Command, -}; - -use std::time::Duration; -use tokio::time::sleep; - -use super::{ - service::{self, Service}, - DEFAULT_JWT_TOKEN, -}; - -#[derive(Default, Debug)] -pub struct OpRbuilderConfig { - auth_rpc_port: Option, - jwt_secret_path: Option, - chain_config_path: Option, - data_dir: Option, - http_port: Option, - network_port: Option, - builder_private_key: Option, - flashblocks_port: Option, - chain_block_time: Option, - flashbots_block_time: Option, - with_revert_protection: Option, - namespaces: Option, - extra_params: Option, -} - -impl OpRbuilderConfig { - pub fn new() -> Self { - Self::default() - } - - pub fn auth_rpc_port(mut self, port: u16) -> Self { - self.auth_rpc_port = Some(port); - self - } - - pub fn chain_config_path>(mut self, path: P) -> Self { - self.chain_config_path = Some(path.into()); - self - } - - pub fn data_dir>(mut self, path: P) -> Self { - self.data_dir = Some(path.into()); - self - } - - pub fn network_port(mut self, port: u16) -> Self { - self.network_port = Some(port); - self - } - - pub fn http_port(mut self, port: u16) -> Self { - self.http_port = Some(port); - self - } - - pub fn with_builder_private_key(mut self, private_key: &str) -> Self { - self.builder_private_key = Some(private_key.to_string()); - self - } - - pub fn with_revert_protection(mut self, revert_protection: bool) -> Self { - self.with_revert_protection = Some(revert_protection); - self - } - - pub fn with_flashblocks_port(mut self, port: u16) -> Self { - self.flashblocks_port = Some(port); - self - } - - pub fn with_chain_block_time(mut self, time: u64) -> Self { - self.chain_block_time = Some(time); - self - } - - pub fn with_flashbots_block_time(mut self, time: u64) -> Self { - self.flashbots_block_time = Some(time); - self - } - - pub fn with_namespaces(mut self, namespaces: Option) -> Self { - self.namespaces = namespaces; - self - } - - pub fn with_extra_params(mut self, extra_params: Option) -> Self { - self.extra_params = extra_params; - self - } -} - -impl Service for OpRbuilderConfig { - fn command(&self) -> Command { - let mut bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - bin_path.push("../../target/debug/op-rbuilder"); - - let mut cmd = Command::new(bin_path); - let jwt_path = get_or_create_jwt_path(self.jwt_secret_path.as_ref()); - - cmd.arg("node") - .arg("--authrpc.port") - .arg( - self.auth_rpc_port - .expect("auth_rpc_port not set") - .to_string(), - ) - .arg("--authrpc.jwtsecret") - .arg( - jwt_path - .to_str() - .expect("Failed to convert jwt_path to string"), - ) - .arg("--chain") - .arg( - self.chain_config_path - .as_ref() - .expect("chain_config_path not set"), - ) - .arg("--datadir") - .arg(self.data_dir.as_ref().expect("data_dir not set")) - .arg("--disable-discovery") - .arg("--color") - .arg("never") - .arg("--builder.log-pool-transactions") - .arg("--port") - .arg(self.network_port.expect("network_port not set").to_string()) - .arg("--ipcdisable") - .arg("-vvvv"); - - if let Some(revert_protection) = self.with_revert_protection { - if revert_protection { - cmd.arg("--builder.enable-revert-protection"); - } - } - - if let Some(builder_private_key) = &self.builder_private_key { - cmd.arg("--rollup.builder-secret-key") - .arg(builder_private_key); - } - - if let Some(http_port) = self.http_port { - cmd.arg("--http") - .arg("--http.port") - .arg(http_port.to_string()); - } - - if let Some(flashblocks_port) = &self.flashblocks_port { - cmd.arg("--flashblocks.enabled"); - cmd.arg("--flashblocks.addr").arg("127.0.0.1"); - cmd.arg("--flashblocks.port") - .arg(flashblocks_port.to_string()); - } - - if let Some(chain_block_time) = self.chain_block_time { - cmd.arg("--rollup.chain-block-time") - .arg(chain_block_time.to_string()); - } - - if let Some(flashbots_block_time) = self.flashbots_block_time { - cmd.arg("--flashblocks.block-time") - .arg(flashbots_block_time.to_string()); - } - - if let Some(namespaces) = &self.namespaces { - cmd.arg("--http.api").arg(namespaces); - } - - if let Some(extra_params) = &self.extra_params { - cmd.args(extra_params.split_ascii_whitespace()); - } - - cmd - } - - #[allow(clippy::manual_async_fn)] - fn ready(&self, log_path: &Path) -> impl Future> + Send { - async move { - poll_logs( - log_path, - "Starting consensus engine", - Duration::from_millis(100), - Duration::from_secs(60), - ) - .await - } - } -} - -#[derive(Default, Debug)] -pub struct OpRethConfig { - auth_rpc_port: Option, - jwt_secret_path: Option, - chain_config_path: Option, - data_dir: Option, - http_port: Option, - network_port: Option, -} - -impl OpRethConfig { - pub fn new() -> Self { - Self::default() - } - - pub fn auth_rpc_port(mut self, port: u16) -> Self { - self.auth_rpc_port = Some(port); - self - } - - pub fn chain_config_path>(mut self, path: P) -> Self { - self.chain_config_path = Some(path.into()); - self - } - - pub fn data_dir>(mut self, path: P) -> Self { - self.data_dir = Some(path.into()); - self - } - - pub fn network_port(mut self, port: u16) -> Self { - self.network_port = Some(port); - self - } -} - -impl Service for OpRethConfig { - fn command(&self) -> Command { - let bin_path = PathBuf::from("op-reth"); - - let mut cmd = Command::new(bin_path); - let jwt_path = get_or_create_jwt_path(self.jwt_secret_path.as_ref()); - - cmd.arg("node") - .arg("--authrpc.port") - .arg( - self.auth_rpc_port - .expect("auth_rpc_port not set") - .to_string(), - ) - .arg("--authrpc.jwtsecret") - .arg( - jwt_path - .to_str() - .expect("Failed to convert jwt_path to string"), - ) - .arg("--chain") - .arg( - self.chain_config_path - .as_ref() - .expect("chain_config_path not set"), - ) - .arg("--datadir") - .arg(self.data_dir.as_ref().expect("data_dir not set")) - .arg("--disable-discovery") - .arg("--color") - .arg("never") - .arg("--port") - .arg(self.network_port.expect("network_port not set").to_string()) - .arg("--ipcdisable"); - - if let Some(http_port) = self.http_port { - cmd.arg("--http") - .arg("--http.port") - .arg(http_port.to_string()); - } - - cmd - } - - #[allow(clippy::manual_async_fn)] - fn ready(&self, log_path: &Path) -> impl Future> + Send { - async move { - poll_logs( - log_path, - "Starting consensus engine", - Duration::from_millis(100), - Duration::from_secs(60), - ) - .await - } - } -} - -fn get_or_create_jwt_path(jwt_path: Option<&PathBuf>) -> PathBuf { - jwt_path.cloned().unwrap_or_else(|| { - let tmp_dir = std::env::temp_dir(); - let jwt_path = tmp_dir.join("jwt.hex"); - std::fs::write(&jwt_path, DEFAULT_JWT_TOKEN).expect("Failed to write JWT secret file"); - jwt_path - }) -} - -/// Helper function to poll logs periodically -pub async fn poll_logs( - log_path: &Path, - pattern: &str, - interval: Duration, - timeout: Duration, -) -> Result<(), service::Error> { - let start = std::time::Instant::now(); - - loop { - if start.elapsed() > timeout { - return Err(service::Error::Spawn(ErrorKind::TimedOut)); - } - - let mut file = File::open(log_path).map_err(|_| service::Error::Logs)?; - let mut contents = String::new(); - file.read_to_string(&mut contents) - .map_err(|_| service::Error::Logs)?; - - if contents.contains(pattern) { - return Ok(()); - } - - sleep(interval).await; - } -} diff --git a/crates/op-rbuilder/src/tests/framework/service.rs b/crates/op-rbuilder/src/tests/framework/service.rs deleted file mode 100644 index 6bc587c4c..000000000 --- a/crates/op-rbuilder/src/tests/framework/service.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::{ - fs::{File, OpenOptions}, - future::Future, - io::{ErrorKind, Read}, - path::{Path, PathBuf}, - process::{Child, Command}, -}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum Error { - #[error("Binary not found")] - BinaryNotFound, - - #[error("Failed to spawn process")] - Spawn(ErrorKind), - - #[error("Failed initialize log streams")] - Logs, - - #[error("Service is already running")] - ServiceAlreadyRunning, -} - -pub struct ServiceInstance { - process: Option, - pub log_path: PathBuf, -} - -impl ServiceInstance { - pub fn new(name: String, test_dir: PathBuf) -> Self { - let log_path = test_dir.join(format!("{name}.log")); - Self { - process: None, - log_path, - } - } - - pub fn start(&mut self, command: Command) -> Result<(), Error> { - if self.process.is_some() { - return Err(Error::ServiceAlreadyRunning); - } - - let log = open_log_file(&self.log_path)?; - let stdout = log.try_clone().map_err(|_| Error::Logs)?; - let stderr = log.try_clone().map_err(|_| Error::Logs)?; - - let mut cmd = command; - cmd.stdout(stdout).stderr(stderr); - - let child = match cmd.spawn() { - Ok(child) => Ok(child), - Err(e) => match e.kind() { - ErrorKind::NotFound => Err(Error::BinaryNotFound), - e => Err(Error::Spawn(e)), - }, - }?; - - self.process = Some(child); - Ok(()) - } - - pub fn stop(&mut self) -> Result<(), Error> { - if let Some(mut process) = self.process.take() { - return process.kill().map_err(|e| Error::Spawn(e.kind())); - } - Ok(()) - } - - /// Start a service using its configuration and wait for it to be ready - pub async fn start_with_config(&mut self, config: &T) -> Result<(), Error> { - self.start(config.command())?; - config.ready(&self.log_path).await?; - Ok(()) - } - - pub async fn find_log_line(&self, pattern: &str) -> eyre::Result<()> { - let mut file = - File::open(&self.log_path).map_err(|_| eyre::eyre!("Failed to open log file"))?; - let mut contents = String::new(); - file.read_to_string(&mut contents) - .map_err(|_| eyre::eyre!("Failed to read log file"))?; - - if contents.contains(pattern) { - Ok(()) - } else { - Err(eyre::eyre!("Pattern not found in log file: {}", pattern)) - } - } -} - -pub struct IntegrationFramework; - -pub trait Service { - /// Configure and return the command to run the service - fn command(&self) -> Command; - - /// Return a future that resolves when the service is ready - fn ready(&self, log_path: &Path) -> impl Future> + Send; -} - -fn open_log_file(path: &PathBuf) -> Result { - let prefix = path.parent().unwrap(); - std::fs::create_dir_all(prefix).map_err(|_| Error::Logs)?; - - OpenOptions::new() - .append(true) - .create(true) - .open(path) - .map_err(|_| Error::Logs) -} diff --git a/crates/op-rbuilder/src/tests/framework/txs.rs b/crates/op-rbuilder/src/tests/framework/txs.rs index aea27663e..95a869fa0 100644 --- a/crates/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/op-rbuilder/src/tests/framework/txs.rs @@ -1,15 +1,23 @@ use crate::{ primitives::bundle::{Bundle, BundleResult}, + tx::FBPooledTransaction, tx_signer::Signer, }; use alloy_consensus::TxEip1559; use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; -use alloy_primitives::{hex, Bytes}; +use alloy_primitives::{hex, Address, Bytes, TxHash, TxKind, B256, U256}; use alloy_provider::{PendingTransactionBuilder, Provider, RootProvider}; use core::cmp::max; +use dashmap::DashMap; +use futures::StreamExt; +use moka::future::Cache; use op_alloy_consensus::{OpTxEnvelope, OpTypedTransaction}; use op_alloy_network::Optimism; use reth_primitives::Recovered; +use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent, TransactionEvent}; +use std::{collections::VecDeque, sync::Arc}; +use tokio::sync::watch; +use tracing::debug; use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; @@ -51,11 +59,26 @@ impl TransactionBuilder { } } + pub fn with_to(mut self, to: Address) -> Self { + self.tx.to = TxKind::Call(to); + self + } + + pub fn with_create(mut self) -> Self { + self.tx.to = TxKind::Create; + self + } + pub fn with_key(mut self, key: u64) -> Self { self.key = Some(key); self } + pub fn with_value(mut self, value: u128) -> Self { + self.tx.value = U256::from(value); + self + } + pub fn with_signer(mut self, signer: Signer) -> Self { self.signer = Some(signer); self @@ -165,7 +188,7 @@ impl TransactionBuilder { let bundle = Bundle { transactions: vec![transaction_encoded.into()], reverting_hashes: if with_reverted_hash { - Some(vec![txn_hash.into()]) + Some(vec![txn_hash]) } else { None }, @@ -189,3 +212,141 @@ impl TransactionBuilder { .await?) } } + +type ObservationsMap = DashMap>; + +pub struct TransactionPoolObserver { + /// Stores a mapping of all observed transactions to their history of events. + observations: Arc, + + /// Fired when this type is dropped, giving a signal to the listener loop + /// to stop listening for events. + term: Option>, +} + +impl Drop for TransactionPoolObserver { + fn drop(&mut self) { + // Signal the listener loop to stop listening for events + if let Some(term) = self.term.take() { + let _ = term.send(true); + } + } +} + +impl TransactionPoolObserver { + pub fn new( + stream: AllTransactionsEvents, + reverts: Cache, + ) -> Self { + let mut stream = stream; + let observations = Arc::new(ObservationsMap::new()); + let observations_clone = Arc::clone(&observations); + let (term, mut term_rx) = watch::channel(false); + + tokio::spawn(async move { + let observations = observations_clone; + + loop { + tokio::select! { + _ = term_rx.changed() => { + if *term_rx.borrow() { + debug!("Transaction pool observer terminated."); + return; + } + } + tx_event = stream.next() => { + match tx_event { + Some(FullTransactionEvent::Pending(hash)) => { + tracing::debug!("Transaction pending: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Pending); + }, + Some(FullTransactionEvent::Queued(hash)) => { + tracing::debug!("Transaction queued: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Queued); + }, + Some(FullTransactionEvent::Mined { tx_hash, block_hash }) => { + tracing::debug!("Transaction mined: {tx_hash} in block {block_hash}"); + observations.entry(tx_hash).or_default().push_back(TransactionEvent::Mined(block_hash)); + }, + Some(FullTransactionEvent::Replaced { transaction, replaced_by }) => { + tracing::debug!("Transaction replaced: {transaction:?} by {replaced_by}"); + observations.entry(*transaction.hash()).or_default().push_back(TransactionEvent::Replaced(replaced_by)); + }, + Some(FullTransactionEvent::Discarded(hash)) => { + tracing::debug!("Transaction discarded: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Discarded); + reverts.insert(hash, ()).await; + }, + Some(FullTransactionEvent::Invalid(hash)) => { + tracing::debug!("Transaction invalid: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Invalid); + }, + Some(FullTransactionEvent::Propagated(_)) => {}, + None => {}, + } + } + } + } + }); + + Self { + observations, + term: Some(term), + } + } + + pub fn tx_status(&self, txhash: TxHash) -> Option { + self.observations + .get(&txhash) + .and_then(|history| history.back().cloned()) + } + + pub fn is_pending(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Pending)) + } + + pub fn is_queued(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Queued)) + } + + pub fn is_dropped(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Discarded)) + } + + pub fn count(&self, status: TransactionEvent) -> usize { + self.observations + .iter() + .filter(|tx| tx.value().back() == Some(&status)) + .count() + } + + pub fn pending_count(&self) -> usize { + self.count(TransactionEvent::Pending) + } + + pub fn queued_count(&self) -> usize { + self.count(TransactionEvent::Queued) + } + + pub fn dropped_count(&self) -> usize { + self.count(TransactionEvent::Discarded) + } + + /// Returns the history of pool events for a transaction. + pub fn history(&self, txhash: TxHash) -> Option> { + self.observations + .get(&txhash) + .map(|history| history.iter().cloned().collect()) + } + + pub fn print_all(&self) { + tracing::debug!("TxPool {:#?}", self.observations); + } + + pub fn exists(&self, txhash: TxHash) -> bool { + matches!( + self.tx_status(txhash), + Some(TransactionEvent::Pending) | Some(TransactionEvent::Queued) + ) + } +} diff --git a/crates/op-rbuilder/src/tests/framework/utils.rs b/crates/op-rbuilder/src/tests/framework/utils.rs new file mode 100644 index 000000000..ceba8434f --- /dev/null +++ b/crates/op-rbuilder/src/tests/framework/utils.rs @@ -0,0 +1,193 @@ +use alloy_eips::Encodable2718; +use alloy_primitives::{hex, Address, BlockHash, TxHash, TxKind, B256, U256}; +use alloy_rpc_types_eth::{Block, BlockTransactionHashes}; +use core::future::Future; +use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; +use op_alloy_rpc_types::Transaction; + +use crate::{ + tests::{framework::driver::ChainDriver, Protocol, ONE_ETH}, + tx_signer::Signer, +}; + +use super::{TransactionBuilder, FUNDED_PRIVATE_KEYS}; + +pub trait TransactionBuilderExt { + fn random_valid_transfer(self) -> Self; + fn random_reverting_transaction(self) -> Self; +} + +impl TransactionBuilderExt for TransactionBuilder { + fn random_valid_transfer(self) -> Self { + self.with_to(rand::random::

()).with_value(1) + } + + fn random_reverting_transaction(self) -> Self { + self.with_create().with_input(hex!("60006000fd").into()) // PUSH1 0x00 PUSH1 0x00 REVERT + } +} + +pub trait ChainDriverExt { + fn fund_default_accounts(&self) -> impl Future>; + fn fund_many( + &self, + addresses: Vec
, + amount: u128, + ) -> impl Future>; + fn fund(&self, address: Address, amount: u128) + -> impl Future>; + fn first_funded_address(&self) -> Address { + FUNDED_PRIVATE_KEYS[0] + .parse() + .expect("Invalid funded private key") + } + + fn fund_accounts( + &self, + count: usize, + amount: u128, + ) -> impl Future>> { + async move { + let accounts = (0..count).map(|_| Signer::random()).collect::>(); + self.fund_many(accounts.iter().map(|a| a.address).collect(), amount) + .await?; + Ok(accounts) + } + } + + fn build_new_block_with_valid_transaction( + &self, + ) -> impl Future)>>; + + fn build_new_block_with_reverrting_transaction( + &self, + ) -> impl Future)>>; +} + +impl ChainDriverExt for ChainDriver

{ + async fn fund_default_accounts(&self) -> eyre::Result<()> { + for key in FUNDED_PRIVATE_KEYS { + let signer: Signer = key.parse()?; + self.fund(signer.address, ONE_ETH).await?; + } + Ok(()) + } + + async fn fund_many(&self, addresses: Vec

, amount: u128) -> eyre::Result { + let mut txs = Vec::with_capacity(addresses.len()); + + for address in addresses { + let deposit = TxDeposit { + source_hash: B256::default(), + from: address, // Set the sender to the address of the account to seed + to: TxKind::Create, + mint: amount, // Amount to deposit + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: Default::default(), // No input data for the deposit + }; + + let signer = Signer::random(); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit))?; + let signed_tx_rlp = signed_tx.encoded_2718(); + txs.push(signed_tx_rlp.into()); + } + + Ok(self.build_new_block_with_txs(txs).await?.header.hash) + } + + async fn fund(&self, address: Address, amount: u128) -> eyre::Result { + let deposit = TxDeposit { + source_hash: B256::default(), + from: address, // Set the sender to the address of the account to seed + to: TxKind::Create, + mint: amount, // Amount to deposit + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: Default::default(), // No input data for the deposit + }; + + let signer = Signer::random(); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit))?; + let signed_tx_rlp = signed_tx.encoded_2718(); + Ok(self + .build_new_block_with_txs(vec![signed_tx_rlp.into()]) + .await? + .header + .hash) + } + + async fn build_new_block_with_valid_transaction( + &self, + ) -> eyre::Result<(TxHash, Block)> { + let tx = self + .create_transaction() + .random_valid_transfer() + .send() + .await?; + Ok((*tx.tx_hash(), self.build_new_block().await?)) + } + + async fn build_new_block_with_reverrting_transaction( + &self, + ) -> eyre::Result<(TxHash, Block)> { + let tx = self + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + + Ok((*tx.tx_hash(), self.build_new_block().await?)) + } +} + +pub trait BlockTransactionsExt { + fn includes(&self, txs: &impl AsTxs) -> bool; +} + +impl BlockTransactionsExt for Block { + fn includes(&self, txs: &impl AsTxs) -> bool { + txs.as_txs() + .into_iter() + .all(|tx| self.transactions.hashes().any(|included| included == tx)) + } +} + +impl BlockTransactionsExt for BlockTransactionHashes<'_, Transaction> { + fn includes(&self, txs: &impl AsTxs) -> bool { + let mut included_tx_iter = self.clone(); + txs.as_txs() + .iter() + .all(|tx| included_tx_iter.any(|included| included == *tx)) + } +} + +pub trait OpRbuilderArgsTestExt { + fn test_default() -> Self; +} + +impl OpRbuilderArgsTestExt for crate::args::OpRbuilderArgs { + fn test_default() -> Self { + let mut default = Self::default(); + default.flashblocks.flashblocks_port = 0; // randomize port + default + } +} + +pub trait AsTxs { + fn as_txs(&self) -> Vec; +} + +impl AsTxs for TxHash { + fn as_txs(&self) -> Vec { + vec![*self] + } +} + +impl AsTxs for Vec { + fn as_txs(&self) -> Vec { + self.clone() + } +} diff --git a/crates/op-rbuilder/src/tests/vanilla/data_availability.rs b/crates/op-rbuilder/src/tests/vanilla/data_availability.rs index cf640bab9..745f10b6e 100644 --- a/crates/op-rbuilder/src/tests/vanilla/data_availability.rs +++ b/crates/op-rbuilder/src/tests/vanilla/data_availability.rs @@ -1,33 +1,30 @@ -use crate::tests::framework::TestHarnessBuilder; +use crate::tests::{BlockTransactionsExt, ChainDriverExt, LocalInstance}; use alloy_provider::Provider; /// This test ensures that the transaction size limit is respected. /// We will set limit to 1 byte and see that the builder will not include any transactions. #[tokio::test] -async fn data_availability_tx_size_limit() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("data_availability_tx_size_limit") - .with_namespaces("admin,eth,miner") - .build() - .await?; - - let mut generator = harness.block_generator().await?; +async fn tx_size_limit() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; // Set (max_tx_da_size, max_block_da_size), with this case block won't fit any transaction - let call = harness - .provider()? + let call = driver + .provider() .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (1, 0)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); - let unfit_tx = harness + let unfit_tx = driver .create_transaction() .with_max_priority_fee_per_gas(50) .send() .await?; - let block = generator.generate_block().await?; + let block = driver.build_new_block().await?; + // tx should not be included because we set the tx_size_limit to 1 assert!( - block.not_includes(*unfit_tx.tx_hash()), + !block.includes(unfit_tx.tx_hash()), "transaction should not be included in the block" ); @@ -37,26 +34,22 @@ async fn data_availability_tx_size_limit() -> eyre::Result<()> { /// This test ensures that the block size limit is respected. /// We will set limit to 1 byte and see that the builder will not include any transactions. #[tokio::test] -async fn data_availability_block_size_limit() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("data_availability_block_size_limit") - .with_namespaces("admin,eth,miner") - .build() - .await?; - - let mut generator = harness.block_generator().await?; +async fn block_size_limit() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; // Set block da size to be small, so it won't include tx - let call = harness - .provider()? + let call = driver + .provider() .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 1)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); - let unfit_tx = harness.send_valid_transaction().await?; - let block = generator.generate_block().await?; + let (unfit_tx, block) = driver.build_new_block_with_valid_transaction().await?; + // tx should not be included because we set the tx_size_limit to 1 assert!( - block.not_includes(*unfit_tx.tx_hash()), + !block.includes(&unfit_tx), "transaction should not be included in the block" ); @@ -68,45 +61,41 @@ async fn data_availability_block_size_limit() -> eyre::Result<()> { /// We will set limit to 3 txs and see that the builder will include 3 transactions. /// We should not forget about builder transaction so we will spawn only 2 regular txs. #[tokio::test] -async fn data_availability_block_fill() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("data_availability_block_fill") - .with_namespaces("admin,eth,miner") - .build() - .await?; - - let mut generator = harness.block_generator().await?; +async fn block_fill() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; // Set block big enough so it could fit 3 transactions without tx size limit - let call = harness - .provider()? + let call = driver + .provider() .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 3)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); // We already have 2 so we will spawn one more to check that it won't be included (it won't fit // because of builder tx) - let fit_tx_1 = harness + let fit_tx_1 = driver .create_transaction() .with_max_priority_fee_per_gas(50) .send() .await?; - let fit_tx_2 = harness + let fit_tx_2 = driver .create_transaction() .with_max_priority_fee_per_gas(50) .send() .await?; - let unfit_tx_3 = harness.create_transaction().send().await?; + let unfit_tx_3 = driver.create_transaction().send().await?; - let block = generator.generate_block().await?; + let block = driver.build_new_block().await?; // Now the first 2 txs will fit into the block - assert!(block.includes(*fit_tx_1.tx_hash()), "tx should be in block"); - assert!(block.includes(*fit_tx_2.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_2.tx_hash()), "tx should be in block"); assert!( - block.not_includes(*unfit_tx_3.tx_hash()), + !block.includes(unfit_tx_3.tx_hash()), "unfit tx should not be in block" ); assert!( - harness.latest_block().await.transactions.len() == 4, + driver.latest_full().await?.transactions.len() == 4, "builder + deposit + 2 valid txs should be in the block" ); diff --git a/crates/op-rbuilder/src/tests/vanilla/mod.rs b/crates/op-rbuilder/src/tests/vanilla/mod.rs index 976a3dc33..5eb1364e1 100644 --- a/crates/op-rbuilder/src/tests/vanilla/mod.rs +++ b/crates/op-rbuilder/src/tests/vanilla/mod.rs @@ -5,5 +5,3 @@ mod ordering; mod revert; mod smoke; mod txpool; - -use super::*; diff --git a/crates/op-rbuilder/src/tests/vanilla/ordering.rs b/crates/op-rbuilder/src/tests/vanilla/ordering.rs index f524e57d4..74fe89677 100644 --- a/crates/op-rbuilder/src/tests/vanilla/ordering.rs +++ b/crates/op-rbuilder/src/tests/vanilla/ordering.rs @@ -1,21 +1,23 @@ -use crate::tests::framework::{TestHarnessBuilder, ONE_ETH}; +use crate::tests::{framework::ONE_ETH, ChainDriverExt, LocalInstance}; use alloy_consensus::Transaction; use futures::{future::join_all, stream, StreamExt}; /// This test ensures that the transactions are ordered by fee priority in the block. #[tokio::test] async fn fee_priority_ordering() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("integration_test_fee_priority_ordering") - .build() - .await?; + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(10, ONE_ETH).await?; - let mut generator = harness.block_generator().await?; - let accounts = generator.create_funded_accounts(10, ONE_ETH).await?; - let base_fee = harness.latest_base_fee().await; + let latest_block = driver.latest().await?; + let base_fee = latest_block + .header + .base_fee_per_gas + .expect("Base fee should be present in the latest block"); // generate transactions with randomized tips let txs = join_all(accounts.iter().map(|signer| { - harness + driver .create_transaction() .with_signer(*signer) .with_max_priority_fee_per_gas(rand::random_range(1..50)) @@ -28,15 +30,16 @@ async fn fee_priority_ordering() -> eyre::Result<()> { .map(|tx| *tx.tx_hash()) .collect::>(); - generator.generate_block().await?; + driver.build_new_block().await?; // verify all transactions are included in the block assert!( stream::iter(txs.iter()) .all(|tx_hash| async { - harness - .latest_block() + driver + .latest_full() .await + .expect("Failed to fetch latest block") .transactions .hashes() .any(|hash| hash == *tx_hash) @@ -46,9 +49,9 @@ async fn fee_priority_ordering() -> eyre::Result<()> { ); // verify all transactions are ordered by fee priority - let txs_tips = harness - .latest_block() - .await + let txs_tips = driver + .latest_full() + .await? .into_transactions_vec() .into_iter() .skip(1) // skip the deposit transaction diff --git a/crates/op-rbuilder/src/tests/vanilla/revert.rs b/crates/op-rbuilder/src/tests/vanilla/revert.rs index be0d139e2..d137c1cc4 100644 --- a/crates/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/op-rbuilder/src/tests/vanilla/revert.rs @@ -2,32 +2,41 @@ use alloy_provider::{PendingTransactionBuilder, Provider}; use op_alloy_network::Optimism; use crate::{ + args::OpRbuilderArgs, + builders::StandardBuilder, primitives::bundle::MAX_BLOCK_RANGE_BLOCKS, - tests::{BundleOpts, TestHarness, TestHarnessBuilder}, + tests::{ + BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, LocalInstance, + TransactionBuilderExt, ONE_ETH, + }, }; /// This test ensures that the transactions that get reverted and not included in the block, /// are eventually dropped from the pool once their block range is reached. /// This test creates N transactions with different block ranges. #[tokio::test] -async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_monitor_transaction_gc") - .with_revert_protection() - .with_namespaces("eth,web3,txpool") - .build() - .await?; +async fn monitor_transaction_gc() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(10, ONE_ETH).await?; + let latest_block_number = driver.latest().await?.header.number; // send 10 bundles with different block ranges let mut pending_txn = Vec::new(); - for i in 1..=10 { - let txn = harness + + for i in 0..accounts.len() { + let txn = driver .create_transaction() - .with_revert() + .random_reverting_transaction() + .with_signer(accounts[i].clone()) .with_bundle(BundleOpts { - block_number_max: Some(i), - block_number_min: None, + block_number_max: Some(latest_block_number + i as u64 + 1), + ..Default::default() }) .send() .await?; @@ -36,21 +45,19 @@ async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { // generate 10 blocks for i in 0..10 { - let generated_block = generator.generate_block().await?; + let generated_block = driver.build_new_block().await?; // blocks should only include two transactions (deposit + builder) - assert_eq!(generated_block.block.transactions.len(), 2); + assert_eq!(generated_block.transactions.len(), 2); // since we created the 10 transactions with increasing block ranges, as we generate blocks // one transaction will be gc on each block. // transactions from [0, i] should be dropped, transactions from [i+1, 10] should be queued for j in 0..=i { - let status = harness.check_tx_in_pool(*pending_txn[j].tx_hash()).await?; - assert!(status.is_dropped()); + assert!(rbuilder.pool().is_dropped(*pending_txn[j].tx_hash())); } for j in i + 1..10 { - let status = harness.check_tx_in_pool(*pending_txn[j].tx_hash()).await?; - assert!(status.is_queued()); + assert!(rbuilder.pool().is_pending(*pending_txn[j].tx_hash())); } } @@ -59,20 +66,26 @@ async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { /// If revert protection is disabled, the transactions that revert are included in the block. #[tokio::test] -async fn revert_protection_disabled() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_disabled") - .build() - .await?; - - let mut generator = harness.block_generator().await?; +async fn disabled() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; for _ in 0..10 { - let valid_tx = harness.send_valid_transaction().await?; - let reverting_tx = harness.send_revert_transaction().await?; - let block_generated = generator.generate_block().await?; + let valid_tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + + let reverting_tx = driver + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + let block = driver.build_new_block().await?; - assert!(block_generated.includes(*valid_tx.tx_hash())); - assert!(block_generated.includes(*reverting_tx.tx_hash())); + assert!(block.includes(valid_tx.tx_hash())); + assert!(block.includes(reverting_tx.tx_hash())); } Ok(()) @@ -81,12 +94,11 @@ async fn revert_protection_disabled() -> eyre::Result<()> { /// If revert protection is disabled, it should not be possible to send a revert bundle /// since the revert RPC endpoint is not available. #[tokio::test] -async fn revert_protection_disabled_bundle_endpoint_error() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_disabled_bundle_endpoint_error") - .build() - .await?; +async fn disabled_bundle_endpoint_error() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; - let res = harness + let res = driver .create_transaction() .with_bundle(BundleOpts::default()) .send() @@ -104,89 +116,72 @@ async fn revert_protection_disabled_bundle_endpoint_error() -> eyre::Result<()> /// is not included in the block and tried again for the next bundle range blocks /// when it will be dropped from the pool. #[tokio::test] -async fn revert_protection_bundle() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_bundle") - .with_revert_protection() - .with_namespaces("eth,web3,txpool") - .build() - .await?; +async fn bundle() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; // Block 1 + let driver = rbuilder.driver().await?; + let _ = driver.build_new_block().await?; // Block 1 // Test 1: Bundle does not revert - let valid_bundle = harness + let valid_bundle = driver .create_transaction() + .random_valid_transfer() .with_bundle(BundleOpts::default()) .send() .await?; - let block_generated = generator.generate_block().await?; // Block 2 - assert!(block_generated.includes(*valid_bundle.tx_hash())); + let block2 = driver.build_new_block().await?; // Block 2 + assert!(block2 + .transactions + .hashes() + .includes(valid_bundle.tx_hash())); let bundle_opts = BundleOpts { - block_number_max: Some(5), - block_number_min: None, + block_number_max: Some(4), + ..Default::default() }; - let reverted_bundle_0 = harness - .create_transaction() - .with_revert() - .with_reverted_hash() - .with_bundle(bundle_opts) - .send() - .await?; - - // Test 2: Bundle reverts. It is included in the block since the transaction - // includes the reverted_hashes field - let block_generated = generator.generate_block().await?; // Block 3 - assert!(block_generated.includes(*reverted_bundle_0.tx_hash())); - - let reverted_bundle_1 = harness + let reverted_bundle = driver .create_transaction() - .with_revert() + .random_reverting_transaction() .with_bundle(bundle_opts) .send() .await?; - // Test 3: Bundle reverts. It is not included in the block since it reverts - // and the hash is not in the reverted_hashes field. - let block_generated = generator.generate_block().await?; // Block 4 - assert!(block_generated.not_includes(*reverted_bundle_1.tx_hash())); + // Test 2: Bundle reverts. It is not included in the block + let block3 = driver.build_new_block().await?; // Block 3 + assert!(!block3.includes(reverted_bundle.tx_hash())); // After the block the transaction is still pending in the pool - assert!(harness - .check_tx_in_pool(*reverted_bundle_1.tx_hash()) - .await? - .is_pending()); + assert!(rbuilder.pool().is_pending(*reverted_bundle.tx_hash())); // Test 3: Chain progresses beyond the bundle range. The transaction is dropped from the pool - generator.generate_block().await?; // Block 5 - assert!(harness - .check_tx_in_pool(*reverted_bundle_1.tx_hash()) - .await? - .is_pending()); - - generator.generate_block().await?; // Block 6 - assert!(harness - .check_tx_in_pool(*reverted_bundle_1.tx_hash()) - .await? - .is_dropped()); + driver.build_new_block().await?; // Block 4 + assert!(rbuilder.pool().is_dropped(*reverted_bundle.tx_hash())); + + driver.build_new_block().await?; // Block 5 + assert!(rbuilder.pool().is_dropped(*reverted_bundle.tx_hash())); Ok(()) } /// Test the behaviour of the revert protection bundle with a min block number. #[tokio::test] -async fn revert_protection_bundle_min_block_number() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_bundle_min_block_number") - .with_revert_protection() - .build() - .await?; +async fn bundle_min_block_number() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; // The bundle is valid when the min block number is equal to the current block - let bundle_with_min_block = harness + let bundle_with_min_block = driver .create_transaction() .with_revert() // the transaction reverts but it is included in the block .with_reverted_hash() @@ -197,14 +192,14 @@ async fn revert_protection_bundle_min_block_number() -> eyre::Result<()> { .send() .await?; - let block = generator.generate_block().await?; // Block 1, bundle still not valid - assert!(block.not_includes(*bundle_with_min_block.tx_hash())); + let block = driver.build_new_block().await?; // Block 1, bundle still not valid + assert!(!block.includes(bundle_with_min_block.tx_hash())); - let block = generator.generate_block().await?; // Block 2, bundle is valid - assert!(block.includes(*bundle_with_min_block.tx_hash())); + let block = driver.build_new_block().await?; // Block 2, bundle is valid + assert!(block.includes(bundle_with_min_block.tx_hash())); // Send a bundle with a match of min and max block number - let bundle_with_min_and_max_block = harness + let bundle_with_min_and_max_block = driver .create_transaction() .with_revert() .with_reverted_hash() @@ -215,35 +210,34 @@ async fn revert_protection_bundle_min_block_number() -> eyre::Result<()> { .send() .await?; - let block = generator.generate_block().await?; // Block 3, bundle still not valid - assert!(block.not_includes(*bundle_with_min_and_max_block.tx_hash())); + let block = driver.build_new_block().await?; // Block 3, bundle still not valid + assert!(!block.includes(bundle_with_min_and_max_block.tx_hash())); - let block = generator.generate_block().await?; // Block 4, bundle is valid - assert!(block.includes(*bundle_with_min_and_max_block.tx_hash())); + let block = driver.build_new_block().await?; // Block 4, bundle is valid + assert!(block.includes(bundle_with_min_and_max_block.tx_hash())); Ok(()) } /// Test the range limits for the revert protection bundle. #[tokio::test] -async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_bundle_range_limits") - .with_revert_protection() - .build() - .await?; +async fn bundle_range_limits() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; - - // Advance two blocks and try to send a bundle with max block = 1 - generator.generate_block().await?; // Block 1 - generator.generate_block().await?; // Block 2 + let driver = rbuilder.driver().await?; + let _ = driver.build_new_block().await?; // Block 1 + let _ = driver.build_new_block().await?; // Block 2 async fn send_bundle( - harness: &TestHarness, + driver: &ChainDriver, block_number_max: Option, block_number_min: Option, ) -> eyre::Result> { - harness + driver .create_transaction() .with_bundle(BundleOpts { block_number_max, @@ -254,19 +248,19 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { } // Max block cannot be a past block - assert!(send_bundle(&harness, Some(1), None).await.is_err()); + assert!(send_bundle(&driver, Some(1), None).await.is_err()); // Bundles are valid if their max block in in between the current block and the max block range let current_block = 2; let next_valid_block = current_block + 1; for i in next_valid_block..next_valid_block + MAX_BLOCK_RANGE_BLOCKS { - assert!(send_bundle(&harness, Some(i), None).await.is_ok()); + assert!(send_bundle(&driver, Some(i), None).await.is_ok()); } // A bundle with a block out of range is invalid assert!(send_bundle( - &harness, + &driver, Some(next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1), None ) @@ -274,21 +268,21 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { .is_err()); // A bundle with a min block number higher than the max block is invalid - assert!(send_bundle(&harness, Some(1), Some(2)).await.is_err()); + assert!(send_bundle(&driver, Some(1), Some(2)).await.is_err()); // A bundle with a min block number lower or equal to the current block is valid assert!( - send_bundle(&harness, Some(next_valid_block), Some(current_block)) + send_bundle(&driver, Some(next_valid_block), Some(current_block)) .await .is_ok() ); - assert!(send_bundle(&harness, Some(next_valid_block), Some(0)) + assert!(send_bundle(&driver, Some(next_valid_block), Some(0)) .await .is_ok()); // A bundle with a min block equal to max block is valid assert!( - send_bundle(&harness, Some(next_valid_block), Some(next_valid_block)) + send_bundle(&driver, Some(next_valid_block), Some(next_valid_block)) .await .is_ok() ); @@ -296,16 +290,16 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { // Test min-only cases (no max specified) // A bundle with only min block that's within the default range is valid let default_max = current_block + MAX_BLOCK_RANGE_BLOCKS; - assert!(send_bundle(&harness, None, Some(current_block)) + assert!(send_bundle(&driver, None, Some(current_block)) .await .is_ok()); - assert!(send_bundle(&harness, None, Some(default_max - 1)) + assert!(send_bundle(&driver, None, Some(default_max - 1)) .await .is_ok()); - assert!(send_bundle(&harness, None, Some(default_max)).await.is_ok()); + assert!(send_bundle(&driver, None, Some(default_max)).await.is_ok()); // A bundle with only min block that exceeds the default max range is invalid - assert!(send_bundle(&harness, None, Some(default_max + 1)) + assert!(send_bundle(&driver, None, Some(default_max + 1)) .await .is_err()); @@ -314,24 +308,32 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { /// If a transaction reverts and was sent as a normal transaction through the eth_sendRawTransaction /// bundle, the transaction should be included in the block. -/// This behaviour is the same as the 'revert_protection_disabled' test. +/// This behaviour is the same as the 'disabled' test. #[tokio::test] -async fn revert_protection_allow_reverted_transactions_without_bundle() -> eyre::Result<()> { - let harness = - TestHarnessBuilder::new("revert_protection_allow_reverted_transactions_without_bundle") - .with_revert_protection() - .build() - .await?; +async fn allow_reverted_transactions_without_bundle() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; for _ in 0..10 { - let valid_tx = harness.send_valid_transaction().await?; - let reverting_tx = harness.send_revert_transaction().await?; - let block_generated = generator.generate_block().await?; + let valid_tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + let reverting_tx = driver + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + let block = driver.build_new_block().await?; - assert!(block_generated.includes(*valid_tx.tx_hash())); - assert!(block_generated.includes(*reverting_tx.tx_hash())); + assert!(block.includes(valid_tx.tx_hash())); + assert!(block.includes(reverting_tx.tx_hash())); } Ok(()) @@ -340,38 +342,39 @@ async fn revert_protection_allow_reverted_transactions_without_bundle() -> eyre: /// If a transaction reverts and gets dropped it, the eth_getTransactionReceipt should return /// an error message that it was dropped. #[tokio::test] -async fn revert_protection_check_transaction_receipt_status_message() -> eyre::Result<()> { - let harness = - TestHarnessBuilder::new("revert_protection_check_transaction_receipt_status_message") - .with_revert_protection() - .build() - .await?; +async fn check_transaction_receipt_status_message() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let provider = harness.provider()?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; - let reverting_tx = harness + let reverting_tx = driver .create_transaction() - .with_revert() + .random_reverting_transaction() .with_bundle(BundleOpts { block_number_max: Some(3), - block_number_min: None, + ..Default::default() }) .send() .await?; let tx_hash = reverting_tx.tx_hash(); - let _ = generator.generate_block().await?; + let _ = driver.build_new_block().await?; let receipt = provider.get_transaction_receipt(*tx_hash).await?; assert!(receipt.is_none()); - let _ = generator.generate_block().await?; + let _ = driver.build_new_block().await?; let receipt = provider.get_transaction_receipt(*tx_hash).await?; assert!(receipt.is_none()); // Dropped - let _ = generator.generate_block().await?; + let _ = driver.build_new_block().await?; let receipt = provider.get_transaction_receipt(*tx_hash).await; + assert!(receipt.is_err()); Ok(()) diff --git a/crates/op-rbuilder/src/tests/vanilla/smoke.rs b/crates/op-rbuilder/src/tests/vanilla/smoke.rs index 33109c4a3..8fd537e25 100644 --- a/crates/op-rbuilder/src/tests/vanilla/smoke.rs +++ b/crates/op-rbuilder/src/tests/vanilla/smoke.rs @@ -1,16 +1,27 @@ -use super::framework::TestHarnessBuilder; -use alloy_provider::Provider; +use crate::tests::{LocalInstance, TransactionBuilderExt}; +use alloy_primitives::TxHash; +use core::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; use std::collections::HashSet; +use tokio::{join, task::yield_now}; +use tracing::info; /// This is a smoke test that ensures that transactions are included in blocks /// and that the block generator is functioning correctly. +/// +/// Generated blocks are also validated against an external op-reth node to +/// ensure their correctness. #[tokio::test] async fn chain_produces_blocks() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("chain_produces_blocks") - .build() - .await?; + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; - let mut generator = harness.block_generator().await?; + #[cfg(target_os = "linux")] + let driver = driver + .with_validation_node(crate::tests::ExternalNode::reth().await?) + .await?; const SAMPLE_SIZE: usize = 10; @@ -18,14 +29,13 @@ async fn chain_produces_blocks() -> eyre::Result<()> { // no user transactions are sent. // the deposit transaction and the block generator's transaction for _ in 0..SAMPLE_SIZE { - generator - .generate_block() - .await - .expect("Failed to generate block"); - let transactions = harness.latest_block().await.transactions; - assert!( - transactions.len() == 2, - "Block should have exactly two transactions" + let block = driver.build_new_block().await?; + let transactions = block.transactions; + + assert_eq!( + transactions.len(), + 2, + "Empty blocks should have exactly two transactions" ); } @@ -33,98 +43,111 @@ async fn chain_produces_blocks() -> eyre::Result<()> { // sent to it during its block time + the two mandatory transactions for _ in 0..SAMPLE_SIZE { let count = rand::random_range(1..8); - let mut tx_hashes = HashSet::new(); + let mut tx_hashes = HashSet::::default(); + for _ in 0..count { - let tx = harness - .send_valid_transaction() + let tx = driver + .create_transaction() + .random_valid_transfer() + .send() .await .expect("Failed to send transaction"); - let tx_hash = *tx.tx_hash(); - tx_hashes.insert(tx_hash); + tx_hashes.insert(*tx.tx_hash()); } - generator - .generate_block() - .await - .expect("Failed to generate block"); - let transactions = harness.latest_block().await.transactions; - - assert!( - transactions.len() == 2 + count, + + let block = driver.build_new_block().await?; + + let txs = block.transactions; + + assert_eq!( + txs.len(), + 2 + count, "Block should have {} transactions", 2 + count ); for tx_hash in tx_hashes { assert!( - transactions.hashes().any(|hash| hash == *tx_hash), + txs.hashes().any(|hash| hash == tx_hash), "Transaction {} should be included in the block", tx_hash ); } } - Ok(()) } /// Ensures that payloads are generated correctly even when the builder is busy /// with other requests, such as fcu or getPayload. -#[tokio::test] -async fn get_payload_close_to_fcu() -> eyre::Result<()> { - let test_harness = TestHarnessBuilder::new("get_payload_close_to_fcu") - .build() - .await?; - let mut block_generator = test_harness.block_generator().await?; - - // add some transactions to the pool so that the builder - // is busy when we send the fcu/getPayload requests - for _ in 0..10 { - // Note, for this test it is okay if they are not valid - let _ = test_harness.send_valid_transaction().await?; - } - - let result = tokio::time::timeout( - std::time::Duration::from_secs(1), - block_generator.submit_payload(None, 0, true), - ) - .await; - - // ensure we didn't timeout - let result = result.expect("Submit payload timed out"); - - // ensure we got a payload - assert!(result.is_ok(), "Failed to get payload: {:?}", result); +#[tokio::test(flavor = "multi_thread")] +async fn produces_blocks_under_load_within_deadline() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?.with_gas_limit(10_00_000); + + let done = AtomicBool::new(false); + + let (populate, produce) = join!( + async { + // Keep the builder busy with new transactions. + loop { + match driver + .create_transaction() + .random_valid_transfer() + .send() + .await + { + Ok(_) => {} + Err(e) if e.to_string().contains("txpool is full") => { + // If the txpool is full, give it a short break + tokio::time::sleep(Duration::from_millis(100)).await; + } + Err(e) => return Err(e), + }; + + if done.load(Ordering::Relaxed) { + break; + } + + yield_now().await; + } + Ok::<(), eyre::Error>(()) + }, + async { + // Wait for a short time to allow the transaction population to start + // and fill up the txpool. + tokio::time::sleep(Duration::from_secs(1)).await; + + // Now, start producing blocks under load. + for _ in 0..10 { + // Ensure that the builder can still produce blocks under + // heavy load of incoming transactions. + let block = tokio::time::timeout( + Duration::from_secs(rbuilder.args().chain_block_time) + + Duration::from_millis(500), + driver.build_new_block(), + ) + .await + .expect("Timeout while waiting for block production") + .expect("Failed to produce block under load"); - Ok(()) -} + info!("Produced a block under load: {block:#?}"); -/// This test validates that if we flood the builder with many transactions -/// and we request short block times, the builder can still eventually resolve all the transactions -#[tokio::test] -async fn transaction_flood_no_sleep() -> eyre::Result<()> { - let test_harness = TestHarnessBuilder::new("transaction_flood_no_sleep") - .build() - .await?; + yield_now().await; + } - let mut block_generator = test_harness.block_generator().await?; - let provider = test_harness.provider()?; + // we're happy with one block produced under load + // set the done flag to true to stop the transaction population + done.store(true, Ordering::Relaxed); + info!("All blocks produced under load"); - // Send 200 valid transactions to the builder - // More than this and there is an issue with the RPC endpoint not being able to handle the load - let mut transactions = vec![]; - for _ in 0..200 { - let tx = test_harness.send_valid_transaction().await?; - let tx_hash = *tx.tx_hash(); - transactions.push(tx_hash); - } + Ok::<(), eyre::Error>(()) + } + ); - // After a 10 blocks all the transactions should be included in a block - for _ in 0..10 { - block_generator.submit_payload(None, 0, true).await.unwrap(); - } + populate.unwrap(); - for tx in transactions { - provider.get_transaction_receipt(tx).await?; - } + //assert!(populate.is_ok(), "Failed to populate transactions"); + assert!(produce.is_ok(), "Failed to produce block under load"); Ok(()) } diff --git a/crates/op-rbuilder/src/tests/vanilla/txpool.rs b/crates/op-rbuilder/src/tests/vanilla/txpool.rs index cbf15512d..be8aae121 100644 --- a/crates/op-rbuilder/src/tests/vanilla/txpool.rs +++ b/crates/op-rbuilder/src/tests/vanilla/txpool.rs @@ -1,45 +1,52 @@ -use crate::tests::{framework::TestHarnessBuilder, ONE_ETH}; -use alloy_provider::ext::TxPoolApi; +use crate::{ + builders::StandardBuilder, + tests::{default_node_config, BlockTransactionsExt, ChainDriverExt, LocalInstance, ONE_ETH}, +}; +use reth::args::TxPoolArgs; +use reth_node_builder::NodeConfig; +use reth_optimism_chainspec::OpChainSpec; /// This test ensures that pending pool custom limit is respected and priority tx would be included even when pool if full. #[tokio::test] async fn pending_pool_limit() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("pending_pool_limit") - .with_namespaces("txpool,eth,debug,admin") - .with_extra_params("--txpool.pending-max-count 50") - .build() - .await?; + let rbuilder = LocalInstance::new_with_config::( + Default::default(), + NodeConfig:: { + txpool: TxPoolArgs { + pending_max_count: 50, + ..Default::default() + }, + ..default_node_config() + }, + ) + .await?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(50, ONE_ETH).await?; // Send 50 txs from different addrs - let accounts = generator.create_funded_accounts(2, ONE_ETH).await?; let acc_no_priority = accounts.first().unwrap(); let acc_with_priority = accounts.last().unwrap(); for _ in 0..50 { - let _ = harness + let _ = driver .create_transaction() .with_signer(*acc_no_priority) .send() .await?; } - let pool = harness - .provider() - .expect("provider not available") - .txpool_status() - .await?; assert_eq!( - pool.pending, 50, + rbuilder.pool().pending_count(), + 50, "Pending pool must contain at max 50 txs {:?}", - pool + rbuilder.pool().pending_count() ); // Send 10 txs that should be included in the block let mut txs = Vec::new(); for _ in 0..10 { - let tx = harness + let tx = driver .create_transaction() .with_signer(*acc_with_priority) .with_max_priority_fee_per_gas(10) @@ -48,22 +55,18 @@ async fn pending_pool_limit() -> eyre::Result<()> { txs.push(*tx.tx_hash()); } - let pool = harness - .provider() - .expect("provider not available") - .txpool_status() - .await?; assert_eq!( - pool.pending, 50, + rbuilder.pool().pending_count(), + 50, "Pending pool must contain at max 50 txs {:?}", - pool + rbuilder.pool().pending_count() ); // After we try building block our reverting tx would be removed and other tx will move to queue pool - let block = generator.generate_block().await?; + let block = driver.build_new_block().await?; // Ensure that 10 extra txs got included - assert!(block.includes_vec(txs)); + assert!(block.includes(&txs)); Ok(()) }