diff --git a/Cargo.lock b/Cargo.lock index 69204ccaecd..a2d1653617b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,9 +128,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.23" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35d744058a9daa51a8cf22a3009607498fcf82d3cf4c5444dd8056cdf651f471" +checksum = "4bc32535569185cbcb6ad5fa64d989a47bccb9a08e27284b1f2a3ccf16e6d010" dependencies = [ "alloy-primitives", "num_enum", @@ -152,7 +152,7 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more 2.1.0", + "derive_more 2.0.1", "either", "k256", "once_cell", @@ -180,9 +180,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b1db3281bcaf03cfadb9d125fac55603526cc1d0577da555dc6184f5188f6f" +checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -245,7 +245,7 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more 2.1.0", + "derive_more 2.0.1", "either", "serde", "serde_with", @@ -255,9 +255,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bfca3dbbcb7498f0f60e67aff2ad6aff57032e22eb2fd03189854be11a22c03" +checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -273,7 +273,7 @@ checksum = "f72cf87cda808e593381fb9f005ffa4d2475552b7a6c5ac33d087bf77d82abd0" dependencies = [ "alloy-primitives", "alloy-sol-types", - "http 1.4.0", + "http 1.3.1", "serde", "serde_json", "thiserror 2.0.17", @@ -299,7 +299,7 @@ dependencies = [ "alloy-sol-types", "async-trait", "auto_impl", - "derive_more 2.1.0", + "derive_more 2.0.1", "futures-utils-wasm", "serde", "serde_json", @@ -321,20 +321,20 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c850e6ccbd34b8a463a1e934ffc8fc00e1efc5e5489f2ad82d7797949f3bd4e" +checksum = "7db950a29746be9e2f2c6288c8bd7a6202a81f999ce109a2933d2379970ec0fa" dependencies = [ "alloy-rlp", "arbitrary", "bytes", "cfg-if", "const-hex", - "derive_more 2.1.0", + "derive_more 2.0.1", "foldhash 0.2.0", "getrandom 0.3.4", - "hashbrown 0.16.1", - "indexmap 2.12.1", + "hashbrown 0.16.0", + "indexmap 2.12.0", "itoa", "k256", "keccak-asm", @@ -408,7 +408,7 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -510,41 +510,41 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2218e3aeb3ee665d117fdf188db0d5acfdc3f7b7502c827421cb78f26a2aec0" +checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231cb8cc48e66dd1c6e11a1402f3ac86c3667cbc13a6969a0ac030ba7bb8c88" +checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.12.1", + "indexmap 2.12.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a522d79929c1bf0152b07567a38f7eaed3ab149e53e7528afa78ff11994668" +checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" dependencies = [ "const-hex", "dunce", @@ -552,15 +552,15 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475c459859c8d9428af6ff3736614655a57efda8cc435a3b8b4796fa5ac1dd0" +checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9" dependencies = [ "serde", "winnow", @@ -568,9 +568,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35287d9d821d5f26011bcd8d9101340898f761c9933cf50fca689bb7ed62fdeb" +checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -587,7 +587,7 @@ dependencies = [ "alloy-json-rpc", "auto_impl", "base64 0.22.1", - "derive_more 2.1.0", + "derive_more 2.0.1", "futures", "futures-utils-wasm", "parking_lot", @@ -625,7 +625,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more 2.1.0", + "derive_more 2.0.1", "nybbles", "serde", "smallvec", @@ -641,7 +641,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -824,7 +824,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -862,7 +862,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -973,7 +973,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "synstructure", ] @@ -985,7 +985,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1064,7 +1064,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1075,7 +1075,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1104,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" dependencies = [ "base64 0.22.1", - "http 1.4.0", + "http 1.3.1", "log", "url", ] @@ -1117,7 +1117,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1136,7 +1136,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "itoa", @@ -1162,7 +1162,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "mime", @@ -1215,9 +1215,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "beacon_chain" @@ -1232,8 +1232,8 @@ dependencies = [ "eth2_network_config", "ethereum_hashing", "ethereum_serde_utils", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "execution_layer", "fixed_bytes", "fork_choice", @@ -1259,10 +1259,12 @@ dependencies = [ "proto_array", "rand 0.9.2", "rayon", + "reth-era", "safe_arith", "sensitive_url", "serde", "serde_json", + "serde_yaml", "slasher", "slot_clock", "smallvec", @@ -1385,7 +1387,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1405,7 +1407,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1425,15 +1427,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" [[package]] name = "bitcoin_hashes" -version = "0.14.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ "bitcoin-io", "hex-conservative", @@ -1508,7 +1510,7 @@ dependencies = [ "blst", "ethereum_hashing", "ethereum_serde_utils", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "fixed_bytes", "hex", "rand 0.9.2", @@ -1555,7 +1557,7 @@ dependencies = [ "clap", "clap_utils", "eth2_network_config", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "hex", "lighthouse_network", "log", @@ -1570,9 +1572,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", "cfg_aliases", @@ -1580,15 +1582,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1613,7 +1615,7 @@ dependencies = [ "bls", "context_deserialize", "eth2", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "lighthouse_version", "mockito", "reqwest", @@ -1625,9 +1627,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-slice-cast" @@ -1667,9 +1669,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" dependencies = [ "serde_core", ] @@ -1714,9 +1716,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "jobserver", @@ -1851,7 +1853,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", "terminal_size", ] @@ -1864,7 +1866,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -1881,7 +1883,7 @@ dependencies = [ "clap", "dirs", "eth2_network_config", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "hex", "serde", "serde_json", @@ -1900,7 +1902,7 @@ dependencies = [ "environment", "eth2", "eth2_config", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "execution_layer", "futures", "genesis", @@ -1934,9 +1936,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] @@ -1970,9 +1972,9 @@ dependencies = [ [[package]] name = "compare_fields" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f45d0b4d61b582303179fb7a1a142bc9d647b7583db3b0d5f25a21d286fab9" +checksum = "05162add7c8618791829528194a271dca93f69194d35b19db1ca7fbfb8275278" dependencies = [ "compare_fields_derive", "itertools 0.14.0", @@ -1980,12 +1982,12 @@ dependencies = [ [[package]] name = "compare_fields_derive" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ff1dbbda10d495b2c92749c002b2025e0be98f42d1741ecc9ff820d2f04dce" +checksum = "5f5ee468b2e568b668e2a686112935e7bbe9a81bf4fa6b9f6fc3410ea45fb7ce" dependencies = [ "quote", - "syn 2.0.111", + "syn 1.0.109", ] [[package]] @@ -2082,9 +2084,9 @@ dependencies = [ [[package]] name = "context_deserialize" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c523eea4af094b5970c321f4604abc42c5549d3cbae332e98325403fbbdbf70" +checksum = "5c5f9ea0a0ae2de4943f5ca71590b6dbd0b952475f0a0cafb30a470cec78c8b9" dependencies = [ "context_deserialize_derive", "serde", @@ -2092,12 +2094,12 @@ dependencies = [ [[package]] name = "context_deserialize_derive" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7bf98c48ffa511b14bb3c76202c24a8742cea1efa9570391c5d41373419a09" +checksum = "5c57b2db1e4e3ed804dcc49894a144b68fe6c754b8f545eb1dda7ad3c7dbe7e6" dependencies = [ "quote", - "syn 2.0.111", + "syn 1.0.109", ] [[package]] @@ -2106,15 +2108,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -2161,9 +2154,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -2331,7 +2324,27 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] @@ -2345,63 +2358,79 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.23.0" +name = "darling_core" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] name = "darling_core" -version = "0.21.3" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "serde", - "strsim", - "syn 2.0.111", + "strsim 0.11.1", + "syn 2.0.110", ] [[package]] name = "darling_core" -version = "0.23.0" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ + "fnv", "ident_case", "proc-macro2", "quote", - "strsim", - "syn 2.0.111", + "serde", + "strsim 0.11.1", + "syn 2.0.110", ] [[package]] name = "darling_macro" -version = "0.21.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core 0.21.3", + "darling_core 0.13.4", "quote", - "syn 2.0.111", + "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.23.0" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.23.0", + "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn 2.0.110", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.110", ] [[package]] @@ -2461,7 +2490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2506,7 +2535,7 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "bls", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "hex", "reqwest", "serde_json", @@ -2550,7 +2579,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2582,7 +2611,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2591,33 +2620,31 @@ version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "convert_case 0.4.0", + "convert_case", "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] name = "derive_more" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "convert_case 0.10.0", "proc-macro2", "quote", - "rustc_version 0.4.1", - "syn 2.0.111", + "syn 2.0.110", "unicode-xid", ] @@ -2733,7 +2760,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2828,7 +2855,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -2842,8 +2869,8 @@ dependencies = [ "context_deserialize", "educe", "eth2_network_config", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "execution_layer", "fork_choice", "fs2", @@ -3051,7 +3078,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -3071,7 +3098,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -3122,8 +3149,8 @@ dependencies = [ "eip_3076", "eth2_keystore", "ethereum_serde_utils", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "futures", "futures-util", "mediatype", @@ -3207,7 +3234,7 @@ dependencies = [ "bytes", "discv5", "eth2_config", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "fixed_bytes", "kzg", "pretty_reqwest_error", @@ -3274,15 +3301,30 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.10.1" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2128a84f7a3850d54ee343334e3392cca61f9f6aa9441eec481b9394b43c238b" +checksum = "7e8cd8c4f47dfb947dbfe3cdf2945ae1da808dbedc592668658e827a12659ba1" dependencies = [ "alloy-primitives", "arbitrary", "context_deserialize", "ethereum_serde_utils", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_derive", "smallvec", @@ -3291,14 +3333,26 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.10.1" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd596f91cff004fc8d02be44c21c0f9b93140a04b66027ae052f5f8e05b48eba" +checksum = "78d247bc40823c365a62e572441a8f8b12df03f171713f06bc76180fcd56ab71" dependencies = [ - "darling 0.23.0", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -3382,7 +3436,7 @@ dependencies = [ "bytes", "eth2", "ethereum_serde_utils", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "fixed_bytes", "fork_choice", "hash-db", @@ -3580,8 +3634,8 @@ name = "fork_choice" version = "0.1.0" dependencies = [ "beacon_chain", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "fixed_bytes", "logging", "metrics", @@ -3702,7 +3756,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -3775,7 +3829,7 @@ version = "0.2.0" dependencies = [ "bls", "ethereum_hashing", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "int_to_bytes", "merkle_proof", "rayon", @@ -3865,7 +3919,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.12.1", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -3883,8 +3937,8 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", - "indexmap 2.12.1", + "http 1.3.1", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -3946,13 +4000,12 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" dependencies = [ "foldhash 0.2.0", "serde", - "serde_core", ] [[package]] @@ -3975,11 +4028,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.0", ] [[package]] @@ -4049,9 +4102,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" dependencies = [ "arrayvec", ] @@ -4140,11 +4193,12 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", + "fnv", "itoa", ] @@ -4166,7 +4220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.3.1", ] [[package]] @@ -4177,7 +4231,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "pin-project-lite", ] @@ -4196,7 +4250,7 @@ dependencies = [ "either", "eth2", "ethereum_serde_utils", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "execution_layer", "fixed_bytes", "futures", @@ -4309,7 +4363,7 @@ dependencies = [ "futures-channel", "futures-core", "h2 0.4.12", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "httparse", "httpdate", @@ -4327,7 +4381,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", + "http 1.3.1", "hyper 1.8.1", "hyper-util", "rustls 0.23.35", @@ -4353,16 +4407,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "hyper 1.8.1", "ipnet", @@ -4447,9 +4501,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ "icu_collections", "icu_locale_core", @@ -4461,9 +4515,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" @@ -4560,7 +4614,7 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 1.4.0", + "http 1.3.1", "http-body-util", "hyper 1.8.1", "hyper-util", @@ -4588,7 +4642,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -4604,13 +4658,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.16.0", "serde", "serde_core", ] @@ -4759,9 +4813,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -4836,8 +4890,8 @@ dependencies = [ "educe", "ethereum_hashing", "ethereum_serde_utils", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "hex", "rayon", "rust_eth_kzg", @@ -4877,7 +4931,7 @@ dependencies = [ "eth2_network_config", "eth2_wallet", "ethereum_hashing", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "execution_layer", "fixed_bytes", "hex", @@ -4925,9 +4979,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" @@ -4963,7 +5017,7 @@ dependencies = [ [[package]] name = "libp2p" version = "0.56.1" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "bytes", "either", @@ -4994,7 +5048,7 @@ dependencies = [ [[package]] name = "libp2p-allow-block-list" version = "0.6.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "libp2p-core", "libp2p-identity", @@ -5004,7 +5058,7 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" version = "0.6.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "libp2p-core", "libp2p-identity", @@ -5014,7 +5068,7 @@ dependencies = [ [[package]] name = "libp2p-core" version = "0.43.2" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "either", "fnv", @@ -5038,7 +5092,7 @@ dependencies = [ [[package]] name = "libp2p-dns" version = "0.44.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "async-trait", "futures", @@ -5053,7 +5107,7 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" version = "0.50.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "async-channel 2.5.0", "asynchronous-codec", @@ -5065,7 +5119,7 @@ dependencies = [ "futures", "futures-timer", "getrandom 0.2.16", - "hashlink 0.10.0", + "hashlink 0.11.0", "hex_fmt", "libp2p-core", "libp2p-identity", @@ -5083,7 +5137,7 @@ dependencies = [ [[package]] name = "libp2p-identify" version = "0.47.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "asynchronous-codec", "either", @@ -5123,7 +5177,7 @@ dependencies = [ [[package]] name = "libp2p-mdns" version = "0.48.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "futures", "hickory-proto", @@ -5141,7 +5195,7 @@ dependencies = [ [[package]] name = "libp2p-metrics" version = "0.17.1" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "futures", "libp2p-core", @@ -5157,7 +5211,7 @@ dependencies = [ [[package]] name = "libp2p-mplex" version = "0.43.1" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "asynchronous-codec", "bytes", @@ -5175,7 +5229,7 @@ dependencies = [ [[package]] name = "libp2p-noise" version = "0.46.1" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "asynchronous-codec", "bytes", @@ -5197,7 +5251,7 @@ dependencies = [ [[package]] name = "libp2p-quic" version = "0.13.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "futures", "futures-timer", @@ -5217,14 +5271,14 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.47.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +version = "0.47.1" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "either", "fnv", "futures", "futures-timer", - "hashlink 0.10.0", + "hashlink 0.11.0", "libp2p-core", "libp2p-identity", "libp2p-swarm-derive", @@ -5239,17 +5293,17 @@ dependencies = [ [[package]] name = "libp2p-swarm-derive" version = "0.35.1" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "heck", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] name = "libp2p-tcp" version = "0.44.1" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "futures", "futures-timer", @@ -5264,7 +5318,7 @@ dependencies = [ [[package]] name = "libp2p-tls" version = "0.6.2" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "futures", "futures-rustls", @@ -5282,7 +5336,7 @@ dependencies = [ [[package]] name = "libp2p-upnp" version = "0.6.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "futures", "futures-timer", @@ -5296,7 +5350,7 @@ dependencies = [ [[package]] name = "libp2p-yamux" version = "0.47.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "either", "futures", @@ -5309,9 +5363,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", @@ -5330,9 +5384,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" dependencies = [ "zlib-rs", ] @@ -5414,8 +5468,8 @@ dependencies = [ "discv5", "either", "eth2", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "fixed_bytes", "fnv", "futures", @@ -5539,9 +5593,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "logging" @@ -5622,7 +5676,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -5644,13 +5698,13 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "match-lookup" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 1.0.109", ] [[package]] @@ -5719,25 +5773,25 @@ dependencies = [ [[package]] name = "metastruct" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969a1be9bd80794bdf93b23ab552c2ec6f3e83b33164824553fd996cdad513b8" +checksum = "d74f54f231f9a18d77393ecc5cc7ab96709b2a61ee326c2b2b291009b0cc5a07" dependencies = [ "metastruct_macro", ] [[package]] name = "metastruct_macro" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9164f767d73a507c19205868c84da411dc7795f4bdabf497d3dd93cfef9930" +checksum = "985e7225f3a4dfbec47a0c6a730a874185fda840d365d7bbd6ba199dd81796d5" dependencies = [ - "darling 0.23.0", - "itertools 0.14.0", + "darling 0.13.4", + "itertools 0.10.5", "proc-macro2", "quote", "smallvec", - "syn 2.0.111", + "syn 1.0.109", ] [[package]] @@ -5758,8 +5812,8 @@ dependencies = [ "context_deserialize", "educe", "ethereum_hashing", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "itertools 0.13.0", "parking_lot", "rayon", @@ -5805,9 +5859,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "wasi", @@ -5843,7 +5897,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -5855,26 +5909,25 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] name = "mockito" -version = "1.7.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0603425789b4a70fcc4ac4f5a46a566c116ee3e2a6b768dc623f7719c611de" +checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48" dependencies = [ "assert-json-diff", "bytes", "colored", - "futures-core", - "http 1.4.0", + "futures-util", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", "hyper-util", "log", - "pin-project-lite", "rand 0.9.2", "regex", "serde_json", @@ -5898,7 +5951,7 @@ dependencies = [ "rustc_version 0.4.1", "smallvec", "tagptr", - "uuid 1.19.0", + "uuid 1.18.1", ] [[package]] @@ -5970,7 +6023,7 @@ dependencies = [ [[package]] name = "multistream-select" version = "0.13.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "bytes", "futures", @@ -6059,7 +6112,7 @@ dependencies = [ "educe", "eth2", "eth2_network_config", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "execution_layer", "fixed_bytes", "fnv", @@ -6082,6 +6135,7 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.3.1", "rand_chacha 0.9.0", + "reth-era", "serde_json", "slot_clock", "smallvec", @@ -6226,9 +6280,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" @@ -6288,7 +6342,7 @@ checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6392,7 +6446,7 @@ checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d" dependencies = [ "async-trait", "bytes", - "http 1.4.0", + "http 1.3.1", "opentelemetry", "reqwest", ] @@ -6403,7 +6457,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b" dependencies = [ - "http 1.4.0", + "http 1.3.1", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", @@ -6452,8 +6506,8 @@ dependencies = [ "bitvec", "bls", "educe", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "fixed_bytes", "itertools 0.14.0", "maplit", @@ -6537,7 +6591,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6612,9 +6666,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" dependencies = [ "memchr", "ucd-trie", @@ -6637,7 +6691,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6840,7 +6894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6882,7 +6936,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6949,7 +7003,7 @@ checksum = "9adf1691c04c0a5ff46ff8f262b58beb07b0dbb61f96f9f54f6cbd82106ed87f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -6979,7 +7033,7 @@ checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -7002,7 +7056,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -7018,8 +7072,8 @@ dependencies = [ name = "proto_array" version = "0.2.0" dependencies = [ - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "fixed_bytes", "safe_arith", "serde", @@ -7064,7 +7118,7 @@ dependencies = [ [[package]] name = "quick-protobuf-codec" version = "0.3.1" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "asynchronous-codec", "bytes", @@ -7253,9 +7307,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.1.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e65c75143ce5d47c55b510297eeb1182f3c739b6043c537670e9fc18612dae" +checksum = "2988730ee014541157f48ce4dcc603940e00915edc3c7f9a8d78092256bb2493" dependencies = [ "rustversion", ] @@ -7348,7 +7402,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -7382,16 +7436,16 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.26" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", @@ -7444,6 +7498,21 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" +[[package]] +name = "reth-era" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=62abfdaeb54e8a205a8ee085ddebd56047d93374#62abfdaeb54e8a205a8ee085ddebd56047d93374" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", + "snap", + "thiserror 2.0.17", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -7517,9 +7586,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.2" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" dependencies = [ "alloy-rlp", "arbitrary", @@ -7691,9 +7760,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "web-time", "zeroize", @@ -7742,7 +7811,7 @@ dependencies = [ [[package]] name = "rw-stream-sink" version = "0.4.0" -source = "git+https://github.com/libp2p/rust-libp2p.git#5e3519fb66b92c7f7c0dc744ab360fd8b669fe54" +source = "git+https://github.com/libp2p/rust-libp2p.git#5bad680f71a88be9a028e349770195d8a72356ce" dependencies = [ "futures", "pin-project", @@ -7978,7 +8047,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8002,7 +8071,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8019,15 +8088,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.1", + "indexmap 2.12.0", "schemars 0.9.0", "schemars 1.1.0", "serde_core", @@ -8038,14 +8107,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8054,7 +8123,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.12.0", "itoa", "ryu", "serde", @@ -8130,9 +8199,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -8167,9 +8236,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "similar" @@ -8225,8 +8294,8 @@ dependencies = [ "bls", "byteorder", "educe", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "filesystem", "fixed_bytes", "flate2", @@ -8378,7 +8447,7 @@ dependencies = [ "context_deserialize", "educe", "ethereum_serde_utils", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "itertools 0.14.0", "serde", "serde_derive", @@ -8402,8 +8471,8 @@ dependencies = [ "bls", "educe", "ethereum_hashing", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "fixed_bytes", "int_to_bytes", "integer-sqrt", @@ -8430,7 +8499,7 @@ version = "0.1.0" dependencies = [ "beacon_chain", "bls", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "fixed_bytes", "state_processing", "tokio", @@ -8452,8 +8521,8 @@ dependencies = [ "criterion", "db-key", "directory", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "fixed_bytes", "itertools 0.14.0", "leveldb", @@ -8480,6 +8549,12 @@ dependencies = [ "zstd", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -8504,7 +8579,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8515,16 +8590,16 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "superstruct" -version = "0.10.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae4a9ccd7882533c1f210e400763ec6ee64c390fc12248c238276281863719e" +checksum = "3b986e4a629907f20a2c2a639a75bc22a8b5d99b444e0d83c395f4cb309022bf" dependencies = [ - "darling 0.23.0", - "itertools 0.14.0", + "darling 0.20.11", + "itertools 0.13.0", "proc-macro2", "quote", "smallvec", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8550,9 +8625,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -8561,14 +8636,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60ceeb7c95a4536de0c0e1649bd98d1a72a4bb9590b1f3e45a8a0bfdb7c188c0" +checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8588,7 +8663,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8706,7 +8781,7 @@ name = "test_random_derive" version = "0.2.0" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8735,7 +8810,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8746,7 +8821,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8800,30 +8875,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde_core", + "serde", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -8927,7 +9002,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -8980,20 +9055,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.4+spec-1.0.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.12.0", "toml_datetime", "toml_parser", "winnow", @@ -9001,9 +9076,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.5+spec-1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] @@ -9020,7 +9095,7 @@ dependencies = [ "base64 0.22.1", "bytes", "h2 0.4.12", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", @@ -9047,7 +9122,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bytes", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.8.1", @@ -9094,7 +9169,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.12.1", + "indexmap 2.12.0", "pin-project-lite", "slab", "sync_wrapper", @@ -9107,14 +9182,14 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "bitflags 2.10.0", "bytes", "futures-util", - "http 1.4.0", + "http 1.3.1", "http-body 1.0.1", "iri-string", "pin-project-lite", @@ -9137,9 +9212,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -9149,32 +9224,32 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 1.0.69", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" -version = "0.1.31" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -9221,9 +9296,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", @@ -9250,27 +9325,27 @@ dependencies = [ [[package]] name = "tree_hash" -version = "0.12.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" +checksum = "2db21caa355767db4fd6129876e5ae278a8699f4a6959b1e3e7aff610b532d52" dependencies = [ "alloy-primitives", "ethereum_hashing", - "ethereum_ssz", + "ethereum_ssz 0.10.0", "smallvec", "typenum", ] [[package]] name = "tree_hash_derive" -version = "0.12.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" +checksum = "711cc655fcbb48384a87dc2bf641b991a15c5ad9afc3caa0b1ab1df3b436f70f" dependencies = [ - "darling 0.23.0", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -9321,8 +9396,8 @@ dependencies = [ "eth2_interop_keypairs", "ethereum_hashing", "ethereum_serde_utils", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.0", + "ethereum_ssz_derive 0.10.0", "fixed_bytes", "hex", "int_to_bytes", @@ -9421,12 +9496,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -9502,9 +9571,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.19.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -9832,9 +9901,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -9845,9 +9914,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -9858,9 +9927,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9868,22 +9937,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -9917,9 +9986,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -10073,7 +10142,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -10084,7 +10153,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -10353,9 +10422,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -10538,28 +10607,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -10579,7 +10648,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", "synstructure", ] @@ -10601,7 +10670,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -10634,7 +10703,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.110", ] [[package]] @@ -10646,16 +10715,16 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.12.1", + "indexmap 2.12.0", "memchr", "zopfli", ] [[package]] name = "zlib-rs" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index 100a916c501..35199be8e73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ alloy-consensus = { version = "1", default-features = false } alloy-dyn-abi = { version = "1", default-features = false } alloy-json-abi = { version = "1", default-features = false } alloy-network = { version = "1", default-features = false } -alloy-primitives = { version = "1", default-features = false, features = ["rlp", "getrandom"] } +alloy-primitives = { version = "1.5", default-features = false, features = ["rlp", "getrandom"] } alloy-provider = { version = "1", default-features = false, features = ["reqwest"] } alloy-rlp = { version = "0.3", default-features = false } alloy-rpc-types-eth = { version = "1", default-features = false, features = ["serde"] } @@ -138,6 +138,7 @@ eip_3076 = { path = "common/eip_3076" } either = "1.9" environment = { path = "lighthouse/environment" } eth2 = { path = "common/eth2" } +reth-era = { git = "https://github.com/paradigmxyz/reth", package = "reth-era", rev = "62abfdaeb54e8a205a8ee085ddebd56047d93374" } eth2_config = { path = "common/eth2_config" } eth2_key_derivation = { path = "crypto/eth2_key_derivation" } eth2_keystore = { path = "crypto/eth2_keystore" } diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 5e1c41b8302..a0b0a46947f 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -47,6 +47,7 @@ parking_lot = { workspace = true } proto_array = { workspace = true } rand = { workspace = true } rayon = { workspace = true } +reth-era = { workspace = true } safe_arith = { workspace = true } sensitive_url = { workspace = true } serde = { workspace = true } @@ -76,6 +77,8 @@ maplit = { workspace = true } mockall = { workspace = true } mockall_double = { workspace = true } serde_json = { workspace = true } +serde_yaml = { workspace = true } +tempfile = { workspace = true } [[bench]] name = "benches" diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index e5b656adf8d..c7de1afc80f 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -6,6 +6,7 @@ use crate::beacon_chain::{ use crate::beacon_proposer_cache::BeaconProposerCache; use crate::custody_context::NodeCustodyType; use crate::data_availability_checker::DataAvailabilityChecker; +use crate::era_file_consumer::EraFileDir; use crate::fork_choice_signal::ForkChoiceSignalTx; use crate::fork_revert::{reset_fork_choice_to_finalization, revert_to_fork_boundary}; use crate::graffiti_calculator::{GraffitiCalculator, GraffitiOrigin}; @@ -37,8 +38,10 @@ use slasher::Slasher; use slot_clock::{SlotClock, TestingSlotClock}; use state_processing::{AllCaches, per_slot_processing}; use std::marker::PhantomData; +use std::path::Path; use std::sync::Arc; -use std::time::Duration; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::{Duration, Instant}; use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp}; use task_executor::{ShutdownReason, TaskExecutor}; use tracing::{debug, error, info}; @@ -206,6 +209,142 @@ where self } + /// Import trusted era files into the store before building the chain. + pub fn era_files( + self, + era_files_dir: &Path, + mut genesis_state: BeaconState, + trusted_state_root: Option, + trusted_slot: Option, + ) -> Result { + let genesis_state_root = genesis_state + .canonical_root() + .map_err(|e| format!("Error computing genesis state root: {e:?}"))?; + + let builder = self.genesis_state(genesis_state.clone())?; + let store = builder.store.clone().ok_or("era_files requires a store.")?; + + // Explicitly store the genesis state in the cold DB. In testing this seems necessary. + // TODO(era): Review why ^ + { + let mut ops = vec![]; + store + .store_cold_state(&genesis_state_root, &genesis_state, &mut ops) + .map_err(|e| format!("Error building genesis state write ops: {e:?}"))?; + store + .cold_db + .do_atomically(ops) + .map_err(|e| format!("Error writing genesis state: {e:?}"))?; + } + + // Import all blocks and states from the ERA files. + let era_dir = EraFileDir::new::(era_files_dir, &builder.spec)?; + let max_era = era_dir.max_era(); + let slots_per_historical_root = E::slots_per_historical_root() as u64; + + let imported_era_files_pointer = store + .get_era_import_pointer() + .map_err(|error| format!("Era import pointer read failed: {error:?}"))? + .unwrap_or(0); + info!( + ?era_files_dir, + max_slot = max_era * slots_per_historical_root, + current_slot = imported_era_files_pointer * slots_per_historical_root, + "Importing blocks and states from ERA files" + ); + let mut import_progress = Speedo::default(); + let mut import_last_log = Instant::now(); + for era_number in imported_era_files_pointer + 1..=max_era { + debug!(?era_files_dir, era_number, "Importing ERA file"); + era_dir.import_era_file( + &store, + era_number, + &builder.spec, + trusted_state_root, + trusted_slot, + )?; + debug!(?era_files_dir, era_number, "Imported ERA file"); + store + .set_era_import_pointer(era_number) + .map_err(|error| format!("Era import pointer write failed: {error:?}"))?; + debug!(?era_files_dir, era_number, "Persisted era pointer"); + + let now = Instant::now(); + let done_slots = era_number * slots_per_historical_root; + import_progress.observe(Slot::new(done_slots), now); + if now.duration_since(import_last_log) >= Duration::from_secs(5) { + import_last_log = now; + info!( + completed_era_files = era_number, + total_era_files = max_era, + completed_slots = done_slots, + total_slots = max_era * slots_per_historical_root, + slots_per_second = import_progress.slots_per_second().unwrap_or(0.0), + "Importing era files" + ); + } + } + + info!( + ?era_files_dir, + max_slot = max_era * slots_per_historical_root, + "Reconstructing states from ERA files" + ); + let total_era_files = max_era; + let completed_era_files = Arc::new(AtomicU64::new(0)); + let progress = Arc::new(Mutex::new((Speedo::default(), Instant::now()))); + + (1..=max_era).into_par_iter().try_for_each(|era_number| { + let already_reconstructed = store + .era_reconstruction_done(era_number) + .map_err(|error| format!("Era reconstruction marker read failed: {error:?}"))?; + + if !already_reconstructed { + let start_slot = Slot::new((era_number - 1) * slots_per_historical_root); + let end_slot = Slot::new(era_number * slots_per_historical_root); + store + .reconstruct_historic_states_on_range( + // Start reconstruction with state at the era file start, but the state has the + // block already applied. So start with the block at the next slot. + start_slot, + start_slot + Slot::new(1), + end_slot, + ) + .map_err(|error| { + format!("Era reconstruction failed for era {era_number}: {error:?}") + })?; + + store + .set_era_reconstruction_done(era_number) + .map_err(|error| { + format!("Era reconstruction marker write failed: {error:?}") + })?; + } + + let now = Instant::now(); + let done_era_files = completed_era_files.fetch_add(1, Ordering::Relaxed) + 1; + let done_slots = done_era_files.saturating_mul(slots_per_historical_root); + let mut progress = progress.lock(); + let (speedo, last_log) = &mut *progress; + speedo.observe(Slot::new(done_slots), now); + if now.duration_since(*last_log) >= Duration::from_secs(5) { + *last_log = now; + info!( + completed_era_files = done_era_files, + total_era_files, + completed_slots = done_slots, + total_slots = total_era_files.saturating_mul(slots_per_historical_root), + slots_per_second = speedo.slots_per_second().unwrap_or(0.0), + "Reconstructing from era files" + ); + } + + Ok::<(), String>(()) + })?; + + Ok(builder) + } + /// Sets the store migrator config (optional). pub fn store_migrator_config(mut self, config: MigratorConfig) -> Self { self.store_migrator_config = Some(config); @@ -1240,6 +1379,46 @@ fn build_data_columns_from_blobs( Ok(data_columns) } +/// Track recent slot completion rates for era reconstruction. +#[derive(Default)] +struct Speedo(Vec<(Slot, Instant)>); + +impl Speedo { + fn observe(&mut self, slot: Slot, instant: Instant) { + const SPEEDO_OBSERVATIONS: usize = 4; + if self.0.len() > SPEEDO_OBSERVATIONS { + self.0.remove(0); + } + self.0.push((slot, instant)); + } + + fn slots_per_second(&self) -> Option { + let speeds = self + .0 + .windows(2) + .filter_map(|windows| { + let (slot_a, instant_a) = windows[0]; + let (slot_b, instant_b) = windows[1]; + let distance = f64::from((slot_b - slot_a).as_u64() as u32); + let seconds = f64::from((instant_b - instant_a).as_millis() as u32) / 1_000.0; + if seconds > 0.0 { + Some(distance / seconds) + } else { + None + } + }) + .collect::>(); + + let count = speeds.len(); + let sum: f64 = speeds.iter().sum(); + if count > 0 { + Some(sum / f64::from(count as u32)) + } else { + None + } + } +} + #[cfg(not(debug_assertions))] #[cfg(test)] mod test { diff --git a/beacon_node/beacon_chain/src/era_file_consumer.rs b/beacon_node/beacon_chain/src/era_file_consumer.rs new file mode 100644 index 00000000000..0f8436bc2b4 --- /dev/null +++ b/beacon_node/beacon_chain/src/era_file_consumer.rs @@ -0,0 +1,441 @@ +use bls::FixedBytesExtended; +use rayon::prelude::*; +use reth_era::common::file_ops::StreamReader; +use reth_era::era::file::EraReader; +use reth_era::era::types::consensus::{CompressedBeaconState, CompressedSignedBeaconBlock}; +use std::fs::{self, File}; +use std::path::{Path, PathBuf}; +use store::{DBColumn, HotColdDB, ItemStore, KeyValueStoreOp}; +use tracing::{debug, debug_span, instrument, warn}; +use tree_hash::TreeHash; +use types::{ + BeaconState, ChainSpec, EthSpec, Hash256, HistoricalBatch, HistoricalSummary, + SignedBeaconBlock, Slot, +}; + +fn decode_block( + compressed: CompressedSignedBeaconBlock, + spec: &ChainSpec, +) -> Result, String> { + let bytes = compressed + .decompress() + .map_err(|error| format!("failed to decompress block: {error:?}"))?; + SignedBeaconBlock::from_ssz_bytes(&bytes, spec) + .map_err(|error| format!("failed to decode block: {error:?}")) +} + +fn decode_state( + compressed: CompressedBeaconState, + spec: &ChainSpec, +) -> Result, String> { + let bytes = compressed + .decompress() + .map_err(|error| format!("failed to decompress state: {error:?}"))?; + BeaconState::from_ssz_bytes(&bytes, spec) + .map_err(|error| format!("failed to decode state: {error:?}")) +} + +pub struct EraFileDir { + dir: PathBuf, + network_name: String, + genesis_validators_root: Hash256, + historical_roots: Vec, + historical_summaries: Vec, + max_era: u64, +} + +impl EraFileDir { + pub fn new(era_files_dir: &Path, spec: &ChainSpec) -> Result { + let mut era_files = list_era_files(era_files_dir)?; + era_files.sort_by_key(|(era_number, _)| *era_number); + + let network_name = spec + .config_name + .clone() + .unwrap_or_else(|| "unknown".to_string()); + + let Some((max_era, reference_path)) = era_files.last().cloned() else { + return Err("era files directory is empty".to_string()); + }; + + let reference_state = read_era_state::(&reference_path, &network_name, spec)?; + + // historical_roots was frozen in capella, and continued as historical_summaries + let historical_roots = reference_state.historical_roots().to_vec(); + // Pre-Capella states don't have historical_summaries property + let historical_summaries = match reference_state.historical_summaries() { + Ok(list) => list.to_vec(), + Err(_) => vec![], + }; + + let dir = era_files_dir.to_path_buf(); + let era_dir = Self { + dir, + network_name, + genesis_validators_root: reference_state.genesis_validators_root(), + historical_roots, + historical_summaries, + max_era, + }; + + // Verify that every expected era file name exists in the directory. + for era_number in 0..=era_dir.max_era { + let expected = era_dir.expected_path(era_number); + if !expected.exists() { + return Err(format!("missing era file: {expected:?}")); + } + } + + Ok(era_dir) + } + + pub fn max_era(&self) -> u64 { + self.max_era + } + + #[instrument(level = "debug", skip_all, fields(era_number = %era_number))] + pub fn import_era_file, Cold: ItemStore>( + &self, + store: &HotColdDB, + era_number: u64, + spec: &ChainSpec, + trusted_state_root: Option, + trusted_slot: Option, + ) -> Result<(), String> { + let path = self.expected_path(era_number); + debug!(?path, era_number, "Importing era file"); + let file = File::open(path).map_err(|error| format!("failed to open era file: {error}"))?; + let era_file = { + let _span = debug_span!("era_import_read").entered(); + EraReader::new(file) + .read_and_assemble(self.network_name.clone()) + .map_err(|error| format!("failed to parse era file: {error:?}"))? + }; + + // Consistency checks: ensure the era state matches the expected historical root and that + // each block root matches the state block_roots for its slot. + let mut state = { + let _span = debug_span!("era_import_decode_state").entered(); + decode_state::(era_file.group.era_state, spec)? + }; + + // Verify trusted state root if provided + if let (Some(expected_root), Some(expected_slot)) = (trusted_state_root, trusted_slot) + && state.slot() == expected_slot + { + let actual_root = state + .canonical_root() + .map_err(|e| format!("Failed to compute state root: {e:?}"))?; + if actual_root != expected_root { + return Err(format!( + "trusted state root mismatch at slot {expected_slot}: expected {expected_root:?}, got {actual_root:?}" + )); + } + } + + let expected_root = self + .era_file_name_root(era_number) + .ok_or_else(|| format!("missing historical root for era {era_number}"))?; + let actual_root = era_root_from_state(&state, era_number)?; + if expected_root != actual_root { + return Err(format!( + "era root mismatch for era {era_number}: expected {expected_root:?}, got {actual_root:?}" + )); + } + + let slots_per_historical_root = E::slots_per_historical_root() as u64; + let _start_slot = Slot::new(era_number.saturating_sub(1) * slots_per_historical_root); + let end_slot = Slot::new(era_number * slots_per_historical_root); + if state.slot() != end_slot { + return Err(format!( + "era state slot mismatch: expected {end_slot}, got {}", + state.slot() + )); + } + + let historical_summaries_active_at_slot = match store.spec.capella_fork_epoch { + // For mainnet case, Capella activates at 194048 epoch = 6209536 slot = 758 era number. + // The last epoch processing before capella transition adds a last entry to historical + // roots. So historical_roots.len() == 758 at the capella fork boundary. An ERA file + // includes the state AFTER advanced (or applying) the block at the final slot, so the + // state for ERA file 758 is of the Capella variant. However, historical summaries are + // still empty. + Some(epoch) => { + epoch.start_slot(E::slots_per_epoch()) + + Slot::new(E::slots_per_historical_root() as u64) + } + None => Slot::max_value(), + }; + + // Check that the block roots vector in this state match the historical summary in the last + // state. Asserts that the blocks are exactly the expected ones given a trusted final state + if era_number == 0 { + // Skip checking genesis state era file for now + } else if state.slot() >= historical_summaries_active_at_slot { + // Post-capella state, check against historical summaries + // ```py + // historical_summary = HistoricalSummary( + // block_summary_root=hash_tree_root(state.block_roots), + // state_summary_root=hash_tree_root(state.state_roots), + // ) + // state.historical_summaries.append(historical_summary) + // ``` + let index = era_number.saturating_sub(1) as usize; + // historical_summaries started to be appended after capella, so we need to offset + let summary_index = index + .checked_sub(self.historical_roots.len()) + .ok_or_else(|| format!( + "Not enough historical roots era number {era_number} index {index} historical_roots len {}", + self.historical_roots.len() + ))?; + let expected_root = self + .historical_summaries + .get(summary_index) + .ok_or_else(|| format!("missing historical summary for era {era_number}"))? + .block_summary_root(); + let actual_root = state.block_roots().tree_hash_root(); + if actual_root != expected_root { + return Err(format!( + "block summary root post-capella mismatch for era {}: {:?} != {:?}", + era_number, expected_root, actual_root + )); + } + } else { + // Pre-capella state, check against historical roots + // ```py + // historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) + // state.historical_roots.append(hash_tree_root(historical_batch)) + // ``` + let index = era_number.saturating_sub(1) as usize; + let expected_root = *self + .historical_roots + .get(index) + .ok_or_else(|| format!("missing historical root for era {era_number}"))?; + let historical_batch = HistoricalBatch:: { + block_roots: state.block_roots().clone(), + state_roots: state.state_roots().clone(), + }; + let actual_root = historical_batch.tree_hash_root(); + if actual_root != expected_root { + return Err(format!( + "block summary root pre-capella mismatch for era {}: {:?} != {:?}", + era_number, expected_root, actual_root + )); + } + } + + debug!(era_number, "Importing blocks from era file"); + // TODO(era): Block signatures are not verified here and are trusted. + // decode and hash is split in two loops to track timings better. If we add spans for each + // block it's too short and the data is not really useful. + let decoded_blocks = { + let _span = debug_span!("era_import_decode_blocks").entered(); + era_file + .group + .blocks + .into_par_iter() + .map(|compressed_block| decode_block::(compressed_block, spec)) + .collect::, _>>()? + }; + let blocks_with_roots = { + let _span = debug_span!("era_import_hash_blocks").entered(); + decoded_blocks + .into_par_iter() + .map(|block| (block.canonical_root(), block)) + .collect::>() + }; + + let mut block_ops = vec![]; + { + let _ = debug_span!("era_import_db_ops_blocks").entered(); + for (block_root, block) in blocks_with_roots { + let slot = block.slot(); + // Check consistency that this block is expected w.r.t. the state in the era file. + // Since we check that the state block roots match the historical summary, we know that + // this block root is the expected one. + let expected_block_root = state + .get_block_root(slot) + .map_err(|error| format!("failed to read block root {slot}: {error:?}"))?; + if *expected_block_root != block_root { + return Err(format!( + "block root mismatch at slot {slot}: expected {expected_block_root:?}, got {block_root:?}" + )); + } + store + .block_as_kv_store_ops(&block_root, block, &mut block_ops) + .map_err(|error| format!("failed to store block: {error:?}"))?; + } + } + { + let _ = debug_span!("era_import_write_blocks").entered(); + store + .hot_db + .do_atomically(block_ops) + .map_err(|error| format!("failed to store blocks: {error:?}"))?; + } + + // Populate the cold DB slot -> block root index from the state.block_roots() + { + let _span = debug_span!("era_import_write_block_index").entered(); + write_block_root_index_for_era(store, &state, era_number)?; + } + + debug!(era_number, "Importing state from era file"); + { + let _span = debug_span!("era_import_write_state").entered(); + let state_root = state + .canonical_root() + .map_err(|error| format!("failed to hash state: {error:?}"))?; + // Use put_cold_state as the split is not updated and we need the state into the cold store. + store + .put_cold_state(&state_root, &state) + .map_err(|error| format!("failed to store state: {error:?}"))?; + } + Ok(()) + } + + fn expected_path(&self, era_number: u64) -> PathBuf { + let root = self + .era_file_name_root(era_number) + .unwrap_or_else(Hash256::zero); + let short = root + .as_slice() + .iter() + .take(4) + .map(|byte| format!("{byte:02x}")) + .collect::(); + let filename = format!("{}-{era_number:05}-{short}.era", self.network_name); + self.dir.join(filename) + } + + // era_file_name_root for file naming: + // short-era-root is the first 4 bytes of the last historical root in the last state in the + // era file, lower-case hex-encoded (8 characters), except the genesis era which instead + // uses the genesis_validators_root field from the genesis state. + // - The root is available as state.historical_roots[era - 1] except for genesis, which is + // state.genesis_validators_root + // - Post-Capella, the root must be computed from + // `state.historical_summaries[era - state.historical_roots.len - 1]` + fn era_file_name_root(&self, era_number: u64) -> Option { + if era_number == 0 { + return Some(self.genesis_validators_root); + } + let index = era_number.saturating_sub(1) as usize; + if let Some(root) = self.historical_roots.get(index) { + return Some(*root); + } + let summary_index = index.saturating_sub(self.historical_roots.len()); + self.historical_summaries + .get(summary_index) + .map(|summary| summary.tree_hash_root()) + } +} + +fn read_era_state( + path: &Path, + network_name: &str, + spec: &ChainSpec, +) -> Result, String> { + let file = File::open(path).map_err(|error| format!("failed to open era file: {error}"))?; + let era_file = EraReader::new(file) + .read_and_assemble(network_name.to_string()) + .map_err(|error| format!("failed to parse era file: {error:?}"))?; + decode_state::(era_file.group.era_state, spec) +} + +fn era_root_from_state( + state: &BeaconState, + era_number: u64, +) -> Result { + if era_number == 0 { + return Ok(state.genesis_validators_root()); + } + let index = era_number + .checked_sub(1) + .ok_or_else(|| "invalid era number".to_string())? as usize; + if let Some(root) = state.historical_roots().get(index) { + return Ok(*root); + } + if let Ok(summaries) = state.historical_summaries() { + let summary_index = index.saturating_sub(state.historical_roots().len()); + let summary = summaries + .get(summary_index) + .ok_or_else(|| "missing historical summary".to_string())?; + return Ok(summary.tree_hash_root()); + } + Err(format!("missing historical root for era {era_number}")) +} + +fn write_block_root_index_for_era, Cold: ItemStore>( + store: &HotColdDB, + state: &BeaconState, + era_number: u64, +) -> Result<(), String> { + let end_slot = state.slot(); + let slots_per_historical_root = E::slots_per_historical_root() as u64; + let expected_end_slot = Slot::new(era_number * slots_per_historical_root); + if end_slot != expected_end_slot { + return Err(format!( + "era state slot mismatch: expected {expected_end_slot}, got {end_slot}" + )); + } + + let start_slot = end_slot.saturating_sub(slots_per_historical_root); + + let ops = (start_slot.as_u64()..end_slot.as_u64()) + .map(|slot_u64| { + let slot = Slot::new(slot_u64); + let block_root = state + .get_block_root(slot) + .map_err(|error| format!("failed to read block root {slot}: {error:?}"))?; + // TODO(era): Should we write BeaconBlockRoots for missed slots? + Ok(KeyValueStoreOp::PutKeyValue( + DBColumn::BeaconBlockRoots, + slot_u64.to_be_bytes().to_vec(), + block_root.as_slice().to_vec(), + )) + }) + .collect::, String>>()?; + + store + .cold_db + .do_atomically(ops) + .map_err(|error| format!("failed to store block root index: {error:?}"))?; + + Ok(()) +} + +fn list_era_files(dir: &Path) -> Result, String> { + let entries = fs::read_dir(dir).map_err(|error| format!("failed to read era dir: {error}"))?; + let mut era_files = Vec::new(); + + for entry in entries { + let entry = entry.map_err(|error| format!("failed to read era entry: {error}"))?; + let path = entry.path(); + let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else { + continue; + }; + + if !file_name.ends_with(".era") { + continue; + } + + let Some((prefix, _hash_part)) = file_name.rsplit_once('-') else { + continue; + }; + let Some((_network_name, era_part)) = prefix.rsplit_once('-') else { + continue; + }; + let Some(era_number) = era_part.parse().ok() else { + continue; + }; + + era_files.push((era_number, path)); + } + + if era_files.is_empty() { + warn!(?dir, "Era files directory is empty"); + } + + Ok(era_files) +} diff --git a/beacon_node/beacon_chain/src/era_file_producer.rs b/beacon_node/beacon_chain/src/era_file_producer.rs new file mode 100644 index 00000000000..73b52d9b418 --- /dev/null +++ b/beacon_node/beacon_chain/src/era_file_producer.rs @@ -0,0 +1,318 @@ +use rand::random; +use reth_era::common::file_ops::{EraFileFormat, EraFileId, StreamWriter}; +use reth_era::era::file::{EraFile, EraWriter}; +use reth_era::era::types::consensus::{CompressedBeaconState, CompressedSignedBeaconBlock}; +use reth_era::era::types::group::{EraGroup, EraId, SlotIndex}; +use ssz::Encode; +use std::fs::{self, File, OpenOptions}; +use std::path::Path; +use store::{HotColdDB, ItemStore}; +use tracing::{error, info}; +use tree_hash::TreeHash; +use types::{BeaconState, EthSpec, Slot}; + +fn era_file_exists(dir: &Path, id: &EraId) -> bool { + dir.join(id.to_file_name()).exists() +} + +fn era_file_exists_for_number(dir: &Path, network_name: &str, era_number: u64) -> bool { + let prefix = format!("{}-{:05}-", network_name, era_number); + let Ok(entries) = fs::read_dir(dir) else { + return false; + }; + + for entry in entries.flatten() { + let file_name = entry.file_name(); + let Some(name) = file_name.to_str() else { + continue; + }; + if name.starts_with(&prefix) && name.ends_with(".era") { + return true; + } + } + false +} + +pub(crate) fn maybe_produce_reconstruction_eras< + E: EthSpec, + Hot: ItemStore, + Cold: ItemStore, +>( + db: &HotColdDB, + output_dir: &Path, +) { + let anchor = db.get_anchor_info(); + let max_era = anchor.state_lower_limit.as_u64() / E::slots_per_historical_root() as u64; + + for era_number in 0..=max_era { + if let Err(error) = create_era_file(db, era_number, output_dir) { + error!( + ?error, + era_number, "Era producer failed during reconstruction" + ); + break; + } + } +} + +pub(crate) fn maybe_produce_finalization_era, Cold: ItemStore>( + db: &HotColdDB, + output_dir: &Path, + finalized_slot: Slot, +) { + // This is the oldest slot for which we have a state and blocks available + let anchor_slot = db.get_anchor_info().anchor_slot; + // And finalized_slot is the most recent for which we have finalized state and blocks available + + // We can produce an era file for era_number if + // - anchor_slot <= start_slot(era_number) AND + // - finalized_slot >= end_slot(era_number) + let lowest_era_file = anchor_slot.as_u64() / E::slots_per_historical_root() as u64; + let max_era_file = (finalized_slot.as_u64() + 1) / E::slots_per_historical_root() as u64 - 1; + for era_number in lowest_era_file..=max_era_file { + if let Err(error) = create_era_file(db, era_number, output_dir) { + error!( + ?error, + era_number, "Era producer failed during finalization" + ); + break; + } + } +} + +pub fn create_era_file, Cold: ItemStore>( + db: &HotColdDB, + era_number: u64, + output_dir: &Path, +) -> Result<(), String> { + let network_name = db + .spec + .config_name + .clone() + .unwrap_or_else(|| "unknown".to_string()); + if era_file_exists_for_number(output_dir, &network_name, era_number) { + return Ok(()); + } + + let end_slot = Slot::new(era_number * E::slots_per_historical_root() as u64); + + let mut state = db + .load_cold_state_by_slot(end_slot) + .map_err(|error| format!("failed to load era state: {error:?}"))?; + + if state.slot() != end_slot { + return Err(format!( + "era state slot mismatch: expected {}, got {}", + end_slot, + state.slot() + )); + } + + let group = build_era_group(db, &mut state, era_number)?; + let file_id = era_file_id::(&network_name, era_number, &mut state)?; + let file = EraFile::new(group, file_id); + + fs::create_dir_all(output_dir) + .map_err(|error| format!("failed to create era files dir: {error}"))?; + + if era_file_exists(output_dir, file.id()) { + return Ok(()); + } + + write_era_file_atomic(output_dir, &file)?; + + info!( + era_number, + file = %file.id().to_file_name(), + "Wrote era file" + ); + + Ok(()) +} + +fn build_era_group, Cold: ItemStore>( + db: &HotColdDB, + state: &mut BeaconState, + era_number: u64, +) -> Result { + // Era file 0 goes from slot 0 to 0, genesis state only + let start_slot = + Slot::new(era_number.saturating_sub(1) * E::slots_per_historical_root() as u64); + let end_slot = Slot::new(era_number * E::slots_per_historical_root() as u64); + + let compressed_state = CompressedBeaconState::from_ssz(&state.as_ssz_bytes()) + .map_err(|error| format!("failed to compress state: {error:?}"))?; + + // Each entry has an 8-byte header; the version record is header-only. + let mut offset: i64 = 8; + let mut blocks: Vec = Vec::new(); + let mut block_data_starts: Vec<(Slot, i64)> = Vec::new(); + + // The era file number 0 contains the genesis state and nothing else + if era_number > 0 { + for slot_u64 in start_slot.as_u64()..end_slot.as_u64() { + let slot = Slot::new(slot_u64); + let block_root = state + .get_block_root(slot) + .map_err(|error| format!("failed to read block root {slot}: {error:?}"))?; + + // Skip duplicate blocks (same root as previous slot), but only within this ERA + if slot_u64 > start_slot.as_u64() + && let Ok(prev_root) = state.get_block_root(Slot::new(slot_u64 - 1)) + && prev_root == block_root + { + continue; + } + + let block = db + .get_full_block(block_root) + .map_err(|error| format!("failed to load block: {error:?}"))? + .ok_or_else(|| format!("missing block for root {block_root:?}"))?; + + let compressed = CompressedSignedBeaconBlock::from_ssz(&block.as_ssz_bytes()) + .map_err(|error| format!("failed to compress block: {error:?}"))?; + + let data_len = compressed.data.len() as i64; + let data_start = offset + 8; + blocks.push(compressed); + block_data_starts.push((slot, data_start)); + offset += 8 + data_len; + } + } + + let state_data_len = compressed_state.data.len() as i64; + // Data starts after the 8-byte header. + let state_data_start = offset + 8; + offset += 8 + state_data_len; + + let block_index_start = offset; + let slot_count = E::slots_per_historical_root(); + // SlotIndex layout: starting_slot (8) + offsets (slot_count * 8) + count (8). + let block_index_len = 8 + slot_count as i64 * 8 + 8; + if era_number > 0 { + offset += 8 + block_index_len; + } + + let state_index_start = offset; + // Offset is relative to the start of the slot index data (after the 8-byte header) + let state_offset = state_data_start - (state_index_start + 8); + let state_slot_index = SlotIndex::new(end_slot.as_u64(), vec![state_offset]); + + let group = if era_number > 0 { + let mut offsets = vec![0i64; slot_count]; + for (slot, data_start) in &block_data_starts { + let slot_index = slot + .as_u64() + .checked_sub(start_slot.as_u64()) + .ok_or_else(|| "slot underflow while building block index".to_string())? + as usize; + offsets[slot_index] = *data_start - block_index_start; + } + let block_index = SlotIndex::new(start_slot.as_u64(), offsets); + EraGroup::with_block_index(blocks, compressed_state, block_index, state_slot_index) + } else { + EraGroup::new(blocks, compressed_state, state_slot_index) + }; + Ok(group) +} + +fn short_historical_root( + state: &mut BeaconState, + era_number: u64, +) -> Result<[u8; 4], String> { + let root = if era_number == 0 { + state.genesis_validators_root() + } else { + let era_index = era_number + .checked_sub(1) + .ok_or_else(|| "era index underflow".to_string())?; + let roots_len = state.historical_roots_mut().len(); + if era_index < roots_len as u64 { + *state + .historical_roots_mut() + .get(era_index as usize) + .ok_or_else(|| "historical root missing".to_string())? + } else { + let summary_index = era_index + .checked_sub(roots_len as u64) + .ok_or_else(|| "historical summary index underflow".to_string())?; + let summaries = state + .historical_summaries_mut() + .map_err(|error| format!("failed to access historical summaries: {error:?}"))?; + let summary = summaries + .get(summary_index as usize) + .ok_or_else(|| "historical summary missing".to_string())?; + summary.tree_hash_root() + } + }; + + let mut short_hash = [0u8; 4]; + short_hash.copy_from_slice(&root.as_slice()[..4]); + Ok(short_hash) +} + +/// Write an era file atomically using a temp file + rename. +/// +/// If the process crashes mid-write, only the temp file is left behind; the final file is +/// created via rename, so it is either complete or absent. The era file existence check only +/// considers `.era` files, so partial temp files are ignored safely. +fn write_era_file_atomic(output_dir: &Path, file: &EraFile) -> Result<(), String> { + let filename = file.id().to_file_name(); + let final_path = output_dir.join(&filename); + if final_path.exists() { + return Ok(()); + } + + // Create a unique temp file and write the full era contents into it. + let tmp_name = format!("{filename}.tmp-{:016x}", random::()); + let tmp_path = output_dir.join(tmp_name); + let mut file_handle = OpenOptions::new() + .write(true) + .create_new(true) + .open(&tmp_path) + .map_err(|error| format!("failed to create temp file: {error}"))?; + { + let mut writer = EraWriter::new(&mut file_handle); + writer + .write_file(file) + .map_err(|error| format!("failed to write era file: {error:?}"))?; + } + file_handle + .sync_all() + .map_err(|error| format!("failed to fsync era temp file: {error}"))?; + + // Atomically publish; if another writer won, clean up and exit. + if let Err(error) = fs::rename(&tmp_path, &final_path) { + if error.kind() == std::io::ErrorKind::AlreadyExists && final_path.exists() { + let _ = fs::remove_file(&tmp_path); + return Ok(()); + } + return Err(format!("failed to rename era temp file: {error}")); + } + + // Best-effort directory sync to make the rename durable. + if let Ok(dir_handle) = File::open(output_dir) { + let _ = dir_handle.sync_all(); + } + + Ok(()) +} + +fn era_file_id( + network_name: &str, + era_number: u64, + state: &mut BeaconState, +) -> Result { + // reth_era uses hardcoded SLOTS_PER_HISTORICAL_ROOT=8192 to compute era number from start_slot. + // To get the correct filename era number, we pass era_number * 8192 as the start_slot. + const RETH_SLOTS_PER_HISTORICAL_ROOT: u64 = 8192; + let reth_start_slot = era_number * RETH_SLOTS_PER_HISTORICAL_ROOT; + + let slot_count = if era_number == 0 { + 0 + } else { + E::slots_per_historical_root() as u32 + }; + let short_hash = short_historical_root(state, era_number)?; + Ok(EraId::new(network_name, reth_start_slot, slot_count).with_hash(short_hash)) +} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index e77739e2d53..b7b5fb4328f 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -20,6 +20,8 @@ pub mod custody_context; pub mod data_availability_checker; pub mod data_column_verification; mod early_attester_cache; +pub mod era_file_consumer; +pub mod era_file_producer; mod errors; pub mod events; pub mod execution_payload; diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index 24258d2d31b..7fa12f68f77 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -1,3 +1,4 @@ +use crate::era_file_producer::{maybe_produce_finalization_era, maybe_produce_reconstruction_eras}; use crate::errors::BeaconChainError; use crate::summaries_dag::{DAGStateSummary, Error as SummariesDagError, StateSummariesDAG}; use parking_lot::Mutex; @@ -215,12 +216,15 @@ impl, Cold: ItemStore> BackgroundMigrator>, opt_tx: Option>, ) { match db.reconstruct_historic_states(Some(BLOCKS_PER_RECONSTRUCTION)) { Ok(()) => { + if let Some(era_files_dir) = &db.get_config().era_files_dir { + maybe_produce_reconstruction_eras(&db, era_files_dir); + } // Schedule another reconstruction batch if required and we have access to the // channel for requeueing. if let Some(tx) = opt_tx @@ -404,6 +408,10 @@ impl, Cold: ItemStore> BackgroundMigrator PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("era_test_vectors") +} + +fn load_test_spec() -> ChainSpec { + let config_str = + std::fs::read_to_string(test_vectors_dir().join("config.yaml")).expect("read config.yaml"); + let config: Config = serde_yaml::from_str(&config_str).expect("parse config"); + config + .apply_to_chain_spec::(&ChainSpec::minimal()) + .expect("apply config") +} + +fn load_genesis_state(spec: &ChainSpec) -> BeaconState { + let bytes = std::fs::read(test_vectors_dir().join("genesis.ssz")).expect("read genesis.ssz"); + BeaconState::from_ssz_bytes(&bytes, spec).expect("decode genesis") +} + +type TestStore = HotColdDB< + MinimalEthSpec, + store::MemoryStore, + store::MemoryStore, +>; + +/// Import all ERA files into a fresh ephemeral store. +fn import_all_era_files() -> (TestStore, ChainSpec, u64) { + let spec = load_test_spec(); + let era_dir_path = test_vectors_dir().join("era"); + let era_dir = EraFileDir::new::(&era_dir_path, &spec).expect("open ERA dir"); + let max_era = era_dir.max_era(); + + let store = HotColdDB::open_ephemeral(StoreConfig::default(), Arc::new(spec.clone())) + .expect("create store"); + + let mut genesis_state = load_genesis_state(&spec); + let root = genesis_state.canonical_root().expect("hash genesis"); + let mut ops = vec![]; + store + .store_cold_state(&root, &genesis_state, &mut ops) + .expect("build ops"); + store.cold_db.do_atomically(ops).expect("write genesis"); + + for era in 1..=max_era { + era_dir + .import_era_file(&store, era, &spec, None, None) + .unwrap_or_else(|e| panic!("import ERA {era}: {e}")); + } + + (store, spec, max_era) +} + +/// Create ephemeral store with genesis state only. +fn empty_store(spec: &ChainSpec) -> TestStore { + let store = + HotColdDB::open_ephemeral(StoreConfig::default(), Arc::new(spec.clone())).expect("store"); + let mut genesis = load_genesis_state(spec); + let root = genesis.canonical_root().expect("hash"); + let mut ops = vec![]; + store + .store_cold_state(&root, &genesis, &mut ops) + .expect("ops"); + store.cold_db.do_atomically(ops).expect("write"); + store +} + +/// Copy ERA files to temp dir, replacing one with a corrupt version. +fn era_dir_with_corrupt(corrupt_file: &str, target_pattern: &str) -> tempfile::TempDir { + let tmp = tempfile::TempDir::new().expect("tmp"); + let src = test_vectors_dir(); + let dst = tmp.path().join("era"); + std::fs::create_dir_all(&dst).expect("mkdir"); + + for entry in std::fs::read_dir(src.join("era")).expect("readdir") { + let entry = entry.expect("entry"); + let name = entry.file_name().to_string_lossy().to_string(); + if name.contains(target_pattern) { + std::fs::copy(src.join("corrupt").join(corrupt_file), dst.join(&name)) + .expect("copy corrupt"); + } else { + std::fs::copy(entry.path(), dst.join(&name)).expect("copy"); + } + } + tmp +} + +// ============================================================================= +// CONSUMER TEST +// ============================================================================= + +/// Import all ERA files, verify block hash chain and state roots. +#[test] +fn era_consumer_imports_and_verifies() { + let (store, spec, max_era) = import_all_era_files(); + let slots_per_era = MinimalEthSpec::slots_per_historical_root() as u64; + let max_slot = max_era * slots_per_era; + + // Collect all blocks by reading block root index, then fetching from store + let mut blocks_by_slot = std::collections::BTreeMap::new(); + let mut seen_roots = std::collections::HashSet::new(); + + for slot in 0..max_slot { + let key = slot.to_be_bytes().to_vec(); + if let Some(root_bytes) = store + .cold_db + .get_bytes(DBColumn::BeaconBlockRoots, &key) + .expect("read index") + { + let block_root = Hash256::from_slice(&root_bytes); + if seen_roots.insert(block_root) { + let block = store + .get_full_block(&block_root) + .expect("query") + .unwrap_or_else(|| panic!("block missing at slot {slot}")); + assert_eq!( + block.canonical_root(), + block_root, + "block root mismatch at slot {slot}" + ); + blocks_by_slot.insert(slot, block); + } + } + } + + assert!( + blocks_by_slot.len() > 700, + "expected >700 blocks, got {}", + blocks_by_slot.len() + ); + + // Verify parent_root chain: each block's parent_root must equal the previous block's root + let slots: Vec<_> = blocks_by_slot.keys().copied().collect(); + for i in 1..slots.len() { + let block = &blocks_by_slot[&slots[i]]; + let prev_block = &blocks_by_slot[&slots[i - 1]]; + assert_eq!( + block.message().parent_root(), + prev_block.canonical_root(), + "broken hash chain at slot {}: parent_root doesn't match previous block root", + slots[i] + ); + } + + // Verify boundary states match ERA file state roots + let era_dir_path = test_vectors_dir().join("era"); + let mut era_files: Vec<_> = std::fs::read_dir(&era_dir_path) + .expect("readdir") + .filter_map(|e| e.ok()) + .filter(|e| e.file_name().to_string_lossy().ends_with(".era")) + .collect(); + era_files.sort_by_key(|e| e.file_name()); + + for entry in &era_files { + let file = std::fs::File::open(entry.path()).expect("open"); + let era = reth_era::era::file::EraReader::new(file) + .read_and_assemble("minimal".to_string()) + .expect("parse"); + let state_bytes = era.group.era_state.decompress().expect("decompress"); + let mut era_state: BeaconState = + BeaconState::from_ssz_bytes(&state_bytes, &spec).expect("decode"); + let expected_root = era_state.canonical_root().expect("root"); + let slot = era_state.slot(); + + // Load state from store and verify root matches + let mut stored_state = store.load_cold_state_by_slot(slot).expect("load state"); + assert_eq!( + stored_state.slot(), + slot, + "stored state slot mismatch at slot {slot}" + ); + let stored_root = stored_state.canonical_root().expect("root"); + assert_eq!( + stored_root, expected_root, + "state root mismatch at slot {slot}" + ); + } +} + +// ============================================================================= +// PRODUCER TEST — byte-identical output +// ============================================================================= + +#[test] +fn era_producer_output_is_byte_identical() { + use beacon_chain::era_file_producer; + + let (store, _spec, max_era) = import_all_era_files(); + let output = PathBuf::from("/tmp/era_producer_test_output"); + let _ = std::fs::remove_dir_all(&output); + std::fs::create_dir_all(&output).expect("mkdir"); + + for era in 0..=max_era { + era_file_producer::create_era_file(&store, era, &output) + .unwrap_or_else(|e| panic!("produce ERA {era}: {e}")); + } + + let mut originals: Vec<_> = std::fs::read_dir(test_vectors_dir().join("era")) + .expect("readdir") + .filter_map(|e| e.ok()) + .filter(|e| e.file_name().to_string_lossy().ends_with(".era")) + .collect(); + originals.sort_by_key(|e| e.file_name()); + + let mut produced: Vec<_> = std::fs::read_dir(&output) + .expect("readdir") + .filter_map(|e| e.ok()) + .filter(|e| e.file_name().to_string_lossy().ends_with(".era")) + .collect(); + produced.sort_by_key(|e| e.file_name()); + + assert_eq!(originals.len(), produced.len(), "file count mismatch"); + + for (orig, prod) in originals.iter().zip(produced.iter()) { + assert_eq!( + std::fs::read(orig.path()).expect("read"), + std::fs::read(prod.path()).expect("read"), + "ERA mismatch: {:?}", + orig.file_name() + ); + } +} + +// ============================================================================= +// CORRUPTION TESTS — verify specific error messages +// ============================================================================= + +fn assert_import_fails( + corrupt_file: &str, + target_pattern: &str, + target_era: u64, + expected_err: &str, +) { + let tmp = era_dir_with_corrupt(corrupt_file, target_pattern); + let spec = load_test_spec(); + let era_dir = EraFileDir::new::(&tmp.path().join("era"), &spec) + .expect("init should succeed"); + let store = empty_store(&spec); + + for era in 0..target_era { + era_dir + .import_era_file(&store, era, &spec, None, None) + .unwrap_or_else(|e| panic!("ERA {era}: {e}")); + } + + let err = era_dir + .import_era_file(&store, target_era, &spec, None, None) + .unwrap_err(); + assert!( + err.contains(expected_err), + "expected \"{expected_err}\", got: {err}" + ); +} + +#[test] +fn era_rejects_corrupted_block_decompression() { + assert_import_fails("era1-corrupt-block.era", "-00001-", 1, "decompress"); +} + +#[test] +fn era_rejects_corrupted_genesis_state() { + assert_import_fails("era0-corrupt-state.era", "-00000-", 0, "decompress"); +} + +#[test] +fn era_rejects_corrupted_middle_state() { + assert_import_fails("era5-corrupt-state.era", "-00005-", 5, "decompress"); +} + +#[test] +fn era_rejects_corrupted_reference_state() { + let tmp = era_dir_with_corrupt("era12-corrupt-state.era", "-00012-"); + let spec = load_test_spec(); + match EraFileDir::new::(&tmp.path().join("era"), &spec) { + Ok(_) => panic!("should fail with corrupted reference state"), + Err(err) => assert!(err.contains("decompress"), "expected decompress: {err}"), + } +} + +#[test] +fn era_rejects_wrong_era_content() { + assert_import_fails( + "era3-wrong-content.era", + "-00003-", + 3, + "era state slot mismatch", + ); +} + +#[test] +fn era_rejects_wrong_era_root() { + assert_import_fails("era0-wrong-root.era", "-00000-", 0, "era root mismatch"); +} + +#[test] +fn era_rejects_corrupt_block_summary() { + assert_import_fails( + "era8-corrupt-block-summary.era", + "-00008-", + 8, + "block summary root post-capella mismatch", + ); +} + +#[test] +fn era_rejects_wrong_block_root() { + assert_import_fails( + "era2-wrong-block-root.era", + "-00002-", + 2, + "block root mismatch", + ); +} + +/// Mutated balance in ERA 3 state → state root doesn't match trusted root. +/// Without a trusted root, the consumer can't detect this (historical_summaries only +/// commit to block_roots/state_roots vectors, not full state content). +/// The trusted state root feature catches it. +#[test] +fn era_rejects_mutated_state_with_trusted_root() { + let tmp = era_dir_with_corrupt("era3-wrong-state-root.era", "-00003-"); + let spec = load_test_spec(); + let era_dir = EraFileDir::new::(&tmp.path().join("era"), &spec) + .expect("init should succeed"); + let store = empty_store(&spec); + + for era in 0..3 { + era_dir + .import_era_file(&store, era, &spec, None, None) + .unwrap_or_else(|e| panic!("ERA {era}: {e}")); + } + + // Get the CORRECT state root from the original ERA 3 file + let orig_era3 = std::fs::read_dir(test_vectors_dir().join("era")) + .expect("readdir") + .filter_map(|e| e.ok()) + .find(|e| e.file_name().to_string_lossy().contains("-00003-")) + .expect("ERA 3"); + let file = std::fs::File::open(orig_era3.path()).expect("open"); + let era = reth_era::era::file::EraReader::new(file) + .read_and_assemble("minimal".to_string()) + .expect("parse"); + let state_bytes = era.group.era_state.decompress().expect("decompress"); + let mut state: BeaconState = + BeaconState::from_ssz_bytes(&state_bytes, &spec).expect("decode"); + let correct_root = state.canonical_root().expect("root"); + let slot = state.slot(); + + // Import mutated ERA 3 with trusted root → should fail because balance was changed + let err = era_dir + .import_era_file(&store, 3, &spec, Some(correct_root), Some(slot)) + .unwrap_err(); + assert!( + err.contains("trusted state root mismatch"), + "expected trusted state root mismatch: {err}" + ); +} + +// ============================================================================= +// TRUSTED STATE ROOT VERIFICATION +// ============================================================================= + +#[test] +fn era_rejects_wrong_trusted_state_root() { + let spec = load_test_spec(); + let store = empty_store(&spec); + let era_dir_path = test_vectors_dir().join("era"); + let era_dir = EraFileDir::new::(&era_dir_path, &spec).expect("open"); + + for era in 0..=2 { + era_dir + .import_era_file(&store, era, &spec, None, None) + .unwrap_or_else(|e| panic!("ERA {era}: {e}")); + } + + // Get correct state root for ERA 3 + let era3_file = std::fs::read_dir(&era_dir_path) + .expect("readdir") + .filter_map(|e| e.ok()) + .find(|e| e.file_name().to_string_lossy().contains("-00003-")) + .expect("ERA 3"); + + let file = std::fs::File::open(era3_file.path()).expect("open"); + let era = reth_era::era::file::EraReader::new(file) + .read_and_assemble("minimal".to_string()) + .expect("parse"); + let state_bytes = era.group.era_state.decompress().expect("decompress"); + let mut state: BeaconState = + BeaconState::from_ssz_bytes(&state_bytes, &spec).expect("decode"); + let correct_root = state.canonical_root().expect("root"); + let slot = state.slot(); + + // Correct root passes + era_dir + .import_era_file(&store, 3, &spec, Some(correct_root), Some(slot)) + .expect("correct root should pass"); + + // Wrong root fails + let wrong_root = { + let mut bytes: [u8; 32] = correct_root.into(); + bytes[0] ^= 0x01; + Hash256::from(bytes) + }; + + let store2 = empty_store(&spec); + for era in 0..=2 { + era_dir + .import_era_file(&store2, era, &spec, None, None) + .unwrap_or_else(|e| panic!("ERA {era}: {e}")); + } + + let err = era_dir + .import_era_file(&store2, 3, &spec, Some(wrong_root), Some(slot)) + .unwrap_err(); + assert!( + err.contains("trusted state root mismatch"), + "expected trusted state root mismatch: {err}" + ); +} diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/README.md b/beacon_node/beacon_chain/tests/era_test_vectors/README.md new file mode 100644 index 00000000000..400a6c5e2e3 --- /dev/null +++ b/beacon_node/beacon_chain/tests/era_test_vectors/README.md @@ -0,0 +1,37 @@ +# ERA File Test Vectors + +Minimal preset test vectors for ERA file import/export testing. + +## Network Configuration + +- **Preset**: minimal (SLOTS_PER_EPOCH=8, SLOTS_PER_HISTORICAL_ROOT=64) +- **One ERA file** = 64 slots = 8 epochs +- **Validators**: 1024 +- **Fork schedule**: All forks active from genesis (Electra), Fulu at epoch 100000 + +## Contents + +- `config.yaml` — Network configuration (CL fork schedule + parameters) +- `genesis.ssz` — Genesis state (SSZ encoded) +- `era/` — 13 ERA files (minimal-00000 through minimal-00012) + - 832 slots total (epochs 0-103) + - ~2.4MB compressed + +## Generation + +Generated using Nimbus `launch_local_testnet.sh` with `--preset minimal --nodes 2 --stop-at-epoch 100 --run-geth --run-spamoor`, then exported via `ncli_db exportEra`. + +ERA files contain real blocks with execution payloads (transactions generated by spamoor). + +## Test Coverage + +### Consumer Tests (4 tests) +- `era_consumer_imports_all_files` — Imports all 13 ERA files into a fresh store, verifies 768 block root index entries +- `era_consumer_blocks_are_readable` — Verifies all 767 unique blocks are loadable from the store +- `era_consumer_genesis_state_intact` — Verifies genesis state with 1024 validators +- `era_files_are_parseable` — Verifies all ERA files can be parsed by reth_era library + +### Producer Test (1 test) +- `era_producer_generates_identical_files` — Re-exports ERA files from imported data and verifies byte-for-byte match with original Nimbus-generated files + +All tests passing ✅ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/config.yaml b/beacon_node/beacon_chain/tests/era_test_vectors/config.yaml new file mode 100644 index 00000000000..a1895239ee9 --- /dev/null +++ b/beacon_node/beacon_chain/tests/era_test_vectors/config.yaml @@ -0,0 +1,186 @@ +# This file should contain the origin run-time config for the minimal +# network [1] without all properties overriden in the local network +# simulation. We use to generate a full run-time config as required +# by third-party binaries, such as Lighthouse and Web3Signer. +# +# [1]: https://raw.githubusercontent.com/ethereum/consensus-specs/dev/configs/minimal.yaml + +# Minimal config + +# Extends the minimal preset +# (overriden in launch_local_testnet.sh) PRESET_BASE: 'minimal' + +# Free-form short name of the network that this configuration applies to - known +# canonical network names include: +# * 'mainnet' - there can be only one +# * 'prater' - testnet +# Must match the regex: [a-z0-9\-] +CONFIG_NAME: 'minimal' + +# Transition +# --------------------------------------------------------------- +# 2**256-2**10 for testing minimal network +# (overriden in launch_local_testnet.sh) TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912 +# By default, don't use these params +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + + + +# Genesis +# --------------------------------------------------------------- +# [customized] +# (overriden in launch_local_testnet.sh) MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64 +# Jan 3, 2020 +# (overriden in launch_local_testnet.sh) MIN_GENESIS_TIME: 1578009600 +# Highest byte set to 0x01 to avoid collisions with mainnet versioning +GENESIS_FORK_VERSION: 0x00000001 +# [customized] Faster to spin up testnets, but does not give validator reasonable warning time for genesis +# (overriden in launch_local_testnet.sh) GENESIS_DELAY: 300 + + +# Forking +# --------------------------------------------------------------- +# Values provided for illustrative purposes. +# Individual tests/testnets may set different values. + +# Altair +ALTAIR_FORK_VERSION: 0x01000001 +# (overriden in launch_local_testnet.sh) ALTAIR_FORK_EPOCH: 18446744073709551615 +# Bellatrix +BELLATRIX_FORK_VERSION: 0x02000001 +# (overriden in launch_local_testnet.sh) BELLATRIX_FORK_EPOCH: 18446744073709551615 +# Capella +CAPELLA_FORK_VERSION: 0x03000001 +# (overriden in launch_local_testnet.sh) CAPELLA_FORK_EPOCH: 18446744073709551615 +# Deneb +DENEB_FORK_VERSION: 0x04000001 +# (overriden in launch_local_testnet.sh) DENEB_FORK_EPOCH: 18446744073709551615 +# Electra +ELECTRA_FORK_VERSION: 0x05000001 +# (overriden in launch_local_testnet.sh) ELECTRA_FORK_EPOCH: 18446744073709551615 +# Fulu +FULU_FORK_VERSION: 0x06000001 +# (overriden in launch_local_testnet.sh) FULU_FORK_EPOCH: 18446744073709551615 + +# Time parameters +# --------------------------------------------------------------- +# [customized] Faster for testing purposes +SECONDS_PER_SLOT: 6 +# 14 (estimate from Eth1 mainnet) +SECONDS_PER_ETH1_BLOCK: 14 +# 2**8 (= 256) epochs +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 +# [customized] higher frequency of committee turnover and faster time to acceptable voluntary exit +SHARD_COMMITTEE_PERIOD: 64 +# [customized] process deposits more quickly, but insecure +# (overriden in launch_local_testnet.sh) ETH1_FOLLOW_DISTANCE: 16 + + +# Validator cycle +# --------------------------------------------------------------- +# 2**2 (= 4) +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 +# 2**4 * 10**9 (= 16,000,000,000) Gwei +EJECTION_BALANCE: 16000000000 +# [customized] more easily demonstrate the difference between this value and the activation churn limit +MIN_PER_EPOCH_CHURN_LIMIT: 2 +# [customized] scale queue churn at much lower validator counts for testing +CHURN_LIMIT_QUOTIENT: 32 +# [New in Deneb:EIP7514] [customized] +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 4 + + +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# `2` epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 + + +# Deposit contract +# --------------------------------------------------------------- +# Ethereum Goerli testnet +DEPOSIT_CHAIN_ID: 5 +DEPOSIT_NETWORK_ID: 5 +# Configured on a per testnet basis +# (overriden in launch_local_testnet.sh) DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890 + + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +MAX_PAYLOAD_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# [customized] `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 272) +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 272 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# `2**7` (=128) +MAX_REQUEST_BLOCKS_DENEB: 128 +# `2**12` (= 4096 epochs, ~18 days) +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# `6` +BLOB_SIDECAR_SUBNET_COUNT: 6 +## `uint64(6)` +MAX_BLOBS_PER_BLOCK: 6 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 + +# Electra +# [customized] 2**6 * 10**9 (= 64,000,000,000) +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 +# [customized] 2**7 * 10**9 (= 128,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000 +# `9` +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# `uint64(9)` +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA +MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152 + +# Fulu +NUMBER_OF_COLUMNS: 128 +NUMBER_OF_CUSTODY_GROUPS: 128 +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +SAMPLES_PER_SLOT: 8 +CUSTODY_REQUIREMENT: 4 +VALIDATOR_CUSTODY_REQUIREMENT: 8 +BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 +PRESET_BASE: minimal +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 1024 +MIN_GENESIS_TIME: 0 +GENESIS_DELAY: 10 +DEPOSIT_CONTRACT_ADDRESS: 0x4242424242424242424242424242424242424242 +ETH1_FOLLOW_DISTANCE: 1 +ALTAIR_FORK_EPOCH: 0 +BELLATRIX_FORK_EPOCH: 0 +CAPELLA_FORK_EPOCH: 0 +DENEB_FORK_EPOCH: 0 +ELECTRA_FORK_EPOCH: 0 +FULU_FORK_EPOCH: 100000 +TERMINAL_TOTAL_DIFFICULTY: 0 diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era0-corrupt-state.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era0-corrupt-state.era new file mode 100644 index 00000000000..41b15550a17 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era0-corrupt-state.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era0-wrong-root.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era0-wrong-root.era new file mode 100644 index 00000000000..ec1aedafc74 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era0-wrong-root.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era1-corrupt-block.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era1-corrupt-block.era new file mode 100644 index 00000000000..fdb1aac68ff Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era1-corrupt-block.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era12-corrupt-state.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era12-corrupt-state.era new file mode 100644 index 00000000000..f291dcad89c Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era12-corrupt-state.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era2-wrong-block-root.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era2-wrong-block-root.era new file mode 100644 index 00000000000..0f28602486a Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era2-wrong-block-root.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era3-wrong-content.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era3-wrong-content.era new file mode 100644 index 00000000000..472496b6e95 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era3-wrong-content.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era3-wrong-state-root.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era3-wrong-state-root.era new file mode 100644 index 00000000000..58eb4141127 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era3-wrong-state-root.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era5-corrupt-state.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era5-corrupt-state.era new file mode 100644 index 00000000000..3b5bb34deca Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era5-corrupt-state.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era8-corrupt-block-summary.era b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era8-corrupt-block-summary.era new file mode 100644 index 00000000000..e6e07854815 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/corrupt/era8-corrupt-block-summary.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/create_corrupt_files.py b/beacon_node/beacon_chain/tests/era_test_vectors/create_corrupt_files.py new file mode 100755 index 00000000000..97e555444ac --- /dev/null +++ b/beacon_node/beacon_chain/tests/era_test_vectors/create_corrupt_files.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +""" +Create corrupted ERA files for testing ERA consumer error handling. + +This script generates specific corrupt ERA files by: +1. Parsing existing ERA files +2. Modifying specific parts (block data, state data) +3. Re-encoding with valid compression + +Requires: pip install python-snappy +""" +import os +import sys +import shutil +from pathlib import Path + +try: + import snappy +except ImportError: + print("ERROR: python-snappy not installed. Run: pip install python-snappy", file=sys.stderr) + sys.exit(1) + +SCRIPT_DIR = Path(__file__).parent +ERA_DIR = SCRIPT_DIR / "era" +CORRUPT_DIR = SCRIPT_DIR / "corrupt" + +def read_era_file(path): + """Read ERA file and return raw bytes.""" + with open(path, 'rb') as f: + return f.read() + +def find_era_file(pattern): + """Find ERA file matching pattern.""" + files = list(ERA_DIR.glob(f"minimal-{pattern}-*.era")) + if not files: + return None + return files[0] + +def corrupt_bytes_at_offset(data, offset, xor_pattern=0xFF): + """Corrupt bytes at specific offset by XOR.""" + result = bytearray(data) + result[offset] ^= xor_pattern + result[offset + 1] ^= xor_pattern + return bytes(result) + +def main(): + print("Creating corrupt ERA test files...\n") + CORRUPT_DIR.mkdir(exist_ok=True) + + # Test 1: ERA root mismatch - corrupt genesis_validators_root in ERA 0 + era0 = find_era_file("00000") + if era0: + data = read_era_file(era0) + # Corrupt bytes in the state section (after 16-byte header) + # The state is compressed, so corruption will propagate through state root + corrupt_data = corrupt_bytes_at_offset(data, 16 + 50) + output = CORRUPT_DIR / "era0-wrong-root.era" + with open(output, 'wb') as f: + f.write(corrupt_data) + print(f"✓ Created era0-wrong-root.era ({len(corrupt_data)} bytes)") + else: + print("⚠ ERA 0 file not found, skipping", file=sys.stderr) + + # Test 2: Block summary root post-Capella mismatch - corrupt block_roots + era8 = find_era_file("00008") + if era8: + data = read_era_file(era8) + # Corrupt state section (different offset than ERA 0) + corrupt_data = corrupt_bytes_at_offset(data, 16 + 100) + output = CORRUPT_DIR / "era8-corrupt-block-summary.era" + with open(output, 'wb') as f: + f.write(corrupt_data) + print(f"✓ Created era8-corrupt-block-summary.era ({len(corrupt_data)} bytes)") + else: + print("⚠ ERA 8 file not found, skipping", file=sys.stderr) + + # Test 3: Block root mismatch - corrupt a block + era2 = find_era_file("00002") + if era2: + data = read_era_file(era2) + # Find and corrupt a block (blocks come after state in ERA file) + # We'll corrupt somewhere in the middle where blocks likely are + corrupt_offset = len(data) // 3 # Rough guess at block location + corrupt_data = corrupt_bytes_at_offset(data, corrupt_offset) + output = CORRUPT_DIR / "era2-wrong-block-root.era" + with open(output, 'wb') as f: + f.write(corrupt_data) + print(f"✓ Created era2-wrong-block-root.era ({len(corrupt_data)} bytes)") + else: + print("⚠ ERA 2 file not found, skipping", file=sys.stderr) + + print(f"\n✓ Corrupt files created in: {CORRUPT_DIR}") + print(f"Total files: {len(list(CORRUPT_DIR.glob('*.era')))}") + +if __name__ == "__main__": + main() diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00000-effed385.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00000-effed385.era new file mode 100644 index 00000000000..f89fda38443 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00000-effed385.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00001-c33842ef.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00001-c33842ef.era new file mode 100644 index 00000000000..9ae316d8127 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00001-c33842ef.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00002-8d6762e2.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00002-8d6762e2.era new file mode 100644 index 00000000000..187b85694f0 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00002-8d6762e2.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00003-62feb608.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00003-62feb608.era new file mode 100644 index 00000000000..01f77497783 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00003-62feb608.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00004-3a2dbbc9.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00004-3a2dbbc9.era new file mode 100644 index 00000000000..19731cccb05 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00004-3a2dbbc9.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00005-8c0cd358.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00005-8c0cd358.era new file mode 100644 index 00000000000..472496b6e95 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00005-8c0cd358.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00006-f142ca09.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00006-f142ca09.era new file mode 100644 index 00000000000..98b2e940cf6 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00006-f142ca09.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00007-dff7bc62.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00007-dff7bc62.era new file mode 100644 index 00000000000..6255ebc6cc8 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00007-dff7bc62.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00008-b2b38a6c.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00008-b2b38a6c.era new file mode 100644 index 00000000000..c1d797b37be Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00008-b2b38a6c.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00009-2a918294.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00009-2a918294.era new file mode 100644 index 00000000000..39791cb392d Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00009-2a918294.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00010-ce2964fb.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00010-ce2964fb.era new file mode 100644 index 00000000000..65184de626c Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00010-ce2964fb.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00011-19a0f945.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00011-19a0f945.era new file mode 100644 index 00000000000..a15e0127597 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00011-19a0f945.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00012-ea166d10.era b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00012-ea166d10.era new file mode 100644 index 00000000000..16682cf1784 Binary files /dev/null and b/beacon_node/beacon_chain/tests/era_test_vectors/era/minimal-00012-ea166d10.era differ diff --git a/beacon_node/beacon_chain/tests/era_test_vectors/metadata.json b/beacon_node/beacon_chain/tests/era_test_vectors/metadata.json new file mode 100644 index 00000000000..c0d5895953c --- /dev/null +++ b/beacon_node/beacon_chain/tests/era_test_vectors/metadata.json @@ -0,0 +1 @@ +{"head_slot": 802, "head_root": "49f82639", "finalized_slot": 784, "finalized_root": "55720c58", "era_count": 13, "last_era_slot": 832} diff --git a/beacon_node/beacon_chain/tests/gen_corrupt_era_files.rs b/beacon_node/beacon_chain/tests/gen_corrupt_era_files.rs new file mode 100644 index 00000000000..3696e88eed7 --- /dev/null +++ b/beacon_node/beacon_chain/tests/gen_corrupt_era_files.rs @@ -0,0 +1,116 @@ +/// Generate corrupt ERA files for testing. +/// Run: cargo test -p beacon_chain --test beacon_chain_tests gen_corrupt -- --nocapture --ignored +use reth_era::common::file_ops::{StreamReader, StreamWriter}; +use reth_era::era::file::{EraFile, EraReader, EraWriter}; +use reth_era::era::types::consensus::{CompressedBeaconState, CompressedSignedBeaconBlock}; +use ssz::Encode; +use std::path::PathBuf; +use types::{BeaconState, ChainSpec, Config, Graffiti, MinimalEthSpec, SignedBeaconBlock}; + +fn test_vectors_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("era_test_vectors") +} + +fn load_test_spec() -> ChainSpec { + let config_str = + std::fs::read_to_string(test_vectors_dir().join("config.yaml")).expect("read config.yaml"); + let config: Config = serde_yaml::from_str(&config_str).expect("parse config"); + config + .apply_to_chain_spec::(&ChainSpec::minimal()) + .expect("apply config") +} + +fn find_era_file(pattern: &str) -> PathBuf { + let era_dir = test_vectors_dir().join("era"); + for entry in std::fs::read_dir(&era_dir).expect("readdir") { + let entry = entry.expect("entry"); + if entry.file_name().to_string_lossy().contains(pattern) { + return entry.path(); + } + } + panic!("ERA file matching {pattern} not found"); +} + +fn read_era(path: &std::path::Path) -> EraFile { + let file = std::fs::File::open(path).expect("open"); + EraReader::new(file) + .read_and_assemble("minimal".to_string()) + .expect("parse") +} + +fn write_era(era_file: &EraFile, output: &std::path::Path) { + let file = std::fs::File::create(output).expect("create"); + let mut writer = EraWriter::new(file); + writer.write_file(era_file).expect("write"); +} + +#[test] +#[ignore] // Run manually: cargo test ... gen_corrupt -- --ignored --nocapture +fn gen_corrupt() { + let spec = load_test_spec(); + let out = test_vectors_dir().join("corrupt"); + std::fs::create_dir_all(&out).expect("mkdir"); + + // --- era0-wrong-root.era: modified genesis_validators_root --- + { + let mut era = read_era(&find_era_file("-00000-")); + let bytes = era.group.era_state.decompress().expect("decompress"); + let mut state: BeaconState = + BeaconState::from_ssz_bytes(&bytes, &spec).expect("decode"); + state.genesis_validators_root_mut().as_mut_slice()[0] ^= 0x01; + era.group.era_state = + CompressedBeaconState::from_ssz(&state.as_ssz_bytes()).expect("compress"); + write_era(&era, &out.join("era0-wrong-root.era")); + println!("✓ era0-wrong-root.era"); + } + + // --- era8-corrupt-block-summary.era: modified block_roots vector --- + { + let mut era = read_era(&find_era_file("-00008-")); + let bytes = era.group.era_state.decompress().expect("decompress"); + let mut state: BeaconState = + BeaconState::from_ssz_bytes(&bytes, &spec).expect("decode"); + if let Some(r) = state.block_roots_mut().get_mut(0) { + r.as_mut_slice()[0] ^= 0x01; + } + era.group.era_state = + CompressedBeaconState::from_ssz(&state.as_ssz_bytes()).expect("compress"); + write_era(&era, &out.join("era8-corrupt-block-summary.era")); + println!("✓ era8-corrupt-block-summary.era"); + } + + // --- era2-wrong-block-root.era: modified block graffiti --- + { + let mut era = read_era(&find_era_file("-00002-")); + if let Some(block_compressed) = era.group.blocks.first_mut() { + let bytes = block_compressed.decompress().expect("decompress"); + let mut block: SignedBeaconBlock = + SignedBeaconBlock::from_ssz_bytes(&bytes, &spec).expect("decode"); + *block.message_mut().body_mut().graffiti_mut() = + Graffiti::from(*b"CORRUPTED_BLOCK_DATA!!!!!!!!!!!!"); + *block_compressed = + CompressedSignedBeaconBlock::from_ssz(&block.as_ssz_bytes()).expect("compress"); + } + write_era(&era, &out.join("era2-wrong-block-root.era")); + println!("✓ era2-wrong-block-root.era"); + } + + // --- era3-wrong-state-root.era: modified balance --- + { + let mut era = read_era(&find_era_file("-00003-")); + let bytes = era.group.era_state.decompress().expect("decompress"); + let mut state: BeaconState = + BeaconState::from_ssz_bytes(&bytes, &spec).expect("decode"); + if let Some(bal) = state.balances_mut().get_mut(0) { + *bal = bal.wrapping_add(1); + } + era.group.era_state = + CompressedBeaconState::from_ssz(&state.as_ssz_bytes()).expect("compress"); + write_era(&era, &out.join("era3-wrong-state-root.era")); + println!("✓ era3-wrong-state-root.era"); + } + + println!("\nDone: {:?}", out); +} diff --git a/beacon_node/beacon_chain/tests/main.rs b/beacon_node/beacon_chain/tests/main.rs index aec44164195..9e02d8a4201 100644 --- a/beacon_node/beacon_chain/tests/main.rs +++ b/beacon_node/beacon_chain/tests/main.rs @@ -5,7 +5,9 @@ mod blob_verification; mod block_verification; mod capella; mod column_verification; +mod era_file_tests; mod events; +mod gen_corrupt_era_files; mod op_verification; mod payload_invalidation; mod rewards; diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index 1b395ac8da5..0e13fbf023a 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -248,7 +248,12 @@ where ClientGenesis::DepositContract } else if chain_exists { - if matches!(client_genesis, ClientGenesis::WeakSubjSszBytes { .. }) + if matches!(client_genesis, ClientGenesis::EraFiles { .. }) { + info!( + msg = "database already exists, use --purge-db to force era import", + "Refusing to import era files" + ); + } else if matches!(client_genesis, ClientGenesis::WeakSubjSszBytes { .. }) || matches!(client_genesis, ClientGenesis::CheckpointSyncUrl { .. }) { info!( @@ -460,6 +465,18 @@ where return Err("Loading genesis from deposit contract no longer supported".to_string()); } ClientGenesis::FromStore => builder.resume_from_db()?, + ClientGenesis::EraFiles { era_files_dir } => { + info!(?era_files_dir, "Importing era files"); + let genesis_state = genesis_state(&runtime_context, &config).await?; + let trusted_state_root = config.store.era_trusted_state_root; + let trusted_slot = config.store.era_trusted_slot; + builder.era_files( + &era_files_dir, + genesis_state, + trusted_state_root, + trusted_slot, + )? + } }; self.beacon_chain_builder = Some(beacon_chain_builder); diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index aeaa196df86..db5ad0d217a 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -45,6 +45,9 @@ pub enum ClientGenesis { CheckpointSyncUrl { url: SensitiveUrl, }, + EraFiles { + era_files_dir: PathBuf, + }, } /// The core configuration of a Lighthouse beacon node. diff --git a/beacon_node/lighthouse_network/src/config.rs b/beacon_node/lighthouse_network/src/config.rs index cb94bfff22b..8f45da2926d 100644 --- a/beacon_node/lighthouse_network/src/config.rs +++ b/beacon_node/lighthouse_network/src/config.rs @@ -118,6 +118,8 @@ pub struct Config { /// Whether we are running a block proposer only node. pub proposer_only: bool, + /// Optional directory containing `.era` files for backfill. + pub era_files_dir: Option, /// Whether metrics are enabled. pub metrics_enabled: bool, @@ -357,6 +359,7 @@ impl Default for Config { shutdown_after_sync: false, topics: Vec::new(), proposer_only: false, + era_files_dir: None, metrics_enabled: false, enable_light_client_server: true, outbound_rate_limiter_config: None, diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index 78dc0c48a75..b7d36b880d6 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -35,6 +35,7 @@ metrics = { workspace = true } operation_pool = { workspace = true } parking_lot = { workspace = true } rand = { workspace = true } +reth-era = { workspace = true } slot_clock = { workspace = true } smallvec = { workspace = true } ssz_types = { workspace = true } diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index a6b3ea9e4b1..55bd523c892 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -613,16 +613,7 @@ impl NetworkBeaconProcessor { process_id: ChainSegmentProcessId, downloaded_blocks: Vec>, ) { - let ChainSegmentProcessId::BackSyncBatchId(epoch) = process_id else { - // this a request from RangeSync, this should _never_ happen - crit!( - error = - "process_chain_segment_backfill called on a variant other than BackSyncBatchId", - "Please notify the devs" - ); - return; - }; - + let block_count = downloaded_blocks.len(); let start_slot = downloaded_blocks.first().map(|b| b.slot().as_u64()); let end_slot = downloaded_blocks.last().map(|b| b.slot().as_u64()); let sent_blocks = downloaded_blocks.len(); @@ -638,15 +629,16 @@ impl NetworkBeaconProcessor { let result = match self.process_backfill_blocks(downloaded_blocks) { (imported_blocks, Ok(_)) => { debug!( - batch_epoch = %epoch, - first_block_slot = start_slot, - keep_execution_payload = !self.chain.store.get_config().prune_payloads, - last_block_slot = end_slot, - processed_blocks = sent_blocks, - processed_blobs = n_blobs, - processed_data_columns = n_data_columns, - service= "sync", - "Backfill batch processed"); + block_count, + first_block_slot = start_slot, + keep_execution_payload = !self.chain.store.get_config().prune_payloads, + last_block_slot = end_slot, + processed_blocks = sent_blocks, + processed_blobs = n_blobs, + processed_data_columns = n_data_columns, + service = "sync", + "Backfill batch processed" + ); BatchProcessResult::Success { sent_blocks, imported_blocks, @@ -654,7 +646,7 @@ impl NetworkBeaconProcessor { } (_, Err(e)) => { debug!( - batch_epoch = %epoch, + block_count, first_block_slot = start_slot, last_block_slot = end_slot, processed_blobs = n_blobs, diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 9553fe60ba2..79355976a61 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -43,6 +43,32 @@ pub fn cli_app() -> Command { .action(ArgAction::Set) .display_order(0) ) + .arg( + Arg::new("era-files-dir") + .long("era-files-dir") + .value_name("DIR") + .help("Directory containing .era files for backfill and where new .era files will be written.") + .action(ArgAction::Set) + .display_order(0) + ) + .arg( + Arg::new("era-trusted-state-root") + .long("era-trusted-state-root") + .value_name("ROOT") + .help("Trusted state root to verify when importing ERA files. Must be used with --era-trusted-slot.") + .requires("era-trusted-slot") + .action(ArgAction::Set) + .display_order(0) + ) + .arg( + Arg::new("era-trusted-slot") + .long("era-trusted-slot") + .value_name("SLOT") + .help("Slot number of the trusted state root. Must be used with --era-trusted-state-root.") + .requires("era-trusted-state-root") + .action(ArgAction::Set) + .display_order(0) + ) /* * Network parameters. */ @@ -414,6 +440,15 @@ pub fn cli_app() -> Command { .help_heading(FLAG_HEADER) .display_order(0) ) + .arg( + Arg::new("era-files-dir") + .long("era-files-dir") + .value_name("DIR") + .help("Directory containing `.era` files to use for backfill.") + .action(ArgAction::Set) + .help_heading(FLAG_HEADER) + .display_order(0) + ) .arg( Arg::new("complete-blob-backfill") .long("complete-blob-backfill") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 752cf105505..c0c0480a3f1 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -350,6 +350,24 @@ pub fn get_config( client_config.freezer_db_path = Some(PathBuf::from(freezer_dir)); } + if let Some(era_files_dir) = cli_args.get_one::("era-files-dir") { + client_config.store.era_files_dir = Some(PathBuf::from(era_files_dir)); + } + + if let Some(root_str) = cli_args.get_one::("era-trusted-state-root") { + let root = root_str + .parse::() + .map_err(|e| format!("Invalid era-trusted-state-root: {}", e))?; + client_config.store.era_trusted_state_root = Some(root); + } + + if let Some(slot_str) = cli_args.get_one::("era-trusted-slot") { + let slot = slot_str + .parse::() + .map_err(|e| format!("Invalid era-trusted-slot: {}", e))?; + client_config.store.era_trusted_slot = Some(Slot::new(slot)); + } + if let Some(blobs_db_dir) = cli_args.get_one::("blobs-dir") { client_config.blobs_db_path = Some(PathBuf::from(blobs_db_dir)); } @@ -808,6 +826,14 @@ pub fn get_config( client_config.chain.genesis_backfill = true; } + if let Some(dir) = clap_utils::parse_optional::(cli_args, "era-files-dir")? { + let path = PathBuf::from(dir); + client_config.store.era_files_dir = Some(path.clone()); + client_config.genesis = ClientGenesis::EraFiles { + era_files_dir: path, + }; + } + client_config.chain.complete_blob_backfill = cli_args.get_flag("complete-blob-backfill"); // Ensure `prune_blobs` is false whenever complete-blob-backfill is set. This overrides any diff --git a/beacon_node/store/src/config.rs b/beacon_node/store/src/config.rs index 29705283fa9..0c21b04f8b7 100644 --- a/beacon_node/store/src/config.rs +++ b/beacon_node/store/src/config.rs @@ -5,10 +5,12 @@ use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use std::io::{Read, Write}; use std::num::NonZeroUsize; +use std::path::PathBuf; use strum::{Display, EnumString, VariantNames}; use superstruct::superstruct; use types::EthSpec; use types::new_non_zero_usize; +use types::{Hash256, Slot}; use zstd::{Decoder, Encoder}; #[cfg(all(feature = "redb", not(feature = "leveldb")))] @@ -64,6 +66,12 @@ pub struct StoreConfig { /// The margin for blob pruning in epochs. The oldest blobs are pruned up until /// data_availability_boundary - blob_prune_margin_epochs. Default: 0. pub blob_prune_margin_epochs: u64, + /// Directory for era file import and production. + pub era_files_dir: Option, + /// Trusted state root for ERA file verification. + pub era_trusted_state_root: Option, + /// Slot number of the trusted state root. + pub era_trusted_slot: Option, } /// Variant of `StoreConfig` that gets written to disk. Contains immutable configuration params. @@ -120,6 +128,9 @@ impl Default for StoreConfig { prune_blobs: true, epochs_per_blob_prune: DEFAULT_EPOCHS_PER_BLOB_PRUNE, blob_prune_margin_epochs: DEFAULT_BLOB_PUNE_MARGIN_EPOCHS, + era_files_dir: None, + era_trusted_state_root: None, + era_trusted_slot: None, } } } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 6e165702a27..44b952233a7 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -88,6 +88,16 @@ pub struct HotColdDB, Cold: ItemStore> { _phantom: PhantomData, } +fn era_import_pointer_key() -> &'static [u8] { + b"era_import_ptr" +} + +fn era_reconstruction_key(era_number: u64) -> Vec { + let mut key = b"era_recon:".to_vec(); + key.extend_from_slice(&era_number.to_be_bytes()); + key +} + #[derive(Debug)] struct BlockCache { block_cache: LruCache>, @@ -196,7 +206,7 @@ pub enum HotColdDBError { RestorePointDecodeError(ssz::DecodeError), BlockReplayBeaconError(BeaconStateError), BlockReplaySlotError(SlotProcessingError), - BlockReplayBlockError(BlockProcessingError), + BlockReplayBlockError(Slot, BlockProcessingError), InvalidSlotsPerRestorePoint { slots_per_restore_point: u64, slots_per_historical_root: u64, @@ -1092,6 +1102,17 @@ impl, Cold: ItemStore> HotColdDB } } + /// Store and commit a state into the cold db store. + pub fn put_cold_state( + &self, + state_root: &Hash256, + state: &BeaconState, + ) -> Result<(), Error> { + let mut ops: Vec = Vec::new(); + self.store_cold_state(state_root, state, &mut ops)?; + self.cold_db.do_atomically(ops) + } + /// Fetch a state from the store. /// /// If `slot` is provided then it will be used as a hint as to which database should @@ -2084,31 +2105,16 @@ impl, Cold: ItemStore> HotColdDB self.store_cold_state_summary(state_root, state.slot(), ops)?; let slot = state.slot(); - match self.cold_storage_strategy(slot)? { - StorageStrategy::ReplayFrom(from) => { - debug!( - strategy = "replay", - from_slot = %from, - %slot, - "Storing cold state", - ); + let strategy = self.cold_storage_strategy(slot)?; + debug!(?strategy, %slot, "Storing cold state"); + match strategy { + StorageStrategy::ReplayFrom(_) => { // Already have persisted the state summary, don't persist anything else } StorageStrategy::Snapshot => { - debug!( - strategy = "snapshot", - %slot, - "Storing cold state" - ); self.store_cold_state_as_snapshot(state, ops)?; } StorageStrategy::DiffFrom(from) => { - debug!( - strategy = "diff", - from_slot = %from, - %slot, - "Storing cold state" - ); self.store_cold_state_as_diff(state, from, ops)?; } } @@ -3137,6 +3143,41 @@ impl, Cold: ItemStore> HotColdDB self.config.compact_on_prune } + pub fn get_era_import_pointer(&self) -> Result, Error> { + let Some(bytes) = self + .hot_db + .get_bytes(DBColumn::BeaconMeta, era_import_pointer_key())? + else { + return Ok(None); + }; + let bytes: [u8; 8] = bytes + .as_slice() + .try_into() + .map_err(|_| Error::InvalidBytes)?; + Ok(Some(u64::from_be_bytes(bytes))) + } + + pub fn set_era_import_pointer(&self, era_number: u64) -> Result<(), Error> { + self.hot_db.put_bytes( + DBColumn::BeaconMeta, + era_import_pointer_key(), + &era_number.to_be_bytes(), + ) + } + + pub fn era_reconstruction_done(&self, era_number: u64) -> Result { + self.hot_db + .key_exists(DBColumn::BeaconMeta, &era_reconstruction_key(era_number)) + } + + pub fn set_era_reconstruction_done(&self, era_number: u64) -> Result<(), Error> { + self.hot_db.put_bytes( + DBColumn::BeaconMeta, + &era_reconstruction_key(era_number), + &[1u8], + ) + } + /// Load the timestamp of the last compaction as a `Duration` since the UNIX epoch. pub fn load_compaction_timestamp(&self) -> Result, Error> { Ok(self diff --git a/beacon_node/store/src/reconstruct.rs b/beacon_node/store/src/reconstruct.rs index 7aca692ef9b..5f20dcb161c 100644 --- a/beacon_node/store/src/reconstruct.rs +++ b/beacon_node/store/src/reconstruct.rs @@ -1,15 +1,16 @@ //! Implementation of historic state reconstruction (given complete block history). +use crate::forwards_iter::FrozenForwardsIterator; use crate::hot_cold_store::{HotColdDB, HotColdDBError}; -use crate::metrics; +use crate::{DBColumn, KeyValueStoreOp, metrics}; use crate::{Error, ItemStore}; -use itertools::{Itertools, process_results}; +use itertools::process_results; use state_processing::{ BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, per_block_processing, per_slot_processing, }; use std::sync::Arc; use tracing::{debug, info}; -use types::EthSpec; +use types::{EthSpec, Slot}; impl HotColdDB where @@ -53,40 +54,103 @@ where return Ok(()); } - // If `num_blocks` is not specified iterate all blocks. Add 1 so that we end on an epoch - // boundary when `num_blocks` is a multiple of an epoch boundary. We want to be *inclusive* - // of the state at slot `lower_limit_slot + num_blocks`. - let block_root_iter = self - .forwards_block_roots_iterator_until(lower_limit_slot, upper_limit_slot - 1, || { - Err(Error::StateShouldNotBeRequired(upper_limit_slot - 1)) - })? - .take(num_blocks.map_or(usize::MAX, |n| n + 1)); + let from_slot = lower_limit_slot; + let to_slot = if let Some(num_blocks) = num_blocks { + std::cmp::min(upper_limit_slot, from_slot + Slot::new(num_blocks as u64)) + } else { + upper_limit_slot + }; + + self.reconstruct_historic_states_on_range(from_slot, from_slot, to_slot)?; + + let remaining = upper_limit_slot + .as_u64() + .saturating_sub(1) + .saturating_sub(to_slot.as_u64()); + info!( + slot = %to_slot, + remaining = %remaining, + "State reconstruction in progress" + ); + + // Update anchor. + let old_anchor = anchor.clone(); + + let reconstruction_complete = to_slot == upper_limit_slot; + if reconstruction_complete { + let new_anchor = old_anchor.as_archive_anchor(); + self.compare_and_set_anchor_info_with_write(old_anchor, new_anchor)?; + + return Ok(()); + } else { + // The lower limit has been raised, store it. + anchor.state_lower_limit = to_slot; + + self.compare_and_set_anchor_info_with_write(old_anchor, anchor.clone())?; + } + + // Check that the split point wasn't mutated during the state reconstruction process. + // It shouldn't have been, due to the serialization of requests through the store migrator, + // so this is just a paranoid check. + let latest_split = self.get_split_info(); + if split != latest_split { + return Err(Error::SplitPointModified(latest_split.slot, split.slot)); + } + + Ok(()) + } + + pub fn reconstruct_historic_states_on_range( + self: &Arc, + with_state_at_slot: Slot, + from_slot: Slot, + to_slot: Slot, + ) -> Result<(), Error> { + debug!( + %from_slot, + %to_slot, + "Starting state reconstruction batch" + ); + + let _t = metrics::start_timer(&metrics::STORE_BEACON_RECONSTRUCTION_TIME); + + let block_root_iter = + FrozenForwardsIterator::new(self, DBColumn::BeaconBlockRoots, from_slot, to_slot)?; // The state to be advanced. - let mut state = self.load_cold_state_by_slot(lower_limit_slot)?; + let mut state = self.load_cold_state_by_slot(with_state_at_slot)?; state.build_caches(&self.spec)?; process_results(block_root_iter, |iter| -> Result<(), Error> { let mut io_batch = vec![]; - let mut prev_state_root = None; - for ((prev_block_root, _), (block_root, slot)) in iter.tuple_windows() { - let is_skipped_slot = prev_block_root == block_root; - - let block = if is_skipped_slot { - None - } else { - Some( - self.get_blinded_block(&block_root)? - .ok_or(Error::BlockNotFound(block_root))?, - ) + for (block_root, slot) in iter { + io_batch.push(KeyValueStoreOp::PutKeyValue( + DBColumn::BeaconBlockRoots, + slot.as_u64().to_be_bytes().to_vec(), + block_root.as_slice().to_vec(), + )); + + let block = { + let block = self + .get_blinded_block(&block_root)? + .ok_or(Error::BlockNotFound(block_root))?; + if block.slot() == slot && block.slot() > self.spec.genesis_slot { + // If block.slot != slot means it's a skipped slot. + // Also skip applying the genesis slot. + Some(block) + } else { + None + } }; // Advance state to slot. - per_slot_processing(&mut state, prev_state_root.take(), &self.spec) - .map_err(HotColdDBError::BlockReplaySlotError)?; + while state.slot() < slot { + per_slot_processing(&mut state, prev_state_root.take(), &self.spec) + .map_err(HotColdDBError::BlockReplaySlotError)?; + } // Apply block. if let Some(block) = block { @@ -102,7 +166,7 @@ where &mut ctxt, &self.spec, ) - .map_err(HotColdDBError::BlockReplayBlockError)?; + .map_err(|e| HotColdDBError::BlockReplayBlockError(block.slot(), e))?; prev_state_root = Some(block.state_root()); } @@ -114,58 +178,21 @@ where // Stage state for storage in freezer DB. self.store_cold_state(&state_root, &state, &mut io_batch)?; - let batch_complete = - num_blocks.is_some_and(|n_blocks| slot == lower_limit_slot + n_blocks as u64); - let reconstruction_complete = slot + 1 == upper_limit_slot; + let batch_complete = slot + 1 == to_slot; // Commit the I/O batch if: // // - The diff/snapshot for this slot is required for future slots, or // - The reconstruction batch is complete (we are about to return), or // - Reconstruction is complete. - if self.hierarchy.should_commit_immediately(slot)? - || batch_complete - || reconstruction_complete - { - info!( - %slot, - remaining = %(upper_limit_slot - 1 - slot), - "State reconstruction in progress" - ); - + if self.hierarchy.should_commit_immediately(slot)? || batch_complete { self.cold_db.do_atomically(std::mem::take(&mut io_batch))?; - // Update anchor. - let old_anchor = anchor.clone(); - - if reconstruction_complete { - // The two limits have met in the middle! We're done! - // Perform one last integrity check on the state reached. - let computed_state_root = state.update_tree_hash_cache()?; - if computed_state_root != state_root { - return Err(Error::StateReconstructionRootMismatch { - slot, - expected: state_root, - computed: computed_state_root, - }); - } - - let new_anchor = old_anchor.as_archive_anchor(); - self.compare_and_set_anchor_info_with_write(old_anchor, new_anchor)?; - - return Ok(()); - } else { - // The lower limit has been raised, store it. - anchor.state_lower_limit = slot; - - self.compare_and_set_anchor_info_with_write(old_anchor, anchor.clone())?; - } - // If this is the end of the batch, return Ok. The caller will run another // batch when there is idle capacity. if batch_complete { debug!( - start_slot = %lower_limit_slot, + start_slot = %from_slot, end_slot = %slot, "Finished state reconstruction batch" ); @@ -179,14 +206,6 @@ where Err(Error::StateReconstructionLogicError) })??; - // Check that the split point wasn't mutated during the state reconstruction process. - // It shouldn't have been, due to the serialization of requests through the store migrator, - // so this is just a paranoid check. - let latest_split = self.get_split_info(); - if split != latest_split { - return Err(Error::SplitPointModified(latest_split.slot, split.slot)); - } - Ok(()) } } diff --git a/book/src/help_bn.md b/book/src/help_bn.md index d3aa27c8a77..03e6747d97c 100644 --- a/book/src/help_bn.md +++ b/book/src/help_bn.md @@ -496,6 +496,8 @@ Flags: --enable-private-discovery Lighthouse by default does not discover private IP addresses. Set this flag to enable connection attempts to local addresses. + --era-files-dir + Directory containing `.era` files to use for backfill. --genesis-backfill Attempts to download blocks all the way back to genesis when checkpoint syncing. diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index 1de5083f6f3..4ec2249ce48 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -221,7 +221,10 @@ pub fn process_block_header( // Verify that the slots match verify!( block_header.slot == state.slot(), - HeaderInvalid::StateSlotMismatch + HeaderInvalid::StateSlotMismatch { + state: state.slot(), + header: block_header.slot, + } ); // Verify that the block is newer than the latest block header diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index 5c1db9d732e..124cef1c665 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -277,7 +277,10 @@ impl From for BlockOperationError { #[derive(Debug, PartialEq, Clone)] pub enum HeaderInvalid { ProposalSignatureInvalid, - StateSlotMismatch, + StateSlotMismatch { + state: Slot, + header: Slot, + }, OlderThanLatestBlockHeader { latest_block_header_slot: Slot, block_slot: Slot, diff --git a/consensus/state_processing/src/per_block_processing/tests.rs b/consensus/state_processing/src/per_block_processing/tests.rs index 739717b33ff..c07c464f9e0 100644 --- a/consensus/state_processing/src/per_block_processing/tests.rs +++ b/consensus/state_processing/src/per_block_processing/tests.rs @@ -107,12 +107,12 @@ async fn invalid_block_header_state_slot() { &spec, ); - assert_eq!( + assert!(matches!( result, Err(BlockProcessingError::HeaderInvalid { - reason: HeaderInvalid::StateSlotMismatch + reason: HeaderInvalid::StateSlotMismatch { .. } }) - ); + )); } #[tokio::test] diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index 6d25e3baf40..2e734cbfdde 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -1733,7 +1733,7 @@ impl<'de> Deserialize<'de> for BlobSchedule { impl BlobSchedule { pub fn new(mut vec: Vec) -> Self { // reverse sort by epoch - vec.sort_by(|a, b| b.epoch.cmp(&a.epoch)); + vec.sort_by_key(|b| std::cmp::Reverse(b.epoch)); Self { schedule: vec, skip_serializing: false, diff --git a/consensus/types/src/state/historical_summary.rs b/consensus/types/src/state/historical_summary.rs index f520e464837..eca28b88fb0 100644 --- a/consensus/types/src/state/historical_summary.rs +++ b/consensus/types/src/state/historical_summary.rs @@ -47,4 +47,8 @@ impl HistoricalSummary { state_summary_root: state.state_roots().tree_hash_root(), } } + + pub fn block_summary_root(&self) -> Hash256 { + self.block_summary_root + } }